instruments);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/MarketStream.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import net.quedex.api.common.CommunicationException;
4 | import net.quedex.api.common.StreamFailureListener;
5 |
6 | /**
7 | * Represents the stream of realtime public trade data streamed from Quedex and allows registering and subscribing for
8 | * particular data types. The registered listeners will be called (in a single thread) for every event that arrives. The
9 | * data come in form of PGP-clearsigned JSON messages - all the verification and deserialization is handled by the
10 | * implementations and the listeners receive Java objects.
11 | *
12 | * The stream gives the following guarantees useful for state initialisation:
13 | *
14 | * -
15 | * {@link InstrumentsListener} will be always called immediately after connection is established, before any
16 | * other listener
17 | *
18 | * -
19 | * after the connection is established, every listener is guaranteed to be called at least once
20 | *
21 | *
22 | *
23 | * To handle all errors properly, always {@link #registerStreamFailureListener} before {@link #start}ing the stream.
24 | */
25 | public interface MarketStream {
26 |
27 | void registerStreamFailureListener(StreamFailureListener streamFailureListener);
28 |
29 | void start() throws CommunicationException;
30 |
31 | void registerInstrumentsListener(InstrumentsListener instrumentsListener);
32 |
33 | Registration registerOrderBookListener(OrderBookListener orderBookListener);
34 |
35 | Registration registerTradeListener(TradeListener tradeListener);
36 |
37 | Registration registerQuotesListener(QuotesListener quotesListener);
38 |
39 | void registerSpotDataListener(SpotDataListener spotDataListener);
40 |
41 | void registerAndSubscribeSessionStateListener(SessionStateListener sessionStateListener);
42 |
43 | void stop() throws CommunicationException;
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/OrderBook.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.util.List;
9 |
10 | import static com.google.common.base.Preconditions.checkNotNull;
11 |
12 | public class OrderBook {
13 |
14 | private final int instrumentId;
15 | private final List bids;
16 | private final List asks;
17 |
18 | @JsonCreator
19 | public OrderBook(
20 | @JsonProperty("instrument_id") int instrumentId,
21 | @JsonProperty("bids") List bids,
22 | @JsonProperty("asks") List asks
23 | ) {
24 | this.instrumentId = instrumentId;
25 | this.bids = checkNotNull(bids, "null bids");
26 | this.asks = checkNotNull(asks, "null asks");
27 | }
28 |
29 | public int getInstrumentId() {
30 | return instrumentId;
31 | }
32 |
33 | public List getBids() {
34 | return bids;
35 | }
36 |
37 | public List getAsks() {
38 | return asks;
39 | }
40 |
41 | @Override
42 | public boolean equals(Object o) {
43 | if (this == o) return true;
44 | if (o == null || getClass() != o.getClass()) return false;
45 | OrderBook orderBook = (OrderBook) o;
46 | return instrumentId == orderBook.instrumentId &&
47 | Objects.equal(bids, orderBook.bids) &&
48 | Objects.equal(asks, orderBook.asks);
49 | }
50 |
51 | @Override
52 | public int hashCode() {
53 | return Objects.hashCode(instrumentId, bids, asks);
54 | }
55 |
56 | @Override
57 | public String toString() {
58 | return MoreObjects.toStringHelper(this)
59 | .add("instrumentId", instrumentId)
60 | .add("bids", bids)
61 | .add("asks", asks)
62 | .toString();
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/OrderBookListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | @FunctionalInterface
4 | public interface OrderBookListener {
5 |
6 | void onOrderBook(OrderBook orderBook);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/PriceQuantity.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.google.common.base.Objects;
5 |
6 | import java.math.BigDecimal;
7 |
8 | import static com.google.common.base.Preconditions.checkArgument;
9 | import static com.google.common.base.Preconditions.checkNotNull;
10 |
11 | public final class PriceQuantity {
12 |
13 | private final BigDecimal price; // may be null for market order
14 | private final int quantity;
15 |
16 | public PriceQuantity(BigDecimal price, int quantity) {
17 | this.price = checkNotNull(price, "null price");
18 | this.quantity = quantity;
19 | }
20 |
21 | public PriceQuantity(int quantity) {
22 | this.price = null;
23 | this.quantity = quantity;
24 | }
25 |
26 | @JsonCreator
27 | private PriceQuantity(BigDecimal[] priceQty) {
28 | checkArgument(priceQty.length == 2, "priceQty.length=%s != 2", priceQty.length);
29 | this.price = priceQty[0];
30 | this.quantity = priceQty[1].intValueExact();
31 | }
32 |
33 | /**
34 | * @return price, null if absent
35 | */
36 | public BigDecimal getPrice() {
37 | return price;
38 | }
39 |
40 | public int getQuantity() {
41 | return quantity;
42 | }
43 |
44 | @Override
45 | public boolean equals(Object o) {
46 | if (this == o) return true;
47 | if (o == null || getClass() != o.getClass()) return false;
48 | PriceQuantity priceQuantity = (PriceQuantity) o;
49 | return quantity == priceQuantity.quantity &&
50 | Objects.equal(price, priceQuantity.price);
51 | }
52 |
53 | @Override
54 | public int hashCode() {
55 | return Objects.hashCode(price, quantity);
56 | }
57 |
58 | @Override
59 | public String toString() {
60 | return "[" + price + ',' + quantity + ']';
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/Quotes.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 |
12 | public class Quotes {
13 |
14 | private final int instrumentId;
15 | private final BigDecimal last;
16 | private final int lastQuantity;
17 | private final BigDecimal bid;
18 | private final Integer bidQuantity;
19 | private final BigDecimal ask;
20 | private final Integer askQuantity;
21 | private final int volume;
22 | private final int openInterest;
23 | private final BigDecimal tap;
24 | private final BigDecimal lowerLimit;
25 | private final BigDecimal upperLimit;
26 |
27 | @JsonCreator
28 | public Quotes(
29 | @JsonProperty("instrument_id") int instrumentId,
30 | @JsonProperty("last") BigDecimal last,
31 | @JsonProperty("last_quantity") int lastQuantity,
32 | @JsonProperty("bid") BigDecimal bid,
33 | @JsonProperty("bid_quantity") Integer bidQuantity,
34 | @JsonProperty("ask") BigDecimal ask,
35 | @JsonProperty("ask_quantity") Integer askQuantity,
36 | @JsonProperty("volume") int volume,
37 | @JsonProperty("open_interest") int openInterest,
38 | @JsonProperty("tap") BigDecimal tap,
39 | @JsonProperty("lower_limit") BigDecimal lowerLimit,
40 | @JsonProperty("upper_limit") BigDecimal upperLimit
41 | ) {
42 | checkArgument(last.compareTo(BigDecimal.ZERO) > 0, "last=%s <= 0", last);
43 | checkArgument(lastQuantity >= 0, "lastQuantity=%s < 0", lastQuantity); // may be 0 when reference trade
44 | checkArgument(volume >= 0, "volume=%s < 0", volume);
45 | checkArgument(bid == null || bid.compareTo(BigDecimal.ZERO) > 0, "bid=%s <= 0", bid);
46 | checkArgument(bidQuantity == null || bidQuantity > 0, "bidQuantity=%s <= 0", bidQuantity);
47 | checkArgument(ask == null || ask.compareTo(BigDecimal.ZERO) > 0, "ask=%s <= 0", ask);
48 | checkArgument(askQuantity == null || askQuantity > 0, "askQuantity=%s <= 0", askQuantity);
49 | checkArgument(openInterest >= 0, "openInterest=%s < 0", openInterest);
50 | checkArgument(tap == null || tap.compareTo(BigDecimal.ZERO) > 0, "tap=%s <= 0", tap);
51 | checkArgument(lowerLimit == null || lowerLimit.compareTo(BigDecimal.ZERO) > 0, "lowerLimit=%s <= 0", lowerLimit);
52 | checkArgument(upperLimit == null || upperLimit.compareTo(BigDecimal.ZERO) > 0, "upperLimit=%s <= 0", upperLimit);
53 | this.instrumentId = instrumentId;
54 | this.last = last;
55 | this.lastQuantity = lastQuantity;
56 | this.bid = bid;
57 | this.bidQuantity = bidQuantity;
58 | this.ask = ask;
59 | this.askQuantity = askQuantity;
60 | this.volume = volume;
61 | this.openInterest = openInterest;
62 | this.tap = tap;
63 | this.lowerLimit = lowerLimit;
64 | this.upperLimit = upperLimit;
65 | }
66 |
67 | public int getInstrumentId() {
68 | return instrumentId;
69 | }
70 |
71 | public BigDecimal getLast() {
72 | return last;
73 | }
74 |
75 | public int getLastQuantity() {
76 | return lastQuantity;
77 | }
78 |
79 | /**
80 | * @return fist level of the order book if present, null otherwise
81 | */
82 | public PriceQuantity getBid() {
83 | return bidQuantity == null ? null : new PriceQuantity(bid, bidQuantity);
84 | }
85 |
86 | /**
87 | * @return fist level of the order book if present, null otherwise
88 | */
89 | public PriceQuantity getAsk() {
90 | return askQuantity == null ? null : new PriceQuantity(ask, askQuantity);
91 | }
92 |
93 | public int getVolume() {
94 | return volume;
95 | }
96 |
97 | public int getOpenInterest() {
98 | return openInterest;
99 | }
100 |
101 | public BigDecimal getTap() {
102 | return tap;
103 | }
104 |
105 | public BigDecimal getLowerLimit() {
106 | return lowerLimit;
107 | }
108 |
109 | public BigDecimal getUpperLimit() {
110 | return upperLimit;
111 | }
112 |
113 | @Override
114 | public boolean equals(Object o) {
115 | if (this == o) return true;
116 | if (o == null || getClass() != o.getClass()) return false;
117 | Quotes quotes = (Quotes) o;
118 | return instrumentId == quotes.instrumentId &&
119 | lastQuantity == quotes.lastQuantity &&
120 | volume == quotes.volume &&
121 | openInterest == quotes.openInterest &&
122 | Objects.equal(last, quotes.last) &&
123 | Objects.equal(bid, quotes.bid) &&
124 | Objects.equal(bidQuantity, quotes.bidQuantity) &&
125 | Objects.equal(ask, quotes.ask) &&
126 | Objects.equal(askQuantity, quotes.askQuantity) &&
127 | Objects.equal(tap, quotes.tap) &&
128 | Objects.equal(lowerLimit, quotes.lowerLimit) &&
129 | Objects.equal(upperLimit, quotes.upperLimit);
130 | }
131 |
132 | @Override
133 | public int hashCode() {
134 | return Objects.hashCode(
135 | instrumentId,
136 | last,
137 | lastQuantity,
138 | bid,
139 | bidQuantity,
140 | ask,
141 | askQuantity,
142 | volume,
143 | openInterest,
144 | tap,
145 | lowerLimit,
146 | upperLimit
147 | );
148 | }
149 |
150 | @Override
151 | public String toString() {
152 | return MoreObjects.toStringHelper(this)
153 | .add("instrumentId", instrumentId)
154 | .add("last", last)
155 | .add("lastQuantity", lastQuantity)
156 | .add("bid", bid)
157 | .add("bidQuantity", bidQuantity)
158 | .add("ask", ask)
159 | .add("askQuantity", askQuantity)
160 | .add("volume", volume)
161 | .add("openInterest", openInterest)
162 | .add("tap", tap)
163 | .add("lowerLimit", lowerLimit)
164 | .add("upperLimit", upperLimit)
165 | .toString();
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/QuotesListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | @FunctionalInterface
4 | public interface QuotesListener {
5 |
6 | void onQuotes(Quotes quotes);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/Registration.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import java.util.Collection;
4 |
5 | /**
6 | * A registration of a single market stream listener. May be subscribed and unsubscribed for particular instruments.
7 | */
8 | public interface Registration {
9 |
10 | Registration subscribe(int instrumentId);
11 |
12 | Registration unsubscribe(int instrumentId);
13 |
14 | Registration subscribe(Collection instrumentIds);
15 |
16 | Registration unsubscribe(Collection instrumentIds);
17 |
18 | Registration unsubscribeAll();
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/SessionState.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | public enum SessionState {
4 | OPENING_AUCTION, CONTINUOUS, AUCTION, CLOSING_AUCTION, NO_TRADING, MAINTENANCE
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/SessionStateListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | @FunctionalInterface
4 | public interface SessionStateListener {
5 |
6 | void onSessionState(SessionState newSessionState);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/SpotDataListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | @FunctionalInterface
4 | public interface SpotDataListener {
5 |
6 | void onSpotData(SpotDataWrapper spotDataWrapper);
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/SpotDataWrapper.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import static com.google.common.base.Preconditions.checkArgument;
13 | import static com.google.common.base.Preconditions.checkNotNull;
14 |
15 | public class SpotDataWrapper {
16 |
17 | // maps from underlying symbol to SpotData
18 | private final @JsonProperty("spot_data") Map spotData;
19 | private final @JsonProperty("update_time") long timestamp;
20 |
21 | @JsonCreator
22 | public SpotDataWrapper(final @JsonProperty("spot_data") Map spotData,
23 | final @JsonProperty("update_time") long timestamp) {
24 |
25 | checkArgument(timestamp >= 0, "timestamp=%s < 0", timestamp);
26 | this.spotData = checkNotNull(spotData, "spotData");
27 | this.timestamp = timestamp;
28 | }
29 |
30 | public Map getSpotData() {
31 | return spotData;
32 | }
33 | public long getTimestamp() {
34 | return timestamp;
35 | }
36 |
37 | @Override
38 | public boolean equals(final Object o) {
39 | if (this == o) {
40 | return true;
41 | }
42 | if (o == null || getClass() != o.getClass()) {
43 | return false;
44 | }
45 | final SpotDataWrapper that = (SpotDataWrapper) o;
46 | return Objects.equal(spotData, that.spotData) &&
47 | timestamp == that.timestamp;
48 | }
49 |
50 | @Override
51 | public int hashCode() {
52 | return Objects.hashCode(spotData, timestamp);
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return MoreObjects.toStringHelper(this)
58 | .add("spotData", spotData)
59 | .add("timestamp", timestamp)
60 | .toString();
61 | }
62 |
63 | public static class SpotData {
64 | private final BigDecimal spotIndex;
65 | private final BigDecimal spotIndexChange;
66 | private final BigDecimal settlementIndex;
67 | private final BigDecimal settlementIndexChange;
68 | private final List constituents;
69 | private final Map spotQuotes;
70 |
71 | @JsonCreator
72 | public SpotData(final @JsonProperty("spot_index") BigDecimal spotIndex,
73 | final @JsonProperty("spot_index_change") BigDecimal spotIndexChange,
74 | final @JsonProperty("settlement_index") BigDecimal settlementIndex,
75 | final @JsonProperty("settlement_index_change") BigDecimal settlementIndexChange,
76 | final @JsonProperty("constituents") List constituents,
77 | final @JsonProperty("spot_quotes") Map spotQuotes) {
78 | checkArgument(spotIndex != null && spotIndex.compareTo(BigDecimal.ZERO) > 0, "spotIndex=%s <= 0", spotIndex);
79 | checkArgument(settlementIndex != null && settlementIndex.compareTo(BigDecimal.ZERO) > 0, "settlementIndex=%s <= 0", settlementIndex);
80 | this.spotIndex = spotIndex;
81 | this.spotIndexChange = checkNotNull(spotIndexChange, "spotIndexChange");
82 | this.settlementIndex = settlementIndex;
83 | this.settlementIndexChange = checkNotNull(settlementIndexChange, "settlementIndexChange");
84 | this.constituents = checkNotNull(constituents, "constituents");
85 | this.spotQuotes = checkNotNull(spotQuotes, "spotQuotes");
86 | }
87 |
88 | public BigDecimal getSpotIndex() {
89 | return spotIndex;
90 | }
91 | public BigDecimal getSpotIndexChange() {
92 | return spotIndexChange;
93 | }
94 | public BigDecimal getSettlementIndex() {
95 | return settlementIndex;
96 | }
97 | public BigDecimal getSettlementIndexChange() {
98 | return settlementIndexChange;
99 | }
100 | public List getConstituents() {
101 | return constituents;
102 | }
103 | public Map getSpotQuotes() {
104 | return spotQuotes;
105 | }
106 |
107 | @Override
108 | public boolean equals(final Object o) {
109 | if (this == o) {
110 | return true;
111 | }
112 | if (o == null || getClass() != o.getClass()) {
113 | return false;
114 | }
115 | final SpotData that = (SpotData) o;
116 | return Objects.equal(spotIndex, that.spotIndex) &&
117 | Objects.equal(spotIndexChange, that.spotIndexChange) &&
118 | Objects.equal(settlementIndex, that.settlementIndex) &&
119 | Objects.equal(settlementIndexChange, that.settlementIndexChange) &&
120 | Objects.equal(constituents, that.constituents) &&
121 | Objects.equal(spotQuotes, that.spotQuotes);
122 | }
123 |
124 | @Override
125 | public int hashCode() {
126 | return Objects.hashCode(spotIndex, spotIndexChange, settlementIndex, settlementIndexChange, constituents, spotQuotes);
127 | }
128 |
129 | @Override
130 | public String toString() {
131 | return MoreObjects.toStringHelper(this)
132 | .add("spotIndex", spotIndex)
133 | .add("spotIndexChange", spotIndexChange)
134 | .add("settlementIndex", settlementIndex)
135 | .add("settlementIndexChange", settlementIndexChange)
136 | .add("constituents", constituents)
137 | .add("spotQuotes", spotQuotes)
138 | .toString();
139 | }
140 | }
141 | }
142 |
143 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/Trade.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 | import static com.google.common.base.Preconditions.checkNotNull;
12 |
13 | public final class Trade {
14 |
15 | public enum LiquidityProvider {
16 | BUYER, SELLER, AUCTION, REFERENCE, SETTLEMENT;
17 |
18 | @JsonCreator
19 | private static LiquidityProvider deserialize(String value) {
20 | return valueOf(value.toUpperCase());
21 | }
22 | }
23 |
24 | private final int instrumentId;
25 | private final long tradeId;
26 | private final long timestamp;
27 | private final BigDecimal price;
28 | private final int quantity;
29 |
30 | private final LiquidityProvider liquidityProvider;
31 |
32 | @JsonCreator
33 | public Trade(
34 | @JsonProperty("instrument_id") int instrumentId,
35 | @JsonProperty("trade_id") long tradeId,
36 | @JsonProperty("timestamp") long timestamp,
37 | @JsonProperty("price") BigDecimal price,
38 | @JsonProperty("quantity") int quantity,
39 | @JsonProperty("liquidity_provider") LiquidityProvider liquidityProvider
40 | ) {
41 | checkArgument(quantity >= 0, "quantity=%s < 0", quantity);
42 | this.instrumentId = instrumentId;
43 | this.tradeId = tradeId;
44 | this.timestamp = timestamp;
45 | this.price = checkNotNull(price, "null price");
46 | this.quantity = quantity;
47 | this.liquidityProvider = checkNotNull(liquidityProvider, "null liquidityProvider");
48 | }
49 |
50 | public int getInstrumentId() {
51 | return instrumentId;
52 | }
53 |
54 | public long getTradeId() {
55 | return tradeId;
56 | }
57 |
58 | public long getTimestamp() {
59 | return timestamp;
60 | }
61 |
62 | public BigDecimal getPrice() {
63 | return price;
64 | }
65 |
66 | public int getQuantity() {
67 | return quantity;
68 | }
69 |
70 | public LiquidityProvider getLiquidityProvider() {
71 | return liquidityProvider;
72 | }
73 |
74 | @Override
75 | public boolean equals(Object o) {
76 | if (this == o) return true;
77 | if (o == null || getClass() != o.getClass()) return false;
78 | Trade trade = (Trade) o;
79 | return tradeId == trade.tradeId;
80 | }
81 |
82 | public boolean equalsFieldByField(Object o) {
83 | if (this == o) return true;
84 | if (o == null || getClass() != o.getClass()) return false;
85 | Trade trade = (Trade) o;
86 | return instrumentId == trade.instrumentId &&
87 | tradeId == trade.tradeId &&
88 | timestamp == trade.timestamp &&
89 | quantity == trade.quantity &&
90 | Objects.equal(price, trade.price) &&
91 | liquidityProvider == trade.liquidityProvider;
92 | }
93 |
94 | @Override
95 | public int hashCode() {
96 | return Objects.hashCode(tradeId);
97 | }
98 |
99 | @Override
100 | public String toString() {
101 | return MoreObjects.toStringHelper(this)
102 | .add("instrumentId", instrumentId)
103 | .add("tradeId", tradeId)
104 | .add("price", price)
105 | .add("quantity", quantity)
106 | .add("liquidityProvider", liquidityProvider)
107 | .toString();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/TradeListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | @FunctionalInterface
4 | public interface TradeListener {
5 |
6 | void onTrade(Trade trade);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/market/WebsocketMarketStream.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import net.quedex.api.common.Config;
4 | import net.quedex.api.common.WebsocketStream;
5 | import net.quedex.api.pgp.BcPublicKey;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | /**
10 | *
11 | */
12 | public class WebsocketMarketStream extends WebsocketStream implements MarketStream {
13 |
14 | private static final Logger LOGGER = LoggerFactory.getLogger(WebsocketMarketStream.class);
15 |
16 | public WebsocketMarketStream(String marketStreamUrl, BcPublicKey qdxPublicKey) {
17 | super(LOGGER, marketStreamUrl, new MarketMessageReceiver(qdxPublicKey));
18 | }
19 |
20 | public WebsocketMarketStream(Config config) {
21 | this(config.getMarketStreamUrl(), config.getQdxPublicKey());
22 | }
23 |
24 | @Override
25 | public void registerInstrumentsListener(final InstrumentsListener instrumentsListener) {
26 | messageReceiver.registerInstrumentsListener(instrumentsListener);
27 | }
28 |
29 | @Override
30 | public Registration registerOrderBookListener(OrderBookListener orderBookListener) {
31 | return messageReceiver.registerOrderBookListener(orderBookListener);
32 | }
33 |
34 | @Override
35 | public Registration registerTradeListener(TradeListener tradeListener) {
36 | return messageReceiver.registerTradeListener(tradeListener);
37 | }
38 |
39 | @Override
40 | public Registration registerQuotesListener(QuotesListener quotesListener) {
41 | return messageReceiver.registerQuotesListener(quotesListener);
42 | }
43 |
44 | @Override
45 | public void registerSpotDataListener(final SpotDataListener spotDataListener) {
46 | messageReceiver.registerSpotDataListener(spotDataListener);
47 | }
48 |
49 | @Override
50 | public void registerAndSubscribeSessionStateListener(SessionStateListener sessionStateListener) {
51 | messageReceiver.registerAndSubscribeSessionStateListener(sessionStateListener);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/BcDecryptor.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | import com.google.common.io.ByteStreams;
4 | import org.bouncycastle.openpgp.*;
5 | import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
6 | import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
7 | import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
8 |
9 | import java.io.ByteArrayInputStream;
10 | import java.io.ByteArrayOutputStream;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.nio.charset.StandardCharsets;
14 | import java.util.Iterator;
15 |
16 | import static com.google.common.base.Preconditions.checkNotNull;
17 |
18 | public class BcDecryptor {
19 |
20 | private static final long HIDDEN_RECIPIENT_KEY_ID = 0;
21 |
22 | private final BcPublicKey publicKey;
23 | private final BcPrivateKey ourKey;
24 |
25 | public BcDecryptor(BcPublicKey publicKey, BcPrivateKey ourKey) {
26 | this.publicKey = checkNotNull(publicKey);
27 | this.ourKey = checkNotNull(ourKey);
28 | }
29 |
30 | public String decrypt(String message)
31 | throws PGPDecryptionException, PGPKeyNotFoundException, PGPUnknownRecipientException, PGPInvalidSignatureException {
32 | try {
33 | InputStream in = PGPUtil.getDecoderStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
34 |
35 | PGPObjectFactory encryptedFactory = new BcPGPObjectFactory(in);
36 | Object object = encryptedFactory.nextObject();
37 |
38 | PGPEncryptedDataList encryptedDataList;
39 |
40 | if (object instanceof PGPEncryptedDataList) {
41 | encryptedDataList = (PGPEncryptedDataList) object;
42 | } else {
43 | encryptedDataList = (PGPEncryptedDataList) encryptedFactory.nextObject();
44 | }
45 |
46 | PGPPublicKeyEncryptedData encryptedData = null;
47 | InputStream clear = null;
48 |
49 | for (Iterator it = encryptedDataList.getEncryptedDataObjects(); it.hasNext(); ) {
50 | encryptedData = (PGPPublicKeyEncryptedData) it.next();
51 |
52 | if (encryptedData.getKeyID() == HIDDEN_RECIPIENT_KEY_ID) {
53 |
54 | for (final PGPPrivateKey keyToCheck : ourKey.getPrivateKeys()) {
55 | try {
56 | clear = encryptedData.getDataStream(new BcPublicKeyDataDecryptorFactory(keyToCheck));
57 | break;
58 | } catch (Exception e) { /* fall through and retry */ }
59 | }
60 | } else {
61 | try {
62 | PGPPrivateKey privateKey = ourKey.getPrivateKeyWithId(encryptedData.getKeyID());
63 | clear = encryptedData.getDataStream(new BcPublicKeyDataDecryptorFactory(privateKey));
64 | } catch (PGPKeyNotFoundException e) { /* fall through and retry */ }
65 | }
66 |
67 | if (clear != null) {
68 | break;
69 | }
70 | }
71 |
72 | if (clear == null) {
73 | throw new PGPUnknownRecipientException("Message is encrypted for unknown recipient");
74 | }
75 |
76 | PGPObjectFactory plainFactory = new BcPGPObjectFactory(clear);
77 | Object nextObject = plainFactory.nextObject();
78 |
79 | PGPCompressedData compressedData = (PGPCompressedData) nextObject;
80 | PGPObjectFactory uncompressedFactory = new BcPGPObjectFactory(compressedData.getDataStream());
81 |
82 | plainFactory = uncompressedFactory;
83 | nextObject = uncompressedFactory.nextObject();
84 |
85 | ByteArrayOutputStream out = new ByteArrayOutputStream();
86 |
87 | PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) nextObject;
88 | PGPOnePassSignature signature = sigList.get(0);
89 |
90 | nextObject = plainFactory.nextObject();
91 | if (!(nextObject instanceof PGPLiteralData)) {
92 | throw new PGPDataValidationException("Expected literal data packet");
93 | }
94 | PGPLiteralData literalData = (PGPLiteralData) nextObject;
95 | ByteStreams.copy(literalData.getInputStream(), out);
96 |
97 | PGPSignatureList signatureList = (PGPSignatureList) plainFactory.nextObject();
98 |
99 | signature.init(new BcPGPContentVerifierBuilderProvider(), publicKey.getSigningKey());
100 | signature.update(out.toByteArray());
101 |
102 | if (signature.verify(signatureList.get(0))) {
103 | checkIntegrity(encryptedData);
104 |
105 | return new String(out.toByteArray(), StandardCharsets.UTF_8);
106 | }
107 |
108 | throw new PGPInvalidSignatureException("The signature is not valid");
109 |
110 | } catch (PGPException | RuntimeException | IOException e) {
111 | throw new PGPDecryptionException("Error Decrypting message", e);
112 | }
113 | }
114 |
115 | private static void checkIntegrity(PGPPublicKeyEncryptedData encryptedData)
116 | throws PGPException, IOException, PGPDecryptionException {
117 | if (encryptedData.isIntegrityProtected()) {
118 | if (!encryptedData.verify()) {
119 | throw new PGPDecryptionException("Message failed integrity check");
120 | }
121 | } else {
122 | throw new PGPDecryptionException("Message not integrity protected");
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/BcEncryptor.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | import org.bouncycastle.bcpg.ArmoredOutputStream;
4 | import org.bouncycastle.bcpg.HashAlgorithmTags;
5 | import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
6 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
7 | import org.bouncycastle.openpgp.*;
8 | import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
9 | import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
10 | import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
11 | import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
12 |
13 | import java.io.ByteArrayOutputStream;
14 | import java.io.IOException;
15 | import java.io.OutputStream;
16 | import java.nio.charset.StandardCharsets;
17 | import java.security.SecureRandom;
18 | import java.security.Security;
19 | import java.util.Date;
20 | import java.util.Hashtable;
21 |
22 | import static com.google.common.base.Preconditions.checkNotNull;
23 |
24 | public class BcEncryptor {
25 |
26 | private static final int BUFFER_SIZE = 2 << 7;
27 |
28 | private final BcPGPDataEncryptorBuilder dataEncryptor;
29 | private final BcPublicKey publicKey;
30 | private final BcPrivateKey ourKey;
31 |
32 | public BcEncryptor(BcPublicKey publicKey, BcPrivateKey ourKey) {
33 | this.publicKey = checkNotNull(publicKey);
34 | this.ourKey = checkNotNull(ourKey);
35 |
36 | Security.addProvider(new BouncyCastleProvider());
37 |
38 | dataEncryptor = new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256);
39 | dataEncryptor.setWithIntegrityPacket(true);
40 | dataEncryptor.setSecureRandom(new SecureRandom());
41 | }
42 |
43 | public String encrypt(String message, boolean sign) throws PGPEncryptionException, PGPKeyNotFoundException {
44 |
45 | try {
46 | PGPSecretKey secretKey = ourKey.getSecretKey();
47 | PGPPrivateKey privateKey = ourKey.getPrivateKey();
48 |
49 | byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
50 |
51 | PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
52 | PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptor);
53 | encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(publicKey.getEncryptionKey()));
54 | PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
55 | PGPContentSignerBuilder signerBuilder = new BcPGPContentSignerBuilder(
56 | secretKey.getPublicKey().getAlgorithm(),
57 | HashAlgorithmTags.SHA256
58 | );
59 | PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder);
60 | signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
61 | PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
62 | spGen.setSignerUserID(false, (String) secretKey.getPublicKey().getUserIDs().next());
63 | signatureGenerator.setHashedSubpackets(spGen.generate());
64 | if (sign) {
65 | signatureGenerator.update(messageBytes);
66 | }
67 |
68 | ByteArrayOutputStream bOut = new ByteArrayOutputStream();
69 | Hashtable headers = new Hashtable<>();
70 | headers.put("Version", "QPG");
71 | ArmoredOutputStream armoredOut = new ArmoredOutputStream(bOut, headers);
72 |
73 | OutputStream encryptedOut = encryptedDataGenerator.open(armoredOut, new byte[BUFFER_SIZE]);
74 | OutputStream compressedOut = compressedDataGenerator.open(encryptedOut);
75 | if (sign) {
76 | signatureGenerator.generateOnePassVersion(false).encode(compressedOut);
77 | }
78 | OutputStream literalOut = literalDataGenerator.open(
79 | compressedOut,
80 | PGPLiteralData.UTF8,
81 | PGPLiteralData.CONSOLE,
82 | messageBytes.length,
83 | new Date()
84 | );
85 | literalOut.write(messageBytes);
86 | literalDataGenerator.close();
87 |
88 | if (sign) {
89 | signatureGenerator.generate().encode(compressedOut);
90 | }
91 |
92 | compressedDataGenerator.close();
93 | encryptedDataGenerator.close();
94 | armoredOut.close();
95 |
96 | return new String(bOut.toByteArray(), StandardCharsets.UTF_8);
97 |
98 | } catch (PGPException | RuntimeException | IOException e) {
99 | throw new PGPEncryptionException("Error encrypting message", e);
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/BcPrivateKey.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | import com.google.common.base.Objects;
4 | import com.google.common.collect.ImmutableMap;
5 | import org.bouncycastle.bcpg.ArmoredInputStream;
6 | import org.bouncycastle.openpgp.*;
7 | import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
8 | import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
9 | import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
10 | import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
11 |
12 | import java.io.ByteArrayInputStream;
13 | import java.io.IOException;
14 | import java.nio.charset.StandardCharsets;
15 | import java.util.ArrayList;
16 | import java.util.Collection;
17 | import java.util.Iterator;
18 | import java.util.List;
19 |
20 | import static com.google.common.base.Preconditions.checkNotNull;
21 |
22 | public final class BcPrivateKey {
23 |
24 | private static final char[] EMPTY_CHAR_ARRAY = new char[0];
25 |
26 | private final PGPSecretKey secretKey;
27 | private final PGPPrivateKey privateKey;
28 | private final String fingerprint;
29 | private final ImmutableMap privateKeys;
30 | private final BcPublicKey publicKey;
31 |
32 | public static BcPrivateKey fromArmored(String armoredKeyString) throws PGPKeyInitialisationException {
33 | return fromArmored(armoredKeyString, EMPTY_CHAR_ARRAY);
34 | }
35 |
36 | public static BcPrivateKey fromArmored(String armoredKeyString, char[] passphrase)
37 | throws PGPKeyInitialisationException {
38 | return new BcPrivateKey(armoredKeyString, passphrase);
39 | }
40 |
41 | BcPrivateKey(String armoredKeyString, char[] passphrase) throws PGPKeyInitialisationException {
42 | try {
43 | PGPSecretKeyRing secKeyRing = new PGPSecretKeyRing(
44 | new ArmoredInputStream(new ByteArrayInputStream(armoredKeyString.getBytes(StandardCharsets.US_ASCII))),
45 | new BcKeyFingerprintCalculator());
46 |
47 | PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider())
48 | .build(passphrase);
49 |
50 | ImmutableMap.Builder builder = ImmutableMap.builder();
51 | List pubKeys = new ArrayList<>(2);
52 |
53 | for (Iterator iterator = secKeyRing.getSecretKeys(); iterator.hasNext(); ) {
54 | PGPSecretKey secretKey = (PGPSecretKey) iterator.next();
55 | PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor);
56 | builder.put(privateKey.getKeyID(), privateKey);
57 | pubKeys.add(secretKey.getPublicKey());
58 | }
59 |
60 | this.secretKey = secKeyRing.getSecretKey();
61 | this.privateKeys = builder.build();
62 | this.privateKey = this.secretKey.extractPrivateKey(decryptor);
63 | if (pubKeys.size() >= 2) {
64 | this.publicKey = new BcPublicKey(pubKeys.get(0), pubKeys.get(1));
65 | } else {
66 | this.publicKey = new BcPublicKey(pubKeys.get(0), pubKeys.get(0));
67 | }
68 |
69 | } catch (PGPException | RuntimeException | IOException e) {
70 | throw new PGPKeyInitialisationException("Error instantiating a private key", e);
71 | }
72 | checkNotNull(this.secretKey);
73 | checkNotNull(this.privateKey);
74 |
75 | this.fingerprint = BcPublicKey.hexFingerprint(secretKey.getPublicKey());
76 | }
77 |
78 | PGPSecretKey getSecretKey() {
79 | return secretKey;
80 | }
81 |
82 | PGPPrivateKey getPrivateKey() {
83 | return privateKey;
84 | }
85 |
86 | public String getFingerprint() {
87 | return fingerprint;
88 | }
89 |
90 | PGPPrivateKey getPrivateKeyWithId(long keyId) throws PGPKeyNotFoundException {
91 | if (!privateKeys.containsKey(keyId)) {
92 | throw new PGPKeyNotFoundException(
93 | String.format("Key with id: %s not found", Long.toHexString(keyId).toUpperCase())
94 | );
95 | }
96 | return privateKeys.get(keyId);
97 | }
98 |
99 | public Collection getPrivateKeys() {
100 | return privateKeys.values();
101 | }
102 |
103 | public BcPublicKey getPublicKey() {
104 | return publicKey;
105 | }
106 |
107 | @Override
108 | public boolean equals(Object o) {
109 | if (this == o) {
110 | return true;
111 | }
112 |
113 | if (o == null || o.getClass() != getClass()) {
114 | return false;
115 | }
116 |
117 | BcPrivateKey that = (BcPrivateKey) o;
118 |
119 | return Objects.equal(fingerprint, that.fingerprint);
120 | }
121 |
122 | @Override
123 | public int hashCode() {
124 | return fingerprint.hashCode();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/BcPublicKey.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | import com.google.common.base.MoreObjects;
4 | import com.google.common.collect.Iterators;
5 | import com.google.common.collect.Lists;
6 | import org.bouncycastle.bcpg.ArmoredInputStream;
7 | import org.bouncycastle.bcpg.ArmoredOutputStream;
8 | import org.bouncycastle.openpgp.PGPPublicKey;
9 | import org.bouncycastle.openpgp.PGPPublicKeyRing;
10 | import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
11 |
12 | import java.io.ByteArrayInputStream;
13 | import java.io.ByteArrayOutputStream;
14 | import java.io.IOException;
15 | import java.nio.charset.StandardCharsets;
16 | import java.util.List;
17 |
18 | public final class BcPublicKey {
19 |
20 | private final PGPPublicKey signingKey;
21 | private final PGPPublicKey encryptionKey;
22 |
23 | private final String fingerprint;
24 | private final String mainKeyIdentity;
25 |
26 | public static BcPublicKey fromArmored(String armoredKeyString) throws PGPKeyInitialisationException {
27 |
28 | try {
29 | PGPPublicKeyRing pubKeyRing = new PGPPublicKeyRing(
30 | new ArmoredInputStream(new ByteArrayInputStream(armoredKeyString.getBytes(StandardCharsets.UTF_8))),
31 | new BcKeyFingerprintCalculator()
32 | );
33 |
34 | if (Iterators.size(pubKeyRing.getPublicKeys()) < 1) {
35 | throw new PGPKeyInitialisationException("No keys in keyring");
36 | }
37 |
38 | PGPPublicKey signingKey = pubKeyRing.getPublicKey();
39 | PGPPublicKey encryptionKey;
40 |
41 | @SuppressWarnings("unchecked")
42 | List keys = Lists.newArrayList(pubKeyRing.getPublicKeys());
43 |
44 | if (keys.size() == 1) {
45 | encryptionKey = signingKey;
46 | } else {
47 | encryptionKey = keys.get(1);
48 | }
49 |
50 | if (!encryptionKey.isEncryptionKey()) {
51 | throw new PGPKeyInitialisationException("Error instatiating public key: sign-only key.");
52 | }
53 |
54 | return new BcPublicKey(signingKey, encryptionKey);
55 |
56 | } catch (RuntimeException | IOException e) {
57 | throw new PGPKeyInitialisationException("Error instantiating a public key", e);
58 | }
59 | }
60 |
61 | BcPublicKey(PGPPublicKey signingKey, PGPPublicKey encryptionKey) {
62 | this.signingKey = signingKey;
63 | this.encryptionKey = encryptionKey;
64 | fingerprint = hexFingerprint(signingKey);
65 | mainKeyIdentity = (String) signingKey.getUserIDs().next();
66 | }
67 |
68 | PGPPublicKey getSigningKey() {
69 | return signingKey;
70 | }
71 |
72 | PGPPublicKey getEncryptionKey() {
73 | return encryptionKey;
74 | }
75 |
76 | public String getFingerprint() {
77 | return fingerprint;
78 | }
79 |
80 | public String getMainKeyIdentity() {
81 | return mainKeyIdentity;
82 | }
83 |
84 | public String armored() {
85 | ByteArrayOutputStream out = new ByteArrayOutputStream();
86 | ArmoredOutputStream armored = new ArmoredOutputStream(out);
87 | try {
88 | signingKey.encode(armored);
89 | encryptionKey.encode(armored);
90 | armored.close();
91 | } catch (IOException e) {
92 | throw new IllegalStateException("Error writing armored public key", e);
93 | }
94 | return new String(out.toByteArray(), StandardCharsets.US_ASCII);
95 | }
96 |
97 | static String hexFingerprint(PGPPublicKey publicKey) {
98 | byte[] bytes = publicKey.getFingerprint();
99 | StringBuilder sb = new StringBuilder();
100 | for (byte b : bytes) {
101 | sb.append(String.format("%02X", b));
102 | }
103 | return sb.toString();
104 | }
105 |
106 | @Override
107 | public boolean equals(Object o) {
108 | if (this == o) {
109 | return true;
110 | }
111 |
112 | if (o == null || o.getClass() != getClass()) {
113 | return false;
114 | }
115 |
116 | BcPublicKey that = (BcPublicKey) o;
117 |
118 | return this.fingerprint.equals(that.fingerprint);
119 | }
120 |
121 | @Override
122 | public int hashCode() {
123 | return fingerprint.hashCode();
124 | }
125 |
126 | @Override
127 | public String toString() {
128 | return MoreObjects.toStringHelper(this)
129 | .add("fingerprint", fingerprint)
130 | .toString();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/BcSignatureVerifier.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | import org.bouncycastle.bcpg.ArmoredInputStream;
4 | import org.bouncycastle.openpgp.PGPException;
5 | import org.bouncycastle.openpgp.PGPSignature;
6 | import org.bouncycastle.openpgp.PGPSignatureList;
7 | import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
8 | import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
9 |
10 | import java.io.ByteArrayInputStream;
11 | import java.io.ByteArrayOutputStream;
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.nio.charset.StandardCharsets;
15 |
16 | import static com.google.common.base.Preconditions.checkNotNull;
17 | import static com.google.common.base.Preconditions.checkState;
18 |
19 | public final class BcSignatureVerifier {
20 |
21 | private final BcPublicKey publicKey;
22 |
23 | public BcSignatureVerifier(BcPublicKey publicKey) {
24 | this.publicKey = checkNotNull(publicKey, "null publicKey");
25 | }
26 |
27 | public String verifySignature(String message)
28 | throws PGPInvalidSignatureException, PGPSignatureVerificationException {
29 |
30 | try {
31 | ArmoredInputStream aIn = new ArmoredInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
32 |
33 | ByteArrayOutputStream bOut = new ByteArrayOutputStream();
34 | int ch;
35 |
36 | while ((ch = aIn.read()) >= 0 && aIn.isClearText()) {
37 | bOut.write((byte) ch);
38 | }
39 |
40 | JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn);
41 | PGPSignatureList p3 = (PGPSignatureList) pgpFact.nextObject();
42 | checkState(p3 != null && p3.size() >= 1, "No signatures");
43 | PGPSignature sig = p3.get(0);
44 |
45 | sig.init(new BcPGPContentVerifierBuilderProvider(), publicKey.getSigningKey());
46 |
47 | ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
48 | byte[] content = bOut.toByteArray();
49 | InputStream sigIn = new ByteArrayInputStream(content);
50 | int lookAhead = readInputLine(lineOut, sigIn);
51 |
52 | processLine(sig, lineOut.toByteArray());
53 |
54 | if (lookAhead != -1) {
55 | do {
56 | lookAhead = readInputLine(lineOut, lookAhead, sigIn);
57 |
58 | sig.update((byte) '\r');
59 | sig.update((byte) '\n');
60 |
61 | processLine(sig, lineOut.toByteArray());
62 | } while (lookAhead != -1);
63 | }
64 |
65 | if (sig.verify()) {
66 | return new String(content, StandardCharsets.UTF_8);
67 | }
68 |
69 | throw new PGPInvalidSignatureException(
70 | "Invalid signature, received keyId=" + Long.toHexString(sig.getKeyID()).toUpperCase()
71 | );
72 |
73 | } catch (IOException | PGPException e) {
74 | throw new PGPSignatureVerificationException("Error verifying message", e);
75 | }
76 | }
77 |
78 | private static void processLine(PGPSignature sig, byte[] line) throws IOException {
79 | int length = getLengthWithoutWhiteSpace(line);
80 | if (length > 0) {
81 | sig.update(line, 0, length);
82 | }
83 | }
84 |
85 | private static int getLengthWithoutWhiteSpace(byte[] line) {
86 | int end = line.length - 1;
87 | while (end >= 0 && isWhiteSpace(line[end])) {
88 | end--;
89 | }
90 | return end + 1;
91 | }
92 |
93 | private static boolean isWhiteSpace(byte b) {
94 | return b == '\r' || b == '\n' || b == '\t' || b == ' ';
95 | }
96 |
97 | private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) throws IOException {
98 | bOut.reset();
99 | int lookAhead = -1;
100 | int ch;
101 | while ((ch = fIn.read()) >= 0) {
102 | bOut.write(ch);
103 | if (ch == '\r' || ch == '\n') {
104 | lookAhead = readPassedEOL(bOut, ch, fIn);
105 | break;
106 | }
107 | }
108 | return lookAhead;
109 | }
110 |
111 | private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) throws IOException {
112 | bOut.reset();
113 | int ch = lookAhead;
114 | do {
115 | bOut.write(ch);
116 | if (ch == '\r' || ch == '\n') {
117 | lookAhead = readPassedEOL(bOut, ch, fIn);
118 | break;
119 | }
120 | } while ((ch = fIn.read()) >= 0);
121 | return lookAhead;
122 | }
123 |
124 | private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) throws IOException {
125 | int lookAhead = fIn.read();
126 | if (lastCh == '\r' && lookAhead == '\n') {
127 | bOut.write(lookAhead);
128 | lookAhead = fIn.read();
129 | }
130 | return lookAhead;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPDecryptionException.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public class PGPDecryptionException extends PGPExceptionBase {
4 |
5 | public PGPDecryptionException() {
6 | }
7 |
8 | public PGPDecryptionException(String message) {
9 | super(message);
10 | }
11 |
12 | public PGPDecryptionException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | public PGPDecryptionException(Throwable cause) {
17 | super(cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPEncryptionException.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public class PGPEncryptionException extends PGPExceptionBase {
4 |
5 | public PGPEncryptionException() {
6 | }
7 |
8 | public PGPEncryptionException(String message) {
9 | super(message);
10 | }
11 |
12 | public PGPEncryptionException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | public PGPEncryptionException(Throwable cause) {
17 | super(cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPExceptionBase.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public class PGPExceptionBase extends Exception {
4 |
5 | PGPExceptionBase() {
6 | }
7 |
8 | PGPExceptionBase(String message) {
9 | super(message);
10 | }
11 |
12 | PGPExceptionBase(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | PGPExceptionBase(Throwable cause) {
17 | super(cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPInvalidSignatureException.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public final class PGPInvalidSignatureException extends PGPExceptionBase {
4 |
5 | public PGPInvalidSignatureException() {
6 | }
7 |
8 | public PGPInvalidSignatureException(String message) {
9 | super(message);
10 | }
11 |
12 | public PGPInvalidSignatureException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | public PGPInvalidSignatureException(Throwable cause) {
17 | super(cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPKeyInitialisationException.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public final class PGPKeyInitialisationException extends PGPExceptionBase {
4 |
5 | public PGPKeyInitialisationException() {
6 | }
7 |
8 | public PGPKeyInitialisationException(String message) {
9 | super(message);
10 | }
11 |
12 | public PGPKeyInitialisationException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | public PGPKeyInitialisationException(Throwable cause) {
17 | super(cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPKeyNotFoundException.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public final class PGPKeyNotFoundException extends PGPExceptionBase {
4 |
5 | public PGPKeyNotFoundException() {
6 | }
7 |
8 | public PGPKeyNotFoundException(String message) {
9 | super(message);
10 | }
11 |
12 | public PGPKeyNotFoundException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | public PGPKeyNotFoundException(Throwable cause) {
17 | super(cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPSignatureVerificationException.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public class PGPSignatureVerificationException extends PGPExceptionBase {
4 |
5 | public PGPSignatureVerificationException() {
6 | }
7 |
8 | public PGPSignatureVerificationException(String message) {
9 | super(message);
10 | }
11 |
12 | public PGPSignatureVerificationException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/pgp/PGPUnknownRecipientException.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.pgp;
2 |
3 | public final class PGPUnknownRecipientException extends PGPExceptionBase {
4 |
5 | public PGPUnknownRecipientException() {
6 | }
7 |
8 | public PGPUnknownRecipientException(String message) {
9 | super(message);
10 | }
11 |
12 | public PGPUnknownRecipientException(String message, Throwable cause) {
13 | super(message, cause);
14 | }
15 |
16 | public PGPUnknownRecipientException(Throwable cause) {
17 | super(cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/AccountState.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkNotNull;
11 |
12 | public class AccountState {
13 |
14 | public enum Status {
15 | ACTIVE, MARGIN_CALL, LIQUIDATION;
16 |
17 | @JsonCreator
18 | private static Status deserialize(String value) {
19 | return valueOf(value.toUpperCase());
20 | }
21 | }
22 |
23 | private final BigDecimal balance;
24 | private final BigDecimal freeBalance;
25 | private final BigDecimal totalInitialMargin;
26 | private final BigDecimal totalMaintenanceMargin;
27 | private final BigDecimal totalLockedForOrders;
28 | private final BigDecimal totalUnsettledPnL;
29 | private final BigDecimal totalPendingWithdrawal;
30 |
31 | private final Status status;
32 |
33 | @JsonCreator
34 | public AccountState(
35 | @JsonProperty("balance") BigDecimal balance,
36 | @JsonProperty("free_balance") BigDecimal freeBalance,
37 | @JsonProperty("total_initial_margin") BigDecimal totalInitialMargin,
38 | @JsonProperty("total_maintenance_margin") BigDecimal totalMaintenanceMargin,
39 | @JsonProperty("total_unsettled_pnl") BigDecimal totalUnsettledPnL,
40 | @JsonProperty("total_locked_for_orders") BigDecimal totalLockedForOrders,
41 | @JsonProperty("total_pending_withdrawal") BigDecimal totalPendingWithdrawal,
42 | @JsonProperty("account_status") Status status
43 | ) {
44 | this.balance = checkNotNull(balance, "null balance");
45 | this.freeBalance = checkNotNull(freeBalance, "null freeBalance");
46 | this.totalInitialMargin = checkNotNull(totalInitialMargin, "null totalInitialMargin");
47 | this.totalMaintenanceMargin = checkNotNull(totalMaintenanceMargin, "null totalMaintenanceMargin");
48 | this.totalUnsettledPnL = checkNotNull(totalUnsettledPnL, "null totalUnsettledPnL");
49 | this.totalLockedForOrders = checkNotNull(totalLockedForOrders, "null totalLockedForOrders");
50 | this.totalPendingWithdrawal = checkNotNull(totalPendingWithdrawal, "null totalPendingWithdrawal");
51 | this.status = checkNotNull(status, "null status");
52 | }
53 |
54 | public BigDecimal getBalance() {
55 | return balance;
56 | }
57 |
58 | public BigDecimal getFreeBalance() {
59 | return freeBalance;
60 | }
61 |
62 | public BigDecimal getTotalInitialMargin() {
63 | return totalInitialMargin;
64 | }
65 |
66 | public BigDecimal getTotalMaintenanceMargin() {
67 | return totalMaintenanceMargin;
68 | }
69 |
70 | public BigDecimal getTotalLockedForOrders() {
71 | return totalLockedForOrders;
72 | }
73 |
74 | public BigDecimal getTotalUnsettledPnL() {
75 | return totalUnsettledPnL;
76 | }
77 |
78 | public BigDecimal getTotalPendingWithdrawal() {
79 | return totalPendingWithdrawal;
80 | }
81 |
82 | public Status getStatus() {
83 | return status;
84 | }
85 |
86 | @Override
87 | public boolean equals(Object o) {
88 | if (this == o) return true;
89 | if (o == null || getClass() != o.getClass()) return false;
90 | AccountState that = (AccountState) o;
91 | return Objects.equal(balance, that.balance) &&
92 | Objects.equal(freeBalance, that.freeBalance) &&
93 | Objects.equal(totalInitialMargin, that.totalInitialMargin) &&
94 | Objects.equal(totalMaintenanceMargin, that.totalMaintenanceMargin) &&
95 | Objects.equal(totalLockedForOrders, that.totalLockedForOrders) &&
96 | Objects.equal(totalUnsettledPnL, that.totalUnsettledPnL) &&
97 | Objects.equal(totalPendingWithdrawal, that.totalPendingWithdrawal) &&
98 | status == that.status;
99 | }
100 |
101 | @Override
102 | public int hashCode() {
103 | return Objects.hashCode(
104 | balance,
105 | freeBalance,
106 | totalInitialMargin,
107 | totalMaintenanceMargin,
108 | totalLockedForOrders,
109 | totalUnsettledPnL,
110 | totalPendingWithdrawal,
111 | status
112 | );
113 | }
114 |
115 | @Override
116 | public String toString() {
117 | return MoreObjects.toStringHelper(this)
118 | .add("balance", balance)
119 | .add("freeBalance", freeBalance)
120 | .add("totalInitialMargin", totalInitialMargin)
121 | .add("totalMaintenanceMargin", totalMaintenanceMargin)
122 | .add("totalLockedForOrders", totalLockedForOrders)
123 | .add("totalUnsettledPnL", totalUnsettledPnL)
124 | .add("totalPendingWithdrawal", totalPendingWithdrawal)
125 | .add("status", status)
126 | .toString();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/AccountStateListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | @FunctionalInterface
4 | public interface AccountStateListener {
5 |
6 | void onAccountState(AccountState accountState);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/CancelAllOrdersFailed.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | public class CancelAllOrdersFailed {
11 | public enum Cause {
12 | SESSION_NOT_ACTIVE;
13 |
14 | @JsonCreator
15 | public static Cause deserialize(final String str) {
16 | return valueOf(str.toUpperCase());
17 | }
18 | }
19 |
20 | private final Cause cause;
21 |
22 | @JsonCreator
23 | public CancelAllOrdersFailed(final @JsonProperty("cause") Cause cause) {
24 | this.cause = checkNotNull(cause, "null cause");
25 | }
26 |
27 | public Cause getCause() {
28 | return cause;
29 | }
30 |
31 | @Override
32 | public boolean equals(Object o) {
33 | if (this == o) return true;
34 | if (o == null || getClass() != o.getClass()) return false;
35 | CancelAllOrdersFailed that = (CancelAllOrdersFailed) o;
36 | return this.cause == that.cause;
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | return Objects.hashCode(cause);
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return MoreObjects.toStringHelper(this)
47 | .add("cause", cause)
48 | .toString();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/CancelAllOrdersSpec.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class CancelAllOrdersSpec implements OrderSpec {
6 | public static final CancelAllOrdersSpec INSTANCE = new CancelAllOrdersSpec();
7 |
8 | @JsonProperty("type")
9 | private String getType() {
10 | return "cancel_all_orders";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/InternalTransfer.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.google.common.base.MoreObjects;
5 | import com.google.common.base.Objects;
6 |
7 | import java.math.BigDecimal;
8 |
9 | import static com.google.common.base.Preconditions.checkArgument;
10 |
11 | public class InternalTransfer {
12 |
13 | private final long destinationAccountId;
14 | private final BigDecimal amount;
15 |
16 | public InternalTransfer(final long destinationAccountId, final BigDecimal amount) {
17 | checkArgument(destinationAccountId > 0, "destinationAccountId=%s <= 0", destinationAccountId);
18 | checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "amount=%s <= 0", amount);
19 | this.destinationAccountId = destinationAccountId;
20 | this.amount = amount;
21 | }
22 |
23 | @JsonProperty("destination_account_id")
24 | public long getDestinationAccountId() {
25 | return destinationAccountId;
26 | }
27 |
28 | @JsonProperty("amount")
29 | public BigDecimal getAmount() {
30 | return amount;
31 | }
32 |
33 | @JsonProperty("type")
34 | private String getType() {
35 | return "internal_transfer";
36 | }
37 |
38 | @Override
39 | public boolean equals(final Object o) {
40 | if (this == o) {
41 | return true;
42 | }
43 | if (o == null || getClass() != o.getClass()) {
44 | return false;
45 | }
46 | final InternalTransfer that = (InternalTransfer) o;
47 | return destinationAccountId == that.destinationAccountId &&
48 | Objects.equal(amount, that.amount);
49 | }
50 |
51 | @Override
52 | public int hashCode() {
53 | return Objects.hashCode(destinationAccountId, amount);
54 | }
55 |
56 | @Override
57 | public String toString() {
58 | return MoreObjects.toStringHelper(this)
59 | .add("destinationAccountId", destinationAccountId)
60 | .add("amount", amount)
61 | .toString();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/InternalTransferExecuted.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 |
12 | public class InternalTransferExecuted {
13 |
14 | private final long destinationUserId;
15 | private final BigDecimal amount;
16 |
17 | @JsonCreator
18 | public InternalTransferExecuted(
19 | @JsonProperty("destination_account_id") long destinationUserId,
20 | @JsonProperty("amount") BigDecimal amount
21 | ) {
22 | checkArgument(destinationUserId > 0, "destinationUserId=%s <= 0", destinationUserId);
23 | checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "amount=%s <= 0", amount);
24 | this.destinationUserId = destinationUserId;
25 | this.amount = amount;
26 | }
27 |
28 | public long getDestinationUserId() {
29 | return destinationUserId;
30 | }
31 |
32 | public BigDecimal getAmount() {
33 | return amount;
34 | }
35 |
36 | @Override
37 | public boolean equals(final Object o) {
38 | if (this == o) {
39 | return true;
40 | }
41 | if (o == null || getClass() != o.getClass()) {
42 | return false;
43 | }
44 | final InternalTransferExecuted that = (InternalTransferExecuted) o;
45 | return destinationUserId == that.destinationUserId &&
46 | Objects.equal(amount, that.amount);
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | return Objects.hashCode(destinationUserId, amount);
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return MoreObjects.toStringHelper(this)
57 | .add("destinationUserId", destinationUserId)
58 | .add("amount", amount)
59 | .toString();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/InternalTransferListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | public interface InternalTransferListener {
4 |
5 | void onInternalTransferExecuted(InternalTransferExecuted internalTransferExecuted);
6 |
7 | void onInternalTransferRejected(InternalTransferRejected internalTransferRejected);
8 |
9 | void onInternalTransferReceived(InternalTransferReceived internalTransferReceived);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/InternalTransferReceived.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 |
12 | public class InternalTransferReceived {
13 |
14 | private final long sourceUserId;
15 | private final BigDecimal amount;
16 |
17 | @JsonCreator
18 | public InternalTransferReceived(
19 | @JsonProperty("source_account_id") long sourceUserId,
20 | @JsonProperty("amount") BigDecimal amount
21 | ) {
22 | checkArgument(sourceUserId > 0, "sourceUserId=%s <= 0", sourceUserId);
23 | checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "amount=%s <= 0", amount);
24 | this.sourceUserId = sourceUserId;
25 | this.amount = amount;
26 | }
27 |
28 | public long getSourceUserId() {
29 | return sourceUserId;
30 | }
31 |
32 | public BigDecimal getAmount() {
33 | return amount;
34 | }
35 |
36 | @Override
37 | public boolean equals(final Object o) {
38 | if (this == o) {
39 | return true;
40 | }
41 | if (o == null || getClass() != o.getClass()) {
42 | return false;
43 | }
44 | final InternalTransferReceived that = (InternalTransferReceived) o;
45 | return sourceUserId == that.sourceUserId &&
46 | Objects.equal(amount, that.amount);
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | return Objects.hashCode(sourceUserId, amount);
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return MoreObjects.toStringHelper(this)
57 | .add("sourceUserId", sourceUserId)
58 | .add("amount", amount)
59 | .toString();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/InternalTransferRejected.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 | import static com.google.common.base.Preconditions.checkNotNull;
12 |
13 | public class InternalTransferRejected {
14 |
15 | public enum Cause {
16 | INSUFFICIENT_FUNDS,
17 | FORBIDDEN;
18 |
19 | @JsonCreator
20 | private static Cause deserialize(String value) {
21 | return valueOf(value.toUpperCase());
22 | }
23 | }
24 |
25 | private final long destinationUserId;
26 | private final BigDecimal amount;
27 | private final Cause cause;
28 |
29 | @JsonCreator
30 | public InternalTransferRejected(
31 | @JsonProperty("destination_account_id") long destinationUserId,
32 | @JsonProperty("amount") BigDecimal amount,
33 | @JsonProperty("cause") Cause cause
34 | ) {
35 | checkArgument(destinationUserId > 0, "destinationUserId=%s <= 0", destinationUserId);
36 | checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "amount=%s <= 0", amount);
37 | this.destinationUserId = destinationUserId;
38 | this.amount = amount;
39 | this.cause = checkNotNull(cause, "cause");
40 | }
41 |
42 | public long getDestinationUserId() {
43 | return destinationUserId;
44 | }
45 |
46 | public BigDecimal getAmount() {
47 | return amount;
48 | }
49 |
50 | public Cause getCause() {
51 | return cause;
52 | }
53 |
54 | @Override
55 | public boolean equals(final Object o) {
56 | if (this == o) {
57 | return true;
58 | }
59 | if (o == null || getClass() != o.getClass()) {
60 | return false;
61 | }
62 | final InternalTransferRejected that = (InternalTransferRejected) o;
63 | return destinationUserId == that.destinationUserId &&
64 | Objects.equal(amount, that.amount) &&
65 | cause == that.cause;
66 | }
67 |
68 | @Override
69 | public int hashCode() {
70 | return Objects.hashCode(destinationUserId, amount, cause);
71 | }
72 |
73 | @Override
74 | public String toString() {
75 | return MoreObjects.toStringHelper(this)
76 | .add("destinationUserId", destinationUserId)
77 | .add("amount", amount)
78 | .add("cause", cause)
79 | .toString();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/LimitOrderSpec.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.google.common.base.MoreObjects;
5 | import com.google.common.base.Objects;
6 |
7 | import java.math.BigDecimal;
8 |
9 | import static com.google.common.base.Preconditions.checkArgument;
10 | import static com.google.common.base.Preconditions.checkNotNull;
11 |
12 | public class LimitOrderSpec implements OrderSpec {
13 |
14 | private final long clientOrderId;
15 | private final int instrumentId;
16 | private final OrderSide side;
17 | private final int quantity;
18 | private final BigDecimal limitPrice;
19 | private final boolean postOnly;
20 |
21 | /**
22 | * @param clientOrderId id of an order given by the client, has to be different than ids of all other pending orders
23 | * @param instrumentId id of an instrument this order is for
24 | * @param side side of the order
25 | * @param quantity quantity of the order, has to be positive
26 | * @param limitPrice limit price of the order, has to be positive
27 | * @param postOnly if true placing of order will fail if it would match immediately
28 | * @throws IllegalArgumentException if {@code quantity} or {@code limitPrice} is not positive
29 | * @throws NullPointerException if any of nullable arguments is null
30 | */
31 | public LimitOrderSpec(final long clientOrderId,
32 | final int instrumentId,
33 | final OrderSide side,
34 | final int quantity,
35 | final BigDecimal limitPrice,
36 | final boolean postOnly) {
37 | checkArgument(clientOrderId > 0, "clientOrderId=%s <= 0", clientOrderId);
38 | checkArgument(instrumentId > 0, "instrumentId=%s <= 0", instrumentId);
39 | checkArgument(quantity > 0, "quantity=%s <= 0", quantity);
40 | checkArgument(limitPrice.compareTo(BigDecimal.ZERO) > 0, "limitPrice=%s <= 0", limitPrice);
41 | this.clientOrderId = clientOrderId;
42 | this.instrumentId = instrumentId;
43 | this.side = checkNotNull(side, "null side");
44 | this.quantity = quantity;
45 | this.limitPrice = limitPrice;
46 | this.postOnly = postOnly;
47 | }
48 |
49 | public LimitOrderSpec(final long clientOrderId,
50 | final int instrumentId,
51 | final OrderSide side,
52 | final int quantity,
53 | final BigDecimal limitPrice) {
54 | this(clientOrderId, instrumentId, side, quantity, limitPrice, false);
55 | }
56 |
57 | @JsonProperty("client_order_id")
58 | public long getClientOrderId() {
59 | return clientOrderId;
60 | }
61 |
62 | @JsonProperty("instrument_id")
63 | public int getInstrumentId() {
64 | return instrumentId;
65 | }
66 |
67 | @JsonProperty("side")
68 | public OrderSide getSide() {
69 | return side;
70 | }
71 |
72 | @JsonProperty("quantity")
73 | public int getQuantity() {
74 | return quantity;
75 | }
76 |
77 | @JsonProperty("limit_price")
78 | public BigDecimal getLimitPrice() {
79 | return limitPrice;
80 | }
81 |
82 | @JsonProperty("order_type")
83 | public OrderType getOrderType() {
84 | return OrderType.LIMIT;
85 | }
86 |
87 | @JsonProperty("type")
88 | private String getType() {
89 | return "place_order";
90 | }
91 |
92 | @JsonProperty("post_only")
93 | private boolean getPostOnly() {
94 | return postOnly;
95 | }
96 |
97 | @Override
98 | public boolean equals(Object o) {
99 | if (this == o) return true;
100 | if (o == null || getClass() != o.getClass()) return false;
101 | LimitOrderSpec that = (LimitOrderSpec) o;
102 | return clientOrderId == that.clientOrderId &&
103 | instrumentId == that.instrumentId &&
104 | quantity == that.quantity &&
105 | side == that.side &&
106 | Objects.equal(limitPrice, that.limitPrice) &&
107 | postOnly == that.postOnly;
108 | }
109 |
110 | @Override
111 | public int hashCode() {
112 | return Objects.hashCode(clientOrderId, instrumentId, side, quantity, limitPrice, postOnly);
113 | }
114 |
115 | @Override
116 | public String toString() {
117 | return MoreObjects.toStringHelper(this)
118 | .add("clientOrderId", clientOrderId)
119 | .add("instrumentId", instrumentId)
120 | .add("side", side)
121 | .add("quantity", quantity)
122 | .add("limitPrice", limitPrice)
123 | .add("postOnly", postOnly)
124 | .toString();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/LiquidationOrderCancelled.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | /**
9 | * Notification of a cancelled liquidation order.
10 | *
11 | * @see LiquidationOrderPlaced
12 | */
13 | public class LiquidationOrderCancelled {
14 |
15 | private final long systemOrderId;
16 |
17 | @JsonCreator
18 | public LiquidationOrderCancelled(@JsonProperty("system_order_id") long systemOrderId) {
19 | this.systemOrderId = systemOrderId;
20 | }
21 |
22 | public long getSystemOrderId() {
23 | return systemOrderId;
24 | }
25 |
26 | @Override
27 | public boolean equals(Object o) {
28 | if (this == o) return true;
29 | if (o == null || getClass() != o.getClass()) return false;
30 | LiquidationOrderCancelled that = (LiquidationOrderCancelled) o;
31 | return systemOrderId == that.systemOrderId;
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | return Objects.hashCode(systemOrderId);
37 | }
38 |
39 | @Override
40 | public String toString() {
41 | return MoreObjects.toStringHelper(this)
42 | .add("systemOrderId", systemOrderId)
43 | .toString();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/LiquidationOrderFilled.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 | import static com.google.common.base.Preconditions.checkNotNull;
12 |
13 | /**
14 | * Notification of a fill of a liquidation order.
15 | *
16 | * @see LiquidationOrderPlaced
17 | */
18 | public class LiquidationOrderFilled {
19 |
20 | private final long systemOrderId;
21 | private final int instrumentId;
22 | private final OrderType orderType;
23 | private final OrderSide side;
24 | private final int orderInitialQuantity;
25 | private final int leavesOrderQuantity;
26 | private final BigDecimal tradePrice;
27 | private final int filledQuantity;
28 |
29 | @JsonCreator
30 | public LiquidationOrderFilled(
31 | @JsonProperty("system_order_id") long systemOrderId,
32 | @JsonProperty("instrument_id") int instrumentId,
33 | @JsonProperty("order_side") OrderSide side,
34 | @JsonProperty("order_initial_quantity") int orderInitialQuantity,
35 | @JsonProperty("leaves_order_quantity") int leavesOrderQuantity,
36 | @JsonProperty("trade_price") BigDecimal tradePrice,
37 | @JsonProperty("trade_quantity") int filledQuantity
38 | ) {
39 | checkArgument(filledQuantity > 0, "filledQuantity=%s <= 0", filledQuantity);
40 | checkArgument(tradePrice.compareTo(BigDecimal.ZERO) > 0, "tradePrice=%s <= 0", tradePrice);
41 | checkArgument(instrumentId > 0, "instrumentId=%s <= 0", instrumentId);
42 | checkArgument(orderInitialQuantity > 0, "orderInitialQuantity=%s ");
43 | this.systemOrderId = systemOrderId;
44 | this.instrumentId = instrumentId;
45 | this.orderType = OrderType.MARKET;
46 | this.side = checkNotNull(side, "side");
47 | this.orderInitialQuantity = orderInitialQuantity;
48 | this.leavesOrderQuantity = leavesOrderQuantity;
49 | this.filledQuantity = filledQuantity;
50 | this.tradePrice = tradePrice;
51 | }
52 |
53 | public long getSystemOrderId() {
54 | return systemOrderId;
55 | }
56 |
57 | public int getInstrumentId() {
58 | return instrumentId;
59 | }
60 |
61 | public OrderType getOrderType() {
62 | return orderType;
63 | }
64 |
65 | public OrderSide getSide() {
66 | return side;
67 | }
68 |
69 | public int getOrderInitialQuantity() {
70 | return orderInitialQuantity;
71 | }
72 |
73 | public int getLeavesOrderQuantity() {
74 | return leavesOrderQuantity;
75 | }
76 |
77 | public BigDecimal getTradePrice() {
78 | return tradePrice;
79 | }
80 |
81 | public int getFilledQuantity() {
82 | return filledQuantity;
83 | }
84 |
85 | @Override
86 | public boolean equals(final Object o) {
87 | if (this == o) {
88 | return true;
89 | }
90 | if (o == null || getClass() != o.getClass()) {
91 | return false;
92 | }
93 | final LiquidationOrderFilled that = (LiquidationOrderFilled) o;
94 | return systemOrderId == that.systemOrderId &&
95 | instrumentId == that.instrumentId &&
96 | orderInitialQuantity == that.orderInitialQuantity &&
97 | leavesOrderQuantity == that.leavesOrderQuantity &&
98 | filledQuantity == that.filledQuantity &&
99 | orderType == that.orderType &&
100 | side == that.side &&
101 | Objects.equal(tradePrice, that.tradePrice);
102 | }
103 |
104 | @Override
105 | public int hashCode() {
106 | return Objects.hashCode(
107 | systemOrderId,
108 | instrumentId,
109 | orderType,
110 | side,
111 | orderInitialQuantity,
112 | leavesOrderQuantity,
113 | tradePrice,
114 | filledQuantity
115 | );
116 | }
117 |
118 | @Override
119 | public String toString() {
120 | return MoreObjects.toStringHelper(this)
121 | .add("systemOrderId", systemOrderId)
122 | .add("instrumentId", instrumentId)
123 | .add("orderType", orderType)
124 | .add("side", side)
125 | .add("orderInitialQuantity", orderInitialQuantity)
126 | .add("leavesOrderQuantity", leavesOrderQuantity)
127 | .add("tradePrice", tradePrice)
128 | .add("filledQuantity", filledQuantity)
129 | .toString();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/LiquidationOrderPlaced.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonIgnore;
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 | import com.google.common.base.MoreObjects;
7 | import com.google.common.base.Objects;
8 |
9 | import static com.google.common.base.Preconditions.checkArgument;
10 | import static com.google.common.base.Preconditions.checkNotNull;
11 |
12 | /**
13 | * Liquidation market order placed automatically by the exchange during liquidation.
14 | */
15 | public class LiquidationOrderPlaced {
16 |
17 | private final long systemOrderId;
18 | private final int instrumentId;
19 | @JsonIgnore
20 | private final OrderType type;
21 | private final OrderSide side;
22 | private final int quantity;
23 | private final int initialQuantity;
24 |
25 | @JsonCreator
26 | public LiquidationOrderPlaced(
27 | @JsonProperty("system_order_id") long systemOrderId,
28 | @JsonProperty("instrument_id") int instrumentId,
29 | @JsonProperty("side") OrderSide side,
30 | @JsonProperty("quantity") int quantity,
31 | @JsonProperty("initial_quantity") int initialQuantity
32 | ) {
33 | checkArgument(systemOrderId > 0, "systemOrderId=%s <= 0", systemOrderId);
34 | checkArgument(instrumentId > 0, "instrumentId=%s <= 0", instrumentId);
35 | checkArgument(quantity > 0, "quantity=%s <= 0", quantity);
36 | checkArgument(initialQuantity > 0, "initialQuantity=%s <= 0", initialQuantity);
37 | this.systemOrderId = systemOrderId;
38 | this.instrumentId = instrumentId;
39 | this.type = OrderType.MARKET;
40 | this.side = checkNotNull(side, "null side");
41 | this.quantity = quantity;
42 | this.initialQuantity = initialQuantity;
43 | }
44 |
45 | public long getSystemOrderId() {
46 | return systemOrderId;
47 | }
48 |
49 | public int getInstrumentId() {
50 | return instrumentId;
51 | }
52 |
53 | public OrderType getType() {
54 | return type;
55 | }
56 |
57 | public OrderSide getSide() {
58 | return side;
59 | }
60 |
61 | public int getQuantity() {
62 | return quantity;
63 | }
64 |
65 | public int getInitialQuantity() {
66 | return initialQuantity;
67 | }
68 |
69 | @Override
70 | public boolean equals(Object o) {
71 | if (this == o) return true;
72 | if (o == null || getClass() != o.getClass()) return false;
73 | LiquidationOrderPlaced that = (LiquidationOrderPlaced) o;
74 | return systemOrderId == that.systemOrderId &&
75 | instrumentId == that.instrumentId &&
76 | quantity == that.quantity &&
77 | initialQuantity == that.initialQuantity &&
78 | type == that.type &&
79 | side == that.side;
80 | }
81 |
82 | @Override
83 | public int hashCode() {
84 | return Objects.hashCode(systemOrderId, instrumentId, type, side, quantity, initialQuantity);
85 | }
86 |
87 | @Override
88 | public String toString() {
89 | return MoreObjects.toStringHelper(this)
90 | .add("systemOrderId", systemOrderId)
91 | .add("instrumentId", instrumentId)
92 | .add("type", type)
93 | .add("side", side)
94 | .add("quantity", quantity)
95 | .add("initialQuantity", initialQuantity)
96 | .toString();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OpenPosition.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 | import static com.google.common.base.Preconditions.checkNotNull;
12 |
13 | public class OpenPosition {
14 |
15 | public enum PositionSide {
16 | LONG, SHORT;
17 |
18 | @JsonCreator
19 | private static PositionSide deserialize(String value) {
20 | return valueOf(value.toUpperCase());
21 | }
22 | }
23 |
24 | private final int instrumentId;
25 | private final BigDecimal pnl; // null for options
26 | private final BigDecimal maintenanceMargin;
27 | private final BigDecimal initialMargin;
28 | private final PositionSide side; // any of LONG, SHORT for a closed position
29 | private final int quantity;
30 | private final BigDecimal averageOpeningPrice; // any number for a closed position
31 |
32 | @JsonCreator
33 | public OpenPosition(
34 | @JsonProperty("instrument_id") int instrumentId,
35 | @JsonProperty("pnl") BigDecimal pnl,
36 | @JsonProperty("maintenance_margin") BigDecimal maintenanceMargin,
37 | @JsonProperty("initial_margin") BigDecimal initialMargin,
38 | @JsonProperty("side") PositionSide side,
39 | @JsonProperty("quantity") int quantity,
40 | @JsonProperty("average_opening_price") BigDecimal averageOpeningPrice
41 | ) {
42 | checkArgument(instrumentId > 0, "instrumentId=%s <= 0", instrumentId);
43 | checkArgument(quantity >= 0, "quantity=%s < 0", quantity);
44 | this.instrumentId = instrumentId;
45 | this.pnl = pnl;
46 | this.maintenanceMargin = checkNotNull(maintenanceMargin, "null maintenanceMargin");
47 | this.initialMargin = checkNotNull(initialMargin, "null initialMargin");
48 | this.side = checkNotNull(side, "null side");
49 | this.quantity = quantity;
50 | this.averageOpeningPrice = averageOpeningPrice;
51 | }
52 |
53 | public int getInstrumentId() {
54 | return instrumentId;
55 | }
56 |
57 | public BigDecimal getPnl() {
58 | return pnl;
59 | }
60 |
61 | public BigDecimal getMaintenanceMargin() {
62 | return maintenanceMargin;
63 | }
64 |
65 | public BigDecimal getInitialMargin() {
66 | return initialMargin;
67 | }
68 |
69 | /**
70 | * @return side of this position, may be any of LONG, SHORT for a closed position
71 | */
72 | public PositionSide getSide() {
73 | return side;
74 | }
75 |
76 | /**
77 | * @return nonnegative quantity of this position, zero for a closed position
78 | */
79 | public int getQuantity() {
80 | return quantity;
81 | }
82 |
83 | public int getQuantitySigned() {
84 | return side == PositionSide.LONG ? quantity : -quantity;
85 | }
86 |
87 | /**
88 | * @return weighted (by quantities) average of the prices this position has been opened at, may be any number for a
89 | * closed position
90 | */
91 | public BigDecimal getAverageOpeningPrice() {
92 | return averageOpeningPrice;
93 | }
94 |
95 | @Override
96 | public boolean equals(Object o) {
97 | if (this == o) return true;
98 | if (o == null || getClass() != o.getClass()) return false;
99 | OpenPosition that = (OpenPosition) o;
100 | return instrumentId == that.instrumentId &&
101 | quantity == that.quantity &&
102 | Objects.equal(pnl, that.pnl) &&
103 | Objects.equal(maintenanceMargin, that.maintenanceMargin) &&
104 | Objects.equal(initialMargin, that.initialMargin) &&
105 | side == that.side &&
106 | Objects.equal(averageOpeningPrice, that.averageOpeningPrice);
107 | }
108 |
109 | @Override
110 | public int hashCode() {
111 | return Objects.hashCode(instrumentId, pnl, maintenanceMargin, initialMargin, side, quantity, averageOpeningPrice);
112 | }
113 |
114 | @Override
115 | public String toString() {
116 | return MoreObjects.toStringHelper(this)
117 | .add("instrumentId", instrumentId)
118 | .add("pnl", pnl)
119 | .add("maintenanceMargin", maintenanceMargin)
120 | .add("initialMargin", initialMargin)
121 | .add("side", side)
122 | .add("quantity", quantity)
123 | .add("averageOpeningPrice", averageOpeningPrice)
124 | .toString();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OpenPositionForcefullyClosed.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 | import net.quedex.api.user.OpenPosition.PositionSide;
8 |
9 | import java.math.BigDecimal;
10 |
11 | import static com.google.common.base.Preconditions.checkArgument;
12 | import static com.google.common.base.Preconditions.checkNotNull;
13 |
14 | public class OpenPositionForcefullyClosed {
15 |
16 | public enum Cause {
17 | BANKRUPTCY,
18 | DELEVERAGING;
19 |
20 | @JsonCreator
21 | private static Cause deserialize(String value) {
22 | return valueOf(value.toUpperCase());
23 | }
24 | }
25 |
26 | private final int instrumentId;
27 | private final PositionSide side;
28 | private final int closedQuantity;
29 | private final int remainingQuantity;
30 | private final BigDecimal closePrice;
31 | private final Cause cause;
32 |
33 | @JsonCreator
34 | public OpenPositionForcefullyClosed(final @JsonProperty("instrument_id") int instrumentId,
35 | final @JsonProperty("side") PositionSide side,
36 | final @JsonProperty("closed_quantity") int closedQuantity,
37 | final @JsonProperty("remaining_quantity") int remainingQuantity,
38 | final @JsonProperty("close_price") BigDecimal closePrice,
39 | final @JsonProperty("cause") Cause cause) {
40 | checkArgument(instrumentId > 0, "instrumentId=%s <= 0", instrumentId);
41 | checkArgument(closedQuantity >= 0, "closedQuantity=%s < 0", closedQuantity);
42 | checkArgument(remainingQuantity >= 0, "remainingQuantity=%s < 0", remainingQuantity);
43 | checkArgument(closePrice != null && closePrice.compareTo(BigDecimal.ZERO) > 0, "closePrice=%s <= 0", closePrice);
44 | this.instrumentId = instrumentId;
45 | this.side = checkNotNull(side, "null side");
46 | this.closedQuantity = closedQuantity;
47 | this.remainingQuantity = remainingQuantity;
48 | this.closePrice = closePrice;
49 | this.cause = checkNotNull(cause, "cause");
50 | }
51 |
52 | public int getInstrumentId() {
53 | return instrumentId;
54 | }
55 | public PositionSide getSide() {
56 | return side;
57 | }
58 | public int getClosedQuantity() {
59 | return closedQuantity;
60 | }
61 | public int getRemainingQuantity() {
62 | return remainingQuantity;
63 | }
64 | public BigDecimal getClosePrice() {
65 | return closePrice;
66 | }
67 | public Cause getCause() {
68 | return cause;
69 | }
70 |
71 | @Override
72 | public boolean equals(final Object o) {
73 | if (this == o) {
74 | return true;
75 | }
76 | if (o == null || getClass() != o.getClass()) {
77 | return false;
78 | }
79 | final OpenPositionForcefullyClosed that = (OpenPositionForcefullyClosed) o;
80 | return instrumentId == that.instrumentId &&
81 | side == that.side &&
82 | closedQuantity == that.closedQuantity &&
83 | remainingQuantity == that.remainingQuantity &&
84 | cause == that.cause &&
85 | Objects.equal(closePrice, that.closePrice);
86 | }
87 |
88 | @Override
89 | public int hashCode() {
90 | return Objects.hashCode(instrumentId, side, closedQuantity, remainingQuantity, closePrice, cause);
91 | }
92 |
93 | @Override
94 | public String toString() {
95 | return MoreObjects.toStringHelper(this)
96 | .add("instrumentId", instrumentId)
97 | .add("side", side)
98 | .add("closedQuantity", closedQuantity)
99 | .add("remainingQuantity", remainingQuantity)
100 | .add("closePrice", closePrice)
101 | .add("cause", cause)
102 | .toString();
103 | }
104 | }
105 |
106 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OpenPositionListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | public interface OpenPositionListener {
4 |
5 | void onOpenPosition(OpenPosition openPosition);
6 |
7 | void onOpenPositionForcefullyClosed(OpenPositionForcefullyClosed openPositionForcefullyClosed);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderCancelFailed.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | public class OrderCancelFailed {
11 |
12 | public enum Cause {
13 | NOT_FOUND,
14 | SESSION_NOT_ACTIVE;
15 |
16 | @JsonCreator
17 | private static Cause deserialize(String value) {
18 | return valueOf(value.toUpperCase());
19 | }
20 | }
21 |
22 | private final long clientOrderId;
23 | private final Cause cause;
24 |
25 | @JsonCreator
26 | public OrderCancelFailed(@JsonProperty("client_order_id") long clientOrderId, @JsonProperty("cause") Cause cause) {
27 | this.clientOrderId = clientOrderId;
28 | this.cause = checkNotNull(cause, "null cause");
29 | }
30 |
31 | public long getClientOrderId() {
32 | return clientOrderId;
33 | }
34 |
35 | public Cause getCause() {
36 | return cause;
37 | }
38 |
39 | @Override
40 | public boolean equals(Object o) {
41 | if (this == o) return true;
42 | if (o == null || getClass() != o.getClass()) return false;
43 | OrderCancelFailed that = (OrderCancelFailed) o;
44 | return clientOrderId == that.clientOrderId &&
45 | cause == that.cause;
46 | }
47 |
48 | @Override
49 | public int hashCode() {
50 | return Objects.hashCode(clientOrderId, cause);
51 | }
52 |
53 | @Override
54 | public String toString() {
55 | return MoreObjects.toStringHelper(this)
56 | .add("clientOrderId", clientOrderId)
57 | .add("cause", cause)
58 | .toString();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderCancelSpec.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.google.common.base.MoreObjects;
5 | import com.google.common.base.Objects;
6 |
7 | public class OrderCancelSpec implements OrderSpec {
8 |
9 | private final long clientOrderId;
10 |
11 | public OrderCancelSpec(long clientOrderId) {
12 | this.clientOrderId = clientOrderId;
13 | }
14 |
15 | @JsonProperty("client_order_id")
16 | public long getClientOrderId() {
17 | return clientOrderId;
18 | }
19 |
20 | @JsonProperty("type")
21 | private String getType() {
22 | return "cancel_order";
23 | }
24 |
25 | @Override
26 | public boolean equals(Object o) {
27 | if (this == o) return true;
28 | if (o == null || getClass() != o.getClass()) return false;
29 | OrderCancelSpec that = (OrderCancelSpec) o;
30 | return clientOrderId == that.clientOrderId;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return Objects.hashCode(clientOrderId);
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return MoreObjects.toStringHelper(this)
41 | .add("clientOrderId", clientOrderId)
42 | .toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderCancelled.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | public class OrderCancelled {
9 |
10 | private final long clientOrderId;
11 |
12 | @JsonCreator
13 | public OrderCancelled(@JsonProperty("client_order_id") long clientOrderId) {
14 | this.clientOrderId = clientOrderId;
15 | }
16 |
17 | public long getClientOrderId() {
18 | return clientOrderId;
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null || getClass() != o.getClass()) return false;
25 | OrderCancelled that = (OrderCancelled) o;
26 | return clientOrderId == that.clientOrderId;
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return Objects.hashCode(clientOrderId);
32 | }
33 |
34 | @Override
35 | public String toString() {
36 | return MoreObjects.toStringHelper(this)
37 | .add("clientOrderId", clientOrderId)
38 | .toString();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderFilled.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import java.math.BigDecimal;
9 |
10 | import static com.google.common.base.Preconditions.checkArgument;
11 | import static com.google.common.base.Preconditions.checkNotNull;
12 |
13 | public class OrderFilled {
14 |
15 | private final long clientOrderId;
16 | private final int instrumentId;
17 | private final BigDecimal orderLimitPrice;
18 | private final OrderType orderType;
19 | private final OrderSide side;
20 | private final int orderInitialQuantity;
21 | private final int leavesOrderQuantity;
22 | private final BigDecimal tradePrice;
23 | private final int filledQuantity;
24 |
25 | @JsonCreator
26 | public OrderFilled(@JsonProperty("client_order_id") long clientOrderId,
27 | @JsonProperty("instrument_id") int instrumentId,
28 | @JsonProperty("order_limit_price") BigDecimal orderLimitPrice,
29 | @JsonProperty("order_side") OrderSide side,
30 | @JsonProperty("order_initial_quantity") int orderInitialQuantity,
31 | @JsonProperty("leaves_order_quantity") int leavesOrderQuantity,
32 | @JsonProperty("trade_price") BigDecimal tradePrice,
33 | @JsonProperty("trade_quantity") int filledQuantity) {
34 | checkArgument(filledQuantity > 0, "filledQuantity=%s <= 0", filledQuantity);
35 | checkArgument(tradePrice.compareTo(BigDecimal.ZERO) > 0, "tradePrice=%s <= 0", tradePrice);
36 | checkArgument(instrumentId > 0, "instrumentId=%s <= 0", instrumentId);
37 | checkArgument(orderLimitPrice.compareTo(BigDecimal.ZERO) > 0, "orderLimitPrice=%s <= 0", orderLimitPrice);
38 | checkArgument(orderInitialQuantity > 0, "orderInitialQuantity=%s ");
39 | this.clientOrderId = clientOrderId;
40 | this.instrumentId = instrumentId;
41 | this.orderLimitPrice = orderLimitPrice;
42 | this.orderType = OrderType.LIMIT;
43 | this.side = checkNotNull(side, "side");
44 | this.orderInitialQuantity = orderInitialQuantity;
45 | this.leavesOrderQuantity = leavesOrderQuantity;
46 | this.filledQuantity = filledQuantity;
47 | this.tradePrice = tradePrice;
48 | }
49 |
50 | public long getClientOrderId() {
51 | return clientOrderId;
52 | }
53 |
54 | public int getInstrumentId() {
55 | return instrumentId;
56 | }
57 |
58 | public BigDecimal getOrderLimitPrice() {
59 | return orderLimitPrice;
60 | }
61 |
62 | public OrderType getOrderType() {
63 | return orderType;
64 | }
65 |
66 | public OrderSide getSide() {
67 | return side;
68 | }
69 |
70 | public int getOrderInitialQuantity() {
71 | return orderInitialQuantity;
72 | }
73 |
74 | public int getLeavesOrderQuantity() {
75 | return leavesOrderQuantity;
76 | }
77 |
78 | public BigDecimal getTradePrice() {
79 | return tradePrice;
80 | }
81 |
82 | public int getFilledQuantity() {
83 | return filledQuantity;
84 | }
85 |
86 | @Override
87 | public boolean equals(final Object o) {
88 | if (this == o) {
89 | return true;
90 | }
91 | if (o == null || getClass() != o.getClass()) {
92 | return false;
93 | }
94 | final OrderFilled that = (OrderFilled) o;
95 | return clientOrderId == that.clientOrderId &&
96 | instrumentId == that.instrumentId &&
97 | orderInitialQuantity == that.orderInitialQuantity &&
98 | leavesOrderQuantity == that.leavesOrderQuantity &&
99 | filledQuantity == that.filledQuantity &&
100 | Objects.equal(orderLimitPrice, that.orderLimitPrice) &&
101 | orderType == that.orderType &&
102 | side == that.side &&
103 | Objects.equal(tradePrice, that.tradePrice);
104 | }
105 |
106 | @Override
107 | public int hashCode() {
108 | return Objects.hashCode(
109 | clientOrderId,
110 | instrumentId,
111 | orderLimitPrice,
112 | orderType,
113 | side,
114 | orderInitialQuantity,
115 | leavesOrderQuantity,
116 | tradePrice,
117 | filledQuantity
118 | );
119 | }
120 |
121 | @Override
122 | public String toString() {
123 | return MoreObjects.toStringHelper(this)
124 | .add("clientOrderId", clientOrderId)
125 | .add("instrumentId", instrumentId)
126 | .add("orderLimitPrice", orderLimitPrice)
127 | .add("orderType", orderType)
128 | .add("side", side)
129 | .add("orderInitialQuantity", orderInitialQuantity)
130 | .add("leavesOrderQuantity", leavesOrderQuantity)
131 | .add("tradePrice", tradePrice)
132 | .add("filledQuantity", filledQuantity)
133 | .toString();
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderForcefullyCancelled.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | /**
11 | * @author Wiktor Gromniak <wgromniak@quedex.net>.
12 | */
13 | public class OrderForcefullyCancelled {
14 |
15 | public enum Cause {
16 | LIQUIDATION,
17 | SETTLEMENT;
18 |
19 | @JsonCreator
20 | private static Cause deserialize(String value) {
21 | return valueOf(value.toUpperCase());
22 | }
23 | }
24 |
25 | private final long clientOrderId;
26 | private final Cause cause;
27 |
28 | @JsonCreator
29 | public OrderForcefullyCancelled(@JsonProperty("client_order_id") long clientOrderId,
30 | @JsonProperty("cause") Cause cause) {
31 | this.clientOrderId = clientOrderId;
32 | this.cause = checkNotNull(cause, "cause");
33 | }
34 |
35 | public long getClientOrderId() {
36 | return clientOrderId;
37 | }
38 |
39 | public Cause getCause() {
40 | return cause;
41 | }
42 |
43 | @Override
44 | public boolean equals(final Object o) {
45 | if (this == o) {
46 | return true;
47 | }
48 | if (o == null || getClass() != o.getClass()) {
49 | return false;
50 | }
51 | final OrderForcefullyCancelled that = (OrderForcefullyCancelled) o;
52 | return clientOrderId == that.clientOrderId &&
53 | cause == that.cause;
54 | }
55 |
56 | @Override
57 | public int hashCode() {
58 | return Objects.hashCode(clientOrderId, cause);
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return MoreObjects.toStringHelper(this)
64 | .add("clientOrderId", clientOrderId)
65 | .add("cause", cause)
66 | .toString();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | public interface OrderListener {
4 |
5 | void onOrderPlaced(OrderPlaced orderPlaced);
6 |
7 | void onOrderPlaceFailed(OrderPlaceFailed orderPlaceFailed);
8 |
9 | void onOrderCancelled(OrderCancelled orderCancelled);
10 |
11 | void onOrderForcefullyCancelled(OrderForcefullyCancelled orderForcefullyCancelled);
12 |
13 | void onOrderCancelFailed(OrderCancelFailed orderCancelFailed);
14 |
15 | void onAllOrdersCancelled();
16 |
17 | void onCancelAllOrdersFailed(CancelAllOrdersFailed cancelAllOrdersFailed);
18 |
19 | void onOrderModified(OrderModified orderModified);
20 |
21 | void onOrderModificationFailed(OrderModificationFailed orderModificationFailed);
22 |
23 | void onOrderFilled(OrderFilled orderFilled);
24 |
25 | void onLiquidationOrderPlaced(LiquidationOrderPlaced liquidationOrderPlaced);
26 |
27 | void onLiquidationOrderCancelled(LiquidationOrderCancelled liquidationOrderCancelled);
28 |
29 | void onLiquidationOrderFilled(LiquidationOrderFilled liquidationOrderFilled);
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderModificationFailed.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | public class OrderModificationFailed {
11 |
12 | public enum Cause {
13 | INVALID_ORDER_ID,
14 | INVALID_INSTRUMENT_ID,
15 | NONPOSITIVE_QUANTITY,
16 | NONPOSITIVE_PRICE,
17 | SESSION_NOT_ACTIVE,
18 | INVALID_TICK_SIZE,
19 | INSUFFICIENT_FUNDS,
20 | MARGIN_CALL,
21 | NOT_FOUND,
22 | OPEN_POSITION_QUANTITY_TOO_HIGH,
23 | NOT_POST;
24 |
25 | @JsonCreator
26 | private static Cause deserialize(String value) {
27 | return valueOf(value.toUpperCase());
28 | }
29 | }
30 |
31 | private final long clientOrderId;
32 | private final Cause cause;
33 |
34 | public OrderModificationFailed(
35 | @JsonProperty("client_order_id") long clientOrderId,
36 | @JsonProperty("cause") Cause cause
37 | ) {
38 | this.clientOrderId = clientOrderId;
39 | this.cause = checkNotNull(cause, "null cause");
40 | }
41 |
42 | public long getClientOrderId() {
43 | return clientOrderId;
44 | }
45 |
46 | public Cause getCause() {
47 | return cause;
48 | }
49 |
50 | @Override
51 | public boolean equals(Object o) {
52 | if (this == o) return true;
53 | if (o == null || getClass() != o.getClass()) return false;
54 | OrderModificationFailed that = (OrderModificationFailed) o;
55 | return clientOrderId == that.clientOrderId &&
56 | cause == that.cause;
57 | }
58 |
59 | @Override
60 | public int hashCode() {
61 | return Objects.hashCode(clientOrderId, cause);
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return MoreObjects.toStringHelper(this)
67 | .add("clientOrderId", clientOrderId)
68 | .add("cause", cause)
69 | .toString();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderModificationSpec.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.google.common.base.MoreObjects;
5 | import com.google.common.base.Objects;
6 |
7 | import java.math.BigDecimal;
8 |
9 | import static com.google.common.base.Preconditions.checkArgument;
10 |
11 | public class OrderModificationSpec implements OrderSpec {
12 |
13 | private final long clientOrderId;
14 | private final Integer newQuantity;
15 | private final BigDecimal newLimitPrice;
16 | private final boolean postOnly;
17 |
18 | public OrderModificationSpec(final long clientOrderId,
19 | final int newQuantity,
20 | final BigDecimal newLimitPrice,
21 | final boolean postOnly) {
22 | this(clientOrderId, newQuantity, newLimitPrice, postOnly, null);
23 | }
24 |
25 | public OrderModificationSpec(final long clientOrderId, final int newQuantity, final BigDecimal newLimitPrice) {
26 | this(clientOrderId, newQuantity, newLimitPrice, false, null);
27 | }
28 |
29 | public OrderModificationSpec(long clientOrderId, int newQuantity) {
30 | this(clientOrderId, newQuantity, null, false, null);
31 | }
32 |
33 | public OrderModificationSpec(final long clientOrderId, final BigDecimal newLimitPrice, final boolean postOnly) {
34 | this(clientOrderId, null, newLimitPrice, postOnly, null);
35 | }
36 |
37 | public OrderModificationSpec(final long clientOrderId, final BigDecimal newLimitPrice) {
38 | this(clientOrderId, null, newLimitPrice, false, null);
39 | }
40 |
41 | private OrderModificationSpec(final long clientOrderId,
42 | final Integer newQuantity,
43 | final BigDecimal newLimitPrice,
44 | final boolean postOnly,
45 | final Void dummy) {
46 | checkArgument(clientOrderId > 0, "clientOrderId=%s <= 0", clientOrderId);
47 | checkArgument(
48 | newQuantity != null || newLimitPrice != null,
49 | "at least one of newQuantity and newLimitPrice must be specified"
50 | );
51 | checkArgument(newQuantity == null || newQuantity > 0, "newQuantity=%s <= 0", newQuantity);
52 | checkArgument(
53 | newLimitPrice == null || newLimitPrice.compareTo(BigDecimal.ZERO) > 0,
54 | "newLImitPrice=%s <= 0", newLimitPrice
55 | );
56 |
57 | this.clientOrderId = clientOrderId;
58 | this.newQuantity = newQuantity;
59 | this.newLimitPrice = newLimitPrice;
60 | this.postOnly = postOnly;
61 | }
62 |
63 | @JsonProperty("client_order_id")
64 | public long getClientOrderId() {
65 | return clientOrderId;
66 | }
67 |
68 | /**
69 | * @return new quantity of the order if present, null otherwise
70 | */
71 | @JsonProperty("new_quantity")
72 | public Integer getNewQuantity() {
73 | return newQuantity;
74 | }
75 |
76 | /**
77 | * @return new price of the order if present, null otherwise
78 | */
79 | @JsonProperty("new_limit_price")
80 | public BigDecimal getNewLimitPrice() {
81 | return newLimitPrice;
82 | }
83 |
84 | @JsonProperty("post_only")
85 | public Boolean getPostOnly() {
86 | return postOnly;
87 | }
88 |
89 | @JsonProperty("type")
90 | private String getType() {
91 | return "modify_order";
92 | }
93 |
94 | @Override
95 | public boolean equals(Object o) {
96 | if (this == o) return true;
97 | if (o == null || getClass() != o.getClass()) return false;
98 | OrderModificationSpec that = (OrderModificationSpec) o;
99 | return clientOrderId == that.clientOrderId &&
100 | Objects.equal(newQuantity, that.newQuantity) &&
101 | Objects.equal(newLimitPrice, that.newLimitPrice) &&
102 | Objects.equal(postOnly, that.postOnly);
103 | }
104 |
105 | @Override
106 | public int hashCode() {
107 | return Objects.hashCode(clientOrderId, newQuantity, newLimitPrice, postOnly);
108 | }
109 |
110 | @Override
111 | public String toString() {
112 | return MoreObjects.toStringHelper(this)
113 | .add("clientOrderId", clientOrderId)
114 | .add("newQuantity", newQuantity)
115 | .add("newLimitPrice", newLimitPrice)
116 | .add("postOnly", postOnly)
117 | .toString();
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderModified.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | public class OrderModified {
9 |
10 | private final long clientOrderId;
11 |
12 | @JsonCreator
13 | public OrderModified(@JsonProperty("client_order_id") long clientOrderId) {
14 | this.clientOrderId = clientOrderId;
15 | }
16 |
17 | public long getClientOrderId() {
18 | return clientOrderId;
19 | }
20 |
21 | @Override
22 | public boolean equals(Object o) {
23 | if (this == o) return true;
24 | if (o == null || getClass() != o.getClass()) return false;
25 | OrderModified that = (OrderModified) o;
26 | return clientOrderId == that.clientOrderId;
27 | }
28 |
29 | @Override
30 | public int hashCode() {
31 | return Objects.hashCode(clientOrderId);
32 | }
33 |
34 | @Override
35 | public String toString() {
36 | return MoreObjects.toStringHelper(this)
37 | .add("clientOrderId", clientOrderId)
38 | .toString();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderPlaceFailed.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | public class OrderPlaceFailed {
11 |
12 | public enum Cause {
13 | INVALID_ORDER_ID,
14 | INVALID_INSTRUMENT_ID,
15 | SESSION_NOT_ACTIVE,
16 | INVALID_TICK_SIZE,
17 | INSUFFICIENT_FUNDS,
18 | TOO_MANY_OPEN_ORDERS,
19 | OPEN_POSITION_QUANTITY_TOO_HIGH,
20 | NOT_POST;
21 |
22 | @JsonCreator
23 | private static Cause deserialize(String value) {
24 | return valueOf(value.toUpperCase());
25 | }
26 | }
27 |
28 | private final long clientOrderId;
29 | private final Cause cause;
30 |
31 | @JsonCreator
32 | public OrderPlaceFailed(@JsonProperty("client_order_id") long clientOrderId, @JsonProperty("cause") Cause cause) {
33 | this.clientOrderId = clientOrderId;
34 | this.cause = checkNotNull(cause, "Null cause");
35 | }
36 |
37 | public long getClientOrderId() {
38 | return clientOrderId;
39 | }
40 |
41 | public Cause getCause() {
42 | return cause;
43 | }
44 |
45 | @Override
46 | public boolean equals(Object o) {
47 | if (this == o) return true;
48 | if (o == null || getClass() != o.getClass()) return false;
49 | OrderPlaceFailed that = (OrderPlaceFailed) o;
50 | return clientOrderId == that.clientOrderId &&
51 | cause == that.cause;
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return Objects.hashCode(clientOrderId, cause);
57 | }
58 |
59 | @Override
60 | public String toString() {
61 | return MoreObjects.toStringHelper(this)
62 | .add("clientOrderId", clientOrderId)
63 | .add("cause", cause)
64 | .toString();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderPlaced.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonIgnore;
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 | import com.google.common.base.MoreObjects;
7 | import com.google.common.base.Objects;
8 |
9 | import java.math.BigDecimal;
10 |
11 | import static com.google.common.base.Preconditions.checkArgument;
12 | import static com.google.common.base.Preconditions.checkNotNull;
13 |
14 | public class OrderPlaced {
15 |
16 | private final long clientOrderId;
17 | private final int instrumentId;
18 | @JsonIgnore
19 | private final OrderType type;
20 | private final BigDecimal price;
21 | private final OrderSide side;
22 | private final int quantity;
23 | private final int initialQuantity;
24 |
25 | @JsonCreator
26 | public OrderPlaced(
27 | @JsonProperty("client_order_id") long clientOrderId,
28 | @JsonProperty("instrument_id") int instrumentId,
29 | @JsonProperty("limit_price") BigDecimal price,
30 | @JsonProperty("side") OrderSide side,
31 | @JsonProperty("quantity") int quantity,
32 | @JsonProperty("initial_quantity") int initialQuantity
33 | ) {
34 | checkArgument(clientOrderId > 0, "clientOrderId=%s <= 0", clientOrderId);
35 | checkArgument(instrumentId > 0, "instrumentId=%s <= 0", instrumentId);
36 | checkArgument(price.compareTo(BigDecimal.ZERO) > 0, "price=%s <= 0", price);
37 | checkArgument(quantity > 0, "quantity=%s <= 0", quantity);
38 | checkArgument(initialQuantity > 0, "initialQuantity=%s <= 0", initialQuantity);
39 | this.clientOrderId = clientOrderId;
40 | this.instrumentId = instrumentId;
41 | this.type = OrderType.LIMIT;
42 | this.price = price;
43 | this.side = checkNotNull(side, "null side");
44 | this.quantity = quantity;
45 | this.initialQuantity = initialQuantity;
46 | }
47 |
48 |
49 | public long getClientOrderId() {
50 | return clientOrderId;
51 | }
52 |
53 | public int getInstrumentId() {
54 | return instrumentId;
55 | }
56 |
57 | public OrderType getType() {
58 | return type;
59 | }
60 |
61 | public BigDecimal getPrice() {
62 | return price;
63 | }
64 |
65 | public OrderSide getSide() {
66 | return side;
67 | }
68 |
69 | public int getQuantity() {
70 | return quantity;
71 | }
72 |
73 | public int getInitialQuantity() {
74 | return initialQuantity;
75 | }
76 |
77 | @Override
78 | public boolean equals(Object o) {
79 | if (this == o) return true;
80 | if (o == null || getClass() != o.getClass()) return false;
81 | OrderPlaced that = (OrderPlaced) o;
82 | return clientOrderId == that.clientOrderId &&
83 | instrumentId == that.instrumentId &&
84 | quantity == that.quantity &&
85 | initialQuantity == that.initialQuantity &&
86 | type == that.type &&
87 | Objects.equal(price, that.price) &&
88 | side == that.side;
89 | }
90 |
91 | @Override
92 | public int hashCode() {
93 | return Objects.hashCode(clientOrderId, instrumentId, type, price, side, quantity, initialQuantity);
94 | }
95 |
96 | @Override
97 | public String toString() {
98 | return MoreObjects.toStringHelper(this)
99 | .add("clientOrderId", clientOrderId)
100 | .add("instrumentId", instrumentId)
101 | .add("type", type)
102 | .add("price", price)
103 | .add("side", side)
104 | .add("quantity", quantity)
105 | .add("initialQuantity", initialQuantity)
106 | .toString();
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderSide.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 |
5 | public enum OrderSide {
6 | BUY, SELL;
7 |
8 | @JsonCreator
9 | private static OrderSide deserialize(String value) {
10 | return valueOf(value.toUpperCase());
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderSpec.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | public interface OrderSpec { }
4 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/OrderType.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | /**
4 | * @author Wiktor Gromniak <wgromniak@quedex.net>.
5 | */
6 | public enum OrderType {
7 | LIMIT, MARKET
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerAdded.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | public class TimerAdded {
9 |
10 | private final long timerId;
11 |
12 | @JsonCreator
13 | public TimerAdded(final @JsonProperty("timer_id") long timerId) {
14 | this.timerId = timerId;
15 | }
16 |
17 | public long getTimerId() {
18 | return timerId;
19 | }
20 |
21 | @Override
22 | public boolean equals(final Object o) {
23 | if (this == o) {
24 | return true;
25 | }
26 | if (o == null || getClass() != o.getClass()) {
27 | return false;
28 | }
29 | final TimerAdded that = (TimerAdded) o;
30 | return timerId == that.timerId;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return Objects.hashCode(timerId);
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return MoreObjects.toStringHelper(this)
41 | .add("timerId", timerId)
42 | .toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerCancelFailed.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | public class TimerCancelFailed {
11 |
12 | public enum Cause {
13 | NOT_FOUND;
14 |
15 | @JsonCreator
16 | private static Cause deserialize(String value) {
17 | return valueOf(value.toUpperCase());
18 | }
19 | }
20 |
21 | private final long timerId;
22 | private final Cause cause;
23 |
24 | @JsonCreator
25 | public TimerCancelFailed(final @JsonProperty("timer_id") long timerId,
26 | final @JsonProperty("cause") Cause cause) {
27 | this.timerId = timerId;
28 | this.cause = checkNotNull(cause, "Null cause");
29 | }
30 |
31 | public long getTimerId() {
32 | return timerId;
33 | }
34 |
35 | public Cause getCause() {
36 | return cause;
37 | }
38 |
39 | @Override
40 | public boolean equals(Object o) {
41 | if (this == o) return true;
42 | if (o == null || getClass() != o.getClass()) return false;
43 | TimerCancelFailed that = (TimerCancelFailed) o;
44 | return timerId == that.timerId &&
45 | cause == that.cause;
46 | }
47 |
48 | @Override
49 | public int hashCode() {
50 | return Objects.hashCode(timerId, cause);
51 | }
52 |
53 | @Override
54 | public String toString() {
55 | return MoreObjects.toStringHelper(this)
56 | .add("timerId", timerId)
57 | .add("cause", cause)
58 | .toString();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerCancelled.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | public class TimerCancelled {
9 |
10 | private final long timerId;
11 |
12 | @JsonCreator
13 | public TimerCancelled(final @JsonProperty("timer_id") long timerId) {
14 | this.timerId = timerId;
15 | }
16 |
17 | public long getTimerId() {
18 | return timerId;
19 | }
20 |
21 | @Override
22 | public boolean equals(final Object o) {
23 | if (this == o) {
24 | return true;
25 | }
26 | if (o == null || getClass() != o.getClass()) {
27 | return false;
28 | }
29 | final TimerCancelled that = (TimerCancelled) o;
30 | return timerId == that.timerId;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return Objects.hashCode(timerId);
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return MoreObjects.toStringHelper(this)
41 | .add("timerId", timerId)
42 | .toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerExpired.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | public class TimerExpired {
9 |
10 | private final long timerId;
11 |
12 | @JsonCreator
13 | public TimerExpired(final @JsonProperty("timer_id") long timerId) {
14 | this.timerId = timerId;
15 | }
16 |
17 | public long getTimerId() {
18 | return timerId;
19 | }
20 |
21 | @Override
22 | public boolean equals(final Object o) {
23 | if (this == o) {
24 | return true;
25 | }
26 | if (o == null || getClass() != o.getClass()) {
27 | return false;
28 | }
29 | final TimerExpired that = (TimerExpired) o;
30 | return timerId == that.timerId;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return Objects.hashCode(timerId);
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return MoreObjects.toStringHelper(this)
41 | .add("timerId", timerId)
42 | .toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerListener.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | public interface TimerListener {
4 |
5 | void onTimerAdded(TimerAdded timerAdded);
6 |
7 | void onTimerRejected(TimerRejected timerRejected);
8 |
9 | void onTimerExpired(TimerExpired timerExpired);
10 |
11 | void onTimerTriggered(TimerTriggered timerTriggered);
12 |
13 | void onTimerUpdated(TimerUpdated timerUpdated);
14 |
15 | void onTimerUpdateFailed(TimerUpdateFailed timerUpdateFailed);
16 |
17 | void onTimerCancelled(TimerCancelled timerCancelled);
18 |
19 | void onTimerCancelFailed(TimerCancelFailed timerCancelFailed);
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerRejected.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | public class TimerRejected {
11 |
12 | public enum Cause {
13 | TOO_MANY_ACTIVE_TIMERS,
14 | TIMER_ALREADY_EXPIRED,
15 | TIMER_ALREADY_EXISTS;
16 |
17 | @JsonCreator
18 | private static Cause deserialize(String value) {
19 | return valueOf(value.toUpperCase());
20 | }
21 | }
22 |
23 | private final long timerId;
24 | private final Cause cause;
25 |
26 | @JsonCreator
27 | public TimerRejected(final @JsonProperty("timer_id") long timerId,
28 | final @JsonProperty("cause") Cause cause) {
29 | this.timerId = timerId;
30 | this.cause = checkNotNull(cause, "Null cause");
31 | }
32 |
33 | public long getTimerId() {
34 | return timerId;
35 | }
36 |
37 | public Cause getCause() {
38 | return cause;
39 | }
40 |
41 | @Override
42 | public boolean equals(Object o) {
43 | if (this == o) return true;
44 | if (o == null || getClass() != o.getClass()) return false;
45 | TimerRejected that = (TimerRejected) o;
46 | return timerId == that.timerId &&
47 | cause == that.cause;
48 | }
49 |
50 | @Override
51 | public int hashCode() {
52 | return Objects.hashCode(timerId, cause);
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return MoreObjects.toStringHelper(this)
58 | .add("timerId", timerId)
59 | .add("cause", cause)
60 | .toString();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerTriggered.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | public class TimerTriggered {
9 |
10 | private final long timerId;
11 |
12 | @JsonCreator
13 | public TimerTriggered(final @JsonProperty("timer_id") long timerId) {
14 | this.timerId = timerId;
15 | }
16 |
17 | public long getTimerId() {
18 | return timerId;
19 | }
20 |
21 | @Override
22 | public boolean equals(final Object o) {
23 | if (this == o) {
24 | return true;
25 | }
26 | if (o == null || getClass() != o.getClass()) {
27 | return false;
28 | }
29 | final TimerTriggered that = (TimerTriggered) o;
30 | return timerId == that.timerId;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return Objects.hashCode(timerId);
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return MoreObjects.toStringHelper(this)
41 | .add("timerId", timerId)
42 | .toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerUpdateFailed.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | import static com.google.common.base.Preconditions.checkNotNull;
9 |
10 | public class TimerUpdateFailed {
11 |
12 | public enum Cause {
13 | NOT_FOUND,
14 | TIMER_EXECUTION_INTERVAL_BROKEN;
15 |
16 | @JsonCreator
17 | private static Cause deserialize(String value) {
18 | return valueOf(value.toUpperCase());
19 | }
20 | }
21 |
22 | private final long timerId;
23 | private final Cause cause;
24 |
25 | @JsonCreator
26 | public TimerUpdateFailed(final @JsonProperty("timer_id") long timerId,
27 | final @JsonProperty("cause") Cause cause) {
28 | this.timerId = timerId;
29 | this.cause = checkNotNull(cause, "Null cause");
30 | }
31 |
32 | public long getTimerId() {
33 | return timerId;
34 | }
35 |
36 | public Cause getCause() {
37 | return cause;
38 | }
39 |
40 | @Override
41 | public boolean equals(Object o) {
42 | if (this == o) return true;
43 | if (o == null || getClass() != o.getClass()) return false;
44 | TimerUpdateFailed that = (TimerUpdateFailed) o;
45 | return timerId == that.timerId &&
46 | cause == that.cause;
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | return Objects.hashCode(timerId, cause);
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return MoreObjects.toStringHelper(this)
57 | .add("timerId", timerId)
58 | .add("cause", cause)
59 | .toString();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/TimerUpdated.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.common.base.MoreObjects;
6 | import com.google.common.base.Objects;
7 |
8 | public class TimerUpdated {
9 |
10 | private final long timerId;
11 |
12 | @JsonCreator
13 | public TimerUpdated(final @JsonProperty("timer_id") long timerId) {
14 | this.timerId = timerId;
15 | }
16 |
17 | public long getTimerId() {
18 | return timerId;
19 | }
20 |
21 | @Override
22 | public boolean equals(final Object o) {
23 | if (this == o) {
24 | return true;
25 | }
26 | if (o == null || getClass() != o.getClass()) {
27 | return false;
28 | }
29 | final TimerUpdated that = (TimerUpdated) o;
30 | return timerId == that.timerId;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return Objects.hashCode(timerId);
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return MoreObjects.toStringHelper(this)
41 | .add("timerId", timerId)
42 | .toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/UserMessageSender.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.JsonNode;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import com.fasterxml.jackson.databind.ObjectWriter;
7 | import com.fasterxml.jackson.databind.node.ObjectNode;
8 | import net.quedex.api.common.CommunicationException;
9 | import net.quedex.api.common.DisconnectedException;
10 | import net.quedex.api.common.MessageReceiver;
11 | import net.quedex.api.common.StreamFailureListener;
12 | import net.quedex.api.pgp.BcEncryptor;
13 | import net.quedex.api.pgp.PGPExceptionBase;
14 | import org.java_websocket.client.WebSocketClient;
15 | import org.java_websocket.exceptions.WebsocketNotConnectedException;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 |
19 | import java.util.List;
20 | import java.util.concurrent.ExecutorService;
21 | import java.util.concurrent.Executors;
22 | import java.util.function.Supplier;
23 |
24 | import static com.google.common.base.Preconditions.checkArgument;
25 | import static com.google.common.base.Preconditions.checkNotNull;
26 | import static com.google.common.base.Preconditions.checkState;
27 |
28 | class UserMessageSender {
29 |
30 | private static final Logger LOGGER = LoggerFactory.getLogger(UserMessageSender.class);
31 | private static final ObjectMapper OBJECT_MAPPER = MessageReceiver.OBJECT_MAPPER;
32 | private static final ObjectWriter OBJECT_WRITER = OBJECT_MAPPER.writer();
33 |
34 | private final WebSocketClient webSocketClient;
35 | private final BcEncryptor encryptor;
36 | private final long accountId;
37 | private final int nonceGroup;
38 | private final ExecutorService executor = Executors.newSingleThreadExecutor(); // single threaded for sequencing
39 |
40 | private volatile StreamFailureListener streamFailureListener;
41 | private volatile long nonce;
42 |
43 | UserMessageSender(
44 | WebSocketClient webSocketClient,
45 | long accountId,
46 | int nonceGroup,
47 | BcEncryptor encryptor
48 | ) {
49 | checkArgument(nonceGroup >= 0, "nonceGroup=%s < 0", nonceGroup);
50 | checkArgument(accountId > 0, "accountId=%s <= 0", accountId);
51 | this.webSocketClient = checkNotNull(webSocketClient, "null webSocketClient");
52 | this.encryptor = checkNotNull(encryptor);
53 | this.accountId = accountId;
54 | this.nonceGroup = nonceGroup;
55 | }
56 |
57 | void registerStreamFailureListener(StreamFailureListener streamFailureListener) {
58 | this.streamFailureListener = streamFailureListener;
59 | }
60 |
61 | void setStartNonce(long startNonce) {
62 | LOGGER.debug("setStartNonce({})", startNonce);
63 | nonce = startNonce;
64 | }
65 |
66 | void sendGetLastNonce() throws CommunicationException {
67 | try {
68 | sendMessage(
69 | OBJECT_MAPPER.createObjectNode()
70 | .put("type", "get_last_nonce")
71 | .put("nonce_group", nonceGroup)
72 | .put("account_id", accountId)
73 | );
74 | } catch (PGPExceptionBase | JsonProcessingException e) {
75 | throw new CommunicationException("Error sending get_last_nonce", e);
76 | }
77 | }
78 |
79 | void sendSubscribe() {
80 | sendMessageQueued(() -> addNonceAccountId(OBJECT_MAPPER.createObjectNode().put("type", "subscribe")));
81 | }
82 |
83 | void sendOrderSpec(OrderSpec orderSpec) {
84 | sendMessageQueued(() -> addNonceAccountId(OBJECT_MAPPER.valueToTree(orderSpec)));
85 | }
86 |
87 | void sendBatch(List extends OrderSpec> batch) {
88 | sendMessageQueued(() -> createBatchNode(batch));
89 | }
90 |
91 | void sendTimeTriggeredBatch(long timerId,
92 | long executionStartTimestamp,
93 | long executionExpirationTimestamp,
94 | List extends OrderSpec> batch) {
95 | sendMessageQueued(() -> {
96 | final ObjectNode mainCommand = OBJECT_MAPPER.createObjectNode()
97 | .put("type", "add_timer")
98 | .put("timer_id", timerId)
99 | .put("execution_start_timestamp", executionStartTimestamp)
100 | .put("execution_expiration_timestamp", executionExpirationTimestamp);
101 |
102 | addNonceAccountId(mainCommand);
103 |
104 | mainCommand.set("command", createBatchNode(batch));
105 |
106 | return mainCommand;
107 | });
108 | }
109 |
110 | void sendTimeTriggeredBatchUpdate(long timerId,
111 | Long executionStartTimestamp,
112 | Long executionExpirationTimestamp,
113 | List extends OrderSpec> batch) {
114 | sendMessageQueued(() -> {
115 | final ObjectNode mainCommand = OBJECT_MAPPER.createObjectNode()
116 | .put("type", "update_timer")
117 | .put("timer_id", timerId)
118 | .put("new_execution_start_timestamp", executionStartTimestamp)
119 | .put("new_execution_expiration_timestamp", executionExpirationTimestamp);
120 |
121 | addNonceAccountId(mainCommand);
122 |
123 | mainCommand.set("new_command", batch != null ? createBatchNode(batch) : null);
124 |
125 | return mainCommand;
126 | });
127 | }
128 |
129 | void sendTimeTriggeredBatchCancellation(long timerId) {
130 | sendMessageQueued(() -> {
131 | final ObjectNode cancelCommand = OBJECT_MAPPER.createObjectNode()
132 | .put("type", "cancel_timer")
133 | .put("timer_id", timerId);
134 |
135 | addNonceAccountId(cancelCommand);
136 |
137 | return cancelCommand;
138 | });
139 | }
140 |
141 | void sendInternalTransfer(InternalTransfer internalTransfer) {
142 | sendMessageQueued(() -> addNonceAccountId(OBJECT_MAPPER.valueToTree(internalTransfer)));
143 | }
144 |
145 | void stop() {
146 | executor.shutdown();
147 | }
148 |
149 | private JsonNode createBatchNode(final List extends OrderSpec> batch) {
150 | JsonNode batchJson = OBJECT_MAPPER.valueToTree(batch);
151 | for (final JsonNode node : batchJson) {
152 | checkState(node instanceof ObjectNode, "Expected ObjectNode");
153 | addNonceAccountId((ObjectNode) node);
154 | }
155 | return OBJECT_MAPPER.createObjectNode()
156 | .put("type", "batch")
157 | .put("account_id", accountId)
158 | .set("batch", batchJson);
159 | }
160 |
161 | private void sendMessageQueued(Supplier supplier) {
162 | executor.execute(() -> {
163 | try {
164 | sendMessage(supplier.get());
165 | } catch (WebsocketNotConnectedException e) {
166 | onError(new DisconnectedException(e));
167 | } catch (Exception e) {
168 | onError(new CommunicationException("Error sending message", e));
169 | }
170 | });
171 | }
172 |
173 | private ObjectNode addNonceAccountId(ObjectNode jsonMessage) {
174 | return jsonMessage
175 | .put("account_id", accountId)
176 | .put("nonce", getNonce())
177 | .put("nonce_group", nonceGroup);
178 | }
179 |
180 | private long getNonce() {
181 | return ++nonce;
182 | }
183 |
184 | private void sendMessage(JsonNode jsonMessage) throws JsonProcessingException, PGPExceptionBase {
185 | String messageStr = OBJECT_WRITER.writeValueAsString(jsonMessage);
186 | webSocketClient.send(encryptor.encrypt(messageStr, true));
187 |
188 | LOGGER.trace("sendMessage({})", messageStr);
189 | }
190 |
191 | private void onError(Exception e) {
192 | LOGGER.warn("onError({})", e);
193 | StreamFailureListener streamFailureListener = this.streamFailureListener;
194 | if (streamFailureListener != null) {
195 | streamFailureListener.onStreamFailure(e);
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/main/java/net/quedex/api/user/UserStream.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.user;
2 |
3 | import net.quedex.api.common.CommunicationException;
4 | import net.quedex.api.common.StreamFailureListener;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Represents the stream of realtime private data streamed from and trading commands which may be sent to Quedex.
10 | * Allows registering and subscribing for particular data types. The registered listeners will be called (in a single
11 | * thread) for every event that arrives. Trading commands include placing, canceling and modifying orders - batching of
12 | * these commands is possible via {@link #batch} methods and should be used whenever possible. The data exchanged on the
13 | * stream has the form of PGP-encrypted messages - all the encryption/decryption and serialization/deserialization is
14 | * handled by the implementations and the interaction with the stream is based on Java objects.
15 | *
16 | * The stream gives some guarantees about the order of received events useful for state initialisation - see
17 | * documentation of {@link #subscribeListeners}.
18 | *
19 | * To handle all errors properly, always {@link #registerStreamFailureListener} before {@link #start}ing the stream.
20 | */
21 | public interface UserStream {
22 |
23 | void registerStreamFailureListener(StreamFailureListener streamFailureListener);
24 |
25 | void start() throws CommunicationException;
26 |
27 | void registerOrderListener(OrderListener orderListener);
28 |
29 | void registerOpenPositionListener(OpenPositionListener openPositionListener);
30 |
31 | void registerAccountStateListener(AccountStateListener accountStateListener);
32 |
33 | void registerInternalTransferListener(InternalTransferListener listener);
34 |
35 | void registerTimerListener(TimerListener listener);
36 |
37 | /**
38 | * Subscribes previously registered listeners. Causes a welcome package to be sent to the listeners. The welcome
39 | * package includes:
40 | *
41 | * - an {@link OrderPlaced} item for each pending order
42 | * - an {@link OpenPosition} item for each opened position
43 | * - an initial {@link AccountState}
44 | *
45 | *
46 | * The welcome package constitutes an initial state that will be modified by the subsequent events received by the
47 | * listeners.
48 | *
49 | * The first received {@link AccountState} marks the end of the welcome package and may be used to detect the end
50 | * of initialisation.
51 | */
52 | void subscribeListeners();
53 |
54 | /**
55 | * Sends the given {@link LimitOrderSpec} to the exchange. This method is asynchronous - the fact that it returned
56 | * does not guarantee that the command has been received nor processed by the exchange.
57 | */
58 | void placeOrder(LimitOrderSpec limitOrderSpec);
59 |
60 | /**
61 | * Sends the given {@link OrderCancelSpec} to the exchange. This method is asynchronous - the fact that it returned
62 | * does not guarantee that the command has been received nor processed by the exchange.
63 | */
64 | void cancelOrder(OrderCancelSpec orderCancelSpec);
65 |
66 | /**
67 | * Sends command to cancel all pending orders for an user to the exchange. This method is asynchronous - the fact
68 | * that it returned does not guarantee that the command has ben received nor processed by the exchange.
69 | */
70 | void cancelAllOrders();
71 |
72 | /**
73 | * Sends the given {@link OrderModificationSpec} to the exchange. This method is asynchronous - the fact that it
74 | * returned does not guarantee that the command has been received nor processed by the exchange.
75 | */
76 | void modifyOrder(OrderModificationSpec orderModificationSpec);
77 |
78 | /**
79 | * Returns an object (not thread-safe) which may be used fluently to send a batch of {@link OrderSpec}s to the
80 | * exchange. Calling {@link Batch#send()} sends batched {@link OrderSpec}s to the exchange. This method is
81 | * asynchronous - the fact that it returned does not guarantee that the commands have been received nor processed by
82 | * the exchange.
83 | */
84 | Batch batch();
85 |
86 | /**
87 | * Sends the given list of {@link OrderSpec}s to the exchange. This method is asynchronous - the fact that it
88 | * returned does not guarantee that the commands have been received nor processed by the exchange.
89 | */
90 | void batch(List extends OrderSpec> batch);
91 |
92 | /**
93 | *
94 | * Returns an object (not thread-safe) which may be used fluently to create a time triggered batch of {@link OrderSpec}s.
95 | * Calling {@link Batch#send()} sends this batch of {@link OrderSpec}s to the exchange.
96 | *
97 | *
98 | * When a time triggered batch is received by the exchange engine, a new timer is registered.
99 | * Based on the timer configuration, at some point in the future (between executionStartTimestamp and executionExpirationTimestamp),
100 | * all the carried order commands are processed, one by one, in the creation order.
101 | *
102 | *
103 | * Please refer to the API documentation for detailed explanation of creating timers
104 | * (https://quedex.net/doc/api/).
105 | *
106 | * This method is asynchronous - the fact that it returned does not guarantee that the commands have been received
107 | * nor processed by the exchange.
108 | *
109 | * @param timerId a user defined timer identifier, can be used to cancel or update batch
110 | * @param executionStartTimestamp the defined batch will not be executed before this timestamp
111 | * @param executionExpirationTimestamp the defined batch will not ne executed after this timestamp
112 | */
113 | Batch timeTriggeredBatch(long timerId, long executionStartTimestamp, long executionExpirationTimestamp);
114 |
115 | /**
116 | *
117 | * Sends a time triggered batch with the given list of {@link OrderSpec}s to the exchange.
118 | *
119 | *
120 | * When a time triggered batch is received by the exchange engine, a new timer is registered.
121 | * Based on the timer configuration, at some point in the future (between executionStartTimestamp and executionExpirationTimestamp),
122 | * all the carried order commands are processed, one by one, in the creation order.
123 | *
124 | *
125 | * Please refer to the API documentation for detailed explanation of creating timers
126 | * (https://quedex.net/doc/api/).
127 | *
128 | * This method is asynchronous - the fact that it returned does not guarantee that the commands have been received
129 | * nor processed by the exchange.
130 | *
131 | * @param timerId a user defined batch identifier, can be used to cancel or update batch
132 | * @param executionStartTimestamp the defined batch will not be executed before this timestamp
133 | * @param executionExpirationTimestamp the defined batch will not be executed after this timestamp
134 | * @param batch list of {@link OrderSpec}s to be executed
135 | */
136 | void timeTriggeredBatch(long timerId,
137 | long executionStartTimestamp,
138 | long executionExpirationTimestamp,
139 | List extends OrderSpec> batch);
140 |
141 | /**
142 | * Returns an object (not thread-safe) which may be used fluently to update an already existing batch of {@link OrderSpec}s on the
143 | * exchange. At least one of the following must be modified:
144 | *
145 | * - executionStartTimestamp
146 | * - executionExpirationTimestamp
147 | * - commands
148 | *
149 | *
150 | * Specified commands replace commands registered during the timer creation.
151 | * Calling {@link Batch#send()} sends modified batch of {@link OrderSpec}s to the exchange.
152 | *
153 | *
154 | * Please refer to the API documentation for detailed explanation of updating timers
155 | * (https://quedex.net/doc/api/).
156 | *
157 | * This method is asynchronous - the fact that it returned does not guarantee that the commands have been received
158 | * nor processed by the exchange.
159 | *
160 | * @param timerId a user defined timer identifier, the same as used when creating the batch
161 | * @param executionStartTimestamp new value of executionStartTimestamp (optional)
162 | * @param executionExpirationTimestamp new value of executionExpirationTimestamp (optional)
163 | */
164 | Batch updateTimeTriggeredBatch(long timerId, Long executionStartTimestamp, Long executionExpirationTimestamp);
165 |
166 | /**
167 | *
168 | * Sends the modified batch to the exchange. At least one of the following must be modified:
169 | *
170 | * - executionStartTimestamp
171 | * - executionExpirationTimestamp
172 | * - batch
173 | *
174 | *
175 | * Specified commands replace commands registered during the timer creation.
176 | *
177 | *
178 | * Please refer to the API documentation for detailed explanation of updating timers
179 | * (https://quedex.net/doc/api/).
180 | *
181 | * This method is asynchronous - the fact that it returned does not guarantee that the commands have been received
182 | * nor processed by the exchange.
183 | *
184 | * @param timerId a user defined timer identifier, the same as used when creating the batch
185 | * @param executionStartTimestamp new value of executionStartTimestamp (optional)
186 | * @param executionExpirationTimestamp new value of executionExpirationTimestamp (optional)
187 | * @param batch new value of batch (optional)
188 | */
189 | void updateTimeTriggeredBatch(long timerId,
190 | Long executionStartTimestamp,
191 | Long executionExpirationTimestamp,
192 | List extends OrderSpec> batch);
193 |
194 | /**
195 | * Sends command to cancel an existing time triggered batch.
196 | * @param timerId a user defined batch identifier, the same as used when creating the batch
197 | */
198 | void cancelTimeTriggeredBatch(long timerId);
199 |
200 | void executeInternalTransfer(InternalTransfer internalTransfer);
201 |
202 | void stop() throws CommunicationException;
203 |
204 | interface Batch {
205 |
206 | Batch placeOrder(LimitOrderSpec limitOrderSpec);
207 |
208 | Batch placeOrders(List limitOrderSpecs);
209 |
210 | Batch cancelOrder(OrderCancelSpec orderCancelSpec);
211 |
212 | Batch cancelOrders(List orderCancelSpecs);
213 |
214 | Batch cancelAllOrders();
215 |
216 | Batch modifyOrder(OrderModificationSpec orderModificationSpec);
217 |
218 | Batch modifyOrders(List orderModificationSpec);
219 |
220 | void send();
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/main/resources/qdxConfig.properties.example:
--------------------------------------------------------------------------------
1 | net.quedex.client.api.marketStreamUrl = wss://api.quedex.net/market_stream
2 | net.quedex.client.api.userStreamUrl = wss://api.quedex.net/user_stream
3 |
4 | net.quedex.client.api.accountId = 123456789
5 | # value between 0 and 9, has to be different for every WebSocket connection opened to the exchange
6 | # (e.g. browser and trading bot); our webapp uses nonce_group=0
7 | net.quedex.client.api.nonceGroup = 5
8 |
9 | net.quedex.client.api.qdxPublicKey = -----BEGIN PGP PUBLIC KEY BLOCK-----\n\
10 | Version: QPG\n\
11 | \n\
12 | mQENBFlPvjsBCACr/UfHzXAezskLqcq9NiiaNFDDT5A+biC8VrOglB0ZSQOYRira\n\
13 | NgQ2Cp8Jd+XU77F+J1012BjB5y87Z+hdnwBDsqF7CjkjeQzsE3PSvm9I+E3cneqx\n\
14 | UcinRaUD1wfwVytbg9Q9rpqQ7CTjVWY1UPYjs6dAo1WAp/ux/VTeOFbpO0R3D7if\n\
15 | ZGY1QeISRpLWiMpcG2YCOALnuazABVCNXLhVqa8Y7tt2I+cI0uE9tBf41gjGPPtd\n\
16 | KdASPVz1plpOEl2dOpmy8jICqcSzUsT4Sy8vAqW3U1HF+TA2QGRcrrUItL4GjxNL\n\
17 | lcL8wh7mclsjRe5Q5dYnrACC9NWS6vSp/eAPABEBAAG0G1F1ZWRleCA8Y29udGFj\n\
18 | dEBxdWVkZXgubmV0PokBOAQTAQIAIgUCWU++OwIbAwYLCQgHAwIGFQgCCQoLBBYC\n\
19 | AwECHgECF4AACgkQzsLQUmv6vk9Rlwf+LiJA37dhDdGFU/fexNRHZWTUh2TdqBsv\n\
20 | MiNtarf+HlZIioWMCzlHmb3bolVrfFUNUh/GGlPENtlaSmFGuPhMlFcNDGYM+I7k\n\
21 | ufhM95jxmtzy97NYMeMx4xjnaBAu8kFsvi80BR/05ZhCHqyI3K9NpYoXBfsyzss+\n\
22 | j/jX1NHayzMmXNdqQ5JjzuICZj0EY9ryLP/jPAZ6DS9LVwi9Vr2JzZheCx5Q77Ud\n\
23 | HuGTOBu3Azor2f4n4ccELs7lgU7uGrt1cK/oiML9UDmqjelunzTFU/5Q0tp7C3Qm\n\
24 | 1wymd+PYTvvX/5htnLar1nIuYmmvtCZb1zyuzPzJWWtCcFFsiV9kerkBDQRZT747\n\
25 | AQgAn/9rwQn4yM7pVYO9rTxNveI60lgp4RLKi1WYpAy6gxwUp3zLE/h59rykb7qM\n\
26 | 9QArABsMEGGKt9urq7hjsKu8bM+hVTcAuoDre5qNFEfhZuPBS9LF7wQWDikORZxR\n\
27 | Mk5WIiCt3U2soQ4Lismw1bLDX8uqkv3GFtR+IaKzuwYBEVPwuZ15EOt9G83JR3uV\n\
28 | MKqeUtFW9+z5WEAh2JLU6C357sftJIJeWDEgF2TPtQOzc8isI8rpIFNyl6x1Aiy6\n\
29 | LaSWmOI3d9EQ8SH4LxCXtAgWvnIoPL0JsP5/FWzt6qJR4teu+A2xwG7001va+DUc\n\
30 | 34AbSV9Ogqa519OfbKK6HDyFIQARAQABiQEfBBgBAgAJBQJZT747AhsMAAoJEM7C\n\
31 | 0FJr+r5PtEUH/0KmXQWbm4qXxOnaXrk+CKLDBxtfY6BaoJ6ekdGfqVMd8YM+UGnL\n\
32 | 6d49vex4O80uIhIDSex446gKVlhdwOjIlUFmTCtMgGOa06G2T4sx8a9y2UYK45hN\n\
33 | rj9aVfhJ8nn9yuPj7cBNtLEYJ4VkRKxJO9XX8cfhUsomhB3DQDbOLfikYqfmupm6\n\
34 | mYX84CO/DD8JAXx4qt9Rg+5AUQegq26iZ/og1ZjYZ/tvBjrc45u23XCWvgVHbGhb\n\
35 | wWCNjZijaY1VnTwTe6uZv1AqovZpprqZKWImN5myaJI3AJU2W2FCbI0ezfoVEVO4\n\
36 | zMipOYZzRziJeCz1gX9geNseLvfJ8EtZRKU=\n\
37 | =e4C9\n\
38 | -----END PGP PUBLIC KEY BLOCK-----
39 |
40 | net.quedex.client.api.userPrivateKey =
--------------------------------------------------------------------------------
/src/test/java/net/quedex/api/market/WebsocketMarketStreamIT.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.market;
2 |
3 | import net.quedex.api.common.Config;
4 | import net.quedex.api.common.StreamFailureListener;
5 | import net.quedex.api.testcommons.Utils;
6 | import org.hamcrest.BaseMatcher;
7 | import org.hamcrest.Description;
8 | import org.hamcrest.Matcher;
9 | import org.mockito.Mock;
10 | import org.mockito.MockitoAnnotations;
11 | import org.testng.annotations.BeforeMethod;
12 | import org.testng.annotations.Test;
13 |
14 | import java.util.ArrayList;
15 | import java.util.Collections;
16 | import java.util.List;
17 |
18 | import static org.assertj.core.api.Assertions.assertThat;
19 | import static org.mockito.Matchers.any;
20 | import static org.mockito.Matchers.argThat;
21 | import static org.mockito.Mockito.never;
22 | import static org.mockito.Mockito.timeout;
23 | import static org.mockito.Mockito.verify;
24 |
25 | /**
26 | * An integration test with live Quedex Websocket. To run it:
27 | *
28 | * - Copy {@code market.properties.example} to {@code market.properties} in test resources.
29 | * - Fill in the details in the properties file.
30 | * - Enable the test method.
31 | * - Run the test.
32 | *
33 | */
34 | public class WebsocketMarketStreamIT {
35 |
36 | private static final boolean TEST_ENABLED = false; // enable to run
37 |
38 | @Mock private OrderBookListener orderBookListener;
39 | @Mock private QuotesListener quotesListener;
40 | @Mock private SessionStateListener sessionStateListener;
41 | @Mock private TradeListener tradeListener;
42 | @Mock private StreamFailureListener streamFailureListener;
43 |
44 | private MarketStream marketStream;
45 |
46 | @BeforeMethod
47 | public void setUp() throws Exception {
48 | MockitoAnnotations.initMocks(this);
49 |
50 | Config config = Config.fromResource(Utils.getKeyPassphraseFromProps());
51 | marketStream = new WebsocketMarketStream(config);
52 | }
53 |
54 | @Test(enabled = TEST_ENABLED)
55 | public void testIntegrationWithLiveWS() throws Exception {
56 |
57 | List instrumentIds = Collections.synchronizedList(new ArrayList<>());
58 |
59 | marketStream.start();
60 |
61 | marketStream.registerStreamFailureListener(streamFailureListener);
62 | marketStream.registerAndSubscribeSessionStateListener(sessionStateListener);
63 |
64 | marketStream.registerInstrumentsListener(instruments -> {
65 | instrumentIds.addAll(instruments.keySet());
66 | assertThat(instrumentIds).isNotEmpty();
67 |
68 | System.out.println("instrumentIds = " + instrumentIds); // for debugging
69 |
70 | marketStream.registerOrderBookListener(orderBookListener).subscribe(instrumentIds);
71 | marketStream.registerQuotesListener(quotesListener).subscribe(instrumentIds);
72 | marketStream.registerTradeListener(tradeListener).subscribe(instrumentIds);
73 | });
74 |
75 | instrumentIds
76 | .forEach(id -> verify(orderBookListener, timeout(1000)).onOrderBook(argThat(obHasInstrumentId(id))));
77 | instrumentIds
78 | .forEach(id -> verify(quotesListener, timeout(1000)).onQuotes(argThat(quotesHaveInstrumentId(id))));
79 | verify(sessionStateListener, timeout(1000)).onSessionState(any());
80 |
81 | marketStream.stop();
82 |
83 | verify(streamFailureListener, never()).onStreamFailure(any());
84 | }
85 |
86 | private static Matcher obHasInstrumentId(int id) {
87 | return new BaseMatcher() {
88 | @Override
89 | public boolean matches(Object o) {
90 | if (o instanceof OrderBook) {
91 | return ((OrderBook) o).getInstrumentId() == id;
92 | } else {
93 | return false;
94 | }
95 | }
96 |
97 | @Override
98 | public void describeTo(Description description) {
99 | description.appendText("OrderBook with instrumentId").appendValue(id);
100 | }
101 | };
102 | }
103 |
104 | private static Matcher quotesHaveInstrumentId(int id) {
105 | return new BaseMatcher() {
106 | @Override
107 | public boolean matches(Object o) {
108 | if (o instanceof Quotes) {
109 | return ((Quotes) o).getInstrumentId() == id;
110 | } else {
111 | return false;
112 | }
113 | }
114 |
115 | @Override
116 | public void describeTo(Description description) {
117 | description.appendText("Wuotes with instrumentId").appendValue(id);
118 | }
119 | };
120 | }
121 | }
--------------------------------------------------------------------------------
/src/test/java/net/quedex/api/testcommons/Keys.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.testcommons;
2 |
3 | /**
4 | * @author Wiktor Gromniak <wgromniak@quedex.net>.
5 | */
6 | public class Keys {
7 |
8 | public static final String QUEDEX_PRIVATE = "-----BEGIN PGP public KEY BLOCK-----\n" +
9 | "Version: GnuPG v1\n" +
10 | "\n" +
11 | "lQHYBFluLQMBBADP0huibLNNcLNRcQ7wTtCbn2S8K0q3V1c2rRrcTGMFmoKK+VD5\n" +
12 | "zo/bcbAtfm/6O+cxm+Cj2toK1bsBdF5Ep4C28MHkpbyJIsrwC/LKJ58k0/iMLF/C\n" +
13 | "nFDin6/hsjU2E7LKKC7uz/4F6LKFybM8gomiUFSC5smaeifj4fvAzkFv3QARAQAB\n" +
14 | "AAP9GLFAulBLD5uhRWnMWhjV6B+3XYo78u5254FdjH5WUk9KTSPpZKe6klrUPDDm\n" +
15 | "h3zARWCIKj/cd2bEtPFe1Vkc+FNd8tABkgGPFmd8MlnH2xjJkmTzBd/fU+iLWFFc\n" +
16 | "SeF3fWdW+5y5P4/8jjFIXXE3T6F2d/XBSzflXvmD+7Jv7rsCANrYKeftdr3Vyf79\n" +
17 | "cxHmofPDinIvrpbeDnw2kWLqzpAnpxrhOzZIDFmsJRvgyTYYtXcWv6u/OfuEZKE+\n" +
18 | "AbvhTOcCAPMa0BtbHiYVq3FQhY5KzXNIhg1y8sVIzGUecu6vvJl53s0uvrHc5AAw\n" +
19 | "86ENpxYrwlS2/W4kt4tLAS1YgjiNIJsB/RBdPA0GUTrP89htBUFpc7BIYTry5LMS\n" +
20 | "vNugK1SSYLMGgDNf/DK12EbkjI8rLfY825eNZ6R1PKf+q2blSVTg9SCkD7QIVGVz\n" +
21 | "dCBLZXmIuAQTAQIAIgUCWW4tAwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA\n" +
22 | "CgkQ0IVp55u0qmt8XAQAuhhHHz6gpORXxmXUdxEPa1Ulz2m5HbnpdB9O/nqGWlGV\n" +
23 | "KB5AGBEvnyQHN3396yG1f3xwwkt+Ow1Au15g96jBWGgbVGA03gUhmWnEtsR+wMvt\n" +
24 | "5zLrv3zzrWsngKMG88kBBdNCCgEq23ioIhxE2uqXiEhFtvPKk+cSRStn4qy1Q2Wd\n" +
25 | "AdgEWW4tAwEEAK0PXO5uq1qwPyJZyoNugn2TESVNuxADkT/FBW8fELrSrVaW8XCN\n" +
26 | "FpGMxPGtGlWQEXPErrZFibfoptMbRB+tFCEHfqgLQURyQtX3o5B/x5UoHscydIJl\n" +
27 | "vPmaB/JTcj/8Gl+D2vsUvefpeIVZlr3mVJDe2T6iOhnz5Rn5dJ/dgGCTABEBAAEA\n" +
28 | "A/0enp6/MwaPc6qf/coWihl9dWVtt7yWv0LWSRpGiHUR0Q/JR8itNNFe7Ey2Q/3q\n" +
29 | "UKS92nldF52f1AKcTHE3t4xdZyuuI9OuM+gmHjc3bbz85yU4osykXxEolxVVC/+B\n" +
30 | "AfjjXiD/jbMpTgFgDmzW3iMzOj2Ub32aJovb9guPPoTz4QIAy7UD59i6Jsng+LjK\n" +
31 | "kJ1G+4l5mWx/IO1dqFzIilSpO3N5XwDwiMwX7eaDYcaGh7JK6yX7CQVJ+AGE+Iis\n" +
32 | "m9kOMwIA2XxRnMLvniJQLdA7mPF8WKtXYK7tGSyQKQpvmTeeP5bJMy7ZFq+iiyAh\n" +
33 | "Y4VYTH7loQYu8YEqGeRjQQX5JkBEIQH9EGKkZroIacX1u3GJjjxD6l70hgH2ql7i\n" +
34 | "6mYdfDE+Ue3l7WwKqTQhSA6QkVwBgXCcB1k3bDyG9qB70ObPLW43np3WiJ8EGAEC\n" +
35 | "AAkFAlluLQMCGwwACgkQ0IVp55u0qmsrggQAuFVzKE7i9jIFzOpGyX2Kay8bGrPx\n" +
36 | "RUE7W8JFSmMFbhRo8AbOeyDPb+ndMwtrJM2lSn3D48wsM8U/06ubONJp/GJg+TVz\n" +
37 | "o/67hpUJ+aPL2e8SuNAnaEDtYFkvg0yJNLdqyExz8Db/bElGbn5jBirCenkC3ooc\n" +
38 | "rxH32RRqhjMh/JU=\n" +
39 | "=9e5q\n" +
40 | "-----END PGP public KEY BLOCK-----\n";
41 |
42 | public static final String QUEDEX_PUBLIC = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
43 | "Version: GnuPG v1\n" +
44 | "\n" +
45 | "mI0EWW4tAwEEAM/SG6Jss01ws1FxDvBO0JufZLwrSrdXVzatGtxMYwWagor5UPnO\n" +
46 | "j9txsC1+b/o75zGb4KPa2grVuwF0XkSngLbwweSlvIkiyvAL8sonnyTT+IwsX8Kc\n" +
47 | "UOKfr+GyNTYTssooLu7P/gXosoXJszyCiaJQVILmyZp6J+Ph+8DOQW/dABEBAAG0\n" +
48 | "CFRlc3QgS2V5iLgEEwECACIFAlluLQMCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B\n" +
49 | "AheAAAoJENCFaeebtKprfFwEALoYRx8+oKTkV8Zl1HcRD2tVJc9puR256XQfTv56\n" +
50 | "hlpRlSgeQBgRL58kBzd9/eshtX98cMJLfjsNQLteYPeowVhoG1RgNN4FIZlpxLbE\n" +
51 | "fsDL7ecy6798861rJ4CjBvPJAQXTQgoBKtt4qCIcRNrql4hIRbbzypPnEkUrZ+Ks\n" +
52 | "tUNluI0EWW4tAwEEAK0PXO5uq1qwPyJZyoNugn2TESVNuxADkT/FBW8fELrSrVaW\n" +
53 | "8XCNFpGMxPGtGlWQEXPErrZFibfoptMbRB+tFCEHfqgLQURyQtX3o5B/x5UoHscy\n" +
54 | "dIJlvPmaB/JTcj/8Gl+D2vsUvefpeIVZlr3mVJDe2T6iOhnz5Rn5dJ/dgGCTABEB\n" +
55 | "AAGInwQYAQIACQUCWW4tAwIbDAAKCRDQhWnnm7SqayuCBAC4VXMoTuL2MgXM6kbJ\n" +
56 | "fYprLxsas/FFQTtbwkVKYwVuFGjwBs57IM9v6d0zC2skzaVKfcPjzCwzxT/Tq5s4\n" +
57 | "0mn8YmD5NXOj/ruGlQn5o8vZ7xK40CdoQO1gWS+DTIk0t2rITHPwNv9sSUZufmMG\n" +
58 | "KsJ6eQLeihyvEffZFGqGMyH8lQ==\n" +
59 | "=TPlM\n" +
60 | "-----END PGP PUBLIC KEY BLOCK-----";
61 |
62 | public static final String TRADER_PRIVATE = "-----BEGIN PGP public KEY BLOCK-----\n" +
63 | "Version: GnuPG v1\n" +
64 | "\n" +
65 | "lQHYBFluLcABBADzVTqi51bbFlKyGybGzTkumKn9mNhaJzJzfnmeOBdCc54gW/pd\n" +
66 | "WnQDlQEchUZj3HR5zaRB8VGYxOjVAvGAGRLzgAcKE5GG2CxRsuFz3Ldc8tvInpG1\n" +
67 | "V81Z75yfpKDMtUPHMsH2GffGl3pbJWTVzxFUvuf/9J9h3XYOWzERODSUxQARAQAB\n" +
68 | "AAP+IbLH8A+Bo82vniLMd/Or25wgzpIARFvxTLVTOmoiLn28hFa4kX0ZW/WitcRv\n" +
69 | "Px0ktEmaWdeFqVZ7uCQ0Nb1DNlh+Evg470BcLfpAF9eTgx+DdurD2MkOiQX1PwKW\n" +
70 | "D2BDsq8Jyc1Ti3gHkrxCtVnxncnLOdBgOm+HOy4wXnv91nECAPaItX17gzmJbbbt\n" +
71 | "iFn94Nf4vzwKq/no9jm6a3xqch7vN0be8hWbplxGTOWvAZ+hTok/jnLgQ5KypZ8b\n" +
72 | "ZNtWgG8CAPytDh5hnys7ehX0/TvGm/L8lFKKi/4fc334oOzWDwqt1iJeMmnAZ6lY\n" +
73 | "+/nUsouILSafp3LLycr/lM8qMD678AsCAKzMlR+q5eqn6OXQSSs4dWHcn+HQD7rJ\n" +
74 | "uCwF9jGUlxoAUmeaygO+Mwh/Twrnp3MlVmOHoPYcKKRapIY+Gp/rRxekl7QIVGVz\n" +
75 | "dCBLZXmIuAQTAQIAIgUCWW4twAIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA\n" +
76 | "CgkQCVkAznkaitL13wP/U247yHUJw2n3JsvH8vLN8Y2+6Ni7Fy3IEqTL228ISHBU\n" +
77 | "UfVbGXotOsP9xLdFAMgW0iE358+vSU5Kss0l5dbXPPIO+PfJ9fUBL3mpst6Kpp8m\n" +
78 | "z4ngLxF1tJv2gul51dUU/3RITDQyqwwYSxCW1uTNuAGQJ3N5NiVeyfzH8GywghSd\n" +
79 | "AdgEWW4twAEEAJ+660sVhrU3zvmsSIx35BhpcWy9GfhgkUKPSkWLVus9ZiS+TVKs\n" +
80 | "ZTHTuoS4KcSfsqqnW/DBt6w7YCepdd0Sf4PFF7wgm9+X++F8kQQhxBvFDwx1BMoh\n" +
81 | "rJH80CnFbbxA39Vf4IUo7mVxajUMZ3O06gAkuZRzpHHr8V8sOMQK3mtNABEBAAEA\n" +
82 | "A/sFqgc1/mlse7InQGjCMm4wP2z6QiptmF8OUS59ENfgN6krnGP5jot4HN38Xtt8\n" +
83 | "UX1wd8bW3se4n9JlFalMUZ+b6kWFZqZto0Ge1VHOwDYEdRmLlWO7z+QUN1FlANjo\n" +
84 | "oqB4MuNzau5jgLUyId352t8oar3EldBUmFci+JMaiaZMsQIAx4Y6+WJ9dU/BKrWC\n" +
85 | "L/rJ2FV9RZ6RmbJ8NMNr7jVojoJzPnxzQa8c0s1SRu+3ccIOH2Jhhgdgnk5WmWg5\n" +
86 | "xCttxwIAzPEnsEaR+jeSjpek+MBvGlQkO18J3ejjngN0AqF4wovHGmEsE/3NyUam\n" +
87 | "80e9DNknIGsZ2+NNJ1UeouOVpjiuSwIAuOlFox2I5k4KJEQpIFrR7YKeNh8SLgQD\n" +
88 | "tOpsWlZ6naePgkD31Y0dQ28mvaAiTlMdGCpOdxhcgiw8MrRClcZv45sRiJ8EGAEC\n" +
89 | "AAkFAlluLcACGwwACgkQCVkAznkaitLEtAP/YNt1fAPF6ieX1U6KM1RlMvuqa08q\n" +
90 | "Pkg7z5GRti34Czq8HdNnYXfiN7dpikT0CwUV9v7xw7BtlENxv9rF/FMFDjh8yWdr\n" +
91 | "YwpCofOLRmMHxgTCpFRP6L3N/fmJaLUVmXy+gZrjVnglhRB+G5q8wemUusvCygSa\n" +
92 | "OGGHLWrBcvtjoq4=\n" +
93 | "=AE6u\n" +
94 | "-----END PGP public KEY BLOCK-----";
95 |
96 | public static final String TRADER_PUBLIC = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
97 | "Version: GnuPG v1\n" +
98 | "\n" +
99 | "mI0EWW4twAEEAPNVOqLnVtsWUrIbJsbNOS6Yqf2Y2FonMnN+eZ44F0JzniBb+l1a\n" +
100 | "dAOVARyFRmPcdHnNpEHxUZjE6NUC8YAZEvOABwoTkYbYLFGy4XPct1zy28iekbVX\n" +
101 | "zVnvnJ+koMy1Q8cywfYZ98aXelslZNXPEVS+5//0n2Hddg5bMRE4NJTFABEBAAG0\n" +
102 | "CFRlc3QgS2V5iLgEEwECACIFAlluLcACGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B\n" +
103 | "AheAAAoJEAlZAM55GorS9d8D/1NuO8h1CcNp9ybLx/LyzfGNvujYuxctyBKky9tv\n" +
104 | "CEhwVFH1Wxl6LTrD/cS3RQDIFtIhN+fPr0lOSrLNJeXW1zzyDvj3yfX1AS95qbLe\n" +
105 | "iqafJs+J4C8RdbSb9oLpedXVFP90SEw0MqsMGEsQltbkzbgBkCdzeTYlXsn8x/Bs\n" +
106 | "sIIUuI0EWW4twAEEAJ+660sVhrU3zvmsSIx35BhpcWy9GfhgkUKPSkWLVus9ZiS+\n" +
107 | "TVKsZTHTuoS4KcSfsqqnW/DBt6w7YCepdd0Sf4PFF7wgm9+X++F8kQQhxBvFDwx1\n" +
108 | "BMohrJH80CnFbbxA39Vf4IUo7mVxajUMZ3O06gAkuZRzpHHr8V8sOMQK3mtNABEB\n" +
109 | "AAGInwQYAQIACQUCWW4twAIbDAAKCRAJWQDOeRqK0sS0A/9g23V8A8XqJ5fVTooz\n" +
110 | "VGUy+6prTyo+SDvPkZG2LfgLOrwd02dhd+I3t2mKRPQLBRX2/vHDsG2UQ3G/2sX8\n" +
111 | "UwUOOHzJZ2tjCkKh84tGYwfGBMKkVE/ovc39+YlotRWZfL6BmuNWeCWFEH4bmrzB\n" +
112 | "6ZS6y8LKBJo4YYctasFy+2Oirg==\n" +
113 | "=zdwG\n" +
114 | "-----END PGP PUBLIC KEY BLOCK-----";
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/net/quedex/api/testcommons/Utils.java:
--------------------------------------------------------------------------------
1 | package net.quedex.api.testcommons;
2 |
3 | import com.google.common.io.Resources;
4 |
5 | import java.io.IOException;
6 | import java.math.BigDecimal;
7 | import java.util.Properties;
8 |
9 | public class Utils {
10 | private Utils() {}
11 |
12 | public static char[] getKeyPassphraseFromProps() {
13 | Properties props = new Properties();
14 | try {
15 | props.load(Resources.getResource("qdxConfig.properties").openStream());
16 | } catch (IOException e) {
17 | throw new IllegalStateException("Error reading properties file", e);
18 | }
19 |
20 | return props.getProperty("dont.do.it.in.production.privateKeyPasspharse").toCharArray();
21 | }
22 |
23 | public static BigDecimal $(String price) {
24 | return new BigDecimal(price);
25 | }
26 |
27 | public static BigDecimal $(int price) {
28 | return BigDecimal.valueOf(price);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %date [%thread] %-5level %logger - %message%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/test/resources/qdxConfig.properties.example:
--------------------------------------------------------------------------------
1 | net.quedex.client.api.marketStreamUrl = wss://api.quedex.net
2 | net.quedex.client.api.userStreamUrl = wss://api.quedex.net
3 |
4 | net.quedex.client.api.accountId = 123456789
5 | net.quedex.client.api.nonceGroup = 5
6 |
7 | net.quedex.client.api.qdxPublicKey = -----BEGIN PGP PUBLIC KEY BLOCK-----\n\
8 | Version: QPG\n\
9 | \n\
10 | mQENBFlPvjsBCACr/UfHzXAezskLqcq9NiiaNFDDT5A+biC8VrOglB0ZSQOYRira\n\
11 | NgQ2Cp8Jd+XU77F+J1012BjB5y87Z+hdnwBDsqF7CjkjeQzsE3PSvm9I+E3cneqx\n\
12 | UcinRaUD1wfwVytbg9Q9rpqQ7CTjVWY1UPYjs6dAo1WAp/ux/VTeOFbpO0R3D7if\n\
13 | ZGY1QeISRpLWiMpcG2YCOALnuazABVCNXLhVqa8Y7tt2I+cI0uE9tBf41gjGPPtd\n\
14 | KdASPVz1plpOEl2dOpmy8jICqcSzUsT4Sy8vAqW3U1HF+TA2QGRcrrUItL4GjxNL\n\
15 | lcL8wh7mclsjRe5Q5dYnrACC9NWS6vSp/eAPABEBAAG0G1F1ZWRleCA8Y29udGFj\n\
16 | dEBxdWVkZXgubmV0PokBOAQTAQIAIgUCWU++OwIbAwYLCQgHAwIGFQgCCQoLBBYC\n\
17 | AwECHgECF4AACgkQzsLQUmv6vk9Rlwf+LiJA37dhDdGFU/fexNRHZWTUh2TdqBsv\n\
18 | MiNtarf+HlZIioWMCzlHmb3bolVrfFUNUh/GGlPENtlaSmFGuPhMlFcNDGYM+I7k\n\
19 | ufhM95jxmtzy97NYMeMx4xjnaBAu8kFsvi80BR/05ZhCHqyI3K9NpYoXBfsyzss+\n\
20 | j/jX1NHayzMmXNdqQ5JjzuICZj0EY9ryLP/jPAZ6DS9LVwi9Vr2JzZheCx5Q77Ud\n\
21 | HuGTOBu3Azor2f4n4ccELs7lgU7uGrt1cK/oiML9UDmqjelunzTFU/5Q0tp7C3Qm\n\
22 | 1wymd+PYTvvX/5htnLar1nIuYmmvtCZb1zyuzPzJWWtCcFFsiV9kerkBDQRZT747\n\
23 | AQgAn/9rwQn4yM7pVYO9rTxNveI60lgp4RLKi1WYpAy6gxwUp3zLE/h59rykb7qM\n\
24 | 9QArABsMEGGKt9urq7hjsKu8bM+hVTcAuoDre5qNFEfhZuPBS9LF7wQWDikORZxR\n\
25 | Mk5WIiCt3U2soQ4Lismw1bLDX8uqkv3GFtR+IaKzuwYBEVPwuZ15EOt9G83JR3uV\n\
26 | MKqeUtFW9+z5WEAh2JLU6C357sftJIJeWDEgF2TPtQOzc8isI8rpIFNyl6x1Aiy6\n\
27 | LaSWmOI3d9EQ8SH4LxCXtAgWvnIoPL0JsP5/FWzt6qJR4teu+A2xwG7001va+DUc\n\
28 | 34AbSV9Ogqa519OfbKK6HDyFIQARAQABiQEfBBgBAgAJBQJZT747AhsMAAoJEM7C\n\
29 | 0FJr+r5PtEUH/0KmXQWbm4qXxOnaXrk+CKLDBxtfY6BaoJ6ekdGfqVMd8YM+UGnL\n\
30 | 6d49vex4O80uIhIDSex446gKVlhdwOjIlUFmTCtMgGOa06G2T4sx8a9y2UYK45hN\n\
31 | rj9aVfhJ8nn9yuPj7cBNtLEYJ4VkRKxJO9XX8cfhUsomhB3DQDbOLfikYqfmupm6\n\
32 | mYX84CO/DD8JAXx4qt9Rg+5AUQegq26iZ/og1ZjYZ/tvBjrc45u23XCWvgVHbGhb\n\
33 | wWCNjZijaY1VnTwTe6uZv1AqovZpprqZKWImN5myaJI3AJU2W2FCbI0ezfoVEVO4\n\
34 | zMipOYZzRziJeCz1gX9geNseLvfJ8EtZRKU=\n\
35 | =e4C9\n\
36 | -----END PGP PUBLIC KEY BLOCK-----
37 |
38 | net.quedex.client.api.userPrivateKey =
--------------------------------------------------------------------------------