Skip to content

Commit e63f1a2

Browse files
authored
Merge pull request #5003 from bigscoop/bitmex-streaming
[bitmex-streaming] Extend functionality
2 parents 98a3ec0 + e545aae commit e63f1a2

38 files changed

+1809
-372
lines changed

xchange-bitmex/README.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## Using IntelliJ Idea HTTP client
2+
3+
There are *.http files stored in `src/test/resources/rest` that can be used with IntelliJ Idea HTTP Client.
4+
5+
Some requests need authorization, so the api credentials have to be stored in `http-client.private.env.json` in module's root. Sample content can be found in `example.http-client.private.env.json`
6+
7+
> [!CAUTION]
8+
> Never commit your api credentials to the repository!
9+
10+
11+
[HTTP Client documentation](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html)
12+
13+
## Running integration tests that require API keys
14+
15+
Integration tests that require API keys read them from environment variables. They can be defined in `integration-test.env.properties`. Sample content can be found in `example.integration-test.env.properties`.
16+
17+
If no keys are provided the integration tests that need them are skipped.
18+
19+
> [!CAUTION]
20+
> Never commit your api credentials to the repository!
21+

xchange-bitmex/src/main/java/org/knowm/xchange/bitmex/BitmexAdapters.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
@UtilityClass
6262
public class BitmexAdapters {
6363

64-
private final Map<String, Instrument> SYMBOL_TO_INSTRUMENT = new HashMap<>();
64+
public final Map<String, Instrument> SYMBOL_TO_INSTRUMENT = new HashMap<>();
6565
private final Map<Instrument, String> INSTRUMENT_TO_SYMBOL = new HashMap<>();
6666

6767
private final Map<Currency, String> CURRENCY_TO_BITMEX_CODE = new HashMap<>();

xchange-stream-bitmex/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# private properties for idea rest client
2+
http-client.private.env.json
3+
4+
# private properties for integration tests
5+
integration-test.env.properties

xchange-stream-bitmex/README.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## Using IntelliJ Idea HTTP client
2+
3+
There are *.http files stored in `src/test/resources/rest` that can be used with IntelliJ Idea HTTP Client.
4+
5+
Some requests need authorization, so the api credentials have to be stored in `http-client.private.env.json` in module's root. Sample content can be found in `example.http-client.private.env.json`
6+
7+
> [!CAUTION]
8+
> Never commit your api credentials to the repository!
9+
10+
11+
[HTTP Client documentation](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html)
12+
13+
## Running integration tests that require API keys
14+
15+
Integration tests that require API keys read them from environment variables. They can be defined in `integration-test.env.properties`. Sample content can be found in `example.integration-test.env.properties`.
16+
17+
If no keys are provided the integration tests that need them are skipped.
18+
19+
> [!CAUTION]
20+
> Never commit your api credentials to the repository!
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"default": {
3+
"api_key": "change_me",
4+
"api_secret": "change_me",
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
apiKey=change_me
2+
secretKey=change_me
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"default": {
3+
"base_url": "wss://ws.bitmex.com"
4+
}
5+
}

xchange-stream-bitmex/lombok.config

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lombok.equalsAndHashCode.callSuper = call
2+
lombok.tostring.callsuper = call

xchange-stream-bitmex/pom.xml

+31
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
<name>XChange Bitmex Stream</name>
1414

1515
<dependencies>
16+
17+
<dependency>
18+
<groupId>org.junit.jupiter</groupId>
19+
<artifactId>junit-jupiter-engine</artifactId>
20+
<scope>test</scope>
21+
</dependency>
22+
1623
<dependency>
1724
<groupId>org.knowm.xchange</groupId>
1825
<artifactId>xchange-bitmex</artifactId>
@@ -28,5 +35,29 @@
2835
<artifactId>xchange-stream-service-netty</artifactId>
2936
<version>${project.parent.version}</version>
3037
</dependency>
38+
39+
<dependency>
40+
<groupId>org.mockito</groupId>
41+
<artifactId>mockito-junit-jupiter</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
3145
</dependencies>
46+
47+
<build>
48+
49+
<pluginManagement>
50+
<plugins>
51+
<plugin>
52+
<groupId>org.apache.maven.plugins</groupId>
53+
<artifactId>maven-failsafe-plugin</artifactId>
54+
<configuration>
55+
<systemPropertiesFile>integration-test.env.properties</systemPropertiesFile>
56+
</configuration>
57+
</plugin>
58+
</plugins>
59+
</pluginManagement>
60+
61+
</build>
62+
3263
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package info.bitrich.xchangestream.bitmex;
2+
3+
import info.bitrich.xchangestream.bitmex.dto.BitmexOrder;
4+
import info.bitrich.xchangestream.bitmex.dto.BitmexPrivateExecution;
5+
import info.bitrich.xchangestream.bitmex.dto.BitmexTicker;
6+
import info.bitrich.xchangestream.bitmex.dto.BitmexTrade;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import lombok.experimental.UtilityClass;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.knowm.xchange.bitmex.BitmexAdapters;
12+
import org.knowm.xchange.currency.CurrencyPair;
13+
import org.knowm.xchange.dto.Order;
14+
import org.knowm.xchange.dto.Order.OrderType;
15+
import org.knowm.xchange.dto.marketdata.Ticker;
16+
import org.knowm.xchange.dto.marketdata.Trade;
17+
import org.knowm.xchange.dto.trade.LimitOrder;
18+
import org.knowm.xchange.dto.trade.MarketOrder;
19+
import org.knowm.xchange.dto.trade.StopOrder;
20+
import org.knowm.xchange.dto.trade.UserTrade;
21+
22+
@Slf4j
23+
@UtilityClass
24+
public class BitmexStreamingAdapters {
25+
26+
public final Map<String, CurrencyPair> SYMBOL_TO_CURRENCY_PAIR = new HashMap<>();
27+
private final Map<CurrencyPair, String> CURRENCY_PAIR_TO_SYMBOL = new HashMap<>();
28+
29+
public void putSymbolMapping(String symbol, CurrencyPair instrument) {
30+
SYMBOL_TO_CURRENCY_PAIR.put(symbol, instrument);
31+
CURRENCY_PAIR_TO_SYMBOL.put(instrument, symbol);
32+
}
33+
34+
public String toString(CurrencyPair instrument) {
35+
if (instrument == null) {
36+
return null;
37+
}
38+
return CURRENCY_PAIR_TO_SYMBOL.get(instrument);
39+
}
40+
41+
public CurrencyPair toCurrencyPair(String bitmexSymbol) {
42+
if (bitmexSymbol == null) {
43+
return null;
44+
}
45+
return SYMBOL_TO_CURRENCY_PAIR.get(bitmexSymbol);
46+
}
47+
48+
49+
public Ticker toTicker(BitmexTicker bitmexTicker) {
50+
return new Ticker.Builder()
51+
.ask(bitmexTicker.getAskPrice())
52+
.askSize(bitmexTicker.getAskSize())
53+
.bid(bitmexTicker.getBidPrice())
54+
.bidSize(bitmexTicker.getBidSize())
55+
.timestamp(BitmexAdapters.toDate(bitmexTicker.getTimestamp()))
56+
.instrument(bitmexTicker.getCurrencyPair())
57+
.build();
58+
}
59+
60+
public Trade toTrade(BitmexTrade bitmexTrade) {
61+
return new Trade.Builder()
62+
.type(bitmexTrade.getSide())
63+
.originalAmount(bitmexTrade.getAssetAmount())
64+
.instrument(bitmexTrade.getCurrencyPair())
65+
.price(bitmexTrade.getPrice())
66+
.timestamp(BitmexAdapters.toDate(bitmexTrade.getTimestamp()))
67+
.id(bitmexTrade.getId())
68+
.build();
69+
}
70+
71+
public UserTrade toUserTrade(BitmexPrivateExecution exec) {
72+
OrderType orderType = exec.getOrderType();
73+
if (orderType == null) {
74+
return null;
75+
}
76+
77+
return UserTrade.builder()
78+
.id(exec.getExecutionId())
79+
.orderId(exec.getOrderId())
80+
.instrument(exec.getInstrument())
81+
.originalAmount(exec.getExecutedQuantity())
82+
.price(exec.getLastPrice())
83+
.feeAmount(exec.getFeeAmount())
84+
.feeCurrency(exec.getFeeCurrency())
85+
.timestamp(BitmexAdapters.toDate(exec.getUpdatedAt()))
86+
.type(orderType)
87+
.orderUserReference(exec.getClientOid())
88+
.build();
89+
}
90+
91+
public Order toOrder(BitmexOrder bitmexOrder) {
92+
Order.Builder builder;
93+
switch (bitmexOrder.getBitmexOrderType()) {
94+
case STOP:
95+
builder = new StopOrder.Builder(bitmexOrder.getOrderType(), bitmexOrder.getInstrument())
96+
.stopPrice(bitmexOrder.getOriginalPrice());
97+
break;
98+
case MARKET:
99+
builder = new MarketOrder.Builder(bitmexOrder.getOrderType(), bitmexOrder.getInstrument());
100+
break;
101+
case LIMIT:
102+
builder = new LimitOrder.Builder(bitmexOrder.getOrderType(), bitmexOrder.getInstrument()).limitPrice(bitmexOrder.getOriginalPrice());
103+
break;
104+
case STOP_LIMIT:
105+
builder = new StopOrder.Builder(bitmexOrder.getOrderType(), bitmexOrder.getInstrument())
106+
.limitPrice(bitmexOrder.getOriginalPrice())
107+
.stopPrice(bitmexOrder.getStopPrice());
108+
break;
109+
case PEGGED:
110+
case MARKET_IF_TOUCHED:
111+
case LIMIT_IF_TOUCHED:
112+
case MARKET_WITH_LEFT_OVER_AS_LIMIT:
113+
log.warn("Unknown order type: {}", bitmexOrder.getOrderType());
114+
return null;
115+
default:
116+
throw new IllegalArgumentException("Can't map " + bitmexOrder.getOrderType());
117+
}
118+
119+
return builder
120+
.originalAmount(bitmexOrder.getOriginalAmount())
121+
.id(bitmexOrder.getOrderId())
122+
.timestamp(BitmexAdapters.toDate(bitmexOrder.getUpdatedAt()))
123+
.cumulativeAmount(bitmexOrder.getCumulativeAmount())
124+
.averagePrice(bitmexOrder.getAveragePrice())
125+
.orderStatus(BitmexAdapters.toOrderStatus(bitmexOrder.getOrderStatus()))
126+
.userReference(bitmexOrder.getText())
127+
.build();
128+
}
129+
130+
}

xchange-stream-bitmex/src/main/java/info/bitrich/xchangestream/bitmex/BitmexStreamingExchange.java

+20-20
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,31 @@
22

33
import info.bitrich.xchangestream.core.ProductSubscription;
44
import info.bitrich.xchangestream.core.StreamingExchange;
5-
import info.bitrich.xchangestream.core.StreamingMarketDataService;
65
import info.bitrich.xchangestream.service.netty.ConnectionStateModel.State;
76
import io.reactivex.rxjava3.core.Completable;
87
import io.reactivex.rxjava3.core.Observable;
8+
import lombok.Getter;
9+
import org.apache.commons.lang3.StringUtils;
910
import org.knowm.xchange.ExchangeSpecification;
11+
import org.knowm.xchange.bitmex.BitmexAdapters;
1012
import org.knowm.xchange.bitmex.BitmexExchange;
13+
import org.knowm.xchange.currency.CurrencyPair;
1114

12-
/** Created by Lukas Zaoralek on 12.11.17. */
15+
@Getter
1316
public class BitmexStreamingExchange extends BitmexExchange implements StreamingExchange {
1417
private static final String API_URI = "wss://www.bitmex.com/realtime";
1518
private static final String TESTNET_API_URI = "wss://testnet.bitmex.com/realtime";
1619

1720
private BitmexStreamingService streamingService;
1821
private BitmexStreamingMarketDataService streamingMarketDataService;
19-
20-
public BitmexStreamingExchange() {}
22+
private BitmexStreamingTradeService streamingTradeService;
2123

2224
@Override
2325
protected void initServices() {
2426
super.initServices();
2527
streamingService = createStreamingService();
26-
streamingMarketDataService = new BitmexStreamingMarketDataService(streamingService, this);
28+
streamingMarketDataService = new BitmexStreamingMarketDataService(streamingService);
29+
streamingTradeService = new BitmexStreamingTradeService(streamingService);
2730
}
2831

2932
@Override
@@ -46,18 +49,6 @@ public Completable disconnect() {
4649
return streamingService.disconnect();
4750
}
4851

49-
@Override
50-
public ExchangeSpecification getDefaultExchangeSpecification() {
51-
ExchangeSpecification spec = super.getDefaultExchangeSpecification();
52-
spec.setShouldLoadRemoteMetaData(false);
53-
return spec;
54-
}
55-
56-
@Override
57-
public StreamingMarketDataService getStreamingMarketDataService() {
58-
return streamingMarketDataService;
59-
}
60-
6152
@Override
6253
public Observable<Throwable> reconnectFailure() {
6354
return streamingService.subscribeReconnectFailure();
@@ -91,12 +82,21 @@ public Observable<Long> messageDelay() {
9182
});
9283
}
9384

85+
@Override
86+
public void remoteInit() {
87+
super.remoteInit();
88+
89+
// adapt spot mappings by removing '_' symbol
90+
BitmexAdapters.SYMBOL_TO_INSTRUMENT.entrySet().stream()
91+
.filter(entry -> entry.getValue() instanceof CurrencyPair)
92+
.forEach(entry -> BitmexStreamingAdapters.putSymbolMapping(
93+
StringUtils.remove(entry.getKey(), "_"),
94+
(CurrencyPair) entry.getValue()));
95+
}
96+
9497
@Override
9598
public void resubscribeChannels() {
9699
streamingService.resubscribeChannels();
97100
}
98101

99-
public BitmexStreamingService getStreamingService() {
100-
return streamingService;
101-
}
102102
}

0 commit comments

Comments
 (0)