Add TradeStatsisticsManager, add CoreOptionKeys, rename method in Storage

This commit is contained in:
Manfred Karrer 2016-07-25 10:16:06 +02:00
parent 242efeecb9
commit a6660d04c9
23 changed files with 202 additions and 177 deletions

View File

@ -2,5 +2,5 @@ package io.bitsquare.common;
public class CommonOptionKeys { public class CommonOptionKeys {
public static final String LOG_LEVEL_KEY = "logLevel"; public static final String LOG_LEVEL_KEY = "logLevel";
public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg";
} }

View File

@ -77,7 +77,7 @@ public class Storage<T extends Serializable> {
} }
@Nullable @Nullable
public T initAndGetPersisted(String fileName) { public T initAndGetPersistedWithFileName(String fileName) {
this.fileName = fileName; this.fileName = fileName;
storageFile = new File(dir, fileName); storageFile = new File(dir, fileName);
fileManager = new FileManager<>(dir, storageFile, 300); fileManager = new FileManager<>(dir, storageFile, 300);

View File

@ -19,7 +19,7 @@ package io.bitsquare.alert;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.p2p.P2PService; import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener; import io.bitsquare.p2p.storage.HashMapChangedListener;
@ -56,7 +56,7 @@ public class AlertManager {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CoreOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService; this.p2PService = p2PService;
this.keyRing = keyRing; this.keyRing = keyRing;
this.user = user; this.user = user;

View File

@ -19,10 +19,13 @@ package io.bitsquare.alert;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import io.bitsquare.app.AppModule; import io.bitsquare.app.AppModule;
import io.bitsquare.app.CoreOptionKeys;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import static com.google.inject.name.Names.named;
public class AlertModule extends AppModule { public class AlertModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(AlertModule.class); private static final Logger log = LoggerFactory.getLogger(AlertModule.class);
@ -34,5 +37,6 @@ public class AlertModule extends AppModule {
protected final void configure() { protected final void configure() {
bind(AlertManager.class).in(Singleton.class); bind(AlertManager.class).in(Singleton.class);
bind(PrivateNotificationManager.class).in(Singleton.class); bind(PrivateNotificationManager.class).in(Singleton.class);
bindConstant().annotatedWith(named(CoreOptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY));
} }
} }

View File

@ -19,7 +19,7 @@ package io.bitsquare.alert;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.DecryptedMsgWithPubKey; import io.bitsquare.crypto.DecryptedMsgWithPubKey;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
@ -58,7 +58,7 @@ public class PrivateNotificationManager {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(CoreOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService; this.p2PService = p2PService;
this.keyRing = keyRing; this.keyRing = keyRing;

View File

@ -85,7 +85,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
private final String btcNetworkDir; private final String btcNetworkDir;
private final String logLevel; private final String logLevel;
private BitcoinNetwork bitcoinNetwork; private BitcoinNetwork bitcoinNetwork;
private final String btcSeedNodes, seedNodes, ignoreDevMsg, useTorForBtc, myAddress, banList; private final String btcSeedNodes, seedNodes, ignoreDevMsg, useTorForBtc, myAddress, banList, dumpStatistics;
public BitsquareEnvironment(OptionSet options) { public BitsquareEnvironment(OptionSet options) {
this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull( this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(
@ -153,8 +153,8 @@ public class BitsquareEnvironment extends StandardEnvironment {
(String) commandLineProperties.getProperty(NetworkOptionKeys.BAN_LIST) : (String) commandLineProperties.getProperty(NetworkOptionKeys.BAN_LIST) :
""; "";
ignoreDevMsg = commandLineProperties.containsProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY) ? ignoreDevMsg = commandLineProperties.containsProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY) ?
(String) commandLineProperties.getProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY) : (String) commandLineProperties.getProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY) :
""; "";
btcSeedNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_SEED_NODES) ? btcSeedNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_SEED_NODES) ?
@ -165,6 +165,10 @@ public class BitsquareEnvironment extends StandardEnvironment {
(String) commandLineProperties.getProperty(BtcOptionKeys.USE_TOR_FOR_BTC) : (String) commandLineProperties.getProperty(BtcOptionKeys.USE_TOR_FOR_BTC) :
""; "";
dumpStatistics = commandLineProperties.containsProperty(CoreOptionKeys.DUMP_STATISTICS) ?
(String) commandLineProperties.getProperty(CoreOptionKeys.DUMP_STATISTICS) :
"";
MutablePropertySources propertySources = this.getPropertySources(); MutablePropertySources propertySources = this.getPropertySources();
propertySources.addFirst(commandLineProperties); propertySources.addFirst(commandLineProperties);
@ -226,7 +230,8 @@ public class BitsquareEnvironment extends StandardEnvironment {
setProperty(NetworkOptionKeys.SEED_NODES_KEY, seedNodes); setProperty(NetworkOptionKeys.SEED_NODES_KEY, seedNodes);
setProperty(NetworkOptionKeys.MY_ADDRESS, myAddress); setProperty(NetworkOptionKeys.MY_ADDRESS, myAddress);
setProperty(NetworkOptionKeys.BAN_LIST, banList); setProperty(NetworkOptionKeys.BAN_LIST, banList);
setProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg); setProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
setProperty(CoreOptionKeys.DUMP_STATISTICS, dumpStatistics);
setProperty(BtcOptionKeys.BTC_SEED_NODES, btcSeedNodes); setProperty(BtcOptionKeys.BTC_SEED_NODES, btcSeedNodes);
setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc); setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);

View File

@ -86,11 +86,15 @@ public abstract class BitsquareExecutable {
parser.accepts(NetworkOptionKeys.BAN_LIST, description("Nodes to exclude from network connections.", "")) parser.accepts(NetworkOptionKeys.BAN_LIST, description("Nodes to exclude from network connections.", ""))
.withRequiredArg(); .withRequiredArg();
parser.accepts(CommonOptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " + parser.accepts(CoreOptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " +
"(Global alert, Version update alert, Filters for offers, nodes or payment account data)", false)) "(Global alert, Version update alert, Filters for offers, nodes or payment account data)", false))
.withRequiredArg() .withRequiredArg()
.ofType(boolean.class); .ofType(boolean.class);
parser.accepts(CoreOptionKeys.DUMP_STATISTICS, description("If set to true the trade statistics are stored as json file in the data dir.", false))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(BtcOptionKeys.BTC_SEED_NODES, description("Custom seed nodes used for BitcoinJ.", "")) parser.accepts(BtcOptionKeys.BTC_SEED_NODES, description("Custom seed nodes used for BitcoinJ.", ""))
.withRequiredArg(); .withRequiredArg();
parser.accepts(BtcOptionKeys.USE_TOR_FOR_BTC, description("If set to true BitcoinJ is routed over our native Tor instance.", "")) parser.accepts(BtcOptionKeys.USE_TOR_FOR_BTC, description("If set to true BitcoinJ is routed over our native Tor instance.", ""))

View File

@ -0,0 +1,7 @@
package io.bitsquare.app;
public class CoreOptionKeys {
public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg";
public static final String DUMP_STATISTICS = "dumpStatistics";
}

View File

@ -135,7 +135,7 @@ public class WalletService {
useTor = preferences.getUseTorForBitcoinJ(); useTor = preferences.getUseTorForBitcoinJ();
storage = new Storage<>(walletDir); storage = new Storage<>(walletDir);
Long persisted = storage.initAndGetPersisted("BloomFilterNonce"); Long persisted = storage.initAndGetPersistedWithFileName("BloomFilterNonce");
if (persisted != null) { if (persisted != null) {
bloomFilterTweak = persisted; bloomFilterTweak = persisted;
} else { } else {

View File

@ -19,7 +19,7 @@ package io.bitsquare.filter;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.util.Tuple3; import io.bitsquare.common.util.Tuple3;
import io.bitsquare.common.util.Utilities; import io.bitsquare.common.util.Utilities;
@ -59,7 +59,7 @@ public class FilterManager {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CommonOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) { public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CoreOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService; this.p2PService = p2PService;
this.keyRing = keyRing; this.keyRing = keyRing;
this.user = user; this.user = user;

View File

@ -19,7 +19,7 @@ package io.bitsquare.filter;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import io.bitsquare.app.AppModule; import io.bitsquare.app.AppModule;
import io.bitsquare.common.CommonOptionKeys; import io.bitsquare.app.CoreOptionKeys;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -36,6 +36,6 @@ public class FilterModule extends AppModule {
@Override @Override
protected final void configure() { protected final void configure() {
bind(FilterManager.class).in(Singleton.class); bind(FilterManager.class).in(Singleton.class);
bindConstant().annotatedWith(named(CommonOptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(CommonOptionKeys.IGNORE_DEV_MSG_KEY)); bindConstant().annotatedWith(named(CoreOptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY));
} }
} }

View File

@ -84,7 +84,8 @@ public class TradeManager {
private final FailedTradesManager failedTradesManager; private final FailedTradesManager failedTradesManager;
private final ArbitratorManager arbitratorManager; private final ArbitratorManager arbitratorManager;
private final P2PService p2PService; private final P2PService p2PService;
private FilterManager filterManager; private final FilterManager filterManager;
private final TradeStatisticsManager tradeStatisticsManager;
private final Storage<TradableList<Trade>> tradableListStorage; private final Storage<TradableList<Trade>> tradableListStorage;
private final TradableList<Trade> trades; private final TradableList<Trade> trades;
@ -107,6 +108,7 @@ public class TradeManager {
P2PService p2PService, P2PService p2PService,
PriceFeed priceFeed, PriceFeed priceFeed,
FilterManager filterManager, FilterManager filterManager,
TradeStatisticsManager tradeStatisticsManager,
@Named(Storage.DIR_KEY) File storageDir) { @Named(Storage.DIR_KEY) File storageDir) {
this.user = user; this.user = user;
this.keyRing = keyRing; this.keyRing = keyRing;
@ -118,6 +120,7 @@ public class TradeManager {
this.arbitratorManager = arbitratorManager; this.arbitratorManager = arbitratorManager;
this.p2PService = p2PService; this.p2PService = p2PService;
this.filterManager = filterManager; this.filterManager = filterManager;
this.tradeStatisticsManager = tradeStatisticsManager;
tradableListStorage = new Storage<>(storageDir); tradableListStorage = new Storage<>(storageDir);
trades = new TradableList<>(tradableListStorage, "PendingTrades"); trades = new TradableList<>(tradableListStorage, "PendingTrades");
@ -197,17 +200,7 @@ public class TradeManager {
toRemove.add(trade); toRemove.add(trade);
} }
// Only offerer publishes statistic data of trades addTradeStatistics(trade);
if (isMyOffer(trade.getOffer()) && isTradeDateValidForStatistics(trade)) {
TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
trade.getContractHash(),
keyRing.getPubKeyRing());
p2PService.addData(tradeStatistics, true);
}
} }
for (Trade trade : toAdd) for (Trade trade : toAdd)
addTradeToFailedTrades(trade); addTradeToFailedTrades(trade);
@ -218,25 +211,24 @@ public class TradeManager {
for (Tradable tradable : closedTradableManager.getClosedTrades()) { for (Tradable tradable : closedTradableManager.getClosedTrades()) {
if (tradable instanceof Trade) { if (tradable instanceof Trade) {
Trade trade = (Trade) tradable; Trade trade = (Trade) tradable;
// Only offerer publishes statistic data of trades addTradeStatistics(trade);
if (isMyOffer(trade.getOffer()) && isTradeDateValidForStatistics(trade)) {
TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
trade.getContractHash(),
keyRing.getPubKeyRing());
p2PService.addData(tradeStatistics, true);
}
} }
} }
pendingTradesInitialized.set(true); pendingTradesInitialized.set(true);
} }
private boolean isTradeDateValidForStatistics(Trade trade) { private void addTradeStatistics(Trade trade) {
return (new Date().getTime() - trade.getDate().getTime()) < TimeUnit.DAYS.toMillis(20); TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
keyRing.getPubKeyRing());
tradeStatisticsManager.add(tradeStatistics);
// Only offerer publishes statistic data of trades, only trades from last 20 days
if (isMyOffer(trade.getOffer()) && (new Date().getTime() - trade.getDate().getTime()) < TimeUnit.DAYS.toMillis(20))
p2PService.addData(tradeStatistics, true);
} }
private void handleInitialTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress) { private void handleInitialTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress) {

View File

@ -19,12 +19,15 @@ package io.bitsquare.trade;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import io.bitsquare.app.AppModule; import io.bitsquare.app.AppModule;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.trade.closed.ClosedTradableManager; import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.failed.FailedTradesManager; import io.bitsquare.trade.failed.FailedTradesManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import static com.google.inject.name.Names.named;
public class TradeModule extends AppModule { public class TradeModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(TradeModule.class); private static final Logger log = LoggerFactory.getLogger(TradeModule.class);
@ -35,7 +38,9 @@ public class TradeModule extends AppModule {
@Override @Override
protected void configure() { protected void configure() {
bind(TradeManager.class).in(Singleton.class); bind(TradeManager.class).in(Singleton.class);
bind(TradeStatisticsManager.class).in(Singleton.class);
bind(ClosedTradableManager.class).in(Singleton.class); bind(ClosedTradableManager.class).in(Singleton.class);
bind(FailedTradesManager.class).in(Singleton.class); bind(FailedTradesManager.class).in(Singleton.class);
bindConstant().annotatedWith(named(CoreOptionKeys.DUMP_STATISTICS)).to(env.getRequiredProperty(CoreOptionKeys.DUMP_STATISTICS));
} }
} }

View File

@ -2,6 +2,7 @@ package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.common.crypto.PubKeyRing; import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.util.JsonExclude;
import io.bitsquare.p2p.storage.payload.CapabilityRequiringPayload; import io.bitsquare.p2p.storage.payload.CapabilityRequiringPayload;
import io.bitsquare.p2p.storage.payload.StoragePayload; import io.bitsquare.p2p.storage.payload.StoragePayload;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
@ -16,29 +17,40 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public final class TradeStatistics implements StoragePayload, CapabilityRequiringPayload { public final class TradeStatistics implements StoragePayload, CapabilityRequiringPayload {
@JsonExclude
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION; private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
@JsonExclude
public static final long TTL = TimeUnit.DAYS.toMillis(10); public static final long TTL = TimeUnit.DAYS.toMillis(10);
public final Offer offer; public final String currency;
public final long tradePriceAsLong; public final Offer.Direction direction;
public final long tradeAmountAsLong; public final long tradePrice;
public final long tradeDateAsTime; 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 depositTxId; public final String depositTxId;
public final byte[] contractHash; @JsonExclude
public final PubKeyRing pubKeyRing; public final PubKeyRing pubKeyRing;
public final int protocolVersion; public TradeStatistics(Offer offer, Fiat tradePrice, Coin tradeAmount, Date tradeDate, String depositTxId, PubKeyRing pubKeyRing) {
this.direction = offer.getDirection();
public TradeStatistics(Offer offer, Fiat tradePrice, Coin tradeAmount, Date tradeDate, String depositTxId, byte[] contractHash, PubKeyRing pubKeyRing) { this.currency = offer.getCurrencyCode();
this.offer = offer; this.paymentMethod = offer.getPaymentMethod().getId();
this.offerDate = offer.getDate().getTime();
this.useMarketBasedPrice = offer.getUseMarketBasedPrice();
this.marketPriceMargin = offer.getMarketPriceMargin();
this.offerAmount = offer.getAmount().value;
this.offerMinAmount = offer.getMinAmount().value;
this.tradePrice = tradePrice.longValue();
this.tradeAmount = tradeAmount.value;
this.tradeDate = tradeDate.getTime();
this.depositTxId = depositTxId; this.depositTxId = depositTxId;
this.tradePriceAsLong = tradePrice.longValue();
tradeAmountAsLong = tradeAmount.value;
this.tradeDateAsTime = tradeDate.getTime();
this.contractHash = contractHash;
this.pubKeyRing = pubKeyRing; this.pubKeyRing = pubKeyRing;
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
} }
@Override @Override
@ -59,15 +71,15 @@ public final class TradeStatistics implements StoragePayload, CapabilityRequirin
} }
public Date getTradeDate() { public Date getTradeDate() {
return new Date(tradeDateAsTime); return new Date(tradeDate);
} }
public Fiat getTradePrice() { public Fiat getTradePrice() {
return Fiat.valueOf(offer.getCurrencyCode(), tradePriceAsLong); return Fiat.valueOf(currency, tradePrice);
} }
public Coin getTradeAmount() { public Coin getTradeAmount() {
return Coin.valueOf(tradeAmountAsLong); return Coin.valueOf(tradeAmount);
} }
public Fiat getTradeVolume() { public Fiat getTradeVolume() {
@ -76,68 +88,4 @@ public final class TradeStatistics implements StoragePayload, CapabilityRequirin
else else
return null; return null;
} }
// We compare the objects of both traders to match.
// pubKeyRing is not matching so we excluded it
public boolean isSameTrade(TradeStatistics o) {
if (this == o) return true;
if (!(o instanceof TradeStatistics)) return false;
TradeStatistics that = (TradeStatistics) o;
if (tradePriceAsLong != that.tradePriceAsLong) return false;
if (tradeAmountAsLong != that.tradeAmountAsLong) return false;
if (tradeDateAsTime != that.tradeDateAsTime) return false;
if (protocolVersion != that.protocolVersion) return false;
if (offer != null ? !offer.equals(that.offer) : that.offer != null) return false;
if (depositTxId != null ? !depositTxId.equals(that.depositTxId) : that.depositTxId != null) return false;
return Arrays.equals(contractHash, that.contractHash);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TradeStatistics)) return false;
TradeStatistics that = (TradeStatistics) o;
if (tradePriceAsLong != that.tradePriceAsLong) return false;
if (tradeAmountAsLong != that.tradeAmountAsLong) return false;
if (tradeDateAsTime != that.tradeDateAsTime) return false;
if (protocolVersion != that.protocolVersion) return false;
if (offer != null ? !offer.equals(that.offer) : that.offer != null) return false;
if (depositTxId != null ? !depositTxId.equals(that.depositTxId) : that.depositTxId != null) return false;
if (!Arrays.equals(contractHash, that.contractHash)) return false;
return !(pubKeyRing != null ? !pubKeyRing.equals(that.pubKeyRing) : that.pubKeyRing != null);
}
@Override
public int hashCode() {
int result = offer != null ? offer.hashCode() : 0;
result = 31 * result + (int) (tradePriceAsLong ^ (tradePriceAsLong >>> 32));
result = 31 * result + (int) (tradeAmountAsLong ^ (tradeAmountAsLong >>> 32));
result = 31 * result + (int) (tradeDateAsTime ^ (tradeDateAsTime >>> 32));
result = 31 * result + (depositTxId != null ? depositTxId.hashCode() : 0);
result = 31 * result + (contractHash != null ? Arrays.hashCode(contractHash) : 0);
result = 31 * result + (pubKeyRing != null ? pubKeyRing.hashCode() : 0);
result = 31 * result + protocolVersion;
return result;
}
@Override
public String toString() {
return "TradeStatistics{" +
"offer=" + offer +
", tradePriceAsLong=" + tradePriceAsLong +
", tradeAmountAsLong=" + tradeAmountAsLong +
", tradeDateAsTime=" + tradeDateAsTime +
", depositTxId='" + depositTxId + '\'' +
", contractHash=" + Arrays.toString(contractHash) +
", pubKeyRing=" + pubKeyRing +
", protocolVersion=" + protocolVersion +
'}';
}
} }

