mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 23:18:17 +01:00
Merge pull request #4611 from chimp1984/new-trade-statistics
New trade statistics
This commit is contained in:
commit
3687a03695
64 changed files with 1170 additions and 975 deletions
|
@ -41,5 +41,6 @@ public enum Capability {
|
|||
MEDIATION, // Supports mediation feature
|
||||
REFUND_AGENT, // Supports refund agents
|
||||
TRADE_STATISTICS_HASH_UPDATE, // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data.
|
||||
NO_ADDRESS_PRE_FIX // At 1.4.0 we removed the prefix filter for mailbox messages. If a peer has that capability we do not sent the prefix.
|
||||
NO_ADDRESS_PRE_FIX, // At 1.4.0 we removed the prefix filter for mailbox messages. If a peer has that capability we do not sent the prefix.
|
||||
TRADE_STATISTICS_3 // We used a new reduced trade statistics model from v1.4.0 on
|
||||
}
|
||||
|
|
|
@ -128,7 +128,6 @@ public class Version {
|
|||
'}');
|
||||
}
|
||||
|
||||
//TODO move to consensus area
|
||||
public static final byte COMPENSATION_REQUEST = (byte) 0x01;
|
||||
public static final byte REIMBURSEMENT_REQUEST = (byte) 0x01;
|
||||
public static final byte PROPOSAL = (byte) 0x01;
|
||||
|
|
|
@ -24,8 +24,5 @@ package bisq.common.consensus;
|
|||
* Better to use the excludeFromJsonDataMap (annotated with @JsonExclude; used in PaymentAccountPayload) to
|
||||
* add a key/value pair.
|
||||
*/
|
||||
// TODO PubKeyRing and NodeAddress (network) are using UsedForTradeContractJson that is why it is in common module,
|
||||
// which is a bit weird... Maybe we need either rename common or split it to util and common where common is common code
|
||||
// used in network and core?
|
||||
public interface UsedForTradeContractJson {
|
||||
}
|
||||
|
|
|
@ -48,7 +48,6 @@ import java.util.Arrays;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// TODO is Hmac needed/make sense?
|
||||
public class Encryption {
|
||||
private static final Logger log = LoggerFactory.getLogger(Encryption.class);
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ import org.jetbrains.annotations.NotNull;
|
|||
|
||||
import static bisq.common.util.Preconditions.checkDir;
|
||||
|
||||
// TODO: use a password protection for key storage
|
||||
@Singleton
|
||||
public class KeyStorage {
|
||||
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);
|
||||
|
|
|
@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
@Slf4j
|
||||
public class JsonFileManager {
|
||||
private final ThreadPoolExecutor executor = Utilities.getThreadPoolExecutor("JsonFileManagerExecutor", 5, 50, 60);
|
||||
private final ThreadPoolExecutor executor;
|
||||
private final File dir;
|
||||
|
||||
|
||||
|
@ -41,6 +41,8 @@ public class JsonFileManager {
|
|||
public JsonFileManager(File dir) {
|
||||
this.dir = dir;
|
||||
|
||||
this.executor = Utilities.getThreadPoolExecutor("JsonFileManagerExecutor", 5, 50, 60);
|
||||
|
||||
if (!dir.exists())
|
||||
if (!dir.mkdir())
|
||||
log.warn("make dir failed");
|
||||
|
|
|
@ -48,7 +48,6 @@ public abstract class NetworkEnvelope implements Envelope {
|
|||
return getNetworkEnvelopeBuilder().build();
|
||||
}
|
||||
|
||||
// todo remove
|
||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||
return getNetworkEnvelopeBuilder().build();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import bisq.core.monetary.Price;
|
|||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
|
@ -196,7 +196,7 @@ public class CoreApi {
|
|||
walletsService.removeWalletPassword(password);
|
||||
}
|
||||
|
||||
public List<TradeStatistics2> getTradeStatistics() {
|
||||
public List<TradeStatistics3> getTradeStatistics() {
|
||||
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import bisq.core.offer.OpenOfferManager;
|
|||
import bisq.core.setup.CorePersistedDataHost;
|
||||
import bisq.core.setup.CoreSetup;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
@ -220,6 +221,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
|
|||
|
||||
try {
|
||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||
injector.getInstance(DaoSetup.class).shutDown();
|
||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||
|
|
|
@ -154,10 +154,10 @@ public class AssetService implements DaoSetupService, DaoStateListener {
|
|||
// TradeAmountDateTuple object holding only the data we need.
|
||||
Map<String, List<TradeAmountDateTuple>> lookupMap = new HashMap<>();
|
||||
tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> CurrencyUtil.isCryptoCurrency(e.getBaseCurrency()))
|
||||
.filter(e -> CurrencyUtil.isCryptoCurrency(e.getCurrency()))
|
||||
.forEach(e -> {
|
||||
lookupMap.putIfAbsent(e.getBaseCurrency(), new ArrayList<>());
|
||||
lookupMap.get(e.getBaseCurrency()).add(new TradeAmountDateTuple(e.getTradeAmount().getValue(), e.getTradeDate().getTime()));
|
||||
lookupMap.putIfAbsent(e.getCurrency(), new ArrayList<>());
|
||||
lookupMap.get(e.getCurrency()).add(new TradeAmountDateTuple(e.getAmount(), e.getDate()));
|
||||
});
|
||||
|
||||
getStatefulAssets().stream()
|
||||
|
|
|
@ -19,7 +19,7 @@ package bisq.core.offer.availability;
|
|||
|
||||
import bisq.core.support.dispute.agent.DisputeAgent;
|
||||
import bisq.core.support.dispute.agent.DisputeAgentManager;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
|
||||
import bisq.common.util.Tuple2;
|
||||
|
@ -42,36 +42,37 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||
|
||||
@Slf4j
|
||||
public class DisputeAgentSelection {
|
||||
public static final int LOOK_BACK_RANGE = 100;
|
||||
|
||||
public static <T extends DisputeAgent> T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
return getLeastUsedDisputeAgent(tradeStatisticsManager,
|
||||
disputeAgentManager,
|
||||
TradeStatistics2.MEDIATOR_ADDRESS);
|
||||
true);
|
||||
}
|
||||
|
||||
public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
return getLeastUsedDisputeAgent(tradeStatisticsManager,
|
||||
disputeAgentManager,
|
||||
TradeStatistics2.REFUND_AGENT_ADDRESS);
|
||||
false);
|
||||
}
|
||||
|
||||
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager,
|
||||
String extraMapKey) {
|
||||
boolean isMediator) {
|
||||
// We take last 100 entries from trade statistics
|
||||
List<TradeStatistics2> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
|
||||
list.sort(Comparator.comparing(TradeStatistics2::getTradeDate));
|
||||
List<TradeStatistics3> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
|
||||
list.sort(Comparator.comparing(TradeStatistics3::getDate));
|
||||
Collections.reverse(list);
|
||||
if (!list.isEmpty()) {
|
||||
int max = Math.min(list.size(), 100);
|
||||
int max = Math.min(list.size(), LOOK_BACK_RANGE);
|
||||
list = list.subList(0, max);
|
||||
}
|
||||
|
||||
// We stored only first 4 chars of disputeAgents onion address
|
||||
List<String> lastAddressesUsedInTrades = list.stream()
|
||||
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap() != null)
|
||||
.map(tradeStatistics2 -> tradeStatistics2.getExtraDataMap().get(extraMapKey))
|
||||
.map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ import bisq.core.payment.payload.VenmoAccountPayload;
|
|||
import bisq.core.payment.payload.WeChatPayAccountPayload;
|
||||
import bisq.core.payment.payload.WesternUnionAccountPayload;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
|
||||
import bisq.common.proto.ProtoResolver;
|
||||
import bisq.common.proto.ProtobufferRuntimeException;
|
||||
|
@ -178,6 +179,8 @@ public class CoreProtoResolver implements ProtoResolver {
|
|||
return BlindVotePayload.fromProto(proto.getBlindVotePayload());
|
||||
case SIGNED_WITNESS:
|
||||
return SignedWitness.fromProto(proto.getSignedWitness());
|
||||
case TRADE_STATISTICS3:
|
||||
return TradeStatistics3.fromProto(proto.getTradeStatistics3());
|
||||
default:
|
||||
throw new ProtobufferRuntimeException("Unknown proto message case (PB.PersistableNetworkPayload). messageCase=" + proto.getMessageCase());
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
|||
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.messages.RefreshTradeStateRequest;
|
||||
import bisq.core.trade.messages.TraderSignedWitnessMessage;
|
||||
import bisq.core.trade.statistics.TradeStatistics;
|
||||
|
||||
import bisq.network.p2p.AckMessage;
|
||||
import bisq.network.p2p.BundleOfEnvelopes;
|
||||
|
@ -265,9 +264,6 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
|||
return RefundAgent.fromProto(proto.getRefundAgent());
|
||||
case FILTER:
|
||||
return Filter.fromProto(proto.getFilter());
|
||||
case TRADE_STATISTICS:
|
||||
// Still used to convert TradeStatistics data from pre v0.6 versions
|
||||
return TradeStatistics.fromProto(proto.getTradeStatistics());
|
||||
case MAILBOX_STORAGE_PAYLOAD:
|
||||
return MailboxStoragePayload.fromProto(proto.getMailboxStoragePayload());
|
||||
case OFFER_PAYLOAD:
|
||||
|
|
|
@ -39,6 +39,7 @@ import bisq.core.support.dispute.mediation.MediationDisputeList;
|
|||
import bisq.core.support.dispute.refund.RefundDisputeList;
|
||||
import bisq.core.trade.TradableList;
|
||||
import bisq.core.trade.statistics.TradeStatistics2Store;
|
||||
import bisq.core.trade.statistics.TradeStatistics3Store;
|
||||
import bisq.core.user.PreferencesPayload;
|
||||
import bisq.core.user.UserPayload;
|
||||
|
||||
|
@ -126,6 +127,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
|
|||
return UnconfirmedBsqChangeOutputList.fromProto(proto.getUnconfirmedBsqChangeOutputList());
|
||||
case SIGNED_WITNESS_STORE:
|
||||
return SignedWitnessStore.fromProto(proto.getSignedWitnessStore());
|
||||
case TRADE_STATISTICS3_STORE:
|
||||
return TradeStatistics3Store.fromProto(proto.getTradeStatistics3Store());
|
||||
|
||||
default:
|
||||
throw new ProtobufferRuntimeException("Unknown proto message case(PB.PersistableEnvelope). " +
|
||||
|
|
|
@ -52,7 +52,6 @@ import javax.annotation.Nullable;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
// TODO use dao parameters for fee
|
||||
@Slf4j
|
||||
public class FeeService {
|
||||
|
||||
|
|
|
@ -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(TradeStatistics3::getTradeDate));
|
||||
TradeStatistics3 tradeStatistics = list.get(list.size() - 1);
|
||||
setBisqMarketPrice(tradeStatistics.getCurrency(), tradeStatistics.getTradePrice());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ public class CoreNetworkCapabilities {
|
|||
Capability.SIGNED_ACCOUNT_AGE_WITNESS,
|
||||
Capability.REFUND_AGENT,
|
||||
Capability.TRADE_STATISTICS_HASH_UPDATE,
|
||||
Capability.NO_ADDRESS_PRE_FIX
|
||||
Capability.NO_ADDRESS_PRE_FIX,
|
||||
Capability.TRADE_STATISTICS_3
|
||||
);
|
||||
|
||||
if (config.daoActivated) {
|
||||
|
|
|
@ -157,7 +157,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
@Override
|
||||
protected String getDisputeInfo(Dispute dispute) {
|
||||
String role = Res.get("shared.arbitrator").toLowerCase();
|
||||
String link = "https://docs.bisq.network/trading-rules.html#legacy-arbitration"; //TODO needs to be created
|
||||
String link = "https://docs.bisq.network/trading-rules.html#legacy-arbitration";
|
||||
return Res.get("support.initialInfo", role, role, link);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -24,7 +24,6 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
|
|||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
|
||||
|
@ -114,8 +113,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
|||
removeMailboxMessageAfterProcessing(message);
|
||||
}))
|
||||
.setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
|
||||
BuyerVerifiesFinalDelayedPayoutTx.class,
|
||||
PublishTradeStatistics.class)
|
||||
BuyerVerifiesFinalDelayedPayoutTx.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
stopTimeout();
|
||||
|
|
|
@ -23,13 +23,13 @@ import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
|
|||
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.messages.TradeMessage;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
|
||||
|
@ -39,12 +39,17 @@ import bisq.network.p2p.NodeAddress;
|
|||
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public abstract class SellerProtocol extends DisputeProtocol {
|
||||
enum SellerEvent implements FluentProtocol.Event {
|
||||
STARTUP,
|
||||
PAYMENT_RECEIVED
|
||||
}
|
||||
|
||||
|
@ -52,6 +57,27 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
super(trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialized() {
|
||||
super.onInitialized();
|
||||
|
||||
// We get called the constructor with any possible state and phase. As we don't want to log an error for such
|
||||
// cases we use the alternative 'given' method instead of 'expect'.
|
||||
|
||||
// We only re-publish for about 2 weeks after 1.4.0 release until most nodes have updated to
|
||||
// achieve sufficient resilience.
|
||||
boolean currentDateBeforeCutOffDate = new Date().before(Utilities.getUTCDate(2020, GregorianCalendar.NOVEMBER, 1));
|
||||
given(anyPhase(Trade.Phase.DEPOSIT_PUBLISHED,
|
||||
Trade.Phase.DEPOSIT_CONFIRMED,
|
||||
Trade.Phase.FIAT_SENT,
|
||||
Trade.Phase.FIAT_RECEIVED,
|
||||
Trade.Phase.PAYOUT_PUBLISHED)
|
||||
.with(SellerEvent.STARTUP)
|
||||
.preCondition(currentDateBeforeCutOffDate))
|
||||
.setup(tasks(SellerPublishesTradeStatistics.class))
|
||||
.executeTasks();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Mailbox
|
||||
|
@ -79,7 +105,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
|||
SellerFinalizesDelayedPayoutTx.class,
|
||||
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishesDepositTx.class,
|
||||
PublishTradeStatistics.class))
|
||||
SellerPublishesTradeStatistics.class))
|
||||
.run(() -> {
|
||||
// We stop timeout here and don't start a new one as the
|
||||
// SellerSendsDepositTxAndDelayedPayoutTxMessage repeats the send the message and has it's own
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks;
|
||||
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.network.TorNetworkNode;
|
||||
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class PublishTradeStatistics extends TradeTask {
|
||||
public PublishTradeStatistics(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
checkNotNull(trade.getDepositTx());
|
||||
|
||||
Map<String, String> extraDataMap = new HashMap<>();
|
||||
if (processModel.getReferralIdService().getOptionalReferralId().isPresent()) {
|
||||
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
|
||||
}
|
||||
|
||||
NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress());
|
||||
// The first 4 chars are sufficient to identify a mediator.
|
||||
// For testing with regtest/localhost we use the full address as its localhost and would result in
|
||||
// same values for multiple mediators.
|
||||
NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode();
|
||||
String address = networkNode instanceof TorNetworkNode ?
|
||||
mediatorNodeAddress.getFullAddress().substring(0, 4) :
|
||||
mediatorNodeAddress.getFullAddress();
|
||||
extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address);
|
||||
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
|
||||
trade.getTradePrice(),
|
||||
checkNotNull(trade.getTradeAmount()),
|
||||
trade.getDate(),
|
||||
trade.getDepositTxId(),
|
||||
extraDataMap);
|
||||
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.protocol.tasks.seller;
|
||||
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.NetworkNode;
|
||||
import bisq.network.p2p.network.TorNetworkNode;
|
||||
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class SellerPublishesTradeStatistics extends TradeTask {
|
||||
public SellerPublishesTradeStatistics(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
checkNotNull(trade.getDepositTx());
|
||||
|
||||
processModel.getP2PService().findPeersCapabilities(trade.getTradingPeerNodeAddress())
|
||||
.filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3))
|
||||
.ifPresentOrElse(capabilities -> {
|
||||
// Our peer has updated, so as we are the seller we will publish the trade statistics.
|
||||
// The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added)
|
||||
|
||||
Map<String, String> extraDataMap = new HashMap<>();
|
||||
if (processModel.getReferralIdService().getOptionalReferralId().isPresent()) {
|
||||
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
|
||||
}
|
||||
|
||||
NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress());
|
||||
// The first 4 chars are sufficient to identify a mediator.
|
||||
// For testing with regtest/localhost we use the full address as its localhost and would result in
|
||||
// same values for multiple mediators.
|
||||
NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode();
|
||||
String truncatedMediatorNodeAddress = networkNode instanceof TorNetworkNode ?
|
||||
mediatorNodeAddress.getFullAddress().substring(0, 4) :
|
||||
mediatorNodeAddress.getFullAddress();
|
||||
|
||||
NodeAddress refundAgentNodeAddress = checkNotNull(trade.getRefundAgentNodeAddress());
|
||||
String truncatedRefundAgentNodeAddress = networkNode instanceof TorNetworkNode ?
|
||||
refundAgentNodeAddress.getFullAddress().substring(0, 4) :
|
||||
refundAgentNodeAddress.getFullAddress();
|
||||
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
TradeStatistics3 tradeStatistics = new TradeStatistics3(offer.getCurrencyCode(),
|
||||
trade.getTradePrice().getValue(),
|
||||
trade.getTradeAmountAsLong(),
|
||||
offer.getPaymentMethod().getId(),
|
||||
trade.getTakeOfferDate().getTime(),
|
||||
truncatedMediatorNodeAddress,
|
||||
truncatedRefundAgentNodeAddress,
|
||||
extraDataMap);
|
||||
if (tradeStatistics.isValid()) {
|
||||
log.info("Publishing trade statistics");
|
||||
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||
} else {
|
||||
log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics);
|
||||
}
|
||||
|
||||
complete();
|
||||
},
|
||||
() -> {
|
||||
log.info("Our peer does not has updated yet, so they will publish the trade statistics. " +
|
||||
"To avoid duplicates we do not publish from our side.");
|
||||
complete();
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.AltcoinExchangeRate;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
|
||||
import bisq.network.p2p.storage.payload.ExpirablePayload;
|
||||
import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload;
|
||||
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
||||
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.proto.persistable.PersistablePayload;
|
||||
import bisq.common.util.CollectionUtils;
|
||||
import bisq.common.util.ExtraDataMapValidator;
|
||||
import bisq.common.util.JsonExclude;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.ExchangeRate;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @deprecated Was used in pre v0.6.0 version
|
||||
*/
|
||||
@Deprecated
|
||||
@Slf4j
|
||||
@EqualsAndHashCode(exclude = {"signaturePubKeyBytes"})
|
||||
@Value
|
||||
public final class TradeStatistics implements ProcessOncePersistableNetworkPayload, ProtectedStoragePayload, ExpirablePayload, PersistablePayload {
|
||||
private final OfferPayload.Direction direction;
|
||||
private final String baseCurrency;
|
||||
private final String counterCurrency;
|
||||
private final String offerPaymentMethod;
|
||||
private final long offerDate;
|
||||
private final boolean offerUseMarketBasedPrice;
|
||||
private final double offerMarketPriceMargin;
|
||||
private final long offerAmount;
|
||||
private final long offerMinAmount;
|
||||
private final String offerId;
|
||||
private final long tradePrice;
|
||||
private final long tradeAmount;
|
||||
private final long tradeDate;
|
||||
private final String depositTxId;
|
||||
@JsonExclude
|
||||
private final byte[] signaturePubKeyBytes;
|
||||
@JsonExclude
|
||||
transient private final PublicKey signaturePubKey;
|
||||
|
||||
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
|
||||
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
|
||||
// field in a class would break that hash and therefore break the storage mechanism.
|
||||
@Nullable
|
||||
private Map<String, String> extraDataMap;
|
||||
|
||||
public TradeStatistics(OfferPayload offerPayload,
|
||||
Price tradePrice,
|
||||
Coin tradeAmount,
|
||||
Date tradeDate,
|
||||
String depositTxId,
|
||||
byte[] signaturePubKeyBytes) {
|
||||
this(offerPayload.getDirection(),
|
||||
offerPayload.getBaseCurrencyCode(),
|
||||
offerPayload.getCounterCurrencyCode(),
|
||||
offerPayload.getPaymentMethodId(),
|
||||
offerPayload.getDate(),
|
||||
offerPayload.isUseMarketBasedPrice(),
|
||||
offerPayload.getMarketPriceMargin(),
|
||||
offerPayload.getAmount(),
|
||||
offerPayload.getMinAmount(),
|
||||
offerPayload.getId(),
|
||||
tradePrice.getValue(),
|
||||
tradeAmount.value,
|
||||
tradeDate.getTime(),
|
||||
depositTxId,
|
||||
signaturePubKeyBytes,
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TradeStatistics(OfferPayload.Direction direction,
|
||||
String baseCurrency,
|
||||
String counterCurrency,
|
||||
String offerPaymentMethod,
|
||||
long offerDate,
|
||||
boolean offerUseMarketBasedPrice,
|
||||
double offerMarketPriceMargin,
|
||||
long offerAmount,
|
||||
long offerMinAmount,
|
||||
String offerId,
|
||||
long tradePrice,
|
||||
long tradeAmount,
|
||||
long tradeDate,
|
||||
String depositTxId,
|
||||
byte[] signaturePubKeyBytes,
|
||||
@Nullable Map<String, String> extraDataMap) {
|
||||
this.direction = direction;
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.counterCurrency = counterCurrency;
|
||||
this.offerPaymentMethod = offerPaymentMethod;
|
||||
this.offerDate = offerDate;
|
||||
this.offerUseMarketBasedPrice = offerUseMarketBasedPrice;
|
||||
this.offerMarketPriceMargin = offerMarketPriceMargin;
|
||||
this.offerAmount = offerAmount;
|
||||
this.offerMinAmount = offerMinAmount;
|
||||
this.offerId = offerId;
|
||||
this.tradePrice = tradePrice;
|
||||
this.tradeAmount = tradeAmount;
|
||||
this.tradeDate = tradeDate;
|
||||
this.depositTxId = depositTxId;
|
||||
this.signaturePubKeyBytes = signaturePubKeyBytes;
|
||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||
|
||||
signaturePubKey = Sig.getPublicKeyFromBytes(signaturePubKeyBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.StoragePayload toProtoMessage() {
|
||||
final protobuf.TradeStatistics.Builder builder = protobuf.TradeStatistics.newBuilder()
|
||||
.setDirection(OfferPayload.Direction.toProtoMessage(direction))
|
||||
.setBaseCurrency(baseCurrency)
|
||||
.setCounterCurrency(counterCurrency)
|
||||
.setPaymentMethodId(offerPaymentMethod)
|
||||
.setOfferDate(offerDate)
|
||||
.setOfferUseMarketBasedPrice(offerUseMarketBasedPrice)
|
||||
.setOfferMarketPriceMargin(offerMarketPriceMargin)
|
||||
.setOfferAmount(offerAmount)
|
||||
.setOfferMinAmount(offerMinAmount)
|
||||
.setOfferId(offerId)
|
||||
.setTradePrice(tradePrice)
|
||||
.setTradeAmount(tradeAmount)
|
||||
.setTradeDate(tradeDate)
|
||||
.setDepositTxId(depositTxId)
|
||||
.setSignaturePubKeyBytes(ByteString.copyFrom(signaturePubKeyBytes));
|
||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||
return protobuf.StoragePayload.newBuilder().setTradeStatistics(builder).build();
|
||||
}
|
||||
|
||||
public protobuf.TradeStatistics toProtoTradeStatistics() {
|
||||
return toProtoMessage().getTradeStatistics();
|
||||
}
|
||||
|
||||
public static TradeStatistics fromProto(protobuf.TradeStatistics proto) {
|
||||
return new TradeStatistics(
|
||||
OfferPayload.Direction.fromProto(proto.getDirection()),
|
||||
proto.getBaseCurrency(),
|
||||
proto.getCounterCurrency(),
|
||||
proto.getPaymentMethodId(),
|
||||
proto.getOfferDate(),
|
||||
proto.getOfferUseMarketBasedPrice(),
|
||||
proto.getOfferMarketPriceMargin(),
|
||||
proto.getOfferAmount(),
|
||||
proto.getOfferMinAmount(),
|
||||
proto.getOfferId(),
|
||||
proto.getTradePrice(),
|
||||
proto.getTradeAmount(),
|
||||
proto.getTradeDate(),
|
||||
proto.getDepositTxId(),
|
||||
proto.getSignaturePubKeyBytes().toByteArray(),
|
||||
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public long getTTL() {
|
||||
return TimeUnit.DAYS.toMillis(30);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getOwnerPubKey() {
|
||||
return signaturePubKey;
|
||||
}
|
||||
|
||||
public Date getTradeDate() {
|
||||
return new Date(tradeDate);
|
||||
}
|
||||
|
||||
public Price getTradePrice() {
|
||||
return Price.valueOf(getCurrencyCode(), tradePrice);
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
return baseCurrency.equals("BTC") ? counterCurrency : baseCurrency;
|
||||
}
|
||||
|
||||
public Coin getTradeAmount() {
|
||||
return Coin.valueOf(tradeAmount);
|
||||
}
|
||||
|
||||
public Volume getTradeVolume() {
|
||||
if (getTradePrice().getMonetary() instanceof Altcoin)
|
||||
return new Volume(new AltcoinExchangeRate((Altcoin) getTradePrice().getMonetary()).coinToAltcoin(getTradeAmount()));
|
||||
else
|
||||
return new Volume(new ExchangeRate((Fiat) getTradePrice().getMonetary()).coinToFiat(getTradeAmount()));
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
/**
|
||||
* Serialized size is about 180-210 byte. Nov 2017 we have 5500 objects
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
@Slf4j
|
||||
@Value
|
||||
public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload,
|
||||
|
|
|
@ -29,6 +29,7 @@ import javax.inject.Named;
|
|||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -65,11 +66,29 @@ public class TradeStatistics2StorageService extends MapStoreService<TradeStatist
|
|||
|
||||
@Override
|
||||
public Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> getMap() {
|
||||
return store.getMap();
|
||||
// As it is used for data request and response and we do not want to send any old trade stat data anymore.
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
// We overwrite that method to receive old trade stats from the network. As we deactivated getMap to not deliver
|
||||
// hashes we needed to use the getMapOfAllData method to actually store the data.
|
||||
// That's a bit of a hack but it's just for transition and can be removed after a few months anyway.
|
||||
// Alternatively we could create a new interface to handle it differently on the other client classes but that
|
||||
// seems to be not justified as it is needed only temporarily.
|
||||
@Override
|
||||
protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payload) {
|
||||
PersistableNetworkPayload previous = getMapOfAllData().putIfAbsent(hash, payload);
|
||||
return previous;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readFromResources(String postFix) {
|
||||
// We do not attempt to read from resources as that file is not provided anymore
|
||||
readStore();
|
||||
}
|
||||
|
||||
public Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> getMapOfAllData() {
|
||||
return getMap();
|
||||
return store.getMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.AltcoinExchangeRate;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
|
||||
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload;
|
||||
|
||||
import bisq.common.app.Capabilities;
|
||||
import bisq.common.app.Capability;
|
||||
import bisq.common.crypto.Hash;
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
import bisq.common.util.CollectionUtils;
|
||||
import bisq.common.util.ExtraDataMapValidator;
|
||||
import bisq.common.util.JsonExclude;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.ExchangeRate;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* This new trade statistics class uses only the bare minimum of data.
|
||||
* Data size is about 50 bytes in average
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload,
|
||||
CapabilityRequiringPayload {
|
||||
|
||||
// This enum must not change the order as we use the ordinal for storage to reduce data size.
|
||||
// The payment method string can be quite long and would consume 15% more space.
|
||||
// When we get a new payment method we can add it to the enum at the end. Old users would add it as string if not
|
||||
// recognized.
|
||||
private enum PaymentMethodMapper {
|
||||
OK_PAY,
|
||||
CASH_APP,
|
||||
VENMO,
|
||||
AUSTRALIA_PAYID, // seems there is a dev trade
|
||||
UPHOLD,
|
||||
MONEY_BEAM,
|
||||
POPMONEY,
|
||||
REVOLUT,
|
||||
PERFECT_MONEY,
|
||||
SEPA,
|
||||
SEPA_INSTANT,
|
||||
FASTER_PAYMENTS,
|
||||
NATIONAL_BANK,
|
||||
JAPAN_BANK,
|
||||
SAME_BANK,
|
||||
SPECIFIC_BANKS,
|
||||
SWISH,
|
||||
ALI_PAY,
|
||||
WECHAT_PAY,
|
||||
CLEAR_X_CHANGE,
|
||||
CHASE_QUICK_PAY,
|
||||
INTERAC_E_TRANSFER,
|
||||
US_POSTAL_MONEY_ORDER,
|
||||
CASH_DEPOSIT,
|
||||
MONEY_GRAM,
|
||||
WESTERN_UNION,
|
||||
HAL_CASH,
|
||||
F2F,
|
||||
BLOCK_CHAINS,
|
||||
PROMPT_PAY,
|
||||
ADVANCED_CASH,
|
||||
BLOCK_CHAINS_INSTANT
|
||||
}
|
||||
|
||||
private final String currency;
|
||||
private final long price;
|
||||
private final long amount;
|
||||
private final String paymentMethod;
|
||||
// As only seller is publishing it is the sellers trade date
|
||||
private final long date;
|
||||
|
||||
// Old converted trade stat objects might not have it set
|
||||
@Nullable
|
||||
@JsonExclude
|
||||
private String mediator;
|
||||
@Nullable
|
||||
@JsonExclude
|
||||
private String refundAgent;
|
||||
|
||||
// todo should we add referrerId as well? get added to extra map atm but not used so far
|
||||
|
||||
// Hash get set in constructor from json of all the other data fields (with hash = null).
|
||||
@JsonExclude
|
||||
private final byte[] hash;
|
||||
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
|
||||
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
|
||||
// field in a class would break that hash and therefore break the storage mechanism.
|
||||
@Nullable
|
||||
@JsonExclude
|
||||
private final Map<String, String> extraDataMap;
|
||||
|
||||
public TradeStatistics3(String currency,
|
||||
long price,
|
||||
long amount,
|
||||
String paymentMethod,
|
||||
long date,
|
||||
String mediator,
|
||||
String refundAgent,
|
||||
@Nullable Map<String, String> extraDataMap) {
|
||||
this(currency,
|
||||
price,
|
||||
amount,
|
||||
paymentMethod,
|
||||
date,
|
||||
mediator,
|
||||
refundAgent,
|
||||
extraDataMap,
|
||||
null);
|
||||
}
|
||||
|
||||
// Used from conversion method where we use the hash of the TradeStatistics2 objects to avoid duplicate entries
|
||||
public TradeStatistics3(String currency,
|
||||
long price,
|
||||
long amount,
|
||||
String paymentMethod,
|
||||
long date,
|
||||
String mediator,
|
||||
String refundAgent,
|
||||
@Nullable byte[] hash) {
|
||||
this(currency,
|
||||
price,
|
||||
amount,
|
||||
paymentMethod,
|
||||
date,
|
||||
mediator,
|
||||
refundAgent,
|
||||
null,
|
||||
hash);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@VisibleForTesting
|
||||
public TradeStatistics3(String currency,
|
||||
long price,
|
||||
long amount,
|
||||
String paymentMethod,
|
||||
long date,
|
||||
@Nullable String mediator,
|
||||
@Nullable String refundAgent,
|
||||
@Nullable Map<String, String> extraDataMap,
|
||||
@Nullable byte[] hash) {
|
||||
this.currency = currency;
|
||||
this.price = price;
|
||||
this.amount = amount;
|
||||
String tempPaymentMethod;
|
||||
try {
|
||||
tempPaymentMethod = String.valueOf(PaymentMethodMapper.valueOf(paymentMethod).ordinal());
|
||||
} catch (Throwable t) {
|
||||
tempPaymentMethod = paymentMethod;
|
||||
}
|
||||
this.paymentMethod = tempPaymentMethod;
|
||||
this.date = date;
|
||||
this.mediator = mediator;
|
||||
this.refundAgent = refundAgent;
|
||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||
|
||||
this.hash = hash == null ? createHash() : hash;
|
||||
}
|
||||
|
||||
public byte[] createHash() {
|
||||
// We create hash from all fields excluding hash itself. We use json as simple data serialisation.
|
||||
// TradeDate is different for both peers so we ignore it for hash. ExtraDataMap is ignored as well as at
|
||||
// software updates we might have different entries which would cause a different hash.
|
||||
return Hash.getSha256Ripemd160hash(Utilities.objectToJson(this).getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
private protobuf.TradeStatistics3.Builder getBuilder() {
|
||||
protobuf.TradeStatistics3.Builder builder = protobuf.TradeStatistics3.newBuilder()
|
||||
.setCurrency(currency)
|
||||
.setPrice(price)
|
||||
.setAmount(amount)
|
||||
.setPaymentMethod(paymentMethod)
|
||||
.setDate(date)
|
||||
.setHash(ByteString.copyFrom(hash));
|
||||
Optional.ofNullable(mediator).ifPresent(builder::setMediator);
|
||||
Optional.ofNullable(refundAgent).ifPresent(builder::setRefundAgent);
|
||||
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public protobuf.TradeStatistics3 toProtoTradeStatistics3() {
|
||||
return getBuilder().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public protobuf.PersistableNetworkPayload toProtoMessage() {
|
||||
return protobuf.PersistableNetworkPayload.newBuilder().setTradeStatistics3(getBuilder()).build();
|
||||
}
|
||||
|
||||
public static TradeStatistics3 fromProto(protobuf.TradeStatistics3 proto) {
|
||||
return new TradeStatistics3(
|
||||
proto.getCurrency(),
|
||||
proto.getPrice(),
|
||||
proto.getAmount(),
|
||||
proto.getPaymentMethod(),
|
||||
proto.getDate(),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getMediator()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getRefundAgent()),
|
||||
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
|
||||
proto.getHash().toByteArray());
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public byte[] getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyHashSize() {
|
||||
checkNotNull(hash, "hash must not be null");
|
||||
return hash.length == 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Capabilities getRequiredCapabilities() {
|
||||
return new Capabilities(Capability.TRADE_STATISTICS_3);
|
||||
}
|
||||
|
||||
public void pruneOptionalData() {
|
||||
mediator = null;
|
||||
refundAgent = null;
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
try {
|
||||
return PaymentMethodMapper.values()[Integer.parseInt(paymentMethod)].name();
|
||||
} catch (Throwable ignore) {
|
||||
return paymentMethod;
|
||||
}
|
||||
}
|
||||
|
||||
public Date getTradeDate() {
|
||||
return new Date(date);
|
||||
}
|
||||
|
||||
public Price getTradePrice() {
|
||||
return Price.valueOf(currency, price);
|
||||
}
|
||||
|
||||
public Coin getTradeAmount() {
|
||||
return Coin.valueOf(amount);
|
||||
}
|
||||
|
||||
public Volume getTradeVolume() {
|
||||
if (getTradePrice().getMonetary() instanceof Altcoin) {
|
||||
return new Volume(new AltcoinExchangeRate((Altcoin) getTradePrice().getMonetary()).coinToAltcoin(getTradeAmount()));
|
||||
} else {
|
||||
Volume volume = new Volume(new ExchangeRate((Fiat) getTradePrice().getMonetary()).coinToFiat(getTradeAmount()));
|
||||
return OfferUtil.getRoundedFiatVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return amount > 0 &&
|
||||
price > 0 &&
|
||||
date > 0 &&
|
||||
paymentMethod != null &&
|
||||
!paymentMethod.isEmpty() &&
|
||||
currency != null &&
|
||||
!currency.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof TradeStatistics3)) return false;
|
||||
|
||||
TradeStatistics3 that = (TradeStatistics3) o;
|
||||
|
||||
if (price != that.price) return false;
|
||||
if (amount != that.amount) return false;
|
||||
if (date != that.date) return false;
|
||||
if (currency != null ? !currency.equals(that.currency) : that.currency != null) return false;
|
||||
if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null)
|
||||
return false;
|
||||
return Arrays.equals(hash, that.hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = currency != null ? currency.hashCode() : 0;
|
||||
result = 31 * result + (int) (price ^ (price >>> 32));
|
||||
result = 31 * result + (int) (amount ^ (amount >>> 32));
|
||||
result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
|
||||
result = 31 * result + (int) (date ^ (date >>> 32));
|
||||
result = 31 * result + Arrays.hashCode(hash);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TradeStatistics3{" +
|
||||
"\n currency='" + currency + '\'' +
|
||||
",\n price=" + price +
|
||||
",\n amount=" + amount +
|
||||
",\n paymentMethod='" + paymentMethod + '\'' +
|
||||
",\n date=" + date +
|
||||
",\n mediator='" + mediator + '\'' +
|
||||
",\n refundAgent='" + refundAgent + '\'' +
|
||||
",\n hash=" + Utilities.bytesAsHexString(hash) +
|
||||
",\n extraDataMap=" + extraDataMap +
|
||||
"\n}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
import bisq.network.p2p.storage.persistence.HistoricalDataStoreService;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class TradeStatistics3StorageService extends HistoricalDataStoreService<TradeStatistics3Store> {
|
||||
private static final String FILE_NAME = "TradeStatistics3Store";
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public TradeStatistics3StorageService(@Named(Config.STORAGE_DIR) File storageDir,
|
||||
PersistenceManager<TradeStatistics3Store> persistenceManager) {
|
||||
super(storageDir, persistenceManager);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return FILE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializePersistenceManager() {
|
||||
persistenceManager.initialize(store, PersistenceManager.Source.NETWORK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(PersistableNetworkPayload payload) {
|
||||
return payload instanceof TradeStatistics3;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected TradeStatistics3Store createStore() {
|
||||
return new TradeStatistics3Store();
|
||||
}
|
||||
|
||||
public void persistNow() {
|
||||
persistenceManager.persistNow(() -> {
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
import bisq.network.p2p.storage.persistence.PersistableNetworkPayloadStore;
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* We store only the payload in the PB file to save disc space. The hash of the payload can be created anyway and
|
||||
* is only used as key in the map. So we have a hybrid data structure which is represented as list in the protobuffer
|
||||
* definition and provide a hashMap for the domain access.
|
||||
*/
|
||||
@Slf4j
|
||||
public class TradeStatistics3Store extends PersistableNetworkPayloadStore<TradeStatistics3> {
|
||||
|
||||
TradeStatistics3Store() {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private TradeStatistics3Store(List<TradeStatistics3> list) {
|
||||
list.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item));
|
||||
}
|
||||
|
||||
public Message toProtoMessage() {
|
||||
return protobuf.PersistableEnvelope.newBuilder()
|
||||
.setTradeStatistics3Store(getBuilder())
|
||||
.build();
|
||||
}
|
||||
|
||||
private protobuf.TradeStatistics3Store.Builder getBuilder() {
|
||||
List<protobuf.TradeStatistics3> protoList = map.values().stream()
|
||||
.map(payload -> (TradeStatistics3) payload)
|
||||
.map(TradeStatistics3::toProtoTradeStatistics3)
|
||||
.collect(Collectors.toList());
|
||||
return protobuf.TradeStatistics3Store.newBuilder().addAllItems(protoList);
|
||||
}
|
||||
|
||||
public static TradeStatistics3Store fromProto(protobuf.TradeStatistics3Store proto) {
|
||||
List<TradeStatistics3> list = proto.getItemsList().stream()
|
||||
.map(TradeStatistics3::fromProto).collect(Collectors.toList());
|
||||
return new TradeStatistics3Store(list);
|
||||
}
|
||||
|
||||
public boolean containsKey(P2PDataStorage.ByteArray hash) {
|
||||
return map.containsKey(hash);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
|
||||
import bisq.network.p2p.BootstrapListener;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.file.FileUtil;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class TradeStatisticsConverter {
|
||||
|
||||
private ExecutorService executor;
|
||||
|
||||
@Inject
|
||||
public TradeStatisticsConverter(P2PService p2PService,
|
||||
P2PDataStorage p2PDataStorage,
|
||||
TradeStatistics2StorageService tradeStatistics2StorageService,
|
||||
TradeStatistics3StorageService tradeStatistics3StorageService,
|
||||
AppendOnlyDataStoreService appendOnlyDataStoreService,
|
||||
@Named(Config.STORAGE_DIR) File storageDir) {
|
||||
File tradeStatistics2Store = new File(storageDir, "TradeStatistics2Store");
|
||||
appendOnlyDataStoreService.addService(tradeStatistics2StorageService);
|
||||
|
||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||
|
||||
@Override
|
||||
public void onTorNodeReady() {
|
||||
if (!tradeStatistics2Store.exists()) {
|
||||
return;
|
||||
}
|
||||
executor = Utilities.getSingleThreadExecutor("TradeStatisticsConverter");
|
||||
executor.submit(() -> {
|
||||
// We convert early once tor is initialized but still not ready to receive data
|
||||
Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> tempMap = new HashMap<>();
|
||||
convertToTradeStatistics3(tradeStatistics2StorageService.getMapOfAllData().values())
|
||||
.forEach(e -> tempMap.put(new P2PDataStorage.ByteArray(e.getHash()), e));
|
||||
|
||||
// We map to user thread to avoid potential threading issues
|
||||
UserThread.execute(() -> {
|
||||
tradeStatistics3StorageService.getMapOfLiveData().putAll(tempMap);
|
||||
tradeStatistics3StorageService.persistNow();
|
||||
});
|
||||
|
||||
try {
|
||||
log.info("We delete now the old trade statistics file as it was converted to the new format.");
|
||||
FileUtil.deleteFileIfExists(tradeStatistics2Store);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdatedDataReceived() {
|
||||
}
|
||||
});
|
||||
|
||||
// We listen to old TradeStatistics2 objects, convert and store them and rebroadcast.
|
||||
p2PDataStorage.addAppendOnlyDataStoreListener(payload -> {
|
||||
if (payload instanceof TradeStatistics2) {
|
||||
TradeStatistics3 tradeStatistics3 = convertToTradeStatistics3((TradeStatistics2) payload, true);
|
||||
// We add it to the p2PDataStorage, which handles to get the data stored in the maps and maybe
|
||||
// re-broadcast as tradeStatistics3 object if not already received.
|
||||
p2PDataStorage.addPersistableNetworkPayload(tradeStatistics3, null, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
if (executor != null)
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
private static List<TradeStatistics3> convertToTradeStatistics3(Collection<PersistableNetworkPayload> persistableNetworkPayloads) {
|
||||
List<TradeStatistics3> list = new ArrayList<>();
|
||||
long ts = System.currentTimeMillis();
|
||||
|
||||
// We might have duplicate entries from both traders as the trade date was different from old clients.
|
||||
// This should not be the case with converting old persisted data as we did filter those out but it is the case
|
||||
// when we receive old trade stat objects from the network of 2 not updated traders.
|
||||
// The hash was ignoring the trade date so we use that to get a unique list
|
||||
Map<P2PDataStorage.ByteArray, TradeStatistics2> mapWithoutDuplicates = new HashMap<>();
|
||||
persistableNetworkPayloads.stream()
|
||||
.filter(e -> e instanceof TradeStatistics2)
|
||||
.map(e -> (TradeStatistics2) e)
|
||||
.filter(TradeStatistics2::isValid)
|
||||
.forEach(e -> mapWithoutDuplicates.putIfAbsent(new P2PDataStorage.ByteArray(e.getHash()), e));
|
||||
|
||||
log.info("We convert the existing {} trade statistics objects to the new format.", mapWithoutDuplicates.size());
|
||||
|
||||
mapWithoutDuplicates.values().stream()
|
||||
.map(e -> convertToTradeStatistics3(e, false))
|
||||
.filter(TradeStatistics3::isValid)
|
||||
.forEach(list::add);
|
||||
|
||||
int size = list.size();
|
||||
log.info("Conversion to {} new trade statistic objects has been completed after {} ms",
|
||||
size, System.currentTimeMillis() - ts);
|
||||
|
||||
// We prune mediator and refundAgent data from all objects but the last 100 as we only use the
|
||||
// last 100 entries (DisputeAgentSelection.LOOK_BACK_RANGE).
|
||||
list.sort(Comparator.comparing(TradeStatistics3::getDate));
|
||||
if (size > DisputeAgentSelection.LOOK_BACK_RANGE) {
|
||||
int start = size - DisputeAgentSelection.LOOK_BACK_RANGE;
|
||||
for (int i = start; i < size; i++) {
|
||||
TradeStatistics3 tradeStatistics3 = list.get(i);
|
||||
tradeStatistics3.pruneOptionalData();
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static TradeStatistics3 convertToTradeStatistics3(TradeStatistics2 tradeStatistics2, boolean fromNetwork) {
|
||||
Map<String, String> extraDataMap = tradeStatistics2.getExtraDataMap();
|
||||
String mediator = extraDataMap != null ? extraDataMap.get(TradeStatistics2.MEDIATOR_ADDRESS) : null;
|
||||
String refundAgent = extraDataMap != null ? extraDataMap.get(TradeStatistics2.REFUND_AGENT_ADDRESS) : null;
|
||||
long time = tradeStatistics2.getTradeDate().getTime();
|
||||
byte[] hash = null;
|
||||
if (fromNetwork) {
|
||||
// We need to avoid that we duplicate tradeStatistics2 objects in case both traders have not udpated yet.
|
||||
// Before v1.4.0 both traders published the trade statistics. If one trader has updated he will check
|
||||
// the capabilities of the peer and if the peer has not updated he will leave publishing to the peer, so we
|
||||
// do not have the problem of duplicated objects.
|
||||
// To ensure we add only one object we will use the hash of the tradeStatistics2 object which is the same
|
||||
// for both traders as it excluded the trade date which is different for both.
|
||||
hash = tradeStatistics2.getHash();
|
||||
}
|
||||
return new TradeStatistics3(tradeStatistics2.getCurrencyCode(),
|
||||
tradeStatistics2.getTradePrice().getValue(),
|
||||
tradeStatistics2.getTradeAmount().getValue(),
|
||||
tradeStatistics2.getOfferPaymentMethod(),
|
||||
time,
|
||||
mediator,
|
||||
refundAgent,
|
||||
hash);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,85 @@ 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 TradeStatisticsConverter tradeStatisticsConverter;
|
||||
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,
|
||||
TradeStatisticsConverter tradeStatisticsConverter,
|
||||
@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.tradeStatisticsConverter = tradeStatisticsConverter;
|
||||
this.storageDir = storageDir;
|
||||
this.dumpStatistics = dumpStatistics;
|
||||
jsonFileManager = new JsonFileManager(storageDir);
|
||||
|
||||
appendOnlyDataStoreService.addService(tradeStatistics2StorageService);
|
||||
|
||||
appendOnlyDataStoreService.addService(tradeStatistics3StorageService);
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
tradeStatisticsConverter.shutDown();
|
||||
if (jsonFileManager != null) {
|
||||
jsonFileManager.shutDown();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
maybeDumpStatistics();
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
maybeDumpStatistics();
|
||||
}
|
||||
|
||||
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 maybeDumpStatistics() {
|
||||
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 +130,14 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import com.natpryce.makeiteasy.Instantiator;
|
||||
import com.natpryce.makeiteasy.Maker;
|
||||
import com.natpryce.makeiteasy.Property;
|
||||
|
||||
import static com.natpryce.makeiteasy.MakeItEasy.a;
|
||||
|
||||
public class TradeStatistics2Maker {
|
||||
|
||||
public static final Property<TradeStatistics2, Date> date = new Property<>();
|
||||
public static final Property<TradeStatistics2, String> depositTxId = new Property<>();
|
||||
public static final Property<TradeStatistics2, Coin> tradeAmount = new Property<>();
|
||||
|
||||
public static final Instantiator<TradeStatistics2> TradeStatistic2 = lookup -> {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(2016, 3, 19);
|
||||
|
||||
return new TradeStatistics2(
|
||||
new OfferPayload("1234",
|
||||
0L,
|
||||
null,
|
||||
null,
|
||||
OfferPayload.Direction.BUY,
|
||||
100000L,
|
||||
0.0,
|
||||
false,
|
||||
100000L,
|
||||
100000L,
|
||||
"BTC",
|
||||
"USD",
|
||||
null,
|
||||
null,
|
||||
"SEPA",
|
||||
"",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
0L,
|
||||
0L,
|
||||
0L,
|
||||
false,
|
||||
0L,
|
||||
0L,
|
||||
0L,
|
||||
0L,
|
||||
false,
|
||||
false,
|
||||
0L,
|
||||
0L,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
0),
|
||||
Price.valueOf("BTC", 100000L),
|
||||
lookup.valueOf(tradeAmount, Coin.SATOSHI),
|
||||
lookup.valueOf(date, new Date(calendar.getTimeInMillis())),
|
||||
lookup.valueOf(depositTxId, "123456"),
|
||||
Collections.emptyMap());
|
||||
};
|
||||
public static final Maker<TradeStatistics2> dayZeroTrade = a(TradeStatistic2);
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static bisq.core.trade.statistics.TradeStatistics2Maker.dayZeroTrade;
|
||||
import static bisq.core.trade.statistics.TradeStatistics2Maker.depositTxId;
|
||||
import static com.natpryce.makeiteasy.MakeItEasy.make;
|
||||
import static com.natpryce.makeiteasy.MakeItEasy.withNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
public class TradeStatistics2Test {
|
||||
|
||||
@Test
|
||||
public void isValid_WithDepositTxId() {
|
||||
|
||||
TradeStatistics2 tradeStatistic = make(dayZeroTrade);
|
||||
|
||||
assertTrue(tradeStatistic.isValid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValid_WithEmptyDepositTxId() {
|
||||
TradeStatistics2 tradeStatistic = make(dayZeroTrade.but(withNull(depositTxId)));
|
||||
|
||||
assertTrue(tradeStatistic.isValid());
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade.statistics;
|
||||
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreListener;
|
||||
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static bisq.core.trade.statistics.TradeStatistics2Maker.dayZeroTrade;
|
||||
import static bisq.core.trade.statistics.TradeStatistics2Maker.depositTxId;
|
||||
import static bisq.core.trade.statistics.TradeStatistics2Maker.tradeAmount;
|
||||
import static com.natpryce.makeiteasy.MakeItEasy.make;
|
||||
import static com.natpryce.makeiteasy.MakeItEasy.with;
|
||||
import static com.natpryce.makeiteasy.MakeItEasy.withNull;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class TradeStatisticsManagerTest {
|
||||
|
||||
private TradeStatisticsManager manager;
|
||||
private TradeStatistics2 tradeWithNullDepositTxId;
|
||||
private ArgumentCaptor<AppendOnlyDataStoreListener> listenerArgumentCaptor;
|
||||
|
||||
@Before
|
||||
public void prepareMocksAndObjects() {
|
||||
P2PService p2PService = mock(P2PService.class);
|
||||
P2PDataStorage p2PDataStorage = mock(P2PDataStorage.class);
|
||||
File storageDir = mock(File.class);
|
||||
TradeStatistics2StorageService tradeStatistics2StorageService = mock(TradeStatistics2StorageService.class);
|
||||
PriceFeedService priceFeedService = mock(PriceFeedService.class);
|
||||
|
||||
AppendOnlyDataStoreService appendOnlyDataStoreService = mock(AppendOnlyDataStoreService.class);
|
||||
when(p2PService.getP2PDataStorage()).thenReturn(p2PDataStorage);
|
||||
|
||||
manager = new TradeStatisticsManager(p2PService, priceFeedService,
|
||||
tradeStatistics2StorageService, appendOnlyDataStoreService, storageDir, false);
|
||||
|
||||
tradeWithNullDepositTxId = make(dayZeroTrade.but(withNull(depositTxId)));
|
||||
|
||||
manager.onAllServicesInitialized();
|
||||
listenerArgumentCaptor = ArgumentCaptor.forClass(AppendOnlyDataStoreListener.class);
|
||||
verify(p2PDataStorage).addAppendOnlyDataStoreListener(listenerArgumentCaptor.capture());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addToSet_ObjectWithNullDepositTxId() {
|
||||
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
|
||||
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addToSet_RemoveExistingObjectIfObjectWithNullDepositTxIdIsAdded() {
|
||||
TradeStatistics2 tradeWithDepositTxId = make(dayZeroTrade);
|
||||
|
||||
listenerArgumentCaptor.getValue().onAdded(tradeWithDepositTxId);
|
||||
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
|
||||
|
||||
assertFalse(manager.getObservableTradeStatisticsSet().contains(tradeWithDepositTxId));
|
||||
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addToSet_NotRemoveExistingObjectIfObjectsNotEqual() {
|
||||
TradeStatistics2 tradeWithDepositTxId = make(dayZeroTrade.but(with(tradeAmount, Coin.FIFTY_COINS)));
|
||||
|
||||
listenerArgumentCaptor.getValue().onAdded(tradeWithDepositTxId);
|
||||
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
|
||||
|
||||
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithDepositTxId));
|
||||
assertFalse(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addToSet_IgnoreObjectIfObjectWithNullDepositTxIdAlreadyExists() {
|
||||
TradeStatistics2 tradeWithDepositTxId = make(dayZeroTrade);
|
||||
|
||||
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
|
||||
listenerArgumentCaptor.getValue().onAdded(tradeWithDepositTxId);
|
||||
|
||||
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
|
||||
assertFalse(manager.getObservableTradeStatisticsSet().contains(tradeWithDepositTxId));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package bisq.daemon.grpc;
|
||||
|
||||
import bisq.core.api.CoreApi;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
|
||||
import bisq.proto.grpc.GetTradeStatisticsGrpc;
|
||||
import bisq.proto.grpc.GetTradeStatisticsReply;
|
||||
|
@ -27,7 +27,7 @@ class GrpcGetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStati
|
|||
StreamObserver<GetTradeStatisticsReply> responseObserver) {
|
||||
|
||||
var tradeStatistics = coreApi.getTradeStatistics().stream()
|
||||
.map(TradeStatistics2::toProtoTradeStatistics2)
|
||||
.map(TradeStatistics3::toProtoTradeStatistics3)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();
|
||||
|
|
|
@ -78,9 +78,6 @@ public class AddressTextField extends AnchorPane {
|
|||
});
|
||||
|
||||
textField.focusTraversableProperty().set(focusTraversableProperty().get());
|
||||
//TODO app wide focus
|
||||
//focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
|
||||
|
||||
Label extWalletIcon = new Label();
|
||||
extWalletIcon.setLayoutY(3);
|
||||
extWalletIcon.getStyleClass().addAll("icon", "highlight");
|
||||
|
|
|
@ -74,9 +74,6 @@ public class BsqAddressTextField extends AnchorPane {
|
|||
});
|
||||
|
||||
textField.focusTraversableProperty().set(focusTraversableProperty().get());
|
||||
//TODO app wide focus
|
||||
//focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
|
||||
|
||||
|
||||
Label copyIcon = new Label();
|
||||
copyIcon.setLayoutY(3);
|
||||
|
|
|
@ -79,9 +79,6 @@ public class TextFieldWithCopyIcon extends AnchorPane {
|
|||
AnchorPane.setRightAnchor(textField, 30.0);
|
||||
AnchorPane.setLeftAnchor(textField, 0.0);
|
||||
textField.focusTraversableProperty().set(focusTraversableProperty().get());
|
||||
//TODO app wide focus
|
||||
//focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
|
||||
|
||||
getChildren().addAll(textField, copyIcon);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,8 +64,6 @@ import static bisq.desktop.util.FormBuilder.addMultilineLabel;
|
|||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
||||
import static bisq.desktop.util.FormBuilder.addTopLabelTextField;
|
||||
|
||||
// TODO translation string keys should renamed to be more generic.
|
||||
// Lets do it for 1.1.7 the translator have time to add new string.
|
||||
public abstract class AgentRegistrationView<R extends DisputeAgent, T extends AgentRegistrationViewModel<R, ?>>
|
||||
extends ActivatableViewAndModel<VBox, T> {
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import bisq.core.locale.Res;
|
|||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
|
@ -121,7 +121,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private BsqDashboardView(DaoFacade daoFacade,
|
||||
public BsqDashboardView(DaoFacade daoFacade,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
PriceFeedService priceFeedService,
|
||||
Preferences preferences,
|
||||
|
@ -173,7 +173,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
|
|||
marketCapTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
|
||||
Res.get("dao.factsAndFigures.dashboard.marketCap")).second;
|
||||
|
||||
availableAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, 1,
|
||||
availableAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
|
||||
Res.get("dao.factsAndFigures.dashboard.availableAmount")).second;
|
||||
}
|
||||
|
||||
|
@ -289,17 +289,17 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
|
|||
private void updateBsqPriceData() {
|
||||
seriesBSQPrice.getData().clear();
|
||||
|
||||
Map<LocalDate, List<TradeStatistics2>> bsqPriceByDate = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> e.getCurrencyCode().equals("BSQ"))
|
||||
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate))
|
||||
.collect(Collectors.groupingBy(item -> new java.sql.Date(item.getTradeDate().getTime()).toLocalDate()
|
||||
Map<LocalDate, List<TradeStatistics3>> bsqPriceByDate = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> e.getCurrency().equals("BSQ"))
|
||||
.sorted(Comparator.comparing(TradeStatistics3::getDate))
|
||||
.collect(Collectors.groupingBy(item -> new java.sql.Date(item.getDate()).toLocalDate()
|
||||
.with(ADJUSTERS.get(DAY))));
|
||||
|
||||
List<XYChart.Data<Number, Number>> updatedBSQPrice = bsqPriceByDate.keySet().stream()
|
||||
.map(e -> {
|
||||
ZonedDateTime zonedDateTime = e.atStartOfDay(ZoneId.systemDefault());
|
||||
return new XYChart.Data<Number, Number>(zonedDateTime.toInstant().getEpochSecond(), bsqPriceByDate.get(e).stream()
|
||||
.map(TradeStatistics2::getTradePrice)
|
||||
.map(TradeStatistics3::getTradePrice)
|
||||
.mapToDouble(Price::getValue)
|
||||
.average()
|
||||
.orElse(Double.NaN)
|
||||
|
@ -370,12 +370,12 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
|
|||
|
||||
private long updateAveragePriceField(TextField textField, int days, boolean isUSDField) {
|
||||
Date pastXDays = getPastDate(days);
|
||||
List<TradeStatistics2> bsqTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> e.getCurrencyCode().equals("BSQ"))
|
||||
List<TradeStatistics3> bsqTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> e.getCurrency().equals("BSQ"))
|
||||
.filter(e -> e.getTradeDate().after(pastXDays))
|
||||
.collect(Collectors.toList());
|
||||
List<TradeStatistics2> usdTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> e.getCurrencyCode().equals("USD"))
|
||||
List<TradeStatistics3> usdTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> e.getCurrency().equals("USD"))
|
||||
.filter(e -> e.getTradeDate().after(pastXDays))
|
||||
.collect(Collectors.toList());
|
||||
long average = isUSDField ? getUSDAverage(bsqTradePastXDays, usdTradePastXDays) :
|
||||
|
@ -391,11 +391,11 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
|
|||
return average;
|
||||
}
|
||||
|
||||
private long getBTCAverage(List<TradeStatistics2> bsqList) {
|
||||
private long getBTCAverage(List<TradeStatistics3> bsqList) {
|
||||
long accumulatedVolume = 0;
|
||||
long accumulatedAmount = 0;
|
||||
|
||||
for (TradeStatistics2 item : bsqList) {
|
||||
for (TradeStatistics3 item : bsqList) {
|
||||
accumulatedVolume += item.getTradeVolume().getValue();
|
||||
accumulatedAmount += item.getTradeAmount().getValue(); // Amount of BTC traded
|
||||
}
|
||||
|
@ -406,17 +406,17 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
|
|||
return averagePrice;
|
||||
}
|
||||
|
||||
private long getUSDAverage(List<TradeStatistics2> bsqList, List<TradeStatistics2> usdList) {
|
||||
private long getUSDAverage(List<TradeStatistics3> bsqList, List<TradeStatistics3> usdList) {
|
||||
// Use next USD/BTC print as price to calculate BSQ/USD rate
|
||||
// Store each trade as amount of USD and amount of BSQ traded
|
||||
List<Tuple2<Double, Double>> usdBsqList = new ArrayList<>(bsqList.size());
|
||||
usdList.sort(Comparator.comparing(o -> o.getTradeDate().getTime()));
|
||||
usdList.sort(Comparator.comparing(TradeStatistics3::getDate));
|
||||
var usdBTCPrice = 10000d; // Default to 10000 USD per BTC if there is no USD feed at all
|
||||
|
||||
for (TradeStatistics2 item : bsqList) {
|
||||
for (TradeStatistics3 item : bsqList) {
|
||||
// Find usdprice for trade item
|
||||
usdBTCPrice = usdList.stream()
|
||||
.filter(usd -> usd.getTradeDate().getTime() > item.getTradeDate().getTime())
|
||||
.filter(usd -> usd.getDate() > item.getDate())
|
||||
.map(usd -> MathUtils.scaleDownByPowerOf10((double) usd.getTradePrice().getValue(),
|
||||
Fiat.SMALLEST_UNIT_EXPONENT))
|
||||
.findFirst()
|
||||
|
|
|
@ -27,7 +27,6 @@ import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
|
|||
import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx;
|
||||
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
|
||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
|
||||
|
@ -56,6 +55,7 @@ import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
|
|||
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
|
||||
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
|
||||
|
@ -145,7 +145,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
SellerFinalizesDelayedPayoutTx.class,
|
||||
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishesDepositTx.class,
|
||||
PublishTradeStatistics.class,
|
||||
SellerPublishesTradeStatistics.class,
|
||||
|
||||
SellerProcessCounterCurrencyTransferStartedMessage.class,
|
||||
ApplyFilter.class,
|
||||
|
@ -179,7 +179,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
|
||||
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
|
||||
BuyerVerifiesFinalDelayedPayoutTx.class,
|
||||
PublishTradeStatistics.class,
|
||||
|
||||
ApplyFilter.class,
|
||||
MakerVerifyTakerFeePayment.class,
|
||||
|
@ -216,7 +215,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
|
||||
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
|
||||
BuyerVerifiesFinalDelayedPayoutTx.class,
|
||||
PublishTradeStatistics.class,
|
||||
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
|
@ -248,7 +246,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
SellerFinalizesDelayedPayoutTx.class,
|
||||
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
|
||||
SellerPublishesDepositTx.class,
|
||||
PublishTradeStatistics.class,
|
||||
SellerPublishesTradeStatistics.class,
|
||||
|
||||
SellerProcessCounterCurrencyTransferStartedMessage.class,
|
||||
ApplyFilter.class,
|
||||
|
|
|
@ -35,7 +35,7 @@ import bisq.desktop.util.DisplayUtils;
|
|||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -82,7 +82,10 @@ public class MarketView extends ActivatableView<TabPane, Void> {
|
|||
|
||||
|
||||
@Inject
|
||||
public MarketView(CachingViewLoader viewLoader, P2PService p2PService, OfferBook offerBook, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
public MarketView(CachingViewLoader viewLoader,
|
||||
P2PService p2PService,
|
||||
OfferBook offerBook,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
Navigation navigation) {
|
||||
this.viewLoader = viewLoader;
|
||||
this.p2PService = p2PService;
|
||||
|
@ -179,20 +182,19 @@ public class MarketView extends ActivatableView<TabPane, Void> {
|
|||
// If both traders had set it the tradeStatistics is only delivered once.
|
||||
// If both traders used a different referral ID then we would get 2 objects.
|
||||
List<String> list = p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().stream()
|
||||
.filter(e -> e instanceof TradeStatistics2)
|
||||
.map(e -> (TradeStatistics2) e)
|
||||
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap() != null)
|
||||
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null)
|
||||
.map(trade -> {
|
||||
.filter(e -> e instanceof TradeStatistics3)
|
||||
.map(e -> (TradeStatistics3) e)
|
||||
.filter(tradeStatistics3 -> tradeStatistics3.getExtraDataMap() != null)
|
||||
.filter(tradeStatistics3 -> tradeStatistics3.getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null)
|
||||
.map(tradeStatistics3 -> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Trade ID: ").append(trade.getOfferId()).append("\n")
|
||||
.append("Date: ").append(DisplayUtils.formatDateTime(trade.getTradeDate())).append("\n")
|
||||
.append("Market: ").append(CurrencyUtil.getCurrencyPair(trade.getCurrencyCode())).append("\n")
|
||||
.append("Price: ").append(FormattingUtils.formatPrice(trade.getTradePrice())).append("\n")
|
||||
.append("Amount: ").append(formatter.formatCoin(trade.getTradeAmount())).append("\n")
|
||||
.append("Volume: ").append(DisplayUtils.formatVolume(trade.getTradeVolume())).append("\n")
|
||||
.append("Payment method: ").append(Res.get(trade.getOfferPaymentMethod())).append("\n")
|
||||
.append("ReferralID: ").append(trade.getExtraDataMap().get(OfferPayload.REFERRAL_ID));
|
||||
sb.append("Date: ").append(DisplayUtils.formatDateTime(tradeStatistics3.getTradeDate())).append("\n")
|
||||
.append("Market: ").append(CurrencyUtil.getCurrencyPair(tradeStatistics3.getCurrency())).append("\n")
|
||||
.append("Price: ").append(FormattingUtils.formatPrice(tradeStatistics3.getTradePrice())).append("\n")
|
||||
.append("Amount: ").append(formatter.formatCoin(tradeStatistics3.getTradeAmount())).append("\n")
|
||||
.append("Volume: ").append(DisplayUtils.formatVolume(tradeStatistics3.getTradeVolume())).append("\n")
|
||||
.append("Payment method: ").append(Res.get(tradeStatistics3.getPaymentMethod())).append("\n")
|
||||
.append("ReferralID: ").append(tradeStatistics3.getExtraDataMap().get(OfferPayload.REFERRAL_ID));
|
||||
return sb.toString();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
|
|
@ -298,7 +298,7 @@ public class SpreadView extends ActivatableViewAndModel<GridPane, SpreadViewMode
|
|||
public void updateItem(final SpreadItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
// TODO maybe show exra colums with item.priceSpread and use real amount diff
|
||||
// TODO maybe show extra columns with item.priceSpread and use real amount diff
|
||||
// not % based
|
||||
if (item.priceSpread != null)
|
||||
setText(item.percentage);
|
||||
|
|
|
@ -172,7 +172,7 @@ class SpreadViewModel extends ActivatableViewModel {
|
|||
else
|
||||
spread = bestBuyOfferPrice.subtract(bestSellOfferPrice);
|
||||
|
||||
// TODO maybe show extra colums with spread and use real amount diff
|
||||
// TODO maybe show extra columns with spread and use real amount diff
|
||||
// not % based. e.g. diff between best buy and sell offer (of small amounts its a smaller gain)
|
||||
|
||||
if (spread != null && marketPrice != null && marketPrice.isPriceAvailable()) {
|
||||
|
|
|
@ -34,8 +34,7 @@ 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.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -103,7 +102,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
|
||||
private final CoinFormatter formatter;
|
||||
|
||||
private TableView<TradeStatistics2> tableView;
|
||||
private TableView<TradeStatistics3> tableView;
|
||||
private AutocompleteComboBox<CurrencyListItem> currencyComboBox;
|
||||
private VolumeChart volumeChart;
|
||||
private CandleStickChart priceChart;
|
||||
|
@ -118,12 +117,12 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
private ChangeListener<Toggle> timeUnitChangeListener;
|
||||
private ToggleGroup toggleGroup;
|
||||
private final ListChangeListener<XYChart.Data<Number, Number>> itemsChangeListener;
|
||||
private SortedList<TradeStatistics2> sortedList;
|
||||
private SortedList<TradeStatistics3> sortedList;
|
||||
private Label nrOfTradeStatisticsLabel;
|
||||
private ListChangeListener<TradeStatistics2> tradeStatisticsByCurrencyListener;
|
||||
private ListChangeListener<TradeStatistics3> tradeStatisticsByCurrencyListener;
|
||||
private ChangeListener<Number> selectedTabIndexListener;
|
||||
private SingleSelectionModel<Tab> tabPaneSelectionModel;
|
||||
private TableColumn<TradeStatistics2, TradeStatistics2> priceColumn, volumeColumn, marketColumn;
|
||||
private TableColumn<TradeStatistics3, TradeStatistics3> priceColumn, volumeColumn, marketColumn;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private MonadicBinding<Void> currencySelectionBinding;
|
||||
private Subscription currencySelectionSubscriber;
|
||||
|
@ -550,7 +549,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
VBox.setVgrow(tableView, Priority.ALWAYS);
|
||||
|
||||
// date
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> dateColumn = new AutoTooltipTableColumn<>(Res.get("shared.dateTime")) {
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> dateColumn = new AutoTooltipTableColumn<>(Res.get("shared.dateTime")) {
|
||||
{
|
||||
setMinWidth(240);
|
||||
setMaxWidth(240);
|
||||
|
@ -561,11 +560,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
dateColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TradeStatistics2, TradeStatistics2> call(
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> column) {
|
||||
public TableCell<TradeStatistics3, TradeStatistics3> call(
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
public void updateItem(final TradeStatistics3 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(DisplayUtils.formatDateTime(item.getTradeDate()));
|
||||
|
@ -575,7 +574,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
};
|
||||
}
|
||||
});
|
||||
dateColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradeDate));
|
||||
dateColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradeDate));
|
||||
tableView.getColumns().add(dateColumn);
|
||||
|
||||
// market
|
||||
|
@ -590,21 +589,21 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
marketColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TradeStatistics2, TradeStatistics2> call(
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> column) {
|
||||
public TableCell<TradeStatistics3, TradeStatistics3> call(
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
public void updateItem(final TradeStatistics3 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(CurrencyUtil.getCurrencyPair(item.getCurrencyCode()));
|
||||
setText(CurrencyUtil.getCurrencyPair(item.getCurrency()));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
marketColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradeDate));
|
||||
marketColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradeDate));
|
||||
tableView.getColumns().add(marketColumn);
|
||||
|
||||
// price
|
||||
|
@ -614,11 +613,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
priceColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TradeStatistics2, TradeStatistics2> call(
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> column) {
|
||||
public TableCell<TradeStatistics3, TradeStatistics3> call(
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
public void updateItem(final TradeStatistics3 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(FormattingUtils.formatPrice(item.getTradePrice()));
|
||||
|
@ -628,21 +627,21 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
};
|
||||
}
|
||||
});
|
||||
priceColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradePrice));
|
||||
priceColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradePrice));
|
||||
tableView.getColumns().add(priceColumn);
|
||||
|
||||
// amount
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> amountColumn = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode()));
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> amountColumn = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode()));
|
||||
amountColumn.getStyleClass().add("number-column");
|
||||
amountColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
|
||||
amountColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TradeStatistics2, TradeStatistics2> call(
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> column) {
|
||||
public TableCell<TradeStatistics3, TradeStatistics3> call(
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
public void updateItem(final TradeStatistics3 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setGraphic(new ColoredDecimalPlacesWithZerosText(formatter.formatCoin(item.getTradeAmount(),
|
||||
|
@ -653,7 +652,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
};
|
||||
}
|
||||
});
|
||||
amountColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradeAmount));
|
||||
amountColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradeAmount));
|
||||
tableView.getColumns().add(amountColumn);
|
||||
|
||||
// volume
|
||||
|
@ -663,11 +662,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
volumeColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TradeStatistics2, TradeStatistics2> call(
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> column) {
|
||||
public TableCell<TradeStatistics3, TradeStatistics3> call(
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
public void updateItem(final TradeStatistics3 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(model.showAllTradeCurrenciesProperty.get() ?
|
||||
|
@ -687,17 +686,17 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
tableView.getColumns().add(volumeColumn);
|
||||
|
||||
// paymentMethod
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> paymentMethodColumn = new AutoTooltipTableColumn<>(Res.get("shared.paymentMethod"));
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> paymentMethodColumn = new AutoTooltipTableColumn<>(Res.get("shared.paymentMethod"));
|
||||
paymentMethodColumn.getStyleClass().add("number-column");
|
||||
paymentMethodColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
|
||||
paymentMethodColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TradeStatistics2, TradeStatistics2> call(
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> column) {
|
||||
public TableCell<TradeStatistics3, TradeStatistics3> call(
|
||||
TableColumn<TradeStatistics3, TradeStatistics3> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
public void updateItem(final TradeStatistics3 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(getPaymentMethodLabel(item));
|
||||
|
@ -710,30 +709,6 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
paymentMethodColumn.setComparator(Comparator.comparing(this::getPaymentMethodLabel));
|
||||
tableView.getColumns().add(paymentMethodColumn);
|
||||
|
||||
// direction
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> directionColumn = new AutoTooltipTableColumn<>(Res.get("shared.offerType"));
|
||||
directionColumn.getStyleClass().addAll("number-column", "last-column");
|
||||
directionColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
|
||||
directionColumn.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<TradeStatistics2, TradeStatistics2> call(
|
||||
TableColumn<TradeStatistics2, TradeStatistics2> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final TradeStatistics2 item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null)
|
||||
setText(getDirectionLabel(item));
|
||||
else
|
||||
setText("");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
directionColumn.setComparator(Comparator.comparing(this::getDirectionLabel));
|
||||
tableView.getColumns().add(directionColumn);
|
||||
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
Label placeholder = new AutoTooltipLabel(Res.get("table.placeholder.noData"));
|
||||
placeholder.setWrapText(true);
|
||||
|
@ -743,13 +718,8 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
|
|||
}
|
||||
|
||||
@NotNull
|
||||
private String getDirectionLabel(TradeStatistics2 item) {
|
||||
return DisplayUtils.getDirectionWithCode(OfferPayload.Direction.valueOf(item.getDirection().name()), item.getCurrencyCode());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getPaymentMethodLabel(TradeStatistics2 item) {
|
||||
return Res.get(item.getOfferPaymentMethod());
|
||||
private String getPaymentMethodLabel(TradeStatistics3 item) {
|
||||
return Res.get(item.getPaymentMethod());
|
||||
}
|
||||
|
||||
private void layout() {
|
||||
|
|
|
@ -34,7 +34,7 @@ import bisq.core.locale.GlobalSettings;
|
|||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
|
@ -97,18 +97,18 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
final Preferences preferences;
|
||||
private PriceFeedService priceFeedService;
|
||||
private Navigation navigation;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final Navigation navigation;
|
||||
|
||||
private final SetChangeListener<TradeStatistics2> setChangeListener;
|
||||
private final SetChangeListener<TradeStatistics3> setChangeListener;
|
||||
final ObjectProperty<TradeCurrency> selectedTradeCurrencyProperty = new SimpleObjectProperty<>();
|
||||
final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(false);
|
||||
private final CurrencyList currencyListItems;
|
||||
private final CurrencyListItem showAllCurrencyListItem = new CurrencyListItem(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, ""), -1);
|
||||
final ObservableList<TradeStatistics2> tradeStatisticsByCurrency = FXCollections.observableArrayList();
|
||||
final ObservableList<TradeStatistics3> tradeStatisticsByCurrency = FXCollections.observableArrayList();
|
||||
final ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList();
|
||||
final ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList();
|
||||
private Map<Long, Pair<Date, Set<TradeStatistics2>>> itemsPerInterval;
|
||||
private Map<Long, Pair<Date, Set<TradeStatistics3>>> itemsPerInterval;
|
||||
|
||||
TickUnit tickUnit;
|
||||
final int maxTicks = 90;
|
||||
|
@ -119,7 +119,8 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences, PriceFeedService priceFeedService, Navigation navigation) {
|
||||
TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences,
|
||||
PriceFeedService priceFeedService, Navigation navigation) {
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.preferences = preferences;
|
||||
this.priceFeedService = priceFeedService;
|
||||
|
@ -145,7 +146,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
private void fillTradeCurrencies() {
|
||||
// Don't use a set as we need all entries
|
||||
List<TradeCurrency> tradeCurrencyList = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.flatMap(e -> CurrencyUtil.getTradeCurrency(e.getCurrencyCode()).stream())
|
||||
.flatMap(e -> CurrencyUtil.getTradeCurrency(e.getCurrency()).stream())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
currencyListItems.updateWithCurrencies(tradeCurrencyList, showAllCurrencyListItem);
|
||||
|
@ -241,15 +242,15 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
|
||||
private void updateChartData() {
|
||||
tradeStatisticsByCurrency.setAll(tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> showAllTradeCurrenciesProperty.get() || e.getCurrencyCode().equals(getCurrencyCode()))
|
||||
.filter(e -> showAllTradeCurrenciesProperty.get() || e.getCurrency().equals(getCurrencyCode()))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
// Generate date range and create sets for all ticks
|
||||
itemsPerInterval = new HashMap<>();
|
||||
Date time = new Date();
|
||||
for (long i = maxTicks + 1; i >= 0; --i) {
|
||||
Set<TradeStatistics2> set = new HashSet<>();
|
||||
Pair<Date, Set<TradeStatistics2>> pair = new Pair<>((Date) time.clone(), set);
|
||||
Set<TradeStatistics3> set = new HashSet<>();
|
||||
Pair<Date, Set<TradeStatistics3>> pair = new Pair<>((Date) time.clone(), set);
|
||||
itemsPerInterval.put(i, pair);
|
||||
time.setTime(time.getTime() - 1);
|
||||
time = roundToTick(time, tickUnit);
|
||||
|
@ -258,7 +259,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
// Get all entries for the defined time interval
|
||||
tradeStatisticsByCurrency.forEach(e -> {
|
||||
for (long i = maxTicks; i > 0; --i) {
|
||||
Pair<Date, Set<TradeStatistics2>> p = itemsPerInterval.get(i);
|
||||
Pair<Date, Set<TradeStatistics3>> p = itemsPerInterval.get(i);
|
||||
if (e.getTradeDate().after(p.getKey())) {
|
||||
p.getValue().add(e);
|
||||
break;
|
||||
|
@ -283,7 +284,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
CandleData getCandleData(long tick, Set<TradeStatistics2> set) {
|
||||
CandleData getCandleData(long tick, Set<TradeStatistics3> set) {
|
||||
long open = 0;
|
||||
long close = 0;
|
||||
long high = 0;
|
||||
|
@ -293,7 +294,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
long numTrades = set.size();
|
||||
List<Long> tradePrices = new ArrayList<>(set.size());
|
||||
|
||||
for (TradeStatistics2 item : set) {
|
||||
for (TradeStatistics3 item : set) {
|
||||
long tradePriceAsLong = item.getTradePrice().getValue();
|
||||
// Previously a check was done which inverted the low and high for cryptocurrencies.
|
||||
low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong;
|
||||
|
@ -305,8 +306,8 @@ class TradesChartsViewModel extends ActivatableViewModel {
|
|||
}
|
||||
Collections.sort(tradePrices);
|
||||
|
||||
List<TradeStatistics2> list = new ArrayList<>(set);
|
||||
list.sort(Comparator.comparingLong(o -> o.getTradeDate().getTime()));
|
||||
List<TradeStatistics3> list = new ArrayList<>(set);
|
||||
list.sort(Comparator.comparingLong(TradeStatistics3::getDate));
|
||||
if (list.size() > 0) {
|
||||
open = list.get(0).getTradePrice().getValue();
|
||||
close = list.get(list.size() - 1).getTradePrice().getValue();
|
||||
|
|
|
@ -43,7 +43,7 @@ import bisq.core.payment.PaymentAccount;
|
|||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
|
@ -344,9 +344,9 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
|
|||
var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset());
|
||||
var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000);
|
||||
var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
|
||||
.filter(e -> e.getCurrencyCode().equals(getTradeCurrency().getCode()))
|
||||
.filter(e -> e.getCurrency().equals(getTradeCurrency().getCode()))
|
||||
.filter(e -> e.getTradeDate().compareTo(startDate) >= 0)
|
||||
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate))
|
||||
.sorted(Comparator.comparing(TradeStatistics3::getTradeDate))
|
||||
.collect(Collectors.toList());
|
||||
var movingAverage = new MathUtils.MovingAverage(10, 0.2);
|
||||
double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE};
|
||||
|
|
|
@ -332,7 +332,7 @@ public class FormBuilder {
|
|||
|
||||
final Tuple2<Label, VBox> topLabelWithVBox = addTopLabelWithVBox(gridPane, rowIndex, title, textField, top);
|
||||
|
||||
// TOD not 100% sure if that is a good idea....
|
||||
// TODO not 100% sure if that is a good idea....
|
||||
//topLabelWithVBox.first.getStyleClass().add("jfx-text-field-top-label");
|
||||
|
||||
return new Tuple3<>(topLabelWithVBox.first, textField, topLabelWithVBox.second);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
</root>
|
||||
|
||||
<logger name="com.neemre.btcdcli4j" level="WARN"/>
|
||||
<!-- <logger name="bisq.core.provider" level="WARN"/>
|
||||
<logger name="bisq.network" level="WARN"/>-->
|
||||
<logger name="com.neemre.btcdcli4j.core.client.ClientConfigurator" level="ERROR"/>
|
||||
|
||||
</configuration>
|
||||
|
|
|
@ -23,14 +23,12 @@ import bisq.desktop.main.market.trades.charts.CandleData;
|
|||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.TradeStatistics2;
|
||||
import bisq.core.trade.statistics.TradeStatistics3;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.KeyStorage;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
|
@ -49,9 +47,6 @@ import java.util.Date;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
@ -63,9 +58,7 @@ public class TradesChartsViewModelTest {
|
|||
TradesChartsViewModel model;
|
||||
TradeStatisticsManager tradeStatisticsManager;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TradesChartsViewModelTest.class);
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
private KeyRing keyRing;
|
||||
private File dir;
|
||||
OfferPayload offer = new OfferPayload(null,
|
||||
0,
|
||||
|
@ -106,6 +99,7 @@ public class TradesChartsViewModelTest {
|
|||
null,
|
||||
1
|
||||
);
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
tradeStatisticsManager = mock(TradeStatisticsManager.class);
|
||||
|
@ -116,7 +110,6 @@ public class TradesChartsViewModelTest {
|
|||
dir.delete();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dir.mkdir();
|
||||
keyRing = new KeyRing(new KeyStorage(dir));
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
|
@ -134,13 +127,45 @@ public class TradesChartsViewModelTest {
|
|||
long volume = Fiat.parseFiat("EUR", "2200").value;
|
||||
boolean isBullish = true;
|
||||
|
||||
Set<TradeStatistics2> set = new HashSet<>();
|
||||
Set<TradeStatistics3> set = new HashSet<>();
|
||||
final Date now = new Date();
|
||||
|
||||
set.add(new TradeStatistics2(offer, Price.parse("EUR", "520"), Coin.parseCoin("1"), new Date(now.getTime()), null, null));
|
||||
set.add(new TradeStatistics2(offer, Price.parse("EUR", "500"), Coin.parseCoin("1"), new Date(now.getTime() + 100), null, null));
|
||||
set.add(new TradeStatistics2(offer, Price.parse("EUR", "600"), Coin.parseCoin("1"), new Date(now.getTime() + 200), null, null));
|
||||
set.add(new TradeStatistics2(offer, Price.parse("EUR", "580"), Coin.parseCoin("1"), new Date(now.getTime() + 300), null, null));
|
||||
set.add(new TradeStatistics3(offer.getCurrencyCode(),
|
||||
Price.parse("EUR", "520").getValue(),
|
||||
Coin.parseCoin("1").getValue(),
|
||||
PaymentMethod.BLOCK_CHAINS_ID,
|
||||
now.getTime(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null));
|
||||
set.add(new TradeStatistics3(offer.getCurrencyCode(),
|
||||
Price.parse("EUR", "500").getValue(),
|
||||
Coin.parseCoin("1").getValue(),
|
||||
PaymentMethod.BLOCK_CHAINS_ID,
|
||||
now.getTime() + 100,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null));
|
||||
set.add(new TradeStatistics3(offer.getCurrencyCode(),
|
||||
Price.parse("EUR", "600").getValue(),
|
||||
Coin.parseCoin("1").getValue(),
|
||||
PaymentMethod.BLOCK_CHAINS_ID,
|
||||
now.getTime() + 200,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null));
|
||||
set.add(new TradeStatistics3(offer.getCurrencyCode(),
|
||||
Price.parse("EUR", "580").getValue(),
|
||||
Coin.parseCoin("1").getValue(),
|
||||
PaymentMethod.BLOCK_CHAINS_ID,
|
||||
now.getTime() + 300,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null));
|
||||
|
||||
CandleData candleData = model.getCandleData(model.roundToTick(now, TradesChartsViewModel.TickUnit.DAY).getTime(), set);
|
||||
assertEquals(open, candleData.open);
|
||||
|
@ -183,7 +208,6 @@ public class TradesChartsViewModelTest {
|
|||
ArrayList<Trade> trades = new ArrayList<>();
|
||||
|
||||
// Set predetermined time to use as "now" during test
|
||||
Date test_time = dateFormat.parse("2018-01-01T00:00:05"); // Monday
|
||||
/* new MockUp<System>() {
|
||||
@Mock
|
||||
long currentTimeMillis() {
|
||||
|
@ -194,11 +218,19 @@ public class TradesChartsViewModelTest {
|
|||
// Two trades 10 seconds apart, different YEAR, MONTH, WEEK, DAY, HOUR, MINUTE_10
|
||||
trades.add(new Trade("2017-12-31T23:59:52", "1", "100", "EUR"));
|
||||
trades.add(new Trade("2018-01-01T00:00:02", "1", "110", "EUR"));
|
||||
Set<TradeStatistics2> set = new HashSet<>();
|
||||
Set<TradeStatistics3> set = new HashSet<>();
|
||||
trades.forEach(t ->
|
||||
set.add(new TradeStatistics2(offer, Price.parse(t.cc, t.price), Coin.parseCoin(t.size), t.date, null, null))
|
||||
set.add(new TradeStatistics3(offer.getCurrencyCode(),
|
||||
Price.parse(t.cc, t.price).getValue(),
|
||||
Coin.parseCoin(t.size).getValue(),
|
||||
PaymentMethod.BLOCK_CHAINS_ID,
|
||||
t.date.getTime(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null))
|
||||
);
|
||||
ObservableSet<TradeStatistics2> tradeStats = FXCollections.observableSet(set);
|
||||
ObservableSet<TradeStatistics3> tradeStats = FXCollections.observableSet(set);
|
||||
|
||||
// Run test for each tick type
|
||||
for (TradesChartsViewModel.TickUnit tick : TradesChartsViewModel.TickUnit.values()) {
|
||||
|
@ -209,7 +241,7 @@ public class TradesChartsViewModelTest {
|
|||
|
||||
// Trigger chart update
|
||||
model.setTickUnit(tick);
|
||||
assertEquals(model.selectedTradeCurrencyProperty.get().getCode(), tradeStats.iterator().next().getCurrencyCode());
|
||||
assertEquals(model.selectedTradeCurrencyProperty.get().getCode(), tradeStats.iterator().next().getCurrency());
|
||||
assertEquals(2, model.priceItems.size());
|
||||
assertEquals(2, model.volumeItems.size());
|
||||
}
|
||||
|
|
|
@ -90,7 +90,8 @@ public class Monitor {
|
|||
Capability.DAO_STATE,
|
||||
Capability.BUNDLE_OF_ENVELOPES,
|
||||
Capability.REFUND_AGENT,
|
||||
Capability.MEDIATION);
|
||||
Capability.MEDIATION,
|
||||
Capability.TRADE_STATISTICS_3);
|
||||
|
||||
// assemble Metrics
|
||||
// - create reporters
|
||||
|
|
|
@ -20,30 +20,15 @@ package bisq.monitor.metric;
|
|||
import bisq.monitor.Metric;
|
||||
import bisq.monitor.Reporter;
|
||||
|
||||
import bisq.asset.Asset;
|
||||
import bisq.asset.AssetRegistry;
|
||||
|
||||
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.TorCtlException;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -110,7 +95,6 @@ public class MarketStats extends Metric {
|
|||
} catch (IllegalStateException ignore) {
|
||||
// no match found
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import bisq.monitor.Reporter;
|
|||
import bisq.core.account.witness.AccountAgeWitnessStore;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
||||
import bisq.core.trade.statistics.TradeStatistics2Store;
|
||||
import bisq.core.trade.statistics.TradeStatistics3Store;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
|
@ -163,8 +163,8 @@ public class P2PMarketStats extends P2PSeedNodeSnapshotBase {
|
|||
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
|
||||
try {
|
||||
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null);
|
||||
TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix);
|
||||
hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
|
||||
TradeStatistics3Store tradeStatistics3Store = (TradeStatistics3Store) persistenceManager.getPersisted(TradeStatistics3Store.class.getSimpleName() + networkPostfix);
|
||||
hashes.addAll(tradeStatistics3Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
|
||||
|
||||
AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix);
|
||||
hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
|
||||
|
|
|
@ -27,7 +27,7 @@ import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
|
|||
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
|
||||
import bisq.core.dao.monitoring.network.messages.GetStateHashesResponse;
|
||||
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
||||
import bisq.core.trade.statistics.TradeStatistics2Store;
|
||||
import bisq.core.trade.statistics.TradeStatistics3Store;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.network.Connection;
|
||||
|
@ -137,8 +137,8 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
|
|||
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
|
||||
try {
|
||||
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null);
|
||||
TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix);
|
||||
hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
|
||||
TradeStatistics3Store tradeStatistics3Store = (TradeStatistics3Store) persistenceManager.getPersisted(TradeStatistics3Store.class.getSimpleName() + networkPostfix);
|
||||
hashes.addAll(tradeStatistics3Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
|
||||
|
||||
AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix);
|
||||
hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
|
||||
|
@ -233,9 +233,9 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
|
|||
int oldest = (int) nodeAddressTupleMap.values().stream().min(Comparator.comparingLong(Tuple::getHeight)).get().height;
|
||||
|
||||
// - update queried height
|
||||
if(type.contains("DaoState"))
|
||||
if (type.contains("DaoState"))
|
||||
daostateheight = oldest - 20;
|
||||
else if(type.contains("Proposal"))
|
||||
else if (type.contains("Proposal"))
|
||||
proposalheight = oldest - 20;
|
||||
else
|
||||
blindvoteheight = oldest - 20;
|
||||
|
@ -255,7 +255,6 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
|
|||
|
||||
List<ByteBuffer> states = hitcount.entrySet().stream().sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue())).map(byteBufferIntegerEntry -> byteBufferIntegerEntry.getKey()).collect(Collectors.toList());
|
||||
hitcount.clear();
|
||||
|
||||
nodeAddressTupleMap.forEach((nodeAddress, tuple) -> daoreport.put(type + "." + OnionParser.prettyPrint(nodeAddress) + ".hash", Integer.toString(Arrays.asList(states.toArray()).indexOf(ByteBuffer.wrap(tuple.hash)))));
|
||||
|
||||
// - report reference head
|
||||
|
|
|
@ -54,7 +54,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
/**
|
||||
* Fetches fee and price data from the configured price nodes.
|
||||
* Based on the work of HarryMcFinned.
|
||||
*
|
||||
*
|
||||
* @author Florian Reimair
|
||||
* @author HarryMcFinned
|
||||
*
|
||||
|
@ -159,7 +159,6 @@ public class PriceNodeStats extends Metric {
|
|||
}
|
||||
}
|
||||
} catch (TorCtlException | IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import bisq.monitor.Metric;
|
|||
import bisq.monitor.OnionParser;
|
||||
import bisq.monitor.Reporter;
|
||||
import bisq.monitor.StatisticsHelper;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import org.berndpruenster.netlayer.tor.Tor;
|
||||
|
@ -33,6 +34,7 @@ import java.io.IOException;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
|
@ -84,7 +86,6 @@ public class TorRoundTripTime extends Metric {
|
|||
reporter.report(StatisticsHelper.process(samples), getName());
|
||||
}
|
||||
} catch (TorCtlException | IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@ public class TorStartupTime extends Metric {
|
|||
try {
|
||||
torOverrides = new Torrc(overrides);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +78,6 @@ public class TorStartupTime extends Metric {
|
|||
// stop the timer and set its timestamp
|
||||
reporter.report(System.currentTimeMillis() - start, getName());
|
||||
} catch (TorCtlException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// cleanup
|
||||
|
|
|
@ -920,6 +920,13 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
|
|||
return keyRing;
|
||||
}
|
||||
|
||||
public Optional<Capabilities> findPeersCapabilities(NodeAddress peer) {
|
||||
return networkNode.getConfirmedConnections().stream()
|
||||
.filter(e -> e.getPeersNodeAddressOptional().isPresent())
|
||||
.filter(e -> e.getPeersNodeAddressOptional().get().equals(peer))
|
||||
.map(Connection::getCapabilities)
|
||||
.findAny();
|
||||
}
|
||||
|
||||
@Value
|
||||
public class MailboxItem {
|
||||
|
|
|
@ -61,7 +61,7 @@ public abstract class MapStoreService<T extends PersistableEnvelope, R extends P
|
|||
requestPersistence();
|
||||
}
|
||||
|
||||
R putIfAbsent(P2PDataStorage.ByteArray hash, R payload) {
|
||||
protected R putIfAbsent(P2PDataStorage.ByteArray hash, R payload) {
|
||||
R previous = getMap().putIfAbsent(hash, payload);
|
||||
requestPersistence();
|
||||
return previous;
|
||||
|
|
BIN
p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET
(Stored with Git LFS)
BIN
p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET
(Stored with Git LFS)
Binary file not shown.
BIN
p2p/src/main/resources/TradeStatistics3Store_1.4.0_BTC_MAINNET
(Stored with Git LFS)
Normal file
BIN
p2p/src/main/resources/TradeStatistics3Store_1.4.0_BTC_MAINNET
(Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -154,7 +154,7 @@ message GetTradeStatisticsRequest {
|
|||
}
|
||||
|
||||
message GetTradeStatisticsReply {
|
||||
repeated TradeStatistics2 TradeStatistics = 1;
|
||||
repeated TradeStatistics3 TradeStatistics = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -510,9 +510,7 @@ message StoragePayload {
|
|||
Mediator mediator = 3;
|
||||
Filter filter = 4;
|
||||
|
||||
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
|
||||
// versions and convert it to TradeStatistics2 objects.
|
||||
TradeStatistics trade_statistics = 5 [deprecated = true];
|
||||
// TradeStatistics trade_statistics = 5 [deprecated = true]; Removed in v.1.4.0
|
||||
|
||||
MailboxStoragePayload mailbox_storage_payload = 6;
|
||||
OfferPayload offer_payload = 7;
|
||||
|
@ -524,10 +522,11 @@ message StoragePayload {
|
|||
message PersistableNetworkPayload {
|
||||
oneof message {
|
||||
AccountAgeWitness account_age_witness = 1;
|
||||
TradeStatistics2 trade_statistics2 = 2;
|
||||
TradeStatistics2 trade_statistics2 = 2 [deprecated = true];
|
||||
ProposalPayload proposal_payload = 3;
|
||||
BlindVotePayload blind_vote_payload = 4;
|
||||
SignedWitness signed_witness = 5;
|
||||
TradeStatistics3 trade_statistics3 = 6;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -650,44 +649,36 @@ message Filter {
|
|||
bool disable_auto_conf = 24;
|
||||
}
|
||||
|
||||
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
|
||||
// versions and convert it to TradeStatistics2 objects.
|
||||
message TradeStatistics {
|
||||
string base_currency = 1;
|
||||
string counter_currency = 2;
|
||||
OfferPayload.Direction direction = 3;
|
||||
int64 trade_price = 4;
|
||||
int64 trade_amount = 5;
|
||||
int64 trade_date = 6;
|
||||
string payment_method_id = 7;
|
||||
int64 offer_date = 8;
|
||||
bool offer_use_market_based_price = 9;
|
||||
double offer_market_price_margin = 10;
|
||||
int64 offer_amount = 11;
|
||||
int64 offer_min_amount = 12;
|
||||
string offer_id = 13;
|
||||
string deposit_tx_id = 14;
|
||||
bytes signature_pub_key_bytes = 15;
|
||||
map<string, string> extra_data = 16;
|
||||
// Deprecated
|
||||
message TradeStatistics2 {
|
||||
string base_currency = 1 [deprecated = true];
|
||||
string counter_currency = 2 [deprecated = true];
|
||||
OfferPayload.Direction direction = 3 [deprecated = true];
|
||||
int64 trade_price = 4 [deprecated = true];
|
||||
int64 trade_amount = 5 [deprecated = true];
|
||||
int64 trade_date = 6 [deprecated = true];
|
||||
string payment_method_id = 7 [deprecated = true];
|
||||
int64 offer_date = 8 [deprecated = true];
|
||||
bool offer_use_market_based_price = 9 [deprecated = true];
|
||||
double offer_market_price_margin = 10 [deprecated = true];
|
||||
int64 offer_amount = 11 [deprecated = true];
|
||||
int64 offer_min_amount = 12 [deprecated = true];
|
||||
string offer_id = 13 [deprecated = true];
|
||||
string deposit_tx_id = 14 [deprecated = true];
|
||||
bytes hash = 15 [deprecated = true];
|
||||
map<string, string> extra_data = 16 [deprecated = true];
|
||||
}
|
||||
|
||||
message TradeStatistics2 {
|
||||
string base_currency = 1;
|
||||
string counter_currency = 2;
|
||||
OfferPayload.Direction direction = 3;
|
||||
int64 trade_price = 4;
|
||||
int64 trade_amount = 5;
|
||||
int64 trade_date = 6;
|
||||
string payment_method_id = 7;
|
||||
int64 offer_date = 8;
|
||||
bool offer_use_market_based_price = 9;
|
||||
double offer_market_price_margin = 10;
|
||||
int64 offer_amount = 11;
|
||||
int64 offer_min_amount = 12;
|
||||
string offer_id = 13;
|
||||
string deposit_tx_id = 14;
|
||||
bytes hash = 15;
|
||||
map<string, string> extra_data = 16;
|
||||
message TradeStatistics3 {
|
||||
string currency = 1;
|
||||
int64 price = 2;
|
||||
int64 amount = 3;
|
||||
string payment_method = 4;
|
||||
int64 date = 5;
|
||||
string mediator = 6;
|
||||
string refund_agent = 7;
|
||||
bytes hash = 8;
|
||||
map<string, string> extra_data = 9;
|
||||
}
|
||||
|
||||
message MailboxStoragePayload {
|
||||
|
@ -1152,7 +1143,7 @@ message PersistableEnvelope {
|
|||
// BsqState bsq_state = 12; // not used but as other non-dao data have a higher index number we leave it to make clear that we cannot change following indexes
|
||||
|
||||
AccountAgeWitnessStore account_age_witness_store = 13;
|
||||
TradeStatistics2Store trade_statistics2_store = 14;
|
||||
TradeStatistics2Store trade_statistics2_store = 14 [deprecated = true];
|
||||
|
||||
// PersistableNetworkPayloadList persistable_network_payload_list = 15; // long deprecated & migration away from it is already done
|
||||
|
||||
|
@ -1171,6 +1162,7 @@ message PersistableEnvelope {
|
|||
SignedWitnessStore signed_witness_store = 28;
|
||||
MediationDisputeList mediation_dispute_list = 29;
|
||||
RefundDisputeList refund_dispute_list = 30;
|
||||
TradeStatistics3Store trade_statistics3_store = 31;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1211,8 +1203,13 @@ message SignedWitnessStore {
|
|||
}
|
||||
|
||||
// We use a list not a hash map to save disc space. The hash can be calculated from the payload anyway
|
||||
// Deprecated
|
||||
message TradeStatistics2Store {
|
||||
repeated TradeStatistics2 items = 1;
|
||||
repeated TradeStatistics2 items = 1 [deprecated = true];
|
||||
}
|
||||
|
||||
message TradeStatistics3Store {
|
||||
repeated TradeStatistics3 items = 1;
|
||||
}
|
||||
|
||||
message PeerList {
|
||||
|
|
Loading…
Add table
Reference in a new issue