forked from uniVocity/univocity-trader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
BinanceExchange.java
219 lines (184 loc) · 7.55 KB
/
BinanceExchange.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package com.univocity.trader.exchange.binance;
import com.univocity.trader.*;
import com.univocity.trader.candles.*;
import com.univocity.trader.exchange.binance.api.client.*;
import com.univocity.trader.exchange.binance.api.client.domain.event.*;
import com.univocity.trader.exchange.binance.api.client.domain.general.*;
import com.univocity.trader.exchange.binance.api.client.domain.market.*;
import com.univocity.trader.indicators.base.*;
import com.univocity.trader.utils.*;
import io.netty.channel.*;
import io.netty.channel.nio.*;
import org.asynchttpclient.*;
import org.slf4j.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;
import static com.univocity.trader.exchange.binance.api.client.domain.general.FilterType.*;
class BinanceExchange implements Exchange<Candlestick, Account> {
private static final Logger log = LoggerFactory.getLogger(BinanceExchange.class);
private BinanceApiWebSocketClient socketClient;
private org.asynchttpclient.ws.WebSocket socketClientCloseable;
private BinanceApiRestClient restClient;
private final Map<String, SymbolInformation> symbolInformation = new ConcurrentHashMap<>();
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);
private final AsyncHttpClient asyncHttpClient = HttpUtils.newAsyncHttpClient(eventLoopGroup, 65536);
private String listenKey;
private Timer timer;
private BinanceClientAccount binanceClientAccount;
private char[] apiSecret;
private String apiKey;
private final double[] NO_PRICE = new double[]{-1.0};
private boolean isTestNet = false;
@Override
public BinanceClientAccount connectToAccount(Account clientConfiguration) {
this.apiKey = clientConfiguration.apiKey();
this.apiSecret = clientConfiguration.secret();
this.isTestNet = clientConfiguration.isTestNet();
this.binanceClientAccount = new BinanceClientAccount(clientConfiguration.apiKey(), new String(clientConfiguration.secret()), this);
return this.binanceClientAccount;
}
@Override
public Candlestick getLatestTick(String symbol, TimeInterval interval) {
List<Candlestick> candles = restClient().getCandlestickBars(symbol, CandlestickInterval.fromTimeInterval(interval), 1, null, null);
if (candles != null && candles.size() > 0) {
return candles.get(0);
}
return null;
}
@Override
public IncomingCandles<Candlestick> getLatestTicks(String symbol, TimeInterval interval) {
try {
return IncomingCandles.fromCollection(restClient().getCandlestickBars(symbol, CandlestickInterval.fromTimeInterval(interval)));
} catch (Exception e) {
throw new IllegalStateException("Error returnning latest ticks of " + symbol, e);
}
}
@Override
public IncomingCandles<Candlestick> getHistoricalTicks(String symbol, TimeInterval interval, long startTime, long endTime) {
return IncomingCandles.fromCollection(restClient().getCandlestickBars(symbol, CandlestickInterval.fromTimeInterval(interval), 1000, startTime, endTime));
}
@Override
public PreciseCandle generatePreciseCandle(Candlestick exchangeCandle) {
return new PreciseCandle(
exchangeCandle.getOpenTime(),
exchangeCandle.getCloseTime(),
new BigDecimal(exchangeCandle.getOpen()),
new BigDecimal(exchangeCandle.getHigh()),
new BigDecimal(exchangeCandle.getLow()),
new BigDecimal(exchangeCandle.getClose()),
new BigDecimal(exchangeCandle.getVolume())
);
}
@Override
public void startKeepAlive(){
new KeepAliveUserDataStream(restClient()).start();
}
@Override
public void openLiveStream(String symbols, TimeInterval tickInterval, TickConsumer<Candlestick> consumer) {
CandlestickInterval interval = CandlestickInterval.fromTimeInterval(tickInterval);
log.info("Opening Binance {} live stream for: {}", tickInterval, symbols);
socketClientCloseable = socketClient().onCandlestickEvent(symbols, interval, new BinanceApiCallback<>() {
@Override
public void onResponse(CandlestickEvent response) {
try {
priceReceived(response.getSymbol(), Double.parseDouble(response.getClose()));
} catch (Exception e){
log.warn("Error updating latest price of " + response.getSymbol(), e);
}
consumer.tickReceived(response.getSymbol(), response);
}
public void onFailure(Throwable cause) {
consumer.streamError(cause);
}
public void onClose() {
consumer.streamClosed();
}
});
}
@Override
public void closeLiveStream() {
if (socketClientCloseable != null) {
socketClientCloseable.sendCloseFrame();
socketClientCloseable = null;
}
}
private final Map<String, double[]> latestPrices = new HashMap<>();
private void priceReceived(String symbol, double price){
latestPrices.compute(symbol, (s, v) -> {
if (v == null) {
return new double[]{price};
} else {
v[0] = price;
return v;
}
});
}
@Override
public Map<String, double[]> getLatestPrices() {
try {
List<TickerPrice> allPrices = restClient().getAllPrices();
allPrices.forEach(ticker -> priceReceived(ticker.getSymbol(), ticker.getPriceAmount()));
} catch (Exception e){
log.warn("Unable to load latest prices from Binance", e);
}
return Collections.unmodifiableMap(latestPrices);
}
@Override
public double getLatestPrice(String assetSymbol, String fundSymbol) {
double price = latestPrices.getOrDefault(assetSymbol, NO_PRICE)[0];
try {
price = Double.parseDouble(restClient().getPrice(assetSymbol + fundSymbol).getPrice());
priceReceived(assetSymbol + fundSymbol, price);
} catch (Exception e) {
log.error("Error getting latest price of " + assetSymbol + fundSymbol, e);
}
return price;
}
@Override
public Map<String, SymbolInformation> getSymbolInformation() {
if (symbolInformation.isEmpty()) {
Map<String, SymbolInfo> symbols = restClient().getExchangeInfo().getSymbols().stream().collect(Collectors.toMap(SymbolInfo::getSymbol, s -> s));
symbols.forEach((symbol, symbolInfo) -> {
SymbolFilter lotSize = symbolInfo.getSymbolFilter(FilterType.LOT_SIZE);
String step = lotSize.getStepSize(); //comes as: 0.01000000
int quantityDecimalPlaces = step.indexOf('1') - 1;
SymbolFilter tickSize = symbolInfo.getSymbolFilter(FilterType.PRICE_FILTER);
String tickStep = tickSize.getTickSize(); //comes as: 0.01000000
int priceDecimalPlaces = tickStep.indexOf('1') - 1;
BigDecimal stepSize = new BigDecimal(step);
SymbolFilter notional = symbolInfo.getSymbolFilter(MIN_NOTIONAL);
BigDecimal minOrderAmount = new BigDecimal(notional.getMinNotional());
SymbolInformation out = new SymbolInformation(symbol);
out.quantityDecimalPlaces(quantityDecimalPlaces);
out.priceDecimalPlaces(priceDecimalPlaces);
out.minimumAssetsPerOrder(minOrderAmount);
symbolInformation.put(symbol, out);
});
}
return symbolInformation;
}
private BinanceApiWebSocketClient socketClient() {
if (socketClient == null) {
BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance(apiKey, apiSecret == null ? null : new String(apiSecret), asyncHttpClient, isTestNet);
socketClient = factory.newWebSocketClient();
}
return socketClient;
}
private BinanceApiRestClient restClient() {
if (restClient == null) {
BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance(apiKey, apiSecret == null ? null : new String(apiSecret), asyncHttpClient, isTestNet);
restClient = factory.newRestClient();
}
return restClient;
}
@Override
public int historicalCandleCountLimit() {
return 1000;
}
// @Override
// public boolean isDirectSwitchSupported(String currentAssetSymbol, String targetAssetSymbol) {
// return symbolInformation.containsKey(currentAssetSymbol + targetAssetSymbol);
// }
}