View File

@ -0,0 +1,84 @@
package io.bitsquare.trade;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.payload.StoragePayload;
import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry;
import io.bitsquare.storage.Storage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
public class TradeStatisticsManager {
private static final Logger log = LoggerFactory.getLogger(TradeStatisticsManager.class);
private final Storage<HashSet<TradeStatistics>> storage;
private Storage<String> jsonStorage;
private boolean dumpStatistics;
private ObservableSet<TradeStatistics> observableTradeStatisticsSet = FXCollections.observableSet();
private HashSet<TradeStatistics> tradeStatisticsSet = new HashSet<>();
@Inject
public TradeStatisticsManager(Storage<HashSet<TradeStatistics>> storage, Storage<String> jsonStorage, P2PService p2PService, @Named(CoreOptionKeys.DUMP_STATISTICS) boolean dumpStatistics) {
this.storage = storage;
this.jsonStorage = jsonStorage;
this.dumpStatistics = dumpStatistics;
if (dumpStatistics)
this.jsonStorage.initAndGetPersistedWithFileName("trade_statistics.json");
HashSet<TradeStatistics> persisted = storage.initAndGetPersistedWithFileName("TradeStatistics");
if (persisted != null)
observableTradeStatisticsSet = FXCollections.observableSet(persisted);
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
@Override
public void onAdded(ProtectedStorageEntry data) {
final StoragePayload storagePayload = data.getStoragePayload();
if (storagePayload instanceof TradeStatistics) {
add((TradeStatistics) storagePayload);
}
}
@Override
public void onRemoved(ProtectedStorageEntry data) {
// We don't remove items
}
});
}
public void add(TradeStatistics tradeStatistics) {
if (!observableTradeStatisticsSet.contains(tradeStatistics)) {
observableTradeStatisticsSet.add(tradeStatistics);
tradeStatisticsSet.add(tradeStatistics);
storage.queueUpForSave(tradeStatisticsSet, 2000);
if (dumpStatistics) {
// 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<TradeStatistics> list = tradeStatisticsSet.stream().collect(Collectors.toList());
list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? 1 : (o1.tradeDate == o2.tradeDate ? 0 : -1)));
TradeStatistics[] array = new TradeStatistics[tradeStatisticsSet.size()];
list.toArray(array);
jsonStorage.queueUpForSave(Utilities.objectToJson(array), 5_000);
}
}
}
public ObservableSet<TradeStatistics> getObservableTradeStatisticsSet() {
return observableTradeStatisticsSet;
}
}

View File

@ -144,6 +144,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
transient private OfferAvailabilityProtocol availabilityProtocol; transient private OfferAvailabilityProtocol availabilityProtocol;
@JsonExclude @JsonExclude
transient private StringProperty errorMessageProperty = new SimpleStringProperty(); transient private StringProperty errorMessageProperty = new SimpleStringProperty();
@JsonExclude
transient private PriceFeed priceFeed; transient private PriceFeed priceFeed;

View File

@ -40,7 +40,6 @@ public class PublishTradeStatistics extends TradeTask {
trade.getTradeAmount(), trade.getTradeAmount(),
trade.getDate(), trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""), (trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
trade.getContractHash(),
processModel.getPubKeyRing()); processModel.getPubKeyRing());
processModel.getP2PService().addData(tradeStatistics, true); processModel.getP2PService().addData(tradeStatistics, true);
complete(); complete();

View File

@ -485,14 +485,14 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
public void updateItem(final TradeStatistics item, boolean empty) { public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) if (item != null)
setText(formatter.getDirection(item.offer.getDirection())); setText(formatter.getDirection(item.direction));
else else
setText(""); setText("");
} }
}; };
} }
}); });
directionColumn.setComparator((o1, o2) -> o1.offer.getDirection().compareTo(o2.offer.getDirection())); directionColumn.setComparator((o1, o2) -> o1.direction.compareTo(o2.direction));
tableView.getColumns().add(directionColumn); tableView.getColumns().add(directionColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

View File

@ -23,16 +23,14 @@ import io.bitsquare.gui.common.model.ActivatableViewModel;
import io.bitsquare.gui.main.markets.trades.charts.CandleData; import io.bitsquare.gui.main.markets.trades.charts.CandleData;
import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency; import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.payload.StoragePayload;
import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry;
import io.bitsquare.trade.TradeStatistics; import io.bitsquare.trade.TradeStatistics;
import io.bitsquare.trade.TradeStatisticsManager;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.SetChangeListener;
import javafx.scene.chart.XYChart; import javafx.scene.chart.XYChart;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -58,13 +56,12 @@ class TradesChartsViewModel extends ActivatableViewModel {
MINUTE MINUTE
} }
private final TradeStatisticsManager tradeStatisticsManager;
final Preferences preferences; final Preferences preferences;
private P2PService p2PService;
private final HashMapChangedListener mapChangedListener; private final SetChangeListener<TradeStatistics> setChangeListener;
final ObjectProperty<TradeCurrency> tradeCurrencyProperty = new SimpleObjectProperty<>(); final ObjectProperty<TradeCurrency> tradeCurrencyProperty = new SimpleObjectProperty<>();
private final Set<TradeStatistics> allTradeStatistics = new HashSet<>();
final ObservableList<TradeStatistics> tradeStatisticsByCurrency = FXCollections.observableArrayList(); final ObservableList<TradeStatistics> tradeStatisticsByCurrency = FXCollections.observableArrayList();
ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList(); ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList();
ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList(); ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList();
@ -78,25 +75,11 @@ class TradesChartsViewModel extends ActivatableViewModel {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public TradesChartsViewModel(P2PService p2PService, Preferences preferences) { public TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences) {
this.p2PService = p2PService; this.tradeStatisticsManager = tradeStatisticsManager;
this.preferences = preferences; this.preferences = preferences;
mapChangedListener = new HashMapChangedListener() { setChangeListener = change -> updateChartData();
@Override
public void onAdded(ProtectedStorageEntry data) {
addItem(data.getStoragePayload(), true);
}
@Override
public void onRemoved(ProtectedStorageEntry data) {
final StoragePayload storagePayload = data.getStoragePayload();
if (storagePayload instanceof TradeStatistics && allTradeStatistics.contains(storagePayload)) {
allTradeStatistics.remove(storagePayload);
updateChartData();
}
}
};
Optional<TradeCurrency> tradeCurrencyOptional = CurrencyUtil.getTradeCurrency(preferences.getTradeStatisticsScreenCurrencyCode()); Optional<TradeCurrency> tradeCurrencyOptional = CurrencyUtil.getTradeCurrency(preferences.getTradeStatisticsScreenCurrencyCode());
if (tradeCurrencyOptional.isPresent()) if (tradeCurrencyOptional.isPresent())
@ -110,21 +93,21 @@ class TradesChartsViewModel extends ActivatableViewModel {
@VisibleForTesting @VisibleForTesting
TradesChartsViewModel() { TradesChartsViewModel() {
mapChangedListener = null; setChangeListener = null;
preferences = null; preferences = null;
tradeStatisticsManager = null;
} }
@Override @Override
protected void activate() { protected void activate() {
p2PService.getDataMap().entrySet().stream().forEach(e -> addItem(e.getValue().getStoragePayload(), false)); tradeStatisticsManager.getObservableTradeStatisticsSet().addListener(setChangeListener);
p2PService.addHashSetChangedListener(mapChangedListener);
updateChartData(); updateChartData();
} }
@Override @Override
protected void deactivate() { protected void deactivate() {
p2PService.removeHashMapChangedListener(mapChangedListener); tradeStatisticsManager.getObservableTradeStatisticsSet().removeListener(setChangeListener);
} }
@ -166,24 +149,17 @@ class TradesChartsViewModel extends ActivatableViewModel {
// Private // Private
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void addItem(StoragePayload storagePayload, boolean doUpdate) {
if (storagePayload instanceof TradeStatistics && !allTradeStatistics.contains(storagePayload)) {
allTradeStatistics.add((TradeStatistics) storagePayload);
if (doUpdate)
updateChartData();
}
}
private void updateChartData() { private void updateChartData() {
tradeStatisticsByCurrency.setAll(allTradeStatistics.stream() tradeStatisticsByCurrency.setAll(tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.offer.getCurrencyCode().equals(getCurrencyCode())) .filter(e -> e.currency.equals(getCurrencyCode()))
.collect(Collectors.toList())); .collect(Collectors.toList()));
// Get all entries for the defined time interval // Get all entries for the defined time interval
Map<Long, Set<TradeStatistics>> itemsPerInterval = new HashMap<>(); Map<Long, Set<TradeStatistics>> itemsPerInterval = new HashMap<>();
tradeStatisticsByCurrency.stream().forEach(e -> { tradeStatisticsByCurrency.stream().forEach(e -> {
Set<TradeStatistics> set; Set<TradeStatistics> set;
final long time = getTickFromTime(e.tradeDateAsTime, tickUnit); final long time = getTickFromTime(e.tradeDate, tickUnit);
final long now = getTickFromTime(new Date().getTime(), tickUnit); final long now = getTickFromTime(new Date().getTime(), tickUnit);
long index = maxTicks - (now - time); long index = maxTicks - (now - time);
if (itemsPerInterval.containsKey(index)) { if (itemsPerInterval.containsKey(index)) {
@ -220,19 +196,19 @@ class TradesChartsViewModel extends ActivatableViewModel {
long accumulatedAmount = 0; long accumulatedAmount = 0;
for (TradeStatistics item : set) { for (TradeStatistics item : set) {
final long tradePriceAsLong = item.tradePriceAsLong; final long tradePriceAsLong = item.tradePrice;
low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong; low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong;
high = (high != 0) ? Math.max(high, tradePriceAsLong) : tradePriceAsLong; high = (high != 0) ? Math.max(high, tradePriceAsLong) : tradePriceAsLong;
accumulatedVolume += item.getTradeVolume().value; accumulatedVolume += (item.getTradeVolume() != null) ? item.getTradeVolume().value : 0;
accumulatedAmount += item.tradeAmountAsLong; accumulatedAmount += item.tradeAmount;
} }
long averagePrice = Math.round(accumulatedVolume * Coin.COIN.value / accumulatedAmount); long averagePrice = Math.round(accumulatedVolume * Coin.COIN.value / accumulatedAmount);
List<TradeStatistics> list = new ArrayList<>(set); List<TradeStatistics> list = new ArrayList<>(set);
list.sort((o1, o2) -> (o1.tradeDateAsTime < o2.tradeDateAsTime ? -1 : (o1.tradeDateAsTime == o2.tradeDateAsTime ? 0 : 1))); list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? -1 : (o1.tradeDate == o2.tradeDate ? 0 : 1)));
if (list.size() > 0) { if (list.size() > 0) {
open = list.get(0).tradePriceAsLong; open = list.get(0).tradePrice;
close = list.get(list.size() - 1).tradePriceAsLong; close = list.get(list.size() - 1).tradePrice;
} }
boolean isBullish = close > open; boolean isBullish = close > open;
return new CandleData(tick, open, close, high, low, averagePrice, accumulatedAmount, accumulatedVolume, isBullish); return new CandleData(tick, open, close, high, low, averagePrice, accumulatedAmount, accumulatedVolume, isBullish);

View File

@ -99,7 +99,7 @@ public class GUIUtil {
String directory = Paths.get(path).getParent().toString(); String directory = Paths.get(path).getParent().toString();
preferences.setDefaultPath(directory); preferences.setDefaultPath(directory);
Storage<ArrayList<PaymentAccount>> paymentAccountsStorage = new Storage<>(new File(directory)); Storage<ArrayList<PaymentAccount>> paymentAccountsStorage = new Storage<>(new File(directory));
ArrayList<PaymentAccount> persisted = paymentAccountsStorage.initAndGetPersisted(fileName); ArrayList<PaymentAccount> persisted = paymentAccountsStorage.initAndGetPersistedWithFileName(fileName);
if (persisted != null) { if (persisted != null) {
final StringBuilder msg = new StringBuilder(); final StringBuilder msg = new StringBuilder();
persisted.stream().forEach(paymentAccount -> { persisted.stream().forEach(paymentAccount -> {

View File

@ -51,10 +51,10 @@ public class TradesChartsViewModelTest {
null, null,
null, null,
null); null);
set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "520"), Coin.parseCoin("1"), new Date(now.getTime()), null, null, null)); set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "520"), Coin.parseCoin("1"), new Date(now.getTime()), null, null));
set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "500"), Coin.parseCoin("1"), new Date(now.getTime() + 100), null, null, null)); set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "500"), Coin.parseCoin("1"), new Date(now.getTime() + 100), null, null));
set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "600"), Coin.parseCoin("1"), new Date(now.getTime() + 200), null, null, null)); set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "600"), Coin.parseCoin("1"), new Date(now.getTime() + 200), null, null));
set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "580"), Coin.parseCoin("1"), new Date(now.getTime() + 300), null, null, null)); set.add(new TradeStatistics(offer, Fiat.parseFiat("EUR", "580"), Coin.parseCoin("1"), new Date(now.getTime() + 300), null, null));
CandleData candleData = model.getCandleData(model.getTickFromTime(now.getTime(), TradesChartsViewModel.TickUnit.DAY), set); CandleData candleData = model.getCandleData(model.getTickFromTime(now.getTime(), TradesChartsViewModel.TickUnit.DAY), set);
assertEquals(open, candleData.open); assertEquals(open, candleData.open);

View File

@ -95,7 +95,7 @@ public class PeerManager implements ConnectionListener {
this.seedNodeAddresses = new HashSet<>(seedNodeAddresses); this.seedNodeAddresses = new HashSet<>(seedNodeAddresses);
networkNode.addConnectionListener(this); networkNode.addConnectionListener(this);
dbStorage = new Storage<>(storageDir); dbStorage = new Storage<>(storageDir);
HashSet<Peer> persistedPeers = dbStorage.initAndGetPersisted("PersistedPeers"); HashSet<Peer> persistedPeers = dbStorage.initAndGetPersistedWithFileName("PersistedPeers");
if (persistedPeers != null) { if (persistedPeers != null) {
log.info("We have persisted reported peers. persistedPeers.size()=" + persistedPeers.size()); log.info("We have persisted reported peers. persistedPeers.size()=" + persistedPeers.size());
this.persistedPeers.addAll(persistedPeers); this.persistedPeers.addAll(persistedPeers);

View File

@ -72,7 +72,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
storage = new Storage<>(storageDir); storage = new Storage<>(storageDir);
HashMap<ByteArray, MapValue> persisted = storage.initAndGetPersisted("SequenceNumberMap"); HashMap<ByteArray, MapValue> persisted = storage.initAndGetPersistedWithFileName("SequenceNumberMap");
if (persisted != null) if (persisted != null)
sequenceNumberMap = getPurgedSequenceNumberMap(persisted); sequenceNumberMap = getPurgedSequenceNumberMap(persisted);
} }