Subversion Repositories Integrator Subversion

Rev

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

package br.com.kronus.ibkr.api;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

import com.ib.client.Bar;
import com.ib.client.Contract;
import com.ib.client.Decimal;
import com.ib.client.DefaultEWrapper;
import com.ib.client.EClientSocket;
import com.ib.client.EJavaSignal;
import com.ib.client.EReader;
import com.ib.client.Execution;
import com.ib.client.Order;
import com.ib.client.OrderState;
import com.ib.client.TickAttrib;

import br.com.kronus.core.StatusOrdemFuturos;

/**
 * Cliente IBKR alinhado com a API nova (JavaClient),
 * centralizando:
 *  - próximo orderId;
 *  - futures de status de ordens;
 *  - futures de preços (tickPrice);
 *  - listeners de realtimeBar;
 *  - futures de histórico (historicalData).
 */

public class IbkrClient extends DefaultEWrapper {

    private final EJavaSignal signal = new EJavaSignal();
    private final EClientSocket client = new EClientSocket(this, signal);

    // ===== Ordens =====
    private final Map<Integer, CompletableFuture<StatusOrdemFuturos>> ordemFutures =
            new ConcurrentHashMap<>();

    // ===== Preço "snapshot" via tickPrice =====
    private final Map<Integer, CompletableFuture<BigDecimal>> precoFutures =
            new ConcurrentHashMap<>();

    // ===== Realtime bars =====
    public interface RealtimeBarListener {
        void onRealtimeBar(int reqId,
                           long time,
                           double open,
                           double high,
                           double low,
                           double close,
                           long volume,
                           double wap,
                           int count);
    }

    private final Map<Integer, RealtimeBarListener> realtimeBarListeners =
            new ConcurrentHashMap<>();

    // ===== Histórico =====
    private final Map<Integer, List<Bar>> historicalBars =
            new ConcurrentHashMap<>();
    private final Map<Integer, CompletableFuture<List<Bar>>> historicalFutures =
            new ConcurrentHashMap<>();

    private volatile int nextOrderId = -1;

    // =========================================================
    // Conexão
    // =========================================================

    public void conectar(String host, int port, int clientId) {
        client.eConnect(host, port, clientId);

        EReader reader = new EReader(client, signal);
        reader.start();

        Thread t = new Thread(() -> {
            while (client.isConnected()) {
                signal.waitForSignal();
                try {
                    reader.processMsgs();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "IbkrClient-Reader");
        t.setDaemon(true);
        t.start();
       
        client.reqMarketDataType(1);
    }

    public void desconectar() {
        client.eDisconnect();
    }

    public EClientSocket getClient() {
        return client;
    }

    // =========================================================
    // Helpers gerais
    // =========================================================

    public int getNextOrderIdBlocking(Duration timeout) {
        Instant inicio = Instant.now();
        while (nextOrderId <= 0 &&
               Duration.between(inicio, Instant.now()).compareTo(timeout) < 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        if (nextOrderId <= 0) {
            throw new IllegalStateException("IBKR não retornou nextValidId dentro do timeout.");
        }
        return nextOrderId++;
    }

    // ===== Ordens =====

    public CompletableFuture<StatusOrdemFuturos> registrarFutureOrdem(int orderId) {
        CompletableFuture<StatusOrdemFuturos> future = new CompletableFuture<>();
        ordemFutures.put(orderId, future);
        return future;
    }

    // ===== Preço (tickPrice) =====

    public CompletableFuture<BigDecimal> registrarFuturePreco(int tickerId) {
        CompletableFuture<BigDecimal> future = new CompletableFuture<>();
        precoFutures.put(tickerId, future);
        return future;
    }

    // ===== Realtime bars =====

    public void registrarRealtimeBarListener(int reqId, RealtimeBarListener listener) {
        realtimeBarListeners.put(reqId, listener);
    }

    // ===== Histórico =====

    public CompletableFuture<List<Bar>> prepararFutureHistorico(int reqId) {
        CompletableFuture<List<Bar>> fut = new CompletableFuture<>();
        historicalFutures.put(reqId, fut);
        historicalBars.put(reqId, new ArrayList<>());
        return fut;
    }

    // =========================================================
    // Callbacks obrigatórios / usados
    // =========================================================

    @Override
    public void nextValidId(int orderId) {
        this.nextOrderId = orderId;
    }

    @Override
    public void orderStatus(int orderId,
                            String status,
                            Decimal filled,
                            Decimal remaining,
                            double avgFillPrice,
                            long permId,
                            int parentId,
                            double lastFillPrice,
                            int clientId,
                            String whyHeld,
                            double mktCapPrice) {

        double filledD    = (filled    != null && filled.isValid())    ? filled.value().doubleValue()    : 0.0;
        double remainingD = (remaining != null && remaining.isValid()) ? remaining.value().doubleValue() : 0.0;

        StatusOrdemFuturos s = new StatusOrdemFuturos();
        s.setOrderId((long) orderId);
        s.setStatus(status != null ? status.toUpperCase() : null);
        s.setExecutedQty(BigDecimal.valueOf(filledD));
        s.setOrigQty(BigDecimal.valueOf(filledD + remainingD));
        s.setAvgPrice(BigDecimal.valueOf(avgFillPrice));

        CompletableFuture<StatusOrdemFuturos> future = ordemFutures.get(orderId);
        if (future != null && !future.isDone()) {
            future.complete(s);
        }
    }

    @Override
    public void openOrder(int orderId, Contract contract, Order order, OrderState orderState) {
        StatusOrdemFuturos s = new StatusOrdemFuturos();
        s.setOrderId((long) orderId);
        s.setSymbol(contract.symbol());
        s.setStatus(orderState.status() != null ? orderState.status().toString().toUpperCase() : null);
        s.setPrice(BigDecimal.valueOf(order.lmtPrice()));
        // totalQuantity é Decimal no JavaClient novo
        s.setOrigQty(BigDecimal.valueOf(order.totalQuantity().longValue()));

        CompletableFuture<StatusOrdemFuturos> future = ordemFutures.get(orderId);
        if (future != null && !future.isDone()) {
            future.complete(s);
        }
    }

    @Override
    public void tickPrice(int tickerId, int field, double price, TickAttrib attribs) {
        if (price <= 0) return;
        CompletableFuture<BigDecimal> future = precoFutures.get(tickerId);
        if (future != null && !future.isDone()) {
            future.complete(BigDecimal.valueOf(price));
        }
    }

    // ===== Realtime bars =====

    @Override
    public void realtimeBar(int reqId,
                            long time,
                            double open,
                            double high,
                            double low,
                            double close,
                            Decimal volume,
                            Decimal wap,
                            int count) {

        RealtimeBarListener l = realtimeBarListeners.get(reqId);
        if (l != null) {
            long vol = (volume != null && volume.isValid()) ? volume.longValue() : 0L;
            double wapD = (wap != null && wap.isValid()) ? wap.value().doubleValue() : 0.0;

            l.onRealtimeBar(reqId, time, open, high, low, close, vol, wapD, count);
        }
    }

    // ===== Histórico =====

    @Override
    public void historicalData(int reqId, Bar bar) {
        List<Bar> lista = historicalBars.computeIfAbsent(reqId, k -> new ArrayList<>());
        lista.add(bar);
    }

    @Override
    public void historicalDataEnd(int reqId, String startDateStr, String endDateStr) {
        List<Bar> lista = historicalBars.get(reqId);
        CompletableFuture<List<Bar>> fut = historicalFutures.get(reqId);
        if (fut != null && lista != null && !fut.isDone()) {
            fut.complete(lista);
        }
    }

    // ===== Erros =====

    @Override
    public void error(Exception e) {
        e.printStackTrace();
    }

    @Override
    public void error(String str) {
        System.err.println("IBKR ERROR: " + str);
    }

    @Override
    public void error(int id,
                      long errorTime,
                      int errorCode,
                      String errorMsg,
                      String advancedOrderRejectJson) {

        // Mensagens informativas de conexão de data farm
        if (errorCode == 2158   // Sec-def data farm connection is OK
            || errorCode == 2157 // Sec-def data farm connection is broken
            || errorCode == 2104 // Market data farm connection is OK
            || errorCode == 2106 // HMDS data farm connection is OK
        ) {
            System.out.println("[IBKR INFO] time=" + errorTime +
                               ", id=" + id +
                               ", code=" + errorCode +
                               ", msg=" + errorMsg);
            return;
        }

        System.err.println("IBKR ERROR: time=" + errorTime +
                           ", id=" + id +
                           ", code=" + errorCode +
                           ", msg=" + errorMsg);

        CompletableFuture<StatusOrdemFuturos> of = ordemFutures.get(id);
        if (of != null && !of.isDone()) {
            of.completeExceptionally(
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
        }

        CompletableFuture<BigDecimal> pf = precoFutures.get(id);
        if (pf != null && !pf.isDone()) {
            pf.completeExceptionally(
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
        }

        CompletableFuture<List<Bar>> hf = historicalFutures.get(id);
        if (hf != null && !hf.isDone()) {
            hf.completeExceptionally(
                    new RuntimeException("IBKR error " + errorCode + ": " + errorMsg));
        }
    }


    // Execuções (se quiser aproveitar depois)
    @Override
    public void execDetails(int reqId, Contract contract, Execution execution) {
        // opcional: logar execuções
    }
}