Subversion Repositories Integrator Subversion

Rev

Go to most recent revision | Details | 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();
94
    }
95
 
96
    public void desconectar() {
97
        client.eDisconnect();
98
    }
99
 
100
    public EClientSocket getClient() {
101
        return client;
102
    }
103
 
104
    // =========================================================
105
    // Helpers gerais
106
    // =========================================================
107
 
108
    public int getNextOrderIdBlocking(Duration timeout) {
109
        Instant inicio = Instant.now();
110
        while (nextOrderId <= 0 &&
111
               Duration.between(inicio, Instant.now()).compareTo(timeout) < 0) {
112
            try {
113
                Thread.sleep(50);
114
            } catch (InterruptedException e) {
115
                Thread.currentThread().interrupt();
116
                break;
117
            }
118
        }
119
        if (nextOrderId <= 0) {
120
            throw new IllegalStateException("IBKR não retornou nextValidId dentro do timeout.");
121
        }
122
        return nextOrderId++;
123
    }
124
 
125
    // ===== Ordens =====
126
 
127
    public CompletableFuture<StatusOrdemFuturos> registrarFutureOrdem(int orderId) {
128
        CompletableFuture<StatusOrdemFuturos> future = new CompletableFuture<>();
129
        ordemFutures.put(orderId, future);
130
        return future;
131
    }
132
 
133
    // ===== Preço (tickPrice) =====
134
 
135
    public CompletableFuture<BigDecimal> registrarFuturePreco(int tickerId) {
136
        CompletableFuture<BigDecimal> future = new CompletableFuture<>();
137
        precoFutures.put(tickerId, future);
138
        return future;
139
    }
140
 
141
    // ===== Realtime bars =====
142
 
143
    public void registrarRealtimeBarListener(int reqId, RealtimeBarListener listener) {
144
        realtimeBarListeners.put(reqId, listener);
145
    }
146
 
147
    // ===== Histórico =====
148
 
149
    public CompletableFuture<List<Bar>> prepararFutureHistorico(int reqId) {
150
        CompletableFuture<List<Bar>> fut = new CompletableFuture<>();
151
        historicalFutures.put(reqId, fut);
152
        historicalBars.put(reqId, new ArrayList<>());
153
        return fut;
154
    }
155
 
156
    // =========================================================
157
    // Callbacks obrigatórios / usados
158
    // =========================================================
159
 
160
    @Override
161
    public void nextValidId(int orderId) {
162
        this.nextOrderId = orderId;
163
    }
164
 
165
    @Override
166
    public void orderStatus(int orderId,
167
                            String status,
168
                            Decimal filled,
169
                            Decimal remaining,
170
                            double avgFillPrice,
171
                            long permId,
172
                            int parentId,
173
                            double lastFillPrice,
174
                            int clientId,
175
                            String whyHeld,
176
                            double mktCapPrice) {
177
 
178
        double filledD    = (filled    != null && filled.isValid())    ? filled.value().doubleValue()    : 0.0;
179
        double remainingD = (remaining != null && remaining.isValid()) ? remaining.value().doubleValue() : 0.0;
180
 
181
        StatusOrdemFuturos s = new StatusOrdemFuturos();
182
        s.setOrderId((long) orderId);
183
        s.setStatus(status != null ? status.toUpperCase() : null);
184
        s.setExecutedQty(BigDecimal.valueOf(filledD));
185
        s.setOrigQty(BigDecimal.valueOf(filledD + remainingD));
186
        s.setAvgPrice(BigDecimal.valueOf(avgFillPrice));
187
 
188
        CompletableFuture<StatusOrdemFuturos> future = ordemFutures.get(orderId);
189
        if (future != null && !future.isDone()) {
190
            future.complete(s);
191
        }
192
    }
193
 
194
    @Override
195
    public void openOrder(int orderId, Contract contract, Order order, OrderState orderState) {
196
        StatusOrdemFuturos s = new StatusOrdemFuturos();
197
        s.setOrderId((long) orderId);
198
        s.setSymbol(contract.symbol());
199
        s.setStatus(orderState.status() != null ? orderState.status().toString().toUpperCase() : null);
200
        s.setPrice(BigDecimal.valueOf(order.lmtPrice()));
201
        // totalQuantity é Decimal no JavaClient novo
202
        s.setOrigQty(BigDecimal.valueOf(order.totalQuantity().longValue()));
203
 
204
        CompletableFuture<StatusOrdemFuturos> future = ordemFutures.get(orderId);
205
        if (future != null && !future.isDone()) {
206
            future.complete(s);
207
        }
208
    }
209
 
210
    @Override
211
    public void tickPrice(int tickerId, int field, double price, TickAttrib attribs) {
212
        if (price <= 0) return;
213
        CompletableFuture<BigDecimal> future = precoFutures.get(tickerId);
214
        if (future != null && !future.isDone()) {
215
            future.complete(BigDecimal.valueOf(price));
216
        }
217
    }
218
 
219
    // ===== Realtime bars =====
220
 
221
    @Override
222
    public void realtimeBar(int reqId,
223
                            long time,
224
                            double open,
225
                            double high,
226
                            double low,
227
                            double close,
228
                            Decimal volume,
229
                            Decimal wap,
230
                            int count) {
231
 
232
        RealtimeBarListener l = realtimeBarListeners.get(reqId);
233
        if (l != null) {
234
            long vol = (volume != null && volume.isValid()) ? volume.longValue() : 0L;
235
            double wapD = (wap != null && wap.isValid()) ? wap.value().doubleValue() : 0.0;
236
 
237
            l.onRealtimeBar(reqId, time, open, high, low, close, vol, wapD, count);
238
        }
239
    }
240
 
241
    // ===== Histórico =====
242
 
243
    @Override
244
    public void historicalData(int reqId, Bar bar) {
245
        List<Bar> lista = historicalBars.computeIfAbsent(reqId, k -> new ArrayList<>());
246
        lista.add(bar);
247
    }
248
 
249
    @Override
250
    public void historicalDataEnd(int reqId, String startDateStr, String endDateStr) {
251
        List<Bar> lista = historicalBars.get(reqId);
252
        CompletableFuture<List<Bar>> fut = historicalFutures.get(reqId);
253
        if (fut != null && lista != null && !fut.isDone()) {
254
            fut.complete(lista);
255
        }
256
    }
257
 
258
    // ===== Erros =====
259
 
260
    @Override
261
    public void error(Exception e) {
262
        e.printStackTrace();
263
    }
264
 
265
    @Override
266
    public void error(String str) {
267
        System.err.println("IBKR ERROR: " + str);
268
    }
269
 
270
    @Override
271
    public void error(int id,
272
                      long errorTime,
273
                      int errorCode,
274
                      String errorMsg,
275
                      String advancedOrderRejectJson) {
276
 
277
        // Mensagens informativas de conexão de data farm
278
        if (errorCode == 2158   // Sec-def data farm connection is OK
279
            || errorCode == 2157 // Sec-def data farm connection is broken
280
            || errorCode == 2104 // Market data farm connection is OK
281
            || errorCode == 2106 // HMDS data farm connection is OK
282
        ) {
283
            System.out.println("[IBKR INFO] time=" + errorTime +
284
                               ", id=" + id +
285
                               ", code=" + errorCode +
286
                               ", msg=" + errorMsg);
287
            return;
288
        }
289
 
290
        System.err.println("IBKR ERROR: time=" + errorTime +
291
                           ", id=" + id +
292
                           ", code=" + errorCode +
293
                           ", msg=" + errorMsg);
294
 
295
        CompletableFuture<StatusOrdemFuturos> of = ordemFutures.get(id);
296
        if (of != null && !of.isDone()) {
297
            of.completeExceptionally(
298
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
299
        }
300
 
301
        CompletableFuture<BigDecimal> pf = precoFutures.get(id);
302
        if (pf != null && !pf.isDone()) {
303
            pf.completeExceptionally(
304
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
305
        }
306
 
307
        CompletableFuture<List<Bar>> hf = historicalFutures.get(id);
308
        if (hf != null && !hf.isDone()) {
309
            hf.completeExceptionally(
310
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
311
        }
312
    }
313
 
314
 
315
    // Execuções (se quiser aproveitar depois)
316
    @Override
317
    public void execDetails(int reqId, Contract contract, Execution execution) {
318
        // opcional: logar execuções
319
    }
320
}