Subversion Repositories Integrator Subversion

Rev

Rev 795 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
795 blopes 1
package br.com.kronus.ibkr.api;
2
 
3
import java.math.BigDecimal;
4
import java.time.Duration;
5
import java.time.Instant;
6
import java.util.ArrayList;
7
import java.util.List;
8
import java.util.Map;
9
import java.util.concurrent.CompletableFuture;
10
import java.util.concurrent.ConcurrentHashMap;
11
 
12
import com.ib.client.Bar;
13
import com.ib.client.Contract;
14
import com.ib.client.Decimal;
15
import com.ib.client.DefaultEWrapper;
16
import com.ib.client.EClientSocket;
17
import com.ib.client.EJavaSignal;
18
import com.ib.client.EReader;
19
import com.ib.client.Execution;
20
import com.ib.client.Order;
21
import com.ib.client.OrderState;
22
import com.ib.client.TickAttrib;
23
 
24
import br.com.kronus.core.StatusOrdemFuturos;
25
 
26
/**
27
 * Cliente IBKR alinhado com a API nova (JavaClient),
28
 * centralizando:
29
 *  - próximo orderId;
30
 *  - futures de status de ordens;
31
 *  - futures de preços (tickPrice);
32
 *  - listeners de realtimeBar;
33
 *  - futures de histórico (historicalData).
34
 */
35
public class IbkrClient extends DefaultEWrapper {
36
 
37
    private final EJavaSignal signal = new EJavaSignal();
38
    private final EClientSocket client = new EClientSocket(this, signal);
39
 
40
    // ===== Ordens =====
41
    private final Map<Integer, CompletableFuture<StatusOrdemFuturos>> ordemFutures =
42
            new ConcurrentHashMap<>();
43
 
44
    // ===== Preço "snapshot" via tickPrice =====
45
    private final Map<Integer, CompletableFuture<BigDecimal>> precoFutures =
46
            new ConcurrentHashMap<>();
47
 
48
    // ===== Realtime bars =====
49
    public interface RealtimeBarListener {
50
        void onRealtimeBar(int reqId,
51
                           long time,
52
                           double open,
53
                           double high,
54
                           double low,
55
                           double close,
56
                           long volume,
57
                           double wap,
58
                           int count);
59
    }
60
 
61
    private final Map<Integer, RealtimeBarListener> realtimeBarListeners =
62
            new ConcurrentHashMap<>();
63
 
64
    // ===== Histórico =====
65
    private final Map<Integer, List<Bar>> historicalBars =
66
            new ConcurrentHashMap<>();
67
    private final Map<Integer, CompletableFuture<List<Bar>>> historicalFutures =
68
            new ConcurrentHashMap<>();
69
 
70
    private volatile int nextOrderId = -1;
71
 
72
    // =========================================================
73
    // Conexão
74
    // =========================================================
75
 
76
    public void conectar(String host, int port, int clientId) {
77
        client.eConnect(host, port, clientId);
78
 
79
        EReader reader = new EReader(client, signal);
80
        reader.start();
81
 
82
        Thread t = new Thread(() -> {
83
            while (client.isConnected()) {
84
                signal.waitForSignal();
85
                try {
86
                    reader.processMsgs();
87
                } catch (Exception e) {
88
                    e.printStackTrace();
89
                }
90
            }
91
        }, "IbkrClient-Reader");
92
        t.setDaemon(true);
93
        t.start();
796 blopes 94
 
95
        client.reqMarketDataType(1);
795 blopes 96
    }
97
 
98
    public void desconectar() {
99
        client.eDisconnect();
100
    }
101
 
102
    public EClientSocket getClient() {
103
        return client;
104
    }
105
 
106
    // =========================================================
107
    // Helpers gerais
108
    // =========================================================
109
 
110
    public int getNextOrderIdBlocking(Duration timeout) {
111
        Instant inicio = Instant.now();
112
        while (nextOrderId <= 0 &&
113
               Duration.between(inicio, Instant.now()).compareTo(timeout) < 0) {
114
            try {
115
                Thread.sleep(50);
116
            } catch (InterruptedException e) {
117
                Thread.currentThread().interrupt();
118
                break;
119
            }
120
        }
121
        if (nextOrderId <= 0) {
122
            throw new IllegalStateException("IBKR não retornou nextValidId dentro do timeout.");
123
        }
124
        return nextOrderId++;
125
    }
126
 
127
    // ===== Ordens =====
128
 
129
    public CompletableFuture<StatusOrdemFuturos> registrarFutureOrdem(int orderId) {
130
        CompletableFuture<StatusOrdemFuturos> future = new CompletableFuture<>();
131
        ordemFutures.put(orderId, future);
132
        return future;
133
    }
134
 
135
    // ===== Preço (tickPrice) =====
136
 
137
    public CompletableFuture<BigDecimal> registrarFuturePreco(int tickerId) {
138
        CompletableFuture<BigDecimal> future = new CompletableFuture<>();
139
        precoFutures.put(tickerId, future);
140
        return future;
141
    }
142
 
143
    // ===== Realtime bars =====
144
 
145
    public void registrarRealtimeBarListener(int reqId, RealtimeBarListener listener) {
146
        realtimeBarListeners.put(reqId, listener);
147
    }
148
 
149
    // ===== Histórico =====
150
 
151
    public CompletableFuture<List<Bar>> prepararFutureHistorico(int reqId) {
152
        CompletableFuture<List<Bar>> fut = new CompletableFuture<>();
153
        historicalFutures.put(reqId, fut);
154
        historicalBars.put(reqId, new ArrayList<>());
155
        return fut;
156
    }
157
 
158
    // =========================================================
159
    // Callbacks obrigatórios / usados
160
    // =========================================================
161
 
162
    @Override
163
    public void nextValidId(int orderId) {
164
        this.nextOrderId = orderId;
165
    }
166
 
167
    @Override
168
    public void orderStatus(int orderId,
169
                            String status,
170
                            Decimal filled,
171
                            Decimal remaining,
172
                            double avgFillPrice,
173
                            long permId,
174
                            int parentId,
175
                            double lastFillPrice,
176
                            int clientId,
177
                            String whyHeld,
178
                            double mktCapPrice) {
179
 
180
        double filledD    = (filled    != null && filled.isValid())    ? filled.value().doubleValue()    : 0.0;
181
        double remainingD = (remaining != null && remaining.isValid()) ? remaining.value().doubleValue() : 0.0;
182
 
183
        StatusOrdemFuturos s = new StatusOrdemFuturos();
184
        s.setOrderId((long) orderId);
185
        s.setStatus(status != null ? status.toUpperCase() : null);
186
        s.setExecutedQty(BigDecimal.valueOf(filledD));
187
        s.setOrigQty(BigDecimal.valueOf(filledD + remainingD));
188
        s.setAvgPrice(BigDecimal.valueOf(avgFillPrice));
189
 
190
        CompletableFuture<StatusOrdemFuturos> future = ordemFutures.get(orderId);
191
        if (future != null && !future.isDone()) {
192
            future.complete(s);
193
        }
194
    }
195
 
196
    @Override
197
    public void openOrder(int orderId, Contract contract, Order order, OrderState orderState) {
198
        StatusOrdemFuturos s = new StatusOrdemFuturos();
199
        s.setOrderId((long) orderId);
200
        s.setSymbol(contract.symbol());
201
        s.setStatus(orderState.status() != null ? orderState.status().toString().toUpperCase() : null);
202
        s.setPrice(BigDecimal.valueOf(order.lmtPrice()));
203
        // totalQuantity é Decimal no JavaClient novo
204
        s.setOrigQty(BigDecimal.valueOf(order.totalQuantity().longValue()));
205
 
206
        CompletableFuture<StatusOrdemFuturos> future = ordemFutures.get(orderId);
207
        if (future != null && !future.isDone()) {
208
            future.complete(s);
209
        }
210
    }
211
 
212
    @Override
213
    public void tickPrice(int tickerId, int field, double price, TickAttrib attribs) {
214
        if (price <= 0) return;
215
        CompletableFuture<BigDecimal> future = precoFutures.get(tickerId);
216
        if (future != null && !future.isDone()) {
217
            future.complete(BigDecimal.valueOf(price));
218
        }
219
    }
220
 
221
    // ===== Realtime bars =====
222
 
223
    @Override
224
    public void realtimeBar(int reqId,
225
                            long time,
226
                            double open,
227
                            double high,
228
                            double low,
229
                            double close,
230
                            Decimal volume,
231
                            Decimal wap,
232
                            int count) {
233
 
234
        RealtimeBarListener l = realtimeBarListeners.get(reqId);
235
        if (l != null) {
236
            long vol = (volume != null && volume.isValid()) ? volume.longValue() : 0L;
237
            double wapD = (wap != null && wap.isValid()) ? wap.value().doubleValue() : 0.0;
238
 
239
            l.onRealtimeBar(reqId, time, open, high, low, close, vol, wapD, count);
240
        }
241
    }
242
 
243
    // ===== Histórico =====
244
 
245
    @Override
246
    public void historicalData(int reqId, Bar bar) {
247
        List<Bar> lista = historicalBars.computeIfAbsent(reqId, k -> new ArrayList<>());
248
        lista.add(bar);
249
    }
250
 
251
    @Override
252
    public void historicalDataEnd(int reqId, String startDateStr, String endDateStr) {
253
        List<Bar> lista = historicalBars.get(reqId);
254
        CompletableFuture<List<Bar>> fut = historicalFutures.get(reqId);
255
        if (fut != null && lista != null && !fut.isDone()) {
256
            fut.complete(lista);
257
        }
258
    }
259
 
260
    // ===== Erros =====
261
 
262
    @Override
263
    public void error(Exception e) {
264
        e.printStackTrace();
265
    }
266
 
267
    @Override
268
    public void error(String str) {
269
        System.err.println("IBKR ERROR: " + str);
270
    }
271
 
272
    @Override
273
    public void error(int id,
274
                      long errorTime,
275
                      int errorCode,
276
                      String errorMsg,
277
                      String advancedOrderRejectJson) {
278
 
279
        // Mensagens informativas de conexão de data farm
280
        if (errorCode == 2158   // Sec-def data farm connection is OK
281
            || errorCode == 2157 // Sec-def data farm connection is broken
282
            || errorCode == 2104 // Market data farm connection is OK
283
            || errorCode == 2106 // HMDS data farm connection is OK
284
        ) {
285
            System.out.println("[IBKR INFO] time=" + errorTime +
286
                               ", id=" + id +
287
                               ", code=" + errorCode +
288
                               ", msg=" + errorMsg);
289
            return;
290
        }
291
 
292
        System.err.println("IBKR ERROR: time=" + errorTime +
293
                           ", id=" + id +
294
                           ", code=" + errorCode +
295
                           ", msg=" + errorMsg);
296
 
297
        CompletableFuture<StatusOrdemFuturos> of = ordemFutures.get(id);
298
        if (of != null && !of.isDone()) {
299
            of.completeExceptionally(
300
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
301
        }
302
 
303
        CompletableFuture<BigDecimal> pf = precoFutures.get(id);
304
        if (pf != null && !pf.isDone()) {
305
            pf.completeExceptionally(
306
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
307
        }
308
 
309
        CompletableFuture<List<Bar>> hf = historicalFutures.get(id);
310
        if (hf != null && !hf.isDone()) {
311
            hf.completeExceptionally(
312
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
313
        }
314
    }
315
 
316
 
317
    // Execuções (se quiser aproveitar depois)
318
    @Override
319
    public void execDetails(int reqId, Contract contract, Execution execution) {
320
        // opcional: logar execuções
321
    }
322
}