Apply TradeStatistics3 to TradeStatisticsManager and some related classes

This commit is contained in:
chimp1984 2020-10-05 18:12:49 -05:00
parent 98207518d6
commit c4a4c878b8
No known key found for this signature in database
GPG Key ID: 9801B4EC591F90E3
4 changed files with 67 additions and 135 deletions

View File

@ -23,7 +23,7 @@ import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price;
import bisq.core.provider.PriceNodeHttpClient;
import bisq.core.provider.ProvidersRepository;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.user.Preferences;
import bisq.network.http.HttpClient;
@ -50,6 +50,7 @@ import javafx.beans.property.StringProperty;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -288,12 +289,12 @@ public class PriceFeedService {
return new Date(epochInMillisAtLastRequest);
}
public void applyLatestBisqMarketPrice(Set<TradeStatistics2> tradeStatisticsSet) {
public void applyLatestBisqMarketPrice(Set<TradeStatistics3> tradeStatisticsSet) {
// takes about 10 ms for 5000 items
Map<String, List<TradeStatistics2>> mapByCurrencyCode = new HashMap<>();
Map<String, List<TradeStatistics3>> mapByCurrencyCode = new HashMap<>();
tradeStatisticsSet.forEach(e -> {
final List<TradeStatistics2> list;
final String currencyCode = e.getCurrencyCode();
List<TradeStatistics3> list;
String currencyCode = e.getCurrency();
if (mapByCurrencyCode.containsKey(currencyCode)) {
list = mapByCurrencyCode.get(currencyCode);
} else {
@ -306,9 +307,9 @@ public class PriceFeedService {
mapByCurrencyCode.values().stream()
.filter(list -> !list.isEmpty())
.forEach(list -> {
list.sort((o1, o2) -> o1.getTradeDate().compareTo(o2.getTradeDate()));
TradeStatistics2 tradeStatistics = list.get(list.size() - 1);
setBisqMarketPrice(tradeStatistics.getCurrencyCode(), tradeStatistics.getTradePrice());
list.sort(Comparator.comparing(o -> o.getTradeDate()));
TradeStatistics3 tradeStatistics = list.get(list.size() - 1);
setBisqMarketPrice(tradeStatistics.getCurrency(), tradeStatistics.getTradePrice());
});
}

View File

@ -24,8 +24,6 @@ import bisq.core.account.witness.AccountAgeWitnessStorageService;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatistics2StorageService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.app.AppModule;
import bisq.common.config.Config;
@ -46,8 +44,6 @@ public class TradeModule extends AppModule {
@Override
protected void configure() {
bind(TradeManager.class).in(Singleton.class);
bind(TradeStatisticsManager.class).in(Singleton.class);
bind(TradeStatistics2StorageService.class).in(Singleton.class);
bind(ClosedTradableManager.class).in(Singleton.class);
bind(FailedTradesManager.class).in(Singleton.class);
bind(AccountAgeWitnessService.class).in(Singleton.class);

View File

@ -21,7 +21,6 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferPayload;
import bisq.common.util.MathUtils;
@ -38,65 +37,44 @@ import javax.annotation.concurrent.Immutable;
@ToString
@Slf4j
public final class TradeStatisticsForJson {
public final String currency;
public final OfferPayload.Direction direction;
public final long tradePrice;
public final long tradeAmount;
public final long tradeDate;
public final String paymentMethod;
public final long offerDate;
public final boolean useMarketBasedPrice;
public final double marketPriceMargin;
public final long offerAmount;
public final long offerMinAmount;
public final String offerId;
public final String depositTxId;
// primaryMarket fields are based on industry standard where primaryMarket is always in the focus (in the app BTC is always in the focus - will be changed in a larger refactoring once)
public String currencyPair;
public OfferPayload.Direction primaryMarketDirection;
public long primaryMarketTradePrice;
public long primaryMarketTradeAmount;
public long primaryMarketTradeVolume;
public TradeStatisticsForJson(TradeStatistics2 tradeStatistics) {
this.direction = OfferPayload.Direction.valueOf(tradeStatistics.getDirection().name());
this.currency = tradeStatistics.getCurrencyCode();
this.paymentMethod = tradeStatistics.getOfferPaymentMethod();
this.offerDate = tradeStatistics.getOfferDate();
this.useMarketBasedPrice = tradeStatistics.isOfferUseMarketBasedPrice();
this.marketPriceMargin = tradeStatistics.getOfferMarketPriceMargin();
this.offerAmount = tradeStatistics.getOfferAmount();
this.offerMinAmount = tradeStatistics.getOfferMinAmount();
this.offerId = tradeStatistics.getOfferId();
this.tradePrice = tradeStatistics.getTradePrice().getValue();
this.tradeAmount = tradeStatistics.getTradeAmount().getValue();
this.tradeDate = tradeStatistics.getTradeDate().getTime();
this.depositTxId = tradeStatistics.getDepositTxId();
public TradeStatisticsForJson(TradeStatistics3 tradeStatistics) {
this.currency = tradeStatistics.getCurrency();
this.paymentMethod = tradeStatistics.getPaymentMethod();
this.tradePrice = tradeStatistics.getPrice();
this.tradeAmount = tradeStatistics.getAmount();
this.tradeDate = tradeStatistics.getDate();
try {
final Price tradePrice = getTradePrice();
Price tradePrice = getTradePrice();
if (CurrencyUtil.isCryptoCurrency(currency)) {
primaryMarketDirection = direction == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
currencyPair = currency + "/" + Res.getBaseCurrencyCode();
primaryMarketTradePrice = tradePrice.getValue();
primaryMarketTradeAmount = getTradeVolume() != null ? getTradeVolume().getValue() : 0;
primaryMarketTradeAmount = getTradeVolume() != null ?
getTradeVolume().getValue() :
0;
primaryMarketTradeVolume = getTradeAmount().getValue();
} else {
primaryMarketDirection = direction;
currencyPair = Res.getBaseCurrencyCode() + "/" + currency;
// we use precision 4 for fiat based price but on the markets api we use precision 8 so we scale up by 10000
primaryMarketTradePrice = (long) MathUtils.scaleUpByPowerOf10(tradePrice.getValue(), 4);
primaryMarketTradeAmount = getTradeAmount().getValue();
// we use precision 4 for fiat but on the markets api we use precision 8 so we scale up by 10000
primaryMarketTradeVolume = getTradeVolume() != null ?
(long) MathUtils.scaleUpByPowerOf10(getTradeVolume().getValue(), 4) : 0;
(long) MathUtils.scaleUpByPowerOf10(getTradeVolume().getValue(), 4) :
0;
}
} catch (Throwable t) {
log.error(t.getMessage());
@ -113,6 +91,10 @@ public final class TradeStatisticsForJson {
}
public Volume getTradeVolume() {
return getTradePrice().getVolumeByAmount(getTradeAmount());
try {
return getTradePrice().getVolumeByAmount(getTradeAmount());
} catch (Throwable t) {
return Volume.parse("0", currency);
}
}
}

View File

@ -32,6 +32,7 @@ import bisq.common.util.Utilities;
import com.google.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
@ -40,94 +41,75 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
public class TradeStatisticsManager {
private final JsonFileManager jsonFileManager;
private final P2PService p2PService;
private final PriceFeedService priceFeedService;
private final TradeStatistics2StorageService tradeStatistics2StorageService;
private final TradeStatistics3StorageService tradeStatistics3StorageService;
private final File storageDir;
private final boolean dumpStatistics;
private final ObservableSet<TradeStatistics2> observableTradeStatisticsSet = FXCollections.observableSet();
private final ObservableSet<TradeStatistics3> observableTradeStatisticsSet = FXCollections.observableSet();
private JsonFileManager jsonFileManager;
@Inject
public TradeStatisticsManager(P2PService p2PService,
PriceFeedService priceFeedService,
TradeStatistics2StorageService tradeStatistics2StorageService,
TradeStatistics3StorageService tradeStatistics3StorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService,
@Named(Config.STORAGE_DIR) File storageDir,
@Named(Config.DUMP_STATISTICS) boolean dumpStatistics) {
this.p2PService = p2PService;
this.priceFeedService = priceFeedService;
this.tradeStatistics2StorageService = tradeStatistics2StorageService;
this.tradeStatistics3StorageService = tradeStatistics3StorageService;
this.storageDir = storageDir;
this.dumpStatistics = dumpStatistics;
jsonFileManager = new JsonFileManager(storageDir);
appendOnlyDataStoreService.addService(tradeStatistics2StorageService);
appendOnlyDataStoreService.addService(tradeStatistics3StorageService);
}
public void onAllServicesInitialized() {
p2PService.getP2PDataStorage().addAppendOnlyDataStoreListener(payload -> {
if (payload instanceof TradeStatistics2)
addToSet((TradeStatistics2) payload);
if (payload instanceof TradeStatistics3) {
TradeStatistics3 tradeStatistics = (TradeStatistics3) payload;
if (!tradeStatistics.isValid()) {
return;
}
observableTradeStatisticsSet.add(tradeStatistics);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
maybeDump();
}
});
Set<TradeStatistics2> set = tradeStatistics2StorageService.getMapOfAllData().values().stream()
.filter(e -> e instanceof TradeStatistics2)
.map(e -> (TradeStatistics2) e)
.map(WrapperTradeStatistics2::new)
.distinct()
.map(WrapperTradeStatistics2::unwrap)
.filter(TradeStatistics2::isValid)
Set<TradeStatistics3> set = tradeStatistics3StorageService.getMapOfAllData().values().stream()
.filter(e -> e instanceof TradeStatistics3)
.map(e -> (TradeStatistics3) e)
.filter(TradeStatistics3::isValid)
.collect(Collectors.toSet());
observableTradeStatisticsSet.addAll(set);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
dump();
maybeDump();
}
public ObservableSet<TradeStatistics2> getObservableTradeStatisticsSet() {
public ObservableSet<TradeStatistics3> getObservableTradeStatisticsSet() {
return observableTradeStatisticsSet;
}
private void addToSet(TradeStatistics2 tradeStatistics) {
if (!observableTradeStatisticsSet.contains(tradeStatistics)) {
Optional<TradeStatistics2> duplicate = observableTradeStatisticsSet.stream().filter(
e -> e.getOfferId().equals(tradeStatistics.getOfferId())).findAny();
if (duplicate.isPresent()) {
// TODO: Can be removed as soon as everyone uses v1.2.6+
// Removes an existing object with a trade id if the new one matches the existing except
// for the deposit tx id
if (tradeStatistics.getDepositTxId() == null &&
tradeStatistics.isValid() &&
duplicate.get().compareTo(tradeStatistics) == 0) {
observableTradeStatisticsSet.remove(duplicate.get());
} else {
return;
}
}
if (!tradeStatistics.isValid()) {
return;
}
observableTradeStatisticsSet.add(tradeStatistics);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
dump();
private void maybeDump() {
if (!dumpStatistics) {
return;
}
}
private void dump() {
if (dumpStatistics) {
if (jsonFileManager == null) {
jsonFileManager = new JsonFileManager(storageDir);
// We only dump once the currencies as they do not change during runtime
ArrayList<CurrencyTuple> fiatCurrencyList = CurrencyUtil.getAllSortedFiatCurrencies().stream()
.map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8))
.collect(Collectors.toCollection(ArrayList::new));
@ -138,44 +120,15 @@ public class TradeStatisticsManager {
.collect(Collectors.toCollection(ArrayList::new));
cryptoCurrencyList.add(0, new CurrencyTuple(Res.getBaseCurrencyCode(), Res.getBaseCurrencyName(), 8));
jsonFileManager.writeToDisc(Utilities.objectToJson(cryptoCurrencyList), "crypto_currency_list");
// We store the statistics as json so it is easy for further processing (e.g. for web based services)
// TODO This is just a quick solution for storing to one file.
// 1 statistic entry has 500 bytes as json.
// Need a more scalable solution later when we get more volume.
// The flag will only be activated by dedicated nodes, so it should not be too critical for the moment, but needs to
// get improved. Maybe a LevelDB like DB...? Could be impl. in a headless version only.
List<TradeStatisticsForJson> list = observableTradeStatisticsSet.stream().map(TradeStatisticsForJson::new)
.sorted((o1, o2) -> (Long.compare(o2.tradeDate, o1.tradeDate)))
.collect(Collectors.toList());
TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()];
list.toArray(array);
jsonFileManager.writeToDisc(Utilities.objectToJson(array), "trade_statistics");
}
List<TradeStatisticsForJson> list = observableTradeStatisticsSet.stream()
.map(TradeStatisticsForJson::new)
.sorted((o1, o2) -> (Long.compare(o2.tradeDate, o1.tradeDate)))
.collect(Collectors.toList());
TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()];
list.toArray(array);
jsonFileManager.writeToDisc(Utilities.objectToJson(array), "trade_statistics");
}
static class WrapperTradeStatistics2 {
private final TradeStatistics2 tradeStatistics;
public WrapperTradeStatistics2(TradeStatistics2 tradeStatistics) {
this.tradeStatistics = tradeStatistics;
}
public TradeStatistics2 unwrap() {
return this.tradeStatistics;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
var wrapper = (WrapperTradeStatistics2) obj;
return Objects.equals(tradeStatistics.getOfferId(), wrapper.tradeStatistics.getOfferId());
}
@Override
public int hashCode() {
return Objects.hash(tradeStatistics.getOfferId());
}
}
}