public class IBEWrapper implements EWrapper {
private EReaderSignal readerSignal_;
private EClientSocket clientSocket_;
private int baseOrderId_ = -1;
private final long logPeriod = 30*1000;
private long tickPriceTimestamp = System.currentTimeMillis();
private long tickSizeTimestamp = System.currentTimeMillis();
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private final BlockingQueue marketDataQueue_ = new LinkedBlockingDeque();
private static Logger LOGGER = LoggerFactory.getLogger(IBEWrapper.class);
public IBEWrapper() {
readerSignal_ = new EJavaSignal();
clientSocket_ = new EClientSocket(this, readerSignal_);
}
public EClientSocket getClientSocket() {
return clientSocket_;
}
public EReaderSignal getReaderSignal() {
return readerSignal_;
}
public int getNextOrderId() {
return ++baseOrderId_;
}
public BlockingQueue getMarketDataQueue() {
return marketDataQueue_;
}
// 建立连接-----------------------------------------------------------------------------------------------/
// 接收服务器的当前系统时间
@Override
public void currentTime(long time) {
SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
Date date = new Date(time);
LOGGER.info("currentTime...{} convert to {}", time, sf.format(date));
}
// 处理请求过程中发生异常
@Override
public void error(Exception e) {
LOGGER.info("处理请求过程中发生异常..." + e.getMessage());
}
// TWS给client发送错误信息
@Override
public void error(String str) {
LOGGER.info("TWS给client发送错误信息..." + str);
}
// 通讯出现错误或TWS给client发送消息
@Override
public void error(int id, int errorCode, String errorMsg) {
LOGGER.info("通讯出现错误或TWS给client发送消息...error id: {}, code: {}, msg: {}", id, errorCode, errorMsg);
}
// TWS关闭socket连接, 或当TWS被关闭时调用
@Override
public void connectionClosed() {
LOGGER.info("TWS关闭socket连接, 或TWS已被关闭");
}
@Override
public void connectAck() {
if (clientSocket_.isAsyncEConnect()) {
LOGGER.info("Acknowledging connection");
clientSocket_.startAPI();
}
}
// 市场数据-----------------------------------------------------------------------------------------------/
// 当市场数据改变时调用该方法, 没有延迟的立即更新价格
@Override
public void tickPrice(int tickerId, int field, double price, int canAutoExecute) {
String fieldDesc = TickType.getField(field);
MarketDataBean marketData = ConfigSingleton.getInstance().getMarketDataMap().get(tickerId);
marketData.setTimeStamp(dateFormat.format(System.currentTimeMillis()));
MarketDataBean marketDataCopy = null;
switch (field) {
case 1:
// 买价 bidPrice
marketData.setBidPrice1(price);
marketDataCopy = new MarketDataBean(marketData);
break;
case 2:
// 卖价 askPrice
marketData.setAskPrice1(price);
marketDataCopy = new MarketDataBean(marketData);
break;
case 4:
// 最新价 lastPrice
marketData.setLastPrice(price);
BigDecimal lastPrice = new BigDecimal(Double.toString(price));
BigDecimal preSettlePrice = new BigDecimal(Double.toString(marketData.getPreSettlePrice()));
marketData.setChangeValue(lastPrice.subtract(preSettlePrice).doubleValue());
if (marketData.getPreSettlePrice() > 0.000001) {
BigDecimal changeValue = new BigDecimal(Double.toString(100.0 * marketData.getChangeValue()));
marketData.setChangeRate(changeValue.divide(preSettlePrice, 6, BigDecimal.ROUND_HALF_UP).doubleValue());
} else {
marketData.setChangeRate(0.0);
}
marketDataCopy = new MarketDataBean(marketData);
break;
case 6:
// 最高价 high
marketData.setHighPrice(price);
marketDataCopy = new MarketDataBean(marketData);
break;
case 7:
// 最低价 low
marketData.setLowPrice(price);
marketDataCopy = new MarketDataBean(marketData);
break;
case 9:
// 收盘价 close
marketData.setPreClosePrice(price);
marketData.setPreSettlePrice(price);
marketDataCopy = new MarketDataBean(marketData);
break;
case 14:
// 开盘价 open
marketData.setOpenPrice(price);
marketDataCopy = new MarketDataBean(marketData);
break;
default:
marketDataCopy = new MarketDataBean(marketData);
LOGGER.error("tick price. ticker Id: {}, field: {}, desc: {}, price: {}, canAutoExecute: {}", tickerId, field, fieldDesc, price, canAutoExecute);
break;
}
try {
marketDataQueue_.put(marketDataCopy);
} catch (InterruptedException e) {
LOGGER.error("添加价格信息出错, 异常信息: " + e.getMessage());
}
if (System.currentTimeMillis() - tickPriceTimestamp > logPeriod) {
tickPriceTimestamp = System.currentTimeMillis();
LOGGER.info("tick price. ticker Id: {}, field: {}, desc: {}, price: {}, canAutoExecute: {}", tickerId, field, fieldDesc, price, canAutoExecute);
}
}
// 当市场数据变动时调用该方法, 尺寸立即更新, 没有延迟
@Override
public void tickSize(int tickerId, int field, int size) {
String fieldDesc = TickType.getField(field);
MarketDataBean marketData = ConfigSingleton.getInstance().getMarketDataMap().get(tickerId);
marketData.setTimeStamp(dateFormat.format(System.currentTimeMillis()));
MarketDataBean marketDataCopy = null;
switch (field) {
case 0:
// 买量 bidSize
marketData.setBidQty1(size);
marketDataCopy = new MarketDataBean(marketData);
break;
case 3:
// 卖量 askSize
marketData.setAskQty1(size);
marketDataCopy = new MarketDataBean(marketData);
break;
case 5:
// 最后价尺寸 lastSize
marketDataCopy = new MarketDataBean(marketData);
break;
case 8:
// 交易量 volume
marketData.setTotalQty(size);
marketDataCopy = new MarketDataBean(marketData);
break;
default:
marketDataCopy = new MarketDataBean(marketData);
LOGGER.error("tick size. ticker Id: {}, field: {}, desc: {}, size: {}", tickerId, field, fieldDesc, size);
break;
}
try {
marketDataQueue_.put(marketDataCopy);
} catch (InterruptedException e) {
LOGGER.error("添加寸头信息出错, 异常信息: " + e.getMessage());
}
if (System.currentTimeMillis() - tickSizeTimestamp > logPeriod) {
tickSizeTimestamp = System.currentTimeMillis();
LOGGER.debug("tick size. ticker Id: {}, field: {}, desc: {}, size: {}", tickerId, field, fieldDesc, size);
}
}
// 当市场数据改变时调用该方法, 值被立即更新, 没有延迟
@Override
public void tickGeneric(int tickerId, int tickType, double value) {
String typeDesc = TickType.getField(tickType);
LOGGER.debug("tick generic. ticker Id: {}, type: {}, desc: {}, value: {}", tickerId, tickType, typeDesc, value);
}
// 当市场数据改变时调用该方法, 值被立即更新, 没有延迟
@Override
public void tickString(int tickerId, int tickType, String value) {
String typeDesc = TickType.getField(tickType);
LOGGER.debug("tick string. ticker Id: {}, type: {}, desc: {}, value: {}", tickerId, tickType, typeDesc, value);
}
@Override
public void tickEFP(int tickerId, int tickType, double basisPoints,
String formattedBasisPoints, double impliedFuture, int holdDays,
String futureLastTradeDate, double dividendImpact,
double dividendsToLastTradeDate) {
System.out.println("TickEFP. " + tickerId + ", Type: " + tickType + ", BasisPoints: " + basisPoints + ", FormattedBasisPoints: " +
formattedBasisPoints + ", ImpliedFuture: " + impliedFuture + ", HoldDays: " + holdDays + ", FutureLastTradeDate: " + futureLastTradeDate +
", DividendImpact: " + dividendImpact + ", DividendsToLastTradeDate: " + dividendsToLastTradeDate);
}
// 当期权市场或期权底层变化时调用该方法
@Override
public void tickOptionComputation(int tickerId, int field,
double impliedVol, double delta, double optPrice,
double pvDividend, double gamma, double vega, double theta,
double undPrice) {
System.out.println("TickOptionComputation. TickerId: " + tickerId + ", field: " + field + ", ImpliedVolatility: " + impliedVol + ", Delta: " + delta
+ ", OptionPrice: " + optPrice + ", pvDividend: " + pvDividend + ", Gamma: " + gamma + ", Vega: " + vega + ", Theta: " + theta + ", UnderlyingPrice: " + undPrice);
}
@Override
public void tickSnapshotEnd(int reqId) {
System.out.println("TickSnapshotEnd: " + reqId);
}
@Override
public void marketDataType(int reqId, int marketDataType) {
System.out.println("MarketDataType. [" + reqId + "], Type: [" + marketDataType + "]\n");
}
// 定单---------------------------------------------------------------------------------------------------/
@Override
public void orderStatus(int orderId, String status, double filled,
double remaining, double avgFillPrice, int permId, int parentId,
double lastFillPrice, int clientId, String whyHeld) {
LOGGER.info("OrderStatus. Id: " + orderId + ", Status: " + status + ", Filled" + filled + ", Remaining: " + remaining
+ ", AvgFillPrice: " + avgFillPrice + ", PermId: " + permId + ", ParentId: " + parentId + ", LastFillPrice: " + lastFillPrice +
", ClientId: " + clientId + ", WhyHeld: " + whyHeld);
switch (status) {
case "PendingSubmit":
// 已经报单, 但还未接到来自定单目的地接收的确认信息
break;
case "PreSubmitted":
// 模拟定单类型已经被IB系统接收, 但还未被选中
break;
case "Submitted":
// 定单已经被定单目的地接受, 并处于工作中
break;
case "Filled":
// 定单已被全部执行
break;
case "Cancelled":
// 定单的剩余部分已被IB系统确认取消了, 这也会在IB或目的地拒绝你的定单时发生
break;
case "Inactive":
// 表示定单已被系统接收(模拟定单)或交易所接收(本地定单), 但由于系统/交易所或其它原因, 目前定单处于非工作状态
break;
case "PendingCancel":
// 已经发送了取消定单的请求, 但还未收到来自定单目的地的取消确认
break;
default:
break;
}
}
@Override
public void openOrder(int orderId, Contract contract, Order order, OrderState orderState) {
System.out.println("OpenOrder. ID: " + orderId + ", " + contract.symbol() + ", " + contract.secType() + " @ " + contract.exchange() + ": " +
order.action() + ", " + order.orderType() + " " + order.totalQuantity() + ", " + orderState.status());
}
@Override
public void openOrderEnd() {
System.out.println("OpenOrderEnd");
}
@Override
public void nextValidId(int orderId) {
LOGGER.info("Next Valid Id: {}", orderId);
baseOrderId_ = orderId;
}
// 账户和投资组合------------------------------------------------------------------------------------------/
@Override
public void updateAccountValue(String key, String value, String currency, String accountName) {
System.out.println("UpdateAccountValue. Key: " + key + ", Value: " + value + ", Currency: " + currency + ", AccountName: " + accountName);
}
@Override
public void updatePortfolio(Contract contract, double position,
double marketPrice, double marketValue, double averageCost,
double unrealizedPNL, double realizedPNL, String accountName) {
System.out.println("UpdatePortfolio. " + contract.symbol() + ", " + contract.secType() + " @ " + contract.exchange()
+ ": Position: " + position + ", MarketPrice: " + marketPrice + ", MarketValue: " + marketValue + ", AverageCost: " + averageCost
+ ", UnrealisedPNL: " + unrealizedPNL + ", RealisedPNL: " + realizedPNL + ", AccountName: " + accountName);
}
@Override
public void updateAccountTime(String timeStamp) {
System.out.println("UpdateAccountTime. Time: " + timeStamp + "\n");
}
@Override
public void accountDownloadEnd(String accountName) {
System.out.println("Account download finished: " + accountName + "\n");
}
// 合约详细-----------------------------------------------------------------------------------------------/
@Override
public void contractDetails(int reqId, ContractDetails contractDetails) {
System.out.println("ContractDetails. ReqId: [" + reqId + "] - [" + contractDetails.contract().symbol() + "], [" + contractDetails.contract().secType() + "], ConId: [" + contractDetails.contract().conid() + "] @ [" + contractDetails.contract().exchange() + "]");
}
@Override
public void bondContractDetails(int reqId, ContractDetails contractDetails) {
System.out.println("bondContractDetails");
}
@Override
public void contractDetailsEnd(int reqId) {
System.out.println("ContractDetailsEnd. " + reqId + "\n");
}
// 执行---------------------------------------------------------------------------------------------------/
@Override
public void execDetails(int reqId, Contract contract, Execution execution) {
System.out.println("ExecDetails. " + reqId + " - [" + contract.symbol() + "], [" + contract.secType() + "], [" + contract.currency() + "], [" + execution.execId() + "], [" + execution.orderId() + "], [" + execution.shares() + "]");
}
@Override
public void execDetailsEnd(int reqId) {
System.out.println("ExecDetailsEnd. " + reqId + "\n");
}
@Override
public void commissionReport(CommissionReport commissionReport) {
System.out.println("CommissionReport. [" + commissionReport.m_execId + "] - [" + commissionReport.m_commission + "] [" + commissionReport.m_currency + "] RPNL [" + commissionReport.m_realizedPNL + "]");
}
// 市场深度-----------------------------------------------------------------------------------------------/
@Override
public void updateMktDepth(int tickerId, int position, int operation, int side, double price, int size) {
System.out.println("UpdateMarketDepth. " + tickerId + " - Position: " + position + ", Operation: " + operation + ", Side: " + side + ", Price: " + price + ", Size: " + size + "");
}
@Override
public void updateMktDepthL2(int tickerId, int position, String marketMaker, int operation, int side, double price, int size) {
System.out.println("updateMktDepthL2");
}
// 新闻公告-----------------------------------------------------------------------------------------------/
@Override
public void updateNewsBulletin(int msgId, int msgType, String message, String origExchange) {
System.out.println("News Bulletins. " + msgId + " - Type: " + msgType + ", Message: " + message + ", Exchange of Origin: " + origExchange + "\n");
}
// 金融顾问-----------------------------------------------------------------------------------------------/
@Override
public void managedAccounts(String accountsList) {
System.out.println("Account list: " + accountsList);
}
@Override
public void receiveFA(int faDataType, String xml) {
System.out.println("Receing FA: " + faDataType + " - " + xml);
}
@Override
public void accountSummary(int reqId, String account, String tag, String value, String currency) {
System.out.println("Acct Summary. ReqId: " + reqId + ", Acct: " + account + ", Tag: " + tag + ", Value: " + value + ", Currency: " + currency);
}
@Override
public void accountSummaryEnd(int reqId) {
System.out.println("AccountSummaryEnd. Req Id: " + reqId + "\n");
}
@Override
public void position(String account, Contract contract, double pos, double avgCost) {
System.out.println("Position. " + account + " - Symbol: " + contract.symbol() + ", SecType: " + contract.secType() + ", Currency: " + contract.currency() + ", Position: " + pos + ", Avg cost: " + avgCost);
}
@Override
public void positionEnd() {
System.out.println("PositionEnd \n");
}
// 历史数据-----------------------------------------------------------------------------------------------/
@Override
public void historicalData(int reqId, String date, double open,
double high, double low, double close, int volume, int count,
double WAP, boolean hasGaps) {
System.out.println("HistoricalData. " + reqId + " - Date: " + date + ", Open: " + open + ", High: " + high + ", Low: " + low + ", Close: " + close + ", Volume: " + volume + ", Count: " + count + ", WAP: " + WAP + ", HasGaps: " + hasGaps);
}
// 市场扫描仪---------------------------------------------------------------------------------------------/
@Override
public void scannerParameters(String xml) {
System.out.println("ScannerParameters. " + xml + "\n");
}
@Override
public void scannerData(int reqId, int rank,
ContractDetails contractDetails, String distance, String benchmark,
String projection, String legsStr) {
System.out.println("ScannerData. " + reqId + " - Rank: " + rank + ", Symbol: " + contractDetails.contract().symbol() + ", SecType: " + contractDetails.contract().secType() + ", Currency: " + contractDetails.contract().currency()
+ ", Distance: " + distance + ", Benchmark: " + benchmark + ", Projection: " + projection + ", Legs String: " + legsStr);
}
@Override
public void scannerDataEnd(int reqId) {
System.out.println("ScannerDataEnd. " + reqId);
}
// 实时柱-------------------------------------------------------------------------------------------------/
@Override
public void realtimeBar(int reqId, long time, double open, double high,
double low, double close, long volume, double wap, int count) {
System.out.println("RealTimeBars. " + reqId + " - Time: " + time + ", Open: " + open + ", High: " + high + ", Low: " + low + ", Close: " + close + ", Volume: " + volume + ", Count: " + count + ", WAP: " + wap);
}
// 基本面数据----------------------------------------------------------------------------------------------/
@Override
public void fundamentalData(int reqId, String data) {
System.out.println("FundamentalData. ReqId: [" + reqId + "] - Data: [" + data + "]");
}
// 其他---------------------------------------------------------------------------------------------------/
@Override
public void deltaNeutralValidation(int reqId, DeltaNeutralContract underComp) {
System.out.println("deltaNeutralValidation");
}
@Override
public void verifyMessageAPI(String apiData) {
System.out.println("verifyMessageAPI");
}
@Override
public void verifyCompleted(boolean isSuccessful, String errorText) {
System.out.println("verifyCompleted");
}
@Override
public void verifyAndAuthMessageAPI(String apiData, String xyzChallange) {
System.out.println("verifyAndAuthMessageAPI");
}
@Override
public void verifyAndAuthCompleted(boolean isSuccessful, String errorText) {
System.out.println("verifyAndAuthCompleted");
}
@Override
public void displayGroupList(int reqId, String groups) {
System.out.println("Display Group List. ReqId: " + reqId + ", Groups: " + groups + "\n");
}
@Override
public void displayGroupUpdated(int reqId, String contractInfo) {
System.out.println("Display Group Updated. ReqId: " + reqId + ", Contract info: " + contractInfo + "\n");
}
@Override
public void positionMulti(int reqId, String account, String modelCode,
Contract contract, double pos, double avgCost) {
System.out.println("Position Multi. Request: " + reqId + ", Account: " + account + ", ModelCode: " + modelCode + ", Symbol: " + contract.symbol() + ", SecType: " + contract.secType() + ", Currency: " + contract.currency() + ", Position: " + pos + ", Avg cost: " + avgCost + "\n");
}
@Override
public void positionMultiEnd(int reqId) {
System.out.println("Position Multi End. Request: " + reqId + "\n");
}
@Override
public void accountUpdateMulti(int reqId, String account, String modelCode,
String key, String value, String currency) {
System.out.println("Account Update Multi. Request: " + reqId + ", Account: " + account + ", ModelCode: " + modelCode + ", Key: " + key + ", Value: " + value + ", Currency: " + currency + "\n");
}
@Override
public void accountUpdateMultiEnd(int reqId) {
System.out.println("Account Update Multi End. Request: " + reqId + "\n");
}
@Override
public void securityDefinitionOptionalParameter(int reqId, String exchange,
int underlyingConId, String tradingClass, String multiplier,
Set expirations, Set strikes) {
System.out.println("Security Definition Optional Parameter. Request: " + reqId + ", Trading Class: " + tradingClass + ", Multiplier: " + multiplier + " \n");
}
@Override
public void securityDefinitionOptionalParameterEnd(int reqId) {
}
@Override
public void softDollarTiers(int reqId, SoftDollarTier[] tiers) {
for (SoftDollarTier tier : tiers) {
System.out.print("tier: " + tier + ", ");
}
System.out.println();
}
}
public class IBServer {
private final EClientSocket client_;
private final EReaderSignal signal_;
private Jedis jedisClient_;
private NetServer netServer_;
private final IBEWrapper wrapper_ = new IBEWrapper();
private final ExecutorService executorService_ = Executors.newFixedThreadPool(2);
private final ScheduledExecutorService subscribeScheduler_ = new ScheduledThreadPoolExecutor(1);
private static Logger LOGGER = LoggerFactory.getLogger(IBServer.class);
public IBServer(String host, int port, int clientId) {
client_ = wrapper_.getClientSocket();
signal_ = wrapper_.getReaderSignal();
client_.eConnect(host, port, clientId);
redisConnect();
startupCallbackThread();
}
public void setNetServer(NetServer netServer) {
this.netServer_ = netServer;
}
// 连接redis服务器
private void redisConnect() {
String host = ConfigSingleton.getInstance().getParamsBean().getRedisHost();
int port = ConfigSingleton.getInstance().getParamsBean().getRedisPort();
jedisClient_ = new Jedis(host, port, 5000);
String authResult = jedisClient_.auth(ConfigSingleton.getInstance().getParamsBean().getRedisPassword());
if (!authResult.equals("OK")) {
LOGGER.info("redis已连接服务器, 但验证失败: " + authResult);
jedisClient_.close();
System.exit(1);
} else {
LOGGER.info("redis已连接服务器, 并验证成功: " + authResult);
}
}
// 行情数据发送线程
public void startupMarketDataDispatcher() {
executorService_.execute(new Runnable() {
@Override
public void run() {
for (;;) {
try {
MarketDataBean marketData = wrapper_.getMarketDataQueue().take();
MarketDataWrapper dataWrapper = new MarketDataWrapper();
dataWrapper.CMDID = 2000;
dataWrapper.DATA = marketData;
String message = JSON.toJSONString(dataWrapper);
// 分发至长连接客户端
netServer_.dispatcher(message);
// 分发至redis服务器
try {
jedisClient_.publish(marketData.getProductName(), message);
} catch (JedisConnectionException e) {
LOGGER.error("redis message: {} {}", marketData.getProductName(), message);
LOGGER.error("redis发布命令异常, 开始重连, 异常信息: " + e.getMessage());
jedisClient_.close();
redisConnect();
}
} catch(Exception e) {
LOGGER.error("行情数据发送出错, 异常信息: " + e.getMessage());
}
}
}
});
}
// 启动回调线程
private void startupCallbackThread() {
final EReader reader = new EReader(client_, signal_);
reader.start();
executorService_.execute(new Runnable() {
@Override
public void run() {
while (client_.isConnected()) {
signal_.waitForSignal();
try {
reader.processMsgs();
} catch (Exception e) {
LOGGER.error("Exception: " + e.getMessage());
}
}
}
});
}
// 订阅股票及期货市场行情
public void subscribeMarketData(int delaySeconds) {
subscribeScheduler_.schedule(new Runnable() {
@Override
public void run() {
List contracts = ConfigSingleton.getInstance().getContracts();
for (ContractBean bean : contracts) {
Contract contract = new Contract();
contract.symbol(bean.getSymbol());// 品种
contract.secType(bean.getSecType());// 证券类型
contract.currency(bean.getCurrency());// 币种
contract.exchange(bean.getExchange());// 交易所
if (bean.getSecType().equals(Constants.FUTURE)) {
contract.lastTradeDateOrContractMonth(bean.getContractMonth());// 合约
}
client_.reqMktData(bean.getTickerId(), contract, ""/*"100,101,105,106,165,233,236,258"*/, false, null);
LOGGER.info("订阅合约, tickerId: {}, symbol: {}{}, secType: {}, exchange: {}",
bean.getTickerId(), bean.getSymbol(), bean.getContractMonth(), bean.getSecType(), bean.getExchange());
}
}
}, delaySeconds, TimeUnit.SECONDS);
}
// 释放资源
public void release() {
executorService_.shutdown();
subscribeScheduler_.shutdown();
jedisClient_.close();
}
}