diff --git a/pom.xml b/pom.xml
index cf60adb83cfe6d19733521a8ea9d0c662e83e61a..23fc37b72c817a0d1dd616cb34aa84940aa14ca2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,64 +1,76 @@
-
- 4.0.0
- org.dromara
- northstar-gateway-tiger
- 0.2.0
-
-
- 17
- UTF-8
- ${java.version}
- ${java.version}
-
-
-
-
- org.dromara
- northstar-api
-
-
- com.alibaba
- fastjson
-
-
- commons-io
- commons-io
-
-
- org.apache.commons
- commons-lang3
-
-
- org.projectlombok
- lombok
-
-
- org.slf4j
- slf4j-api
-
-
- io.github.tigerbrokers
- openapi-java-sdk
- 1.4.9
-
-
- org.springframework.boot
- spring-boot-starter-test
- 2.6.14
- test
-
-
-
-
-
-
- org.dromara
- northstar
- 6.1.1.Final
- pom
- import
-
-
-
-
-
\ No newline at end of file
+
+ 4.0.0
+ org.dromara
+ northstar-gateway-tiger
+ 0.2.0
+
+
+ 21
+ UTF-8
+ ${java.version}
+ ${java.version}
+
+
+
+
+ org.dromara
+ northstar-api
+
+
+ com.alibaba
+ fastjson
+
+
+ commons-io
+ commons-io
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.projectlombok
+ lombok
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.github.tigerbrokers
+ openapi-java-sdk
+ 2.1.5
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 21
+
+
+
+
+
+
+
+
+ org.dromara
+ northstar
+ 7.3.4
+ pom
+ import
+
+
+
+
+
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxy.java b/src/main/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxy.java
index a19789e34cb7ca611d9799004c2b915402d3936b..ba1001678ad9b490e960fefcdc9734c20d8cf5c6 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxy.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxy.java
@@ -1,22 +1,5 @@
package org.dromara.northstar.gateway.tiger;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-import org.apache.commons.lang3.StringUtils;
-import org.dromara.northstar.common.constant.ChannelType;
-import org.dromara.northstar.common.constant.DateTimeConstant;
-import org.dromara.northstar.gateway.IContractManager;
-
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@@ -26,224 +9,227 @@ import com.tigerbrokers.stock.openapi.client.https.response.TigerHttpResponse;
import com.tigerbrokers.stock.openapi.client.struct.enums.MethodName;
import com.tigerbrokers.stock.openapi.client.struct.enums.SecType;
import com.tigerbrokers.stock.openapi.client.util.builder.AccountParamBuilder;
-
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.dromara.northstar.common.constant.ChannelType;
+import org.dromara.northstar.common.constant.DateTimeConstant;
+import org.dromara.northstar.common.model.core.Contract;
+import org.dromara.northstar.common.model.core.Order;
+import org.dromara.northstar.common.model.core.Trade;
+import org.dromara.northstar.gateway.IContractManager;
import xyz.redtorch.pb.CoreEnum.DirectionEnum;
import xyz.redtorch.pb.CoreEnum.OffsetFlagEnum;
import xyz.redtorch.pb.CoreEnum.OrderStatusEnum;
import xyz.redtorch.pb.CoreEnum.TimeConditionEnum;
-import xyz.redtorch.pb.CoreField.ContractField;
-import xyz.redtorch.pb.CoreField.OrderField;
-import xyz.redtorch.pb.CoreField.TradeField;
+
+import java.time.*;
+import java.util.*;
@Slf4j
public class OrderTradeQueryProxy {
- private static final long EXPIRY = 24 * 3600 * 1000; // 一天以内
- private final TigerHttpClient client;
- private final IContractManager contractMgr;
- private final String accountId;
- private final String gatewayId;
-
- private Map orderIds = new HashMap<>();
- private Set tradeIds = new HashSet<>();
-
- public OrderTradeQueryProxy(TigerHttpClient client, IContractManager contractMgr,String gatewayId, String accountId) {
- this.client = client;
- this.contractMgr = contractMgr;
- this.accountId = accountId;
- this.gatewayId = gatewayId;
- }
-
- public List getDeltaOrder() {
- TigerHttpRequest request = new TigerHttpRequest(MethodName.ORDERS);
- String bizContent = AccountParamBuilder.instance()
- .account(accountId)
- .limit(100)
- .buildJson();
-
- request.setBizContent(bizContent);
- TigerHttpResponse response = client.execute(request);
- if(!response.isSuccess()) {
- log.warn("查询订单返回异常:{}", response.getMessage());
- throw new IllegalStateException(response.getMessage());
- }
- JSONObject data = JSON.parseObject(response.getData());
- JSONArray items = data.getJSONArray("items");
- List resultList = new ArrayList<>(100);
- for(int i=0; i EXPIRY;
- }
-
- private long getOpenTime(JSONObject json) {
- return json.getLongValue("openTime");
- }
-
- private OrderField convertOrder(JSONObject json) {
- Long id = json.getLong("id");
- String orderId = id + "";
- DirectionEnum direction = switch(json.getString("action")) {
- case "BUY" -> DirectionEnum.D_Buy;
- case "SELL" -> DirectionEnum.D_Sell;
- default -> DirectionEnum.D_Unknown;
- };
-
- OffsetFlagEnum offset = switch(direction) {
- case D_Buy -> OffsetFlagEnum.OF_Open;
- case D_Sell -> OffsetFlagEnum.OF_Close;
- default -> throw new IllegalArgumentException("Unexpected value: " + direction);
- };
-
- OrderStatusEnum orderStatus = switch(json.getString("status")) {
- case "Filled" -> OrderStatusEnum.OS_AllTraded;
- case "Cancelled" -> OrderStatusEnum.OS_Canceled;
- case "Initial", "PendingSubmit", "Submitted" -> OrderStatusEnum.OS_Touched;
- case "Invalid", "Inactive" -> OrderStatusEnum.OS_Rejected;
- default -> throw new IllegalArgumentException("Unexpected value: " + json.getString("status"));
- };
-
- TimeConditionEnum timeCondition = switch(json.getString("timeInForce")) {
- case "DAY" -> TimeConditionEnum.TC_GFD;
- case "GTC" -> TimeConditionEnum.TC_GTC;
- case "GTD" -> TimeConditionEnum.TC_GTD;
- default -> throw new IllegalArgumentException("Unexpected value: " + json.getString(""));
- };
-
- String symbol = json.getString("symbol");
- ContractField contract = contractMgr.getContract(ChannelType.TIGER, symbol).contractField();
- String orderMsg = json.getString("remark");
- if(orderStatus == OrderStatusEnum.OS_Rejected && !orderIds.containsKey(id)) {
- if(StringUtils.isEmpty(orderMsg)) {
- log.warn("废单反馈:{}", json.toString(SerializerFeature.PrettyFormat));
- } else {
- long openTime = getOpenTime(json);
- LocalDateTime ldt = LocalDateTime.ofInstant(Instant.ofEpochMilli(openTime), ZoneId.systemDefault());
- log.warn("废单信息:{} {} {}", ldt, contract.getName(), orderMsg);
- }
- }
- Instant ins = Instant.ofEpochMilli(getOpenTime(json));
- String tradingDay = LocalDateTime.ofInstant(ins, ZoneOffset.ofHours(0)).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER);
- String orderDate = LocalDateTime.ofInstant(ins, ZoneId.systemDefault()).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER);
- int tradedVol = json.getIntValue("filledQuantity");
- int totalVol = json.getIntValue("totalQuantity");
- double price = (int) (json.getDoubleValue("limitPrice") / contract.getPriceTick()) * contract.getPriceTick();
- return OrderField.newBuilder()
- .setAccountId(accountId)
- .setGatewayId(gatewayId)
- .setContract(contract)
- .setOrderId(orderId)
- .setDirection(direction)
- .setOffsetFlag(offset)
- .setOrderDate(orderDate)
- .setTotalVolume(totalVol)
- .setTradedVolume(tradedVol)
- .setPrice(price)
- .setTradingDay(tradingDay)
- .setOrderDate(orderDate)
- .setOrderTime(LocalDateTime.ofInstant(ins, ZoneId.systemDefault()).toLocalTime().format(DateTimeConstant.T_FORMAT_FORMATTER))
- .setTimeCondition(timeCondition)
- .setOrderStatus(orderStatus)
- .build();
- }
-
- public List getTrades(String symbol) {
- log.trace("查询TIGER成交信息");
-
- TigerHttpRequest request = new TigerHttpRequest(MethodName.ORDER_TRANSACTIONS);
- String bizContent = AccountParamBuilder.instance()
- .account(accountId)
- .secType(SecType.STK)
- .symbol(symbol)
- .limit(100)
- .buildJson();
- request.setBizContent(bizContent);
- TigerHttpResponse response = client.execute(request);
- if(!response.isSuccess()) {
- log.warn("查询成交返回异常:{}", response.getMessage());
- throw new IllegalStateException(response.getMessage());
- }
-
- return resolveData(response);
- }
-
- public List getDeltaTrade(Long id) {
- log.trace("查询TIGER成交信息");
-
- TigerHttpRequest request = new TigerHttpRequest(MethodName.ORDER_TRANSACTIONS);
- String bizContent = AccountParamBuilder.instance()
- .account(accountId)
- .secType(SecType.STK)
- .orderId(id)
- .limit(100)
- .buildJson();
- request.setBizContent(bizContent);
- TigerHttpResponse response = client.execute(request);
- if(!response.isSuccess()) {
- log.warn("查询成交返回异常:{}", response.getMessage());
- throw new IllegalStateException(response.getMessage());
- }
-
- return resolveData(response);
- }
-
- private List resolveData(TigerHttpResponse response){
- JSONArray data = JSON.parseObject(response.getData()).getJSONArray("items");
- List resultList = new ArrayList<>();
- for(int i=0; i DirectionEnum.D_Buy;
- case "SELL" -> DirectionEnum.D_Sell;
- default -> DirectionEnum.D_Unknown;
- };
- OffsetFlagEnum offset = switch(direction) {
- case D_Buy -> OffsetFlagEnum.OF_Open;
- case D_Sell -> OffsetFlagEnum.OF_Close;
- default -> throw new IllegalArgumentException("Unexpected value: " + direction);
- };
- TradeField trade = TradeField.newBuilder()
- .setAccountId(accountId)
- .setGatewayId(gatewayId)
- .setTradeId(tradeId + "")
- .setOrderId(orderId)
- .setDirection(direction)
- .setOffsetFlag(offset)
- .setContract(contract)
- .setPrice(json.getDoubleValue("filledPrice"))
- .setVolume(json.getIntValue("filledQuantity"))
- .setTradeDate(json.getString("transactedAt").split(" ")[0])
- .setTradeTime(json.getString("transactedAt").split(" ")[1])
- .setTradeTimestamp(tradeTime)
- .setContract(contractMgr.getContract(ChannelType.TIGER, symbol).contractField())
- .build();
- resultList.add(trade);
- }
- return resultList;
- }
+ private static final long EXPIRY = 24 * 3600 * 1000; // 一天以内
+ private final TigerHttpClient client;
+ private final IContractManager contractMgr;
+ private final String accountId;
+ private final String gatewayId;
+
+ private Map orderIds = new HashMap<>();
+ private Set tradeIds = new HashSet<>();
+
+ public OrderTradeQueryProxy(TigerHttpClient client, IContractManager contractMgr, String gatewayId, String accountId) {
+ this.client = client;
+ this.contractMgr = contractMgr;
+ this.accountId = accountId;
+ this.gatewayId = gatewayId;
+ }
+
+ public List getDeltaOrder() {
+ TigerHttpRequest request = new TigerHttpRequest(MethodName.ORDERS);
+ String bizContent = AccountParamBuilder.instance()
+ .account(accountId)
+ .limit(100)
+ .buildJson();
+
+ request.setBizContent(bizContent);
+ TigerHttpResponse response = client.execute(request);
+ if (!response.isSuccess()) {
+ log.warn("查询订单返回异常:{}", response.getMessage());
+ throw new IllegalStateException(response.getMessage());
+ }
+ JSONObject data = JSON.parseObject(response.getData());
+ JSONArray items = data.getJSONArray("items");
+ List resultList = new ArrayList<>(100);
+ for (int i = 0; i < items.size(); i++) {
+ JSONObject json = items.getJSONObject(i);
+ long openTime = getOpenTime(json);
+ if (expired(openTime)) {
+ continue;
+ }
+ Long id = json.getLong("id");
+ Order order = convertOrder(json);
+ Order oldOrder = orderIds.get(id);
+ if (Objects.isNull(oldOrder) || order.tradedVolume() != oldOrder.tradedVolume() || order.orderStatus() != oldOrder.orderStatus()) {
+ orderIds.put(id, order);
+ resultList.add(order);
+ }
+ }
+ return resultList;
+ }
+
+ private boolean expired(long time) {
+ return System.currentTimeMillis() - time > EXPIRY;
+ }
+
+ private long getOpenTime(JSONObject json) {
+ return json.getLongValue("openTime");
+ }
+
+ private Order convertOrder(JSONObject json) {
+ Long id = json.getLong("id");
+ String orderId = id + "";
+ DirectionEnum direction = switch (json.getString("action")) {
+ case "BUY" -> DirectionEnum.D_Buy;
+ case "SELL" -> DirectionEnum.D_Sell;
+ default -> DirectionEnum.D_Unknown;
+ };
+
+ OffsetFlagEnum offset = switch (direction) {
+ case D_Buy -> OffsetFlagEnum.OF_Open;
+ case D_Sell -> OffsetFlagEnum.OF_Close;
+ default -> throw new IllegalArgumentException("Unexpected value: " + direction);
+ };
+
+ OrderStatusEnum orderStatus = switch (json.getString("status")) {
+ case "Filled" -> OrderStatusEnum.OS_AllTraded;
+ case "Cancelled" -> OrderStatusEnum.OS_Canceled;
+ case "Initial", "PendingSubmit", "Submitted" -> OrderStatusEnum.OS_Touched;
+ case "Invalid", "Inactive" -> OrderStatusEnum.OS_Rejected;
+ default -> throw new IllegalArgumentException("Unexpected value: " + json.getString("status"));
+ };
+
+ TimeConditionEnum timeCondition = switch (json.getString("timeInForce")) {
+ case "DAY" -> TimeConditionEnum.TC_GFD;
+ case "GTC" -> TimeConditionEnum.TC_GTC;
+ case "GTD" -> TimeConditionEnum.TC_GTD;
+ default -> throw new IllegalArgumentException("Unexpected value: " + json.getString(""));
+ };
+
+ String symbol = json.getString("symbol");
+ Contract contract = contractMgr.getContract(ChannelType.TIGER, symbol).contract();
+ String orderMsg = json.getString("remark");
+ if (orderStatus == OrderStatusEnum.OS_Rejected && !orderIds.containsKey(id)) {
+ if (StringUtils.isEmpty(orderMsg)) {
+ log.warn("废单反馈:{}", json.toString(SerializerFeature.PrettyFormat));
+ } else {
+ long openTime = getOpenTime(json);
+ LocalDateTime ldt = LocalDateTime.ofInstant(Instant.ofEpochMilli(openTime), ZoneId.systemDefault());
+ log.warn("废单信息:{} {} {}", ldt, contract.name(), orderMsg);
+ }
+ }
+ Instant ins = Instant.ofEpochMilli(getOpenTime(json));
+ String tradingDay = LocalDateTime.ofInstant(ins, ZoneOffset.ofHours(0)).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER);
+ String orderDate = LocalDateTime.ofInstant(ins, ZoneId.systemDefault()).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER);
+ int tradedVol = json.getIntValue("filledQuantity");
+ int totalVol = json.getIntValue("totalQuantity");
+ double price = (int) (json.getDoubleValue("limitPrice") / contract.priceTick()) * contract.priceTick();
+
+ return Order.builder()
+ .gatewayId(gatewayId)
+ .contract(contract)
+ .orderId(orderId)
+ .direction(direction)
+ .offsetFlag(offset)
+ .orderDate(LocalDate.parse(orderDate))
+ .totalVolume(totalVol)
+ .tradedVolume(tradedVol)
+ .price(price)
+ .tradingDay(LocalDate.parse(tradingDay))
+ .orderTime(LocalTime.parse(LocalDateTime.ofInstant(ins, ZoneId.systemDefault()).toLocalTime().format(DateTimeConstant.T_FORMAT_FORMATTER)))
+ .timeCondition(timeCondition)
+ .orderStatus(orderStatus).build();
+
+ }
+
+ public List getTrades(String symbol) {
+ log.trace("查询TIGER成交信息");
+
+ TigerHttpRequest request = new TigerHttpRequest(MethodName.ORDER_TRANSACTIONS);
+ String bizContent = AccountParamBuilder.instance()
+ .account(accountId)
+ .secType(SecType.STK)
+ .symbol(symbol)
+ .limit(100)
+ .buildJson();
+ request.setBizContent(bizContent);
+ TigerHttpResponse response = client.execute(request);
+ if (!response.isSuccess()) {
+ log.warn("查询成交返回异常:{}", response.getMessage());
+ throw new IllegalStateException(response.getMessage());
+ }
+
+ return resolveData(response);
+ }
+
+ public List getDeltaTrade(Long id) {
+ log.trace("查询TIGER成交信息");
+
+ TigerHttpRequest request = new TigerHttpRequest(MethodName.ORDER_TRANSACTIONS);
+ String bizContent = AccountParamBuilder.instance()
+ .account(accountId)
+ .secType(SecType.STK)
+ .orderId(id)
+ .limit(100)
+ .buildJson();
+ request.setBizContent(bizContent);
+ TigerHttpResponse response = client.execute(request);
+ if (!response.isSuccess()) {
+ log.warn("查询成交返回异常:{}", response.getMessage());
+ throw new IllegalStateException(response.getMessage());
+ }
+
+ return resolveData(response);
+ }
+
+ private List resolveData(TigerHttpResponse response) {
+ JSONArray data = JSON.parseObject(response.getData()).getJSONArray("items");
+ List resultList = new ArrayList<>();
+ for (int i = 0; i < data.size(); i++) {
+ JSONObject json = data.getJSONObject(i);
+ String symbol = json.getString("symbol");
+ Contract contract = contractMgr.getContract(ChannelType.TIGER, symbol).contract();
+ Long tradeId = json.getLong("id");
+ Long tradeTime = json.getLongValue("transactionTime");
+ if (tradeIds.contains(tradeId) || expired(tradeTime)) {
+ continue;
+ }
+ tradeIds.add(tradeId);
+ String orderId = json.getString("orderId");
+ DirectionEnum direction = switch (json.getString("action")) {
+ case "BUY" -> DirectionEnum.D_Buy;
+ case "SELL" -> DirectionEnum.D_Sell;
+ default -> DirectionEnum.D_Unknown;
+ };
+ OffsetFlagEnum offset = switch (direction) {
+ case D_Buy -> OffsetFlagEnum.OF_Open;
+ case D_Sell -> OffsetFlagEnum.OF_Close;
+ default -> throw new IllegalArgumentException("Unexpected value: " + direction);
+ };
+ Trade trade = Trade.builder()
+ .gatewayId(gatewayId)
+ .orderId(orderId)
+ .direction(direction)
+ .offsetFlag(offset)
+ .contract(contract)
+ .price(json.getDoubleValue("filledPrice"))
+ .volume(json.getIntValue("filledQuantity"))
+ .tradeDate(LocalDate.parse(json.getString("transactedAt").split(" ")[0]))
+ .tradeTime(LocalTime.parse(json.getString("transactedAt").split(" ")[1]))
+ .tradeTimestamp(tradeTime)
+ .contract(contractMgr.getContract(ChannelType.TIGER, symbol).contract())
+ .build();
+ resultList.add(trade);
+ }
+ return resultList;
+ }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerConfig.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerConfig.java
index bae88d20d5cd1914954bc8a12a53d27404f64660..bb649a729aaa647fca5a3b630dc575eb64a5ab14 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerConfig.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerConfig.java
@@ -1,29 +1,35 @@
package org.dromara.northstar.gateway.tiger;
+import lombok.extern.slf4j.Slf4j;
import org.dromara.northstar.common.event.FastEventEngine;
import org.dromara.northstar.gateway.IMarketCenter;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import lombok.extern.slf4j.Slf4j;
-
@Slf4j
@Configuration
public class TigerConfig {
-
- static {
- log.info("=====================================================");
- log.info(" 加载gateway-tiger ");
- log.info("=====================================================");
- }
-
- @Bean
- TigerGatewayFactory tigerGatewayFactory(FastEventEngine feEngine, IMarketCenter marketCenter) {
- return new TigerGatewayFactory(feEngine, marketCenter);
- }
-
- @Bean
- TigerDataServiceManager tigerDataServiceManager() {
- return new TigerDataServiceManager();
- }
+
+ static {
+ log.info("=====================================================");
+ log.info(" 加载gateway-tiger ");
+ log.info("=====================================================");
+ }
+
+ @Bean
+ TigerDataServiceManager tigerDataServiceManager() {
+ return new TigerDataServiceManager();
+ }
+
+ @Bean
+ TigerGatewayFactory tigerGatewayFactory(FastEventEngine feEngine, IMarketCenter marketCenter,
+ @Qualifier("tigerDataServiceManager") TigerDataServiceManager dataMgr) {
+ return new TigerGatewayFactory(feEngine, marketCenter, dataMgr);
+ }
+
+ @Bean
+ TigerGatewaySettings tigerGatewaySettings() {
+ return new TigerGatewaySettings();
+ }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerContract.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerContract.java
index c9bc649d6325b0143d2d75d1c0d3f863d41d0805..aea39677eb0ace939c5eb9e4e9104da1126282ac 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerContract.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerContract.java
@@ -1,112 +1,116 @@
package org.dromara.northstar.gateway.tiger;
-import java.util.Objects;
-import java.util.Optional;
-
+import com.tigerbrokers.stock.openapi.client.https.domain.contract.item.ContractItem;
+import com.tigerbrokers.stock.openapi.client.https.domain.contract.item.TickSizeItem;
+import org.dromara.northstar.common.IDataSource;
import org.dromara.northstar.common.constant.ChannelType;
import org.dromara.northstar.common.model.Identifier;
+import org.dromara.northstar.common.model.core.Contract;
+import org.dromara.northstar.common.model.core.ContractDefinition;
import org.dromara.northstar.gateway.Instrument;
-import org.dromara.northstar.gateway.TradeTimeDefinition;
-import org.dromara.northstar.gateway.model.ContractDefinition;
-import org.dromara.northstar.gateway.tiger.time.CnStockTradeTime;
-import org.dromara.northstar.gateway.tiger.time.HkStockTradeTime;
-import org.dromara.northstar.gateway.tiger.time.UsStockTradeTime;
-
-import com.tigerbrokers.stock.openapi.client.https.domain.contract.item.ContractItem;
-
import xyz.redtorch.pb.CoreEnum.CurrencyEnum;
import xyz.redtorch.pb.CoreEnum.ExchangeEnum;
import xyz.redtorch.pb.CoreEnum.ProductClassEnum;
-import xyz.redtorch.pb.CoreField.ContractField;
-
-public class TigerContract implements Instrument{
-
- private ContractItem item;
-
- private ContractDefinition contractDef;
-
- public TigerContract(ContractItem item) {
- this.item = item;
- }
-
- @Override
- public String name() {
- return item.getName() + "-" + item.getSymbol();
- }
-
- @Override
- public Identifier identifier() {
- return Identifier.of(String.format("%s@%s@%s@%s", item.getSymbol(), exchange(), productClass(), channelType()));
- }
-
- @Override
- public ProductClassEnum productClass() {
- return switch(item.getSecType()) {
- case "STK" -> ProductClassEnum.EQUITY;
- case "OPT" -> ProductClassEnum.OPTION;
- case "FUT" -> ProductClassEnum.FUTURES;
- case "WAR" -> ProductClassEnum.WARRANTS;
- case "IOPT" -> ProductClassEnum.SPOTOPTION;
- default -> throw new IllegalArgumentException("Unexpected value: " + item.getSecType());
- };
- }
-
- @Override
- public ExchangeEnum exchange() {
- return switch(item.getExchange()) {
- case "SMART","VALUE" -> ExchangeEnum.SMART;
- case "SEHKSZSE" -> ExchangeEnum.SZSE;
- case "SEHKNTL" -> ExchangeEnum.SSE;
- case "SEHK" -> ExchangeEnum.SEHK;
- default -> throw new IllegalArgumentException("Unexpected value: " + item.getExchange());
- };
- }
-
- @Override
- public TradeTimeDefinition tradeTimeDefinition() {
- if(Objects.isNull(contractDef)) {
- throw new IllegalStateException(identifier().value() + " 没有合约定义信息");
- }
- return switch(contractDef.getTradeTimeType()) {
- case "CN_STK_TT" -> new CnStockTradeTime();
- case "HK_STK_TT" -> new HkStockTradeTime();
- case "US_STK_TT" -> new UsStockTradeTime();
- default -> throw new IllegalArgumentException("Unexpected value: " + contractDef.getTradeTimeType());
- };
- }
-
- @Override
- public ChannelType channelType() {
- return ChannelType.TIGER;
- }
-
- @Override
- public ContractField contractField() {
- return ContractField.newBuilder()
- .setGatewayId("TIGER")
- .setSymbol(item.getSymbol())
- .setUnifiedSymbol(String.format("%s@%s@%s", item.getSymbol(), exchange(), productClass()))
- .setName(item.getName())
- .setFullName(item.getName())
- .setCurrency(CurrencyEnum.valueOf(item.getCurrency()))
- .setExchange(exchange())
- .setProductClass(productClass())
- .setContractId(identifier().value())
- .setMultiplier(Optional.ofNullable(item.getMultiplier()).orElse(1D))
- .setPriceTick(item.getMinTick())
- .setLongMarginRatio(Optional.ofNullable(item.getLongInitialMargin()).orElse(0D))
- .setShortMarginRatio(Optional.ofNullable(item.getShortInitialMargin()).orElse(0D))
- .setLastTradeDateOrContractMonth(Optional.ofNullable(item.getContractMonth()).orElse(""))
- .setStrikePrice(Optional.ofNullable(item.getStrike()).orElse(0D))
- .setThirdPartyId(String.format("%s@TIGER", item.getSymbol()))
- .setCommissionFee(contractDef.getCommissionFee())
- .setCommissionRate(contractDef.getCommissionRate())
- .build();
- }
-
- @Override
- public void setContractDefinition(ContractDefinition contractDef) {
- this.contractDef = contractDef;
- }
-
+
+import java.util.List;
+import java.util.Optional;
+
+public class TigerContract implements Instrument {
+
+ private ContractItem item;
+
+ private ContractDefinition contractDef;
+
+ private IDataSource dataSrc;
+
+ public TigerContract(ContractItem item, IDataSource dataSrc) {
+ this.item = item;
+ this.dataSrc = dataSrc;
+ }
+
+ @Override
+ public String name() {
+ return item.getSymbol() + "-" + item.getName();
+ }
+
+ @Override
+ public Identifier identifier() {
+ return Identifier.of(String.format("%s-%s@%s@%s@%s", item.getSymbol(), item.getName(), exchange(), productClass(), channelType()));
+ }
+
+ @Override
+ public ProductClassEnum productClass() {
+ return switch (item.getSecType()) {
+ case "STK" -> ProductClassEnum.EQUITY;
+ case "OPT" -> ProductClassEnum.OPTION;
+ case "FUT" -> ProductClassEnum.FUTURES;
+ case "WAR" -> ProductClassEnum.WARRANTS;
+ case "IOPT" -> ProductClassEnum.SPOTOPTION;
+ default -> throw new IllegalArgumentException("Unexpected value: " + item.getSecType());
+ };
+ }
+
+ @Override
+ public ExchangeEnum exchange() {
+ //交易所(exchange),STK类型的合约一般不会用到交易所字段,订单会自动路由,期货合约都用到交易所字段。
+ if (item.getExchange() == null) {
+ return ExchangeEnum.SMART;
+ } else {
+ return switch (item.getExchange()) {
+ case "SMART", "VALUE" -> ExchangeEnum.SMART;
+ case "SEHKSZSE" -> ExchangeEnum.SZSE;
+ case "SEHKNTL" -> ExchangeEnum.SSE;
+ case "SEHK" -> ExchangeEnum.SEHK;
+ default -> throw new IllegalArgumentException("Unexpected value: " + item.getExchange());
+ };
+ }
+ }
+
+ @Override
+ public ChannelType channelType() {
+ return ChannelType.TIGER;
+ }
+
+ @Override
+ public Contract contract() {
+ // 检查是否有有效的 tickSizes 数据
+ Double minTick = item.getMinTick(); // 默认最小报价单位
+ List tickSizes = item.getTickSizes();
+
+ if ("STK".equals(item.getSecType())) {
+ // 假设我们使用第一个 tickSize 的 tickSize 作为最小报价单位
+ minTick = tickSizes.getFirst().getTickSize();
+ } else if (minTick == null && tickSizes != null) {
+ minTick = tickSizes.getFirst().getTickSize();
+ }
+ return Contract.builder()
+ .gatewayId(ChannelType.TIGER.toString())
+ .symbol(item.getSymbol())
+ .unifiedSymbol(String.format("%s@%s@%s", item.getSymbol(), exchange(), productClass()))
+ .name(item.getName())
+ .fullName(item.getName())
+ .currency(CurrencyEnum.valueOf(item.getCurrency()))
+ .exchange(exchange())
+ .productClass(productClass())
+ .contractId(identifier().value())
+ .multiplier(Optional.ofNullable(item.getMultiplier()).orElse(1D))
+ .priceTick(minTick)
+ .longMarginRatio(Optional.ofNullable(item.getLongInitialMargin()).orElse(0D))
+ .shortMarginRatio(Optional.ofNullable(item.getShortInitialMargin()).orElse(0D))
+ //.lastTradeDate(LocalDate.parse(Optional.ofNullable(item.getContractMonth()).orElse("")))
+ //.strikePrice(Optional.ofNullable(item.getStrike()).orElse(0D))
+ .thirdPartyId(String.format("%s@TIGER", item.getSymbol()))
+ .build();
+ }
+
+ @Override
+ public IDataSource dataSource() {
+ return dataSrc;
+ }
+
+ @Override
+ public void setContractDefinition(ContractDefinition contractDef) {
+ this.contractDef = contractDef;
+ }
+
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerContractProvider.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerContractProvider.java
index 934ded4f62a4ed2ff9f33915bfb2127b81a2074a..36e1006a0069a4d39f8970e4cabd00ec976ad13e 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerContractProvider.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerContractProvider.java
@@ -1,11 +1,5 @@
package org.dromara.northstar.gateway.tiger;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.dromara.northstar.gateway.IMarketCenter;
-
import com.tigerbrokers.stock.openapi.client.config.ClientConfig;
import com.tigerbrokers.stock.openapi.client.https.domain.contract.model.ContractsModel;
import com.tigerbrokers.stock.openapi.client.https.domain.quote.item.SymbolNameItem;
@@ -15,53 +9,90 @@ import com.tigerbrokers.stock.openapi.client.https.response.contract.ContractsRe
import com.tigerbrokers.stock.openapi.client.https.response.quote.QuoteSymbolNameResponse;
import com.tigerbrokers.stock.openapi.client.struct.enums.Language;
import com.tigerbrokers.stock.openapi.client.struct.enums.Market;
-
import lombok.extern.slf4j.Slf4j;
+import org.dromara.northstar.common.model.core.ContractDefinition;
+import org.dromara.northstar.common.model.core.TimeSlot;
+import org.dromara.northstar.common.model.core.TradeTimeDefinition;
+import org.dromara.northstar.common.utils.DateTimeUtils;
+import org.dromara.northstar.gateway.IMarketCenter;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
@Slf4j
+@Component
public class TigerContractProvider {
- private TigerGatewaySettings settings;
-
- private IMarketCenter mktCenter;
-
- public TigerContractProvider(TigerGatewaySettings settings, IMarketCenter mktCenter) {
- this.mktCenter = mktCenter;
- this.settings = settings;
- }
-
- /**
- * 加载可用合约
- */
- public void loadContractOptions() {
- ClientConfig clientConfig = ClientConfig.DEFAULT_CONFIG;
- clientConfig.tigerId = settings.getTigerId();
+ private TigerGatewaySettings settings;
+
+ private TigerDataServiceManager dataMgr;
+
+ private IMarketCenter mktCenter;
+
+ private TimeSlot allDay = TimeSlot.builder().start(DateTimeUtils.fromCacheTime(0, 0)).end(DateTimeUtils.fromCacheTime(0, 0)).build();
+
+ public TigerContractProvider(TigerGatewaySettings settings, IMarketCenter mktCenter, TigerDataServiceManager dataMgr) {
+ this.settings = settings;
+ this.mktCenter = mktCenter;
+ this.dataMgr = dataMgr;
+ }
+
+ /**
+ * 加载可用合约
+ */
+ public void loadContractOptions() {
+ ClientConfig clientConfig = ClientConfig.DEFAULT_CONFIG;
+ clientConfig.tigerId = settings.getTigerId();
clientConfig.defaultAccount = settings.getAccountId();
clientConfig.privateKey = settings.getPrivateKey();
clientConfig.license = settings.getLicense();
clientConfig.secretKey = settings.getSecretKey();
clientConfig.language = Language.zh_CN;
- TigerHttpClient client = TigerHttpClient.getInstance().clientConfig(clientConfig);
- doLoadContracts(Market.CN, client);
- doLoadContracts(Market.HK, client);
- doLoadContracts(Market.US, client);
- }
-
- private void doLoadContracts(Market market, TigerHttpClient client) {
- QuoteSymbolNameResponse response = client.execute(QuoteSymbolNameRequest.newRequest(market));
- if(!response.isSuccess()) {
- log.warn("TIGER 加载 [{}] 市场合约失败", market);
- return;
- }
- Map symbolNameMap = response.getSymbolNameItems().stream().collect(Collectors.toMap(item -> item.getSymbol(), item -> item.getName()));
- List symbols = response.getSymbolNameItems().stream().map(SymbolNameItem::getSymbol).toList();
- ContractsRequest contractsRequest = ContractsRequest.newRequest(new ContractsModel(symbols));
- ContractsResponse contractsResponse = client.execute(contractsRequest);
- contractsResponse.getItems().stream().map(item -> {
- item.setName(symbolNameMap.get(item.getSymbol()));
- return new TigerContract(item);
- }).forEach(c -> mktCenter.addInstrument(c));
- log.info("加载TIGER网关 [{}] 的合约{}个", market, contractsResponse.getItems().size());
- }
-
+ TigerHttpClient client = TigerHttpClient.getInstance().clientConfig(clientConfig);
+ doLoadContracts(Market.CN, client);
+ doLoadContracts(Market.HK, client);
+ doLoadContracts(Market.US, client);
+ }
+
+ private void doLoadContracts(Market market, TigerHttpClient client) {
+ QuoteSymbolNameResponse response = client.execute(QuoteSymbolNameRequest.newRequest(market));
+ if (!response.isSuccess()) {
+ log.warn("TIGER 加载 [{}] 市场合约失败", market);
+ return;
+ }
+ Map symbolNameMap = response.getSymbolNameItems().stream().collect(Collectors.toMap(SymbolNameItem::getSymbol, SymbolNameItem::getName));
+ List symbols = response.getSymbolNameItems().stream().map(SymbolNameItem::getSymbol).collect(Collectors.toList());
+ ContractsRequest contractsRequest = ContractsRequest.newRequest(new ContractsModel(symbols));
+ ContractsResponse contractsResponse = client.execute(contractsRequest);
+
+ // 创建合约定义列表
+ List contractDefs = contractsResponse.getItems().stream().map(item -> {
+ item.setName(symbolNameMap.get(item.getSymbol()));
+ TigerContract contract = new TigerContract(item, dataMgr);
+ return ContractDefinition.builder()
+ .name(contract.name())
+ .exchange(contract.exchange())
+ .productClass(contract.productClass())
+ .symbolPattern(Pattern.compile(contract.name() + "@[A-Z]+@[A-Z]+@[A-Z]+$"))
+ .commissionRate(3 / 10000D)
+ .dataSource(contract.dataSource())
+ .tradeTimeDef(TradeTimeDefinition.builder().timeSlots(List.of(allDay)).build())
+ .build();
+ }).collect(Collectors.toList());
+
+ // 增加合约定义
+ mktCenter.addDefinitions(contractDefs);
+
+ // 注册合约
+ contractsResponse.getItems().forEach(item -> {
+ TigerContract contract = new TigerContract(item, dataMgr);
+ mktCenter.addInstrument(contract);
+ });
+
+ log.info("加载TIGER网关 [{}] 的合约{}个", market, contractsResponse.getItems().size());
+ }
+
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerDataServiceManager.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerDataServiceManager.java
index db50bef2e493495a4f874bd4a14ccbfb2bd14351..73bee3262b825223b31dae56b6cfc381c8fe3645 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerDataServiceManager.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerDataServiceManager.java
@@ -1,50 +1,43 @@
package org.dromara.northstar.gateway.tiger;
+import org.dromara.northstar.common.IDataSource;
+import org.dromara.northstar.common.constant.ChannelType;
+import org.dromara.northstar.common.model.core.Bar;
+import org.dromara.northstar.common.model.core.Contract;
+
import java.time.LocalDate;
import java.util.List;
-import org.dromara.northstar.common.IDataServiceManager;
-
-import xyz.redtorch.pb.CoreEnum.ExchangeEnum;
-import xyz.redtorch.pb.CoreField.BarField;
-import xyz.redtorch.pb.CoreField.ContractField;
-
-public class TigerDataServiceManager implements IDataServiceManager{
-
- @Override
- public List getMinutelyData(ContractField contract, LocalDate startDate, LocalDate endDate) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getQuarterlyData(ContractField contract, LocalDate startDate, LocalDate endDate) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getHourlyData(ContractField contract, LocalDate startDate, LocalDate endDate) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getDailyData(ContractField contract, LocalDate startDate, LocalDate endDate) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getHolidays(ExchangeEnum exchange, LocalDate startDate, LocalDate endDate) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getAllContracts(ExchangeEnum exchange) {
- // TODO Auto-generated method stub
- return null;
- }
+public class TigerDataServiceManager implements IDataSource {
+
+
+ @Override
+ public List getMinutelyData(Contract contract, LocalDate startDate, LocalDate endDate) {
+ return List.of();
+ }
+
+ @Override
+ public List getQuarterlyData(Contract contract, LocalDate startDate, LocalDate endDate) {
+ return List.of();
+ }
+
+ @Override
+ public List getHourlyData(Contract contract, LocalDate startDate, LocalDate endDate) {
+ return List.of();
+ }
+
+ @Override
+ public List getDailyData(Contract contract, LocalDate startDate, LocalDate endDate) {
+ return List.of();
+ }
+
+ @Override
+ public List getHolidays(ChannelType channelType, LocalDate startDate, LocalDate endDate) {
+ return List.of();
+ }
+ @Override
+ public List getAllContracts() {
+ return List.of();
+ }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewayFactory.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewayFactory.java
index b28d5ec4d4540d726617de1e145ebe7c0e374412..a11aa00450424482ca5708fc25eec7d37393fb9e 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewayFactory.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewayFactory.java
@@ -1,40 +1,43 @@
package org.dromara.northstar.gateway.tiger;
+import com.alibaba.fastjson.JSON;
import org.dromara.northstar.common.event.FastEventEngine;
import org.dromara.northstar.common.model.GatewayDescription;
import org.dromara.northstar.gateway.Gateway;
import org.dromara.northstar.gateway.GatewayFactory;
+import org.dromara.northstar.gateway.IContractManager;
import org.dromara.northstar.gateway.IMarketCenter;
-import com.alibaba.fastjson.JSON;
+public class TigerGatewayFactory implements GatewayFactory {
+
+ private FastEventEngine feEngine;
+
+ private IMarketCenter mktCenter;
+
+ private TigerDataServiceManager dataMgr;
+
+ private boolean contractLoaded;
+
+ public TigerGatewayFactory(FastEventEngine feEngine, IMarketCenter mktCenter, TigerDataServiceManager dataMgr) {
+ this.feEngine = feEngine;
+ this.mktCenter = mktCenter;
+ this.dataMgr = dataMgr;
+ }
+
+ @Override
+ public Gateway newInstance(GatewayDescription gatewayDescription) {
+ TigerGatewaySettings settings = JSON.parseObject(JSON.toJSONString(gatewayDescription.getSettings()), TigerGatewaySettings.class);
+ gatewayDescription.setSettings(settings);
+ if (!contractLoaded) {
+ new TigerContractProvider(settings, mktCenter,dataMgr).loadContractOptions();
+ contractLoaded = true;
+ }
-public class TigerGatewayFactory implements GatewayFactory{
-
- private FastEventEngine feEngine;
-
- private IMarketCenter mktCenter;
-
- private boolean contractLoaded;
-
- public TigerGatewayFactory(FastEventEngine feEngine, IMarketCenter mktCenter) {
- this.feEngine = feEngine;
- this.mktCenter = mktCenter;
- }
-
- @Override
- public Gateway newInstance(GatewayDescription gatewayDescription) {
- TigerGatewaySettings settings = JSON.parseObject(JSON.toJSONString(gatewayDescription.getSettings()), TigerGatewaySettings.class);
- gatewayDescription.setSettings(settings);
- if(!contractLoaded) {
- new TigerContractProvider(settings, mktCenter).loadContractOptions();
- contractLoaded = true;
- }
-
- return switch(gatewayDescription.getGatewayUsage()) {
- case MARKET_DATA -> new TigerMarketGatewayAdapter(gatewayDescription, feEngine, mktCenter);
- case TRADE -> new TigerTradeGatewayAdapter(feEngine, gatewayDescription, mktCenter);
- default -> throw new IllegalArgumentException("未知网关用途:" + gatewayDescription.getGatewayUsage());
- };
- }
+ return switch (gatewayDescription.getGatewayUsage()) {
+ case MARKET_DATA -> new TigerMarketGatewayAdapter(gatewayDescription, feEngine, mktCenter);
+ case TRADE -> new TigerTradeGatewayAdapter(feEngine, gatewayDescription, mktCenter);
+ default -> throw new IllegalArgumentException("未知网关用途:" + gatewayDescription.getGatewayUsage());
+ };
+ }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewaySettings.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewaySettings.java
index 80e574cd059285db5873c871ed55693c2eb800d7..4689c4ee9380ce06b36be9fe764124208cdcebf4 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewaySettings.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerGatewaySettings.java
@@ -1,32 +1,30 @@
package org.dromara.northstar.gateway.tiger;
-import org.dromara.northstar.common.constant.FieldType;
-import org.dromara.northstar.common.model.DynamicParams;
-import org.dromara.northstar.common.model.GatewaySettings;
-import org.dromara.northstar.common.model.Setting;
-
import com.tigerbrokers.stock.openapi.client.struct.enums.License;
-
import lombok.Getter;
import lombok.Setter;
+import org.dromara.northstar.common.GatewaySettings;
+import org.dromara.northstar.common.constant.FieldType;
+import org.dromara.northstar.common.model.DynamicParams;
+import org.dromara.northstar.common.model.Setting;
@Getter
@Setter
public class TigerGatewaySettings extends DynamicParams implements GatewaySettings {
- @Setting(label="用户ID", order=10, type=FieldType.TEXT)
- private String tigerId;
-
- @Setting(label="账户ID", order=20, type=FieldType.TEXT)
- private String accountId;
-
- @Setting(label="RSA私钥", order=30, type=FieldType.TEXT)
- private String privateKey;
-
- @Setting(label="证书类型", order=40, type=FieldType.SELECT, optionsVal = {"TBNZ", "TBSG"})
- private License license;
-
- @Setting(label="secretKey", order=50, type=FieldType.TEXT, required = false)
- private String secretKey;
-
+ @Setting(label = "用户ID", order = 10, type = FieldType.TEXT)
+ private String tigerId;
+
+ @Setting(label = "账户ID", order = 20, type = FieldType.TEXT)
+ private String accountId;
+
+ @Setting(label = "RSA私钥", order = 30, type = FieldType.TEXT)
+ private String privateKey;
+
+ @Setting(label = "证书类型", order = 40, type = FieldType.SELECT, optionsVal = {"TBNZ", "TBSG"})
+ private License license;
+
+ @Setting(label = "secretKey", order = 50, type = FieldType.TEXT, required = false)
+ private String secretKey;
+
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerHttpClient.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerHttpClient.java
index 16d5da95ada257c3d9b4f5027178fa2739905789..870ec1537f9a988ac0009babfa9900124194e18b 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerHttpClient.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerHttpClient.java
@@ -3,403 +3,347 @@ package org.dromara.northstar.gateway.tiger;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.alibaba.fastjson2.JSONWriter.Feature;
import com.tigerbrokers.stock.openapi.client.TigerApiException;
import com.tigerbrokers.stock.openapi.client.config.ClientConfig;
import com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants;
import com.tigerbrokers.stock.openapi.client.https.client.TigerClient;
+import com.tigerbrokers.stock.openapi.client.https.client.TokenManager;
import com.tigerbrokers.stock.openapi.client.https.domain.ApiModel;
import com.tigerbrokers.stock.openapi.client.https.domain.BatchApiModel;
-import com.tigerbrokers.stock.openapi.client.https.domain.contract.item.ContractItem;
import com.tigerbrokers.stock.openapi.client.https.domain.trade.model.TradeOrderModel;
-import com.tigerbrokers.stock.openapi.client.https.domain.user.item.LicenseItem;
+import com.tigerbrokers.stock.openapi.client.https.request.TigerCommonRequest;
import com.tigerbrokers.stock.openapi.client.https.request.TigerHttpRequest;
import com.tigerbrokers.stock.openapi.client.https.request.TigerRequest;
+import com.tigerbrokers.stock.openapi.client.https.request.user.UserLicenseRequest;
import com.tigerbrokers.stock.openapi.client.https.response.TigerHttpResponse;
import com.tigerbrokers.stock.openapi.client.https.response.TigerResponse;
-import com.tigerbrokers.stock.openapi.client.https.response.contract.ContractResponse;
+import com.tigerbrokers.stock.openapi.client.https.response.user.UserLicenseResponse;
import com.tigerbrokers.stock.openapi.client.https.validator.ValidatorManager;
-import com.tigerbrokers.stock.openapi.client.struct.enums.AccountType;
-import com.tigerbrokers.stock.openapi.client.struct.enums.BizType;
-import com.tigerbrokers.stock.openapi.client.struct.enums.Env;
-import com.tigerbrokers.stock.openapi.client.struct.enums.License;
-import com.tigerbrokers.stock.openapi.client.struct.enums.MethodName;
-import com.tigerbrokers.stock.openapi.client.struct.enums.MethodType;
-import com.tigerbrokers.stock.openapi.client.struct.enums.TigerApiCode;
-import com.tigerbrokers.stock.openapi.client.util.AccountUtil;
-import com.tigerbrokers.stock.openapi.client.util.ApiLogger;
-import com.tigerbrokers.stock.openapi.client.util.FastJsonPropertyFilter;
-import com.tigerbrokers.stock.openapi.client.util.HttpUtils;
-import com.tigerbrokers.stock.openapi.client.util.NetworkUtil;
-import com.tigerbrokers.stock.openapi.client.util.SdkVersionUtils;
-import com.tigerbrokers.stock.openapi.client.util.StringUtils;
-import com.tigerbrokers.stock.openapi.client.util.TigerSignature;
+import com.tigerbrokers.stock.openapi.client.struct.enums.*;
+import com.tigerbrokers.stock.openapi.client.util.*;
import com.tigerbrokers.stock.openapi.client.util.builder.AccountParamBuilder;
+
import java.security.Security;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.ACCESS_TOKEN;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.ACCOUNT_TYPE;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.BIZ_CONTENT;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.CHARSET;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.DEVICE_ID;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.METHOD;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.SDK_VERSION;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.SIGN;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.SIGN_TYPE;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.TIGER_ID;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.TIMESTAMP;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.TRADE_TOKEN;
-import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.VERSION;
-import static com.tigerbrokers.stock.openapi.client.https.request.TigerCommonRequest.V2_0;
+import static com.tigerbrokers.stock.openapi.client.constant.TigerApiConstants.*;
public class TigerHttpClient implements TigerClient {
- private String serverUrl;
- private String quoteServerUrl;
- private String paperServerUrl;
- private String tigerId;
- private String privateKey;
- private String tigerPublicKey;
- private String accessToken;
- private String tradeToken;
- private String accountType;
- private String deviceId;
-
- private static final String ONLINE_PUBLIC_KEY =
- "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNF3G8SoEcCZh2rshUbayDgLLrj6rKgzNMxDL2HSnKcB0+GPOsndqSv+a4IBu9+I3fyBp5hkyMMG2+AXugd9pMpy6VxJxlNjhX1MYbNTZJUT4nudki4uh+LMOkIBHOceGNXjgB+cXqmlUnjlqha/HgboeHSnSgpM3dKSJQlIOsDwIDAQAB";
-
- private static final String SANDBOX_PUBLIC_KEY =
- "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbm21i11hgAENGd3/f280PSe4g9YGkS3TEXBYMidihTvHHf+tJ0PYD0o3PruI0hl3qhEjHTAxb75T5YD3SGK4IBhHn/Rk6mhqlGgI+bBrBVYaXixmHfRo75RpUUuWACyeqQkZckgR0McxuW9xRMIa2cXZOoL1E4SL4lXKGhKoWbwIDAQAB";
-
- private String signType = TigerApiConstants.SIGN_TYPE_RSA;
- private String charset = TigerApiConstants.CHARSET_UTF8;
-
- private static final long REFRESH_URL_INTERVAL_SECONDS = 300;
- private ScheduledThreadPoolExecutor domainExecutorService;
-
- static {
- Security.setProperty("jdk.certpath.disabledAlgorithms", "");
- }
-
- private TigerHttpClient() {
- }
-
- private static class SingletonInner {
- private static TigerHttpClient singleton = new TigerHttpClient();
- }
-
- /**
- * get TigerHttpClient instance
- * @return TigerHttpClient
- */
- public static TigerHttpClient getInstance() {
- return TigerHttpClient.SingletonInner.singleton;
- }
-
- public TigerHttpClient clientConfig(ClientConfig clientConfig) {
- init(clientConfig.serverUrl, clientConfig.tigerId, clientConfig.privateKey);
- initDomainRefreshTask();
- if (clientConfig.isAutoGrabPermission) {
- TigerHttpRequest request = new TigerHttpRequest(MethodName.GRAB_QUOTE_PERMISSION);
- request.setBizContent(AccountParamBuilder.instance().buildJsonWithoutDefaultAccount());
- TigerHttpResponse response = execute(request);
- ApiLogger.info("tigerId:{}, grab_quote_permission:{}, data:{}",
- tigerId, response.getMessage(), response.getData());
- }
- return this;
- }
-
- /** please use TigerHttpClient.getInstance().clientConfig(ClientConfig.DEFAULT_CONFIG) */
- @Deprecated
- public TigerHttpClient(String serverUrl, String tigerId, String privateKey) {
- init(serverUrl, tigerId, privateKey);
- }
-
- private void init(String serverUrl, String tigerId, String privateKey) {
- if (tigerId == null) {
- throw new RuntimeException("tigerId is empty.");
+ private String serverUrl;
+ private String quoteServerUrl;
+ private String paperServerUrl;
+ private String tigerId;
+ private String privateKey;
+ private String tigerPublicKey;
+ private String accessToken;
+ private String tradeToken;
+ private String accountType;
+ private String deviceId;
+ private int failRetryCounts = TigerApiConstants.DEFAULT_FAIL_RETRY_COUNT;
+
+ private static final String ONLINE_PUBLIC_KEY =
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNF3G8SoEcCZh2rshUbayDgLLrj6rKgzNMxDL2HSnKcB0+GPOsndqSv+a4IBu9+I3fyBp5hkyMMG2+AXugd9pMpy6VxJxlNjhX1MYbNTZJUT4nudki4uh+LMOkIBHOceGNXjgB+cXqmlUnjlqha/HgboeHSnSgpM3dKSJQlIOsDwIDAQAB";
+
+ private static final String SANDBOX_PUBLIC_KEY =
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbm21i11hgAENGd3/f280PSe4g9YGkS3TEXBYMidihTvHHf+tJ0PYD0o3PruI0hl3qhEjHTAxb75T5YD3SGK4IBhHn/Rk6mhqlGgI+bBrBVYaXixmHfRo75RpUUuWACyeqQkZckgR0McxuW9xRMIa2cXZOoL1E4SL4lXKGhKoWbwIDAQAB";
+
+ private String signType = TigerApiConstants.SIGN_TYPE_RSA;
+ private String charset = TigerApiConstants.UTF_8;
+
+ private static final long REFRESH_URL_INTERVAL_SECONDS = 300;
+ private ScheduledThreadPoolExecutor domainExecutorService;
+
+ static {
+ Security.setProperty("jdk.certpath.disabledAlgorithms", "");
}
- if (privateKey == null) {
- throw new RuntimeException("privateKey is empty.");
+
+ private TigerHttpClient() {
}
- this.tigerId = tigerId;
- this.privateKey = privateKey;
- if (ClientConfig.DEFAULT_CONFIG.getEnv() == Env.PROD) {
- this.tigerPublicKey = ONLINE_PUBLIC_KEY;
- } else {
- this.tigerPublicKey = SANDBOX_PUBLIC_KEY;
+
+ private static class SingletonInner {
+ private static TigerHttpClient singleton = new TigerHttpClient();
}
- this.deviceId = NetworkUtil.getDeviceId();
- initLicense();
- if (Env.PROD == ClientConfig.DEFAULT_CONFIG.getEnv() || StringUtils.isEmpty(serverUrl)) {
- refreshUrl();
- } else {
- this.serverUrl = serverUrl;
+ /**
+ * get TigerHttpClient instance
+ * @return TigerHttpClient
+ */
+ public static TigerHttpClient getInstance() {
+ return TigerHttpClient.SingletonInner.singleton;
}
- if (this.serverUrl == null) {
- throw new RuntimeException("serverUrl is empty.");
+
+ public TigerHttpClient clientConfig(ClientConfig clientConfig) {
+ ConfigFileUtil.loadConfigFile(clientConfig);
+ init(clientConfig.tigerId, clientConfig.privateKey);
+ if (clientConfig.failRetryCounts <= TigerApiConstants.MAX_FAIL_RETRY_COUNT) {
+ this.failRetryCounts = Math.max(clientConfig.failRetryCounts, 0);
+ }
+ TokenManager.getInstance().init(clientConfig);
+ initDomainRefreshTask();
+ if (clientConfig.isAutoGrabPermission) {
+ TigerHttpRequest request = new TigerHttpRequest(MethodName.GRAB_QUOTE_PERMISSION);
+ request.setBizContent(AccountParamBuilder.instance().buildJsonWithoutDefaultAccount());
+ TigerHttpResponse response = execute(request);
+ ApiLogger.info("tigerId:{}, grab_quote_permission:{}, data:{}",
+ tigerId, response.getMessage(), response.getData());
+ }
+ return this;
}
- }
-
- @Deprecated
- public TigerHttpClient(String serverUrl) {
- this.serverUrl = serverUrl;
- }
-
- @Deprecated
- public TigerHttpClient(String serverUrl, String accessToken) {
- this.serverUrl = serverUrl;
- this.accessToken = accessToken;
- }
-
- private void initLicense() {
- if (null == ClientConfig.DEFAULT_CONFIG.license) {
- try {
- Map urlMap = NetworkUtil.getHttpServerAddress(null, this.serverUrl);
- this.serverUrl = urlMap.get(BizType.COMMON);
- TigerHttpRequest request = new TigerHttpRequest(MethodName.USER_LICENSE);
- request.setBizContent(AccountParamBuilder.instance().buildJsonWithoutDefaultAccount());
- TigerHttpResponse response = execute(request);
- if (response.isSuccess()) {
- LicenseItem data = JSON.parseObject(response.getData(), LicenseItem.class);
- ApiLogger.debug("license:{}", data);
- ClientConfig.DEFAULT_CONFIG.license = License.valueOf(data.getLicense());
+
+ private void init(String tigerId, String privateKey) {
+ if (tigerId == null) {
+ throw new RuntimeException("tigerId is empty.");
+ }
+ if (privateKey == null) {
+ throw new RuntimeException("privateKey is empty.");
+ }
+ this.tigerId = tigerId;
+ this.privateKey = privateKey;
+ if (ClientConfig.DEFAULT_CONFIG.getEnv() == Env.PROD) {
+ this.tigerPublicKey = ONLINE_PUBLIC_KEY;
+ } else {
+ this.tigerPublicKey = SANDBOX_PUBLIC_KEY;
+ }
+ this.deviceId = NetworkUtil.getDeviceId();
+
+ initLicense();
+ refreshUrl();
+ if (this.serverUrl == null) {
+ throw new RuntimeException("serverUrl is empty.");
}
- } catch (Exception e) {
- ApiLogger.debug("get license fail. tigerId:{}", tigerId);
- }
}
- }
-
- private void initDomainRefreshTask() {
- synchronized (TigerHttpClient.SingletonInner.singleton) {
- if (domainExecutorService == null || domainExecutorService.isTerminated()) {
- domainExecutorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread t = Executors.defaultThreadFactory().newThread(r);
- t.setDaemon(true);
- return t;
- }
- });
- domainExecutorService.scheduleWithFixedDelay(
- new Runnable() {
- @Override
- public void run() {
- refreshUrl();
- }
- }, REFRESH_URL_INTERVAL_SECONDS,
- REFRESH_URL_INTERVAL_SECONDS, TimeUnit.SECONDS);
- }
+
+ public TigerHttpClient accessToken(String accessToken) {
+ this.accessToken = accessToken;
+ return this;
}
- }
-
- private void refreshUrl() {
- try {
- Map urlMap = NetworkUtil.getHttpServerAddress(ClientConfig.DEFAULT_CONFIG.license, this.serverUrl);
- String newServerUrl = urlMap.get(BizType.TRADE);
- if (newServerUrl == null) {
- newServerUrl = urlMap.get(BizType.COMMON);
- }
- String newQuoteServerUrl = urlMap.get(BizType.QUOTE) == null ? newServerUrl : urlMap.get(BizType.QUOTE);
- String newPaperServerUrl = urlMap.get(BizType.PAPER) == null ? newServerUrl : urlMap.get(BizType.PAPER);
-
- this.serverUrl = newServerUrl;
- this.quoteServerUrl = newQuoteServerUrl;
- this.paperServerUrl = newPaperServerUrl;
- } catch (Throwable t) {
- ApiLogger.error("refresh serverUrl error", t);
+
+ private void initLicense() {
+ if (null == ClientConfig.DEFAULT_CONFIG.license) {
+ try {
+ Map urlMap = NetworkUtil.getHttpServerAddress(null, this.serverUrl);
+ this.serverUrl = urlMap.get(BizType.COMMON);
+ UserLicenseRequest request = UserLicenseRequest.newRequest();
+ UserLicenseResponse response = execute(request);
+ if (response.isSuccess() && response.getLicenseItem() != null) {
+ ApiLogger.debug("license:{}", JSON.toJSONString(response.getLicenseItem(), SerializerFeature.WriteEnumUsingToString));
+ ClientConfig.DEFAULT_CONFIG.license = License.valueOf(response.getLicenseItem().getLicense());
+ }
+ } catch (Exception e) {
+ ApiLogger.debug("get license fail. tigerId:{}", tigerId);
+ }
+ }
}
- }
- public String getAccessToken() {
- return accessToken;
- }
+ private void initDomainRefreshTask() {
+ synchronized (TigerHttpClient.SingletonInner.singleton) {
+ if (domainExecutorService == null || domainExecutorService.isTerminated()) {
+ domainExecutorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = Executors.defaultThreadFactory().newThread(r);
+ t.setDaemon(true);
+ return t;
+ }
+ });
+ domainExecutorService.scheduleWithFixedDelay(
+ new Runnable() {
+ @Override
+ public void run() {
+ refreshUrl();
+ }
+ }, REFRESH_URL_INTERVAL_SECONDS,
+ REFRESH_URL_INTERVAL_SECONDS, TimeUnit.SECONDS);
+ }
+ }
+ }
- public void setAccessToken(String accessToken) {
- this.accessToken = accessToken;
- }
+ private void refreshUrl() {
+ try {
+ Map urlMap = NetworkUtil.getHttpServerAddress(ClientConfig.DEFAULT_CONFIG.license, this.serverUrl);
+ String newServerUrl = urlMap.get(BizType.TRADE);
+ if (newServerUrl == null) {
+ newServerUrl = urlMap.get(BizType.COMMON);
+ }
+ String newQuoteServerUrl = urlMap.get(BizType.QUOTE) == null ? newServerUrl : urlMap.get(BizType.QUOTE);
+ String newPaperServerUrl = urlMap.get(BizType.PAPER) == null ? newServerUrl : urlMap.get(BizType.PAPER);
+
+ this.serverUrl = newServerUrl;
+ this.quoteServerUrl = newQuoteServerUrl;
+ this.paperServerUrl = newPaperServerUrl;
+ } catch (Throwable t) {
+ ApiLogger.error("refresh serverUrl error", t);
+ }
+ }
- public void setTradeToken(String tradeToken) {
- this.tradeToken = tradeToken;
- }
+ public String getAccessToken() {
+ return accessToken;
+ }
- public String getTradeToken() {
- return tradeToken;
- }
+ public void setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ }
- public void setAccountType(AccountType accountType) {
- if (accountType != null) {
- this.accountType = accountType.name();
+ public void setTradeToken(String tradeToken) {
+ this.tradeToken = tradeToken;
}
- }
-
- public String getAccountType() {
- return accountType;
- }
-
- @Override
- public T execute(TigerRequest request) {
- T response;
- String param = null;
- String data = null;
- try {
- validate(request);
- // after successful verification(string enumeration values may be reset), generate JSON data
- param = JSON.toJSONString(buildParams(request), SerializerFeature.WriteEnumUsingToString);
- ApiLogger.debug("request param:{}", param);
-
- data = HttpUtils.post(getServerUrl(request), param);
-
- if (StringUtils.isEmpty(data)) {
- throw new TigerApiException(TigerApiCode.EMPTY_DATA_ERROR);
- }
- response = JSON.parseObject(data, request.getResponseClass());
- if (MethodName.CONTRACT == request.getApiMethodName()) {
- convertContractItem(response, request.getApiVersion());
- }
- if (StringUtils.isEmpty(this.tigerPublicKey) || response.getSign() == null) {
- return response;
- }
- boolean signSuccess =
- TigerSignature.rsaCheckContent(request.getTimestamp(), response.getSign(), this.tigerPublicKey, this.charset);
-
- if (!signSuccess) {
- throw new TigerApiException(TigerApiCode.SIGN_CHECK_FAILED);
- }
- return response;
- } catch (RuntimeException e) {
- ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), param, data, e);
- return errorResponse(tigerId, request, e);
- } catch (TigerApiException e) {
- ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), param, data, e);
- return errorResponse(tigerId, request, e);
- } catch (Exception e) {
- ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), param, data, e);
- return errorResponse(tigerId, request, e);
+
+ public String getTradeToken() {
+ return tradeToken;
}
- }
-
- private void convertContractItem(TigerResponse response, String apiVersion) {
- if (response instanceof ContractResponse) {
- ContractResponse contractResponse = (ContractResponse) response;
- if (StringUtils.isEmpty(contractResponse.getData())) {
- return;
- }
- if (V2_0.equals(apiVersion)) {
- List items = ContractItem.convertFromJsonV2(contractResponse.getData());
- contractResponse.setItems(items);
- if (items != null && items.size() > 0) {
- contractResponse.setItem(items.get(0));
+
+ public void setAccountType(AccountType accountType) {
+ if (accountType != null) {
+ this.accountType = accountType.name();
}
- } else {
- contractResponse.setItem(ContractItem.convertFromJson(contractResponse.getData()));
- }
- }
- }
-
- private T errorResponse(String tigerId, TigerRequest request, TigerApiException e) {
- try {
- ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), e);
-
- T response = request.getResponseClass().newInstance();
- response.setCode(e.getErrCode());
- response.setMessage(e.getErrMsg());
- return response;
- } catch (Exception e1) {
- ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), e1);
- return null;
- }
- }
-
- private T errorResponse(String tigerId, TigerRequest request, Exception e) {
- try {
- ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), e);
-
- T response = request.getResponseClass().newInstance();
- response.setCode(TigerApiCode.CLIENT_API_ERROR.getCode());
- response.setMessage(TigerApiCode.CLIENT_API_ERROR.getMessage() + "(" + e.getMessage() + ")");
- return response;
- } catch (Exception e1) {
- ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), e1);
- return null;
- }
- }
-
- private Map buildParams(TigerRequest request) {
- Map params = new HashMap<>();
- params.put(METHOD, request.getApiMethodName().getValue());
- params.put(VERSION, request.getApiVersion());
- params.put(SDK_VERSION, SdkVersionUtils.getSdkVersion());
- if (request instanceof TigerHttpRequest) {
- params.put(BIZ_CONTENT, ((TigerHttpRequest) request).getBizContent());
- } else {
- ApiModel apiModel = request.getApiModel();
- if (apiModel instanceof BatchApiModel) {
- params.put(BIZ_CONTENT, JSON.toJSONString(((BatchApiModel) apiModel).getItems(), SerializerFeature.WriteEnumUsingToString));
- } else if (apiModel instanceof TradeOrderModel) {
- params.put(BIZ_CONTENT, JSON.toJSONString(apiModel, SerializerFeature.WriteEnumUsingToString));
- } else {
- params.put(BIZ_CONTENT, JSON.toJSONString(apiModel, SerializerFeature.WriteEnumUsingToString));
- }
}
- params.put(TIMESTAMP, request.getTimestamp());
- params.put(CHARSET, this.charset);
- params.put(TIGER_ID, this.tigerId);
- params.put(SIGN_TYPE, this.signType);
- if (this.accessToken != null) {
- params.put(ACCESS_TOKEN, this.accessToken);
+
+ public String getAccountType() {
+ return accountType;
}
- if (this.tradeToken != null) {
- params.put(TRADE_TOKEN, this.tradeToken);
+
+ @Override
+ public T execute(TigerRequest request) {
+ T response;
+ String param = null;
+ String data = null;
+ try {
+ validate(request);
+ // after successful verification(string enumeration values may be reset), generate JSON data
+ param = JSONObject.toJSONString(buildParams(request), SerializerFeature.WriteEnumUsingToString);
+ ApiLogger.debug("request param:{}", param);
+
+ data = HttpUtils.post(getServerUrl(request), param,
+ MethodName.PLACE_ORDER == request.getApiMethodName() ? 0 : failRetryCounts);
+
+ ApiLogger.debug("response result:{}", data);
+ if (StringUtils.isEmpty(data)) {
+ throw new TigerApiException(TigerApiCode.EMPTY_DATA_ERROR);
+ }
+ response = JSON.parseObject(data, request.getResponseClass());
+ if (StringUtils.isEmpty(this.tigerPublicKey) || response.getSign() == null) {
+ return response;
+ }
+ boolean signSuccess =
+ TigerSignature.rsaCheckContent(request.getTimestamp(), response.getSign(), this.tigerPublicKey, this.charset);
+
+ if (!signSuccess) {
+ throw new TigerApiException(TigerApiCode.SIGN_CHECK_FAILED);
+ }
+ return response;
+ } catch (RuntimeException e) {
+ ApiLogger.error("request fail. tigerId:{}, method:{}, param:{}, response:{}",
+ tigerId, request == null ? null : request.getApiMethodName(), param, data, e);
+ return errorResponse(tigerId, request, e);
+ } catch (TigerApiException e) {
+ ApiLogger.error("request fail. tigerId:{}, method:{}, param:{}, response:{}",
+ tigerId, request == null ? null : request.getApiMethodName(), param, data, e);
+ return errorResponse(tigerId, request, e);
+ } catch (Exception e) {
+ ApiLogger.error("request fail. tigerId:{}, method:{}, param:{}, response:{}",
+ tigerId, request == null ? null : request.getApiMethodName(), param, data, e);
+ return errorResponse(tigerId, request, e);
+ }
}
- if (this.accountType != null) {
- params.put(ACCOUNT_TYPE, this.accountType);
+
+ private T errorResponse(String tigerId, TigerRequest request, TigerApiException e) {
+ try {
+ T response = request.getResponseClass().newInstance();
+ response.setCode(e.getErrCode());
+ response.setMessage(e.getErrMsg());
+ return response;
+ } catch (Exception e1) {
+ ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), e1);
+ return null;
+ }
}
- if (this.deviceId != null) {
- params.put(DEVICE_ID, this.deviceId);
+
+ private T errorResponse(String tigerId, TigerRequest request, Exception e) {
+ try {
+ T response = request.getResponseClass().newInstance();
+ response.setCode(TigerApiCode.CLIENT_API_ERROR.getCode());
+ response.setMessage(TigerApiCode.CLIENT_API_ERROR.getMessage() + "(" + e.getMessage() + ")");
+ return response;
+ } catch (Exception e1) {
+ ApiLogger.error(tigerId, request.getApiMethodName(), request.getApiVersion(), e1);
+ return null;
+ }
}
- if (this.tigerId != null) {
- String content = TigerSignature.getSignContent(params);
- params.put(SIGN, TigerSignature.rsaSign(content, privateKey, charset));
+
+ private Map buildParams(TigerRequest request) {
+ Map params = new HashMap<>();
+ params.put(METHOD, request.getApiMethodName().getValue());
+ params.put(VERSION, request.getApiVersion());
+ params.put(SDK_VERSION, SdkVersionUtils.getSdkVersion());
+ if (request instanceof TigerHttpRequest) {
+ params.put(BIZ_CONTENT, ((TigerHttpRequest) request).getBizContent());
+ } else if (request.getApiModel() == null && request instanceof TigerCommonRequest) {
+ params.put(BIZ_CONTENT, ((TigerCommonRequest) request).getBizContent());
+ } else {
+ ApiModel apiModel = request.getApiModel();
+ if (apiModel instanceof BatchApiModel) {
+ params.put(BIZ_CONTENT, JSONObject.toJSONString(((BatchApiModel) apiModel).getItems(), SerializerFeature.WriteEnumUsingToString));
+ } else if (apiModel instanceof TradeOrderModel) {
+ params.put(BIZ_CONTENT, JSONObject.toJSONString(apiModel));
+ } else {
+ params.put(BIZ_CONTENT, JSONObject.toJSONString(apiModel, SerializerFeature.WriteEnumUsingToString));
+ }
+ }
+ params.put(TIMESTAMP, request.getTimestamp());
+ params.put(CHARSET, this.charset);
+ params.put(TIGER_ID, this.tigerId);
+ params.put(SIGN_TYPE, this.signType);
+ if (this.accessToken != null) {
+ params.put(ACCESS_TOKEN, this.accessToken);
+ }
+ if (this.tradeToken != null) {
+ params.put(TRADE_TOKEN, this.tradeToken);
+ }
+ if (this.accountType != null) {
+ params.put(ACCOUNT_TYPE, this.accountType);
+ }
+ if (this.deviceId != null) {
+ params.put(DEVICE_ID, this.deviceId);
+ }
+ if (this.tigerId != null) {
+ String content = TigerSignature.getSignContent(params);
+ params.put(SIGN, TigerSignature.rsaSign(content, privateKey, charset));
+ }
+
+ return params;
}
- return params;
- }
-
- /**
- * validate parameters
- * @param request
- * @throws TigerApiException
- */
- private void validate(TigerRequest request) throws TigerApiException {
- if (request instanceof TigerHttpRequest) {
- return;
+ /**
+ * validate parameters
+ * @param request
+ * @throws TigerApiException
+ */
+ private void validate(TigerRequest request) throws TigerApiException {
+ if (request instanceof TigerHttpRequest) {
+ return;
+ }
+ // TigerCommonRequest
+ ValidatorManager.getInstance().validate(request.getApiModel());
}
- // TigerCommonRequest
- ValidatorManager.getInstance().validate(request.getApiModel());
- }
-
- private String getServerUrl(TigerRequest request) {
- String url = null;
- MethodType methodType = request.getApiMethodName().getType();
- if (MethodType.QUOTE == methodType) {
- url = this.quoteServerUrl;
- } else if (MethodType.TRADE == methodType && paperServerUrl != null) {
- String account = AccountUtil.parseAccount(request);
- if (AccountUtil.isVirtualAccount(account)) {
- url = this.paperServerUrl;
- }
+
+ private String getServerUrl(TigerRequest request) {
+ String url = null;
+ MethodType methodType = request.getApiMethodName().getType();
+ if (MethodType.QUOTE == methodType) {
+ url = this.quoteServerUrl;
+ } else if (MethodType.TRADE == methodType && paperServerUrl != null) {
+ String account = AccountUtil.parseAccount(request);
+ if (AccountUtil.isVirtualAccount(account)) {
+ url = this.paperServerUrl;
+ }
+ }
+ return url == null ? this.serverUrl : url;
}
- return url == null ? this.serverUrl : url;
- }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerLoader.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerLoader.java
index 09cd82b835777b67efd9ac0d3f46c1c1fa27ee0d..4aec613b8bb907c7017fef28ea088c5a20e532dd 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerLoader.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerLoader.java
@@ -1,5 +1,6 @@
package org.dromara.northstar.gateway.tiger;
+import lombok.extern.slf4j.Slf4j;
import org.dromara.northstar.common.constant.ChannelType;
import org.dromara.northstar.gateway.GatewayMetaProvider;
import org.dromara.northstar.gateway.IMarketCenter;
@@ -8,28 +9,26 @@ import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
-import lombok.extern.slf4j.Slf4j;
-
@Slf4j
-@Order(0) // 加载顺序需要显式声明,否则会最后才被加载,从而导致加载网关与模组时报异常
+@Order(0) // 加载顺序需要显式声明,否则会最后才被加载,从而导致加载网关与模组时报异常
@Component
-public class TigerLoader implements CommandLineRunner{
-
- @Autowired
- private IMarketCenter mktCenter;
-
- @Autowired
- private TigerDataServiceManager dsMgr;
-
- @Autowired
- private GatewayMetaProvider gatewayMetaProvider;
-
- @Autowired
- private TigerGatewayFactory tigerFactory;
-
- public void run(String... args) throws Exception {
- gatewayMetaProvider.add(ChannelType.TIGER, new TigerGatewaySettings(), tigerFactory, dsMgr);
-
- }
+public class TigerLoader implements CommandLineRunner {
+
+ @Autowired
+ private IMarketCenter mktCenter;
+
+ @Autowired
+ private TigerDataServiceManager dsMgr;
+
+ @Autowired
+ private GatewayMetaProvider gatewayMetaProvider;
+
+ @Autowired
+ private TigerGatewayFactory tigerFactory;
+
+ public void run(String... args) throws Exception {
+ gatewayMetaProvider.add(ChannelType.TIGER, new TigerGatewaySettings(), tigerFactory);
+
+ }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerMarketGatewayAdapter.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerMarketGatewayAdapter.java
index f8437c05131952213ef77cecaa76d27cc2c39e48..e598ff54bbc899bd11434c12dc3e2e1cfb509d38 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerMarketGatewayAdapter.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerMarketGatewayAdapter.java
@@ -1,39 +1,37 @@
package org.dromara.northstar.gateway.tiger;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
+import com.tigerbrokers.stock.openapi.client.config.ClientConfig;
+import com.tigerbrokers.stock.openapi.client.socket.ApiComposeCallback;
+import com.tigerbrokers.stock.openapi.client.socket.WebSocketClient;
+import com.tigerbrokers.stock.openapi.client.socket.data.TradeTick;
+import com.tigerbrokers.stock.openapi.client.socket.data.pb.*;
+import com.tigerbrokers.stock.openapi.client.struct.SubscribedSymbol;
+import com.tigerbrokers.stock.openapi.client.struct.enums.Language;
+import com.tigerbrokers.stock.openapi.client.util.ApiLogger;
+import lombok.extern.slf4j.Slf4j;
import org.dromara.northstar.common.constant.ChannelType;
import org.dromara.northstar.common.constant.ConnectionState;
import org.dromara.northstar.common.constant.DateTimeConstant;
import org.dromara.northstar.common.event.FastEventEngine;
import org.dromara.northstar.common.event.NorthstarEventType;
import org.dromara.northstar.common.model.GatewayDescription;
+import org.dromara.northstar.common.model.core.Contract;
import org.dromara.northstar.gateway.IContractManager;
import org.dromara.northstar.gateway.MarketGateway;
-
-import com.alibaba.fastjson.JSONObject;
-import com.tigerbrokers.stock.openapi.client.config.ClientConfig;
-import com.tigerbrokers.stock.openapi.client.socket.ApiComposeCallback;
-import com.tigerbrokers.stock.openapi.client.socket.WebSocketClient;
-import com.tigerbrokers.stock.openapi.client.struct.SubscribedSymbol;
-import com.tigerbrokers.stock.openapi.client.struct.enums.Language;
-import com.tigerbrokers.stock.openapi.client.struct.enums.QuoteKeyType;
-import com.tigerbrokers.stock.openapi.client.util.ApiLogger;
-
-import lombok.extern.slf4j.Slf4j;
import xyz.redtorch.pb.CoreEnum.CommonStatusEnum;
import xyz.redtorch.pb.CoreEnum.ProductClassEnum;
-import xyz.redtorch.pb.CoreField.ContractField;
import xyz.redtorch.pb.CoreField.NoticeField;
import xyz.redtorch.pb.CoreField.TickField;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
/**
* 老虎证券行情网关适配器
* @author KevinHuangwl
@@ -42,276 +40,319 @@ import xyz.redtorch.pb.CoreField.TickField;
@Slf4j
public class TigerMarketGatewayAdapter implements MarketGateway {
- private GatewayDescription gd;
-
- private FastEventEngine feEngine;
-
- private WebSocketClient client;
-
- private TigerSpi spi;
-
- private ConnectionState connState = ConnectionState.DISCONNECTED;
-
- public TigerMarketGatewayAdapter(GatewayDescription gd, FastEventEngine feEngine, IContractManager contractMgr) {
- this.gd = gd;
- this.feEngine = feEngine;
- this.spi = new TigerSpi(feEngine, contractMgr);
-
- TigerGatewaySettings settings = (TigerGatewaySettings) gd.getSettings();
- ClientConfig clientConfig = ClientConfig.DEFAULT_CONFIG;
- clientConfig.tigerId = settings.getTigerId();
+ private GatewayDescription gd;
+
+ private FastEventEngine feEngine;
+
+ private WebSocketClient client;
+
+ private TigerSpi spi;
+
+ private ConnectionState connState = ConnectionState.DISCONNECTED;
+
+ public TigerMarketGatewayAdapter(GatewayDescription gd, FastEventEngine feEngine, IContractManager contractMgr) {
+ this.gd = gd;
+ this.feEngine = feEngine;
+ this.spi = new TigerSpi(feEngine, contractMgr);
+
+ TigerGatewaySettings settings = (TigerGatewaySettings) gd.getSettings();
+ ClientConfig clientConfig = ClientConfig.DEFAULT_CONFIG;
+ clientConfig.tigerId = settings.getTigerId();
clientConfig.defaultAccount = settings.getAccountId();
clientConfig.privateKey = settings.getPrivateKey();
clientConfig.license = settings.getLicense();
clientConfig.secretKey = settings.getSecretKey();
clientConfig.language = Language.zh_CN;
- this.client = WebSocketClient.getInstance().clientConfig(clientConfig).apiComposeCallback(spi);
- ApiLogger.setEnabled(true, "logs/");
- }
-
- @Override
- public void connect() {
- client.connect();
- connState = ConnectionState.CONNECTED;
- feEngine.emitEvent(NorthstarEventType.GATEWAY_READY, gd.getGatewayId());
- }
-
- @Override
- public void disconnect() {
- client.disconnect();
- connState = ConnectionState.DISCONNECTED;
- }
-
- @Override
- public ConnectionState getConnectionState() {
- return connState;
- }
-
- @Override
- public boolean getAuthErrorFlag() {
- return false;
- }
-
- @Override
- public boolean subscribe(ContractField contract) {
- if(contract.getProductClass() == ProductClassEnum.EQUITY) {
- client.subscribeQuote(Set.of(contract.getSymbol()), QuoteKeyType.ALL);
- log.info("TIGER网关订阅合约 {} {}", contract.getName(), contract.getUnifiedSymbol());
- }
- // TODO 期货期权暂没实现
- return true;
- }
-
- @Override
- public boolean unsubscribe(ContractField contract) {
- if(contract.getProductClass() == ProductClassEnum.EQUITY) {
- client.cancelSubscribeQuote(Set.of(contract.getSymbol()));
- }
- // TODO 期货期权暂没实现
- return true;
- }
-
- @Override
- public boolean isActive() {
- return spi.isActive();
- }
-
- @Override
- public ChannelType channelType() {
- return ChannelType.TIGER;
- }
-
- @Override
- public GatewayDescription gatewayDescription() {
- gd.setConnectionState(getConnectionState());
- return gd;
- }
-
- @Override
- public String gatewayId() {
- return gd.getGatewayId();
- }
-
- class TigerSpi implements ApiComposeCallback {
-
- static final String MKT_STAT = "marketStatus";
- static final String ASK_P = "askPrice";
- static final String ASK_V = "askSize";
- static final String BID_P = "bidPrice";
- static final String BID_V = "bidSize";
-
- private ConcurrentMap tickBuilderMap = new ConcurrentHashMap<>();
-
- private FastEventEngine feEngine;
- private IContractManager contractMgr;
-
- private long lastActive;
-
- public TigerSpi(FastEventEngine feEngine, IContractManager contractMgr) {
- this.feEngine = feEngine;
- this.contractMgr = contractMgr;
- }
-
- @Override
- public void orderStatusChange(JSONObject jsonObject) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void positionChange(JSONObject jsonObject) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void assetChange(JSONObject jsonObject) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void quoteChange(JSONObject jsonObject) {
- lastActive = System.currentTimeMillis();
- if(log.isTraceEnabled()) {
- log.trace("数据回报:{}", jsonObject);
- }
- if(jsonObject.containsKey("hourTradingTag")) {
- return; //忽略盘前盘后数据
- }
- try {
-
- String symbol = jsonObject.getString("symbol");
- long timestamp = jsonObject.getLongValue("timestamp");
- LocalDateTime ldt = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
- ZoneId londonTimeZone = ZoneId.of(ZoneOffset.ofHours(0).getId()); // 伦敦时区正好可以让所有时区的交易计算在同一天
- tickBuilderMap.computeIfAbsent(symbol, key -> TickField.newBuilder()
- .setGatewayId(gd.getGatewayId())
- .addAllAskPrice(List.of(0D))
- .addAllAskVolume(List.of(0))
- .addAllBidPrice(List.of(0D))
- .addAllBidVolume(List.of(0))
- .setUnifiedSymbol(contractMgr.getContract(ChannelType.TIGER, symbol).contractField().getUnifiedSymbol()));
- if(jsonObject.containsKey(ASK_P)) tickBuilderMap.get(symbol).setAskPrice(0, jsonObject.getDoubleValue(ASK_P));
- if(jsonObject.containsKey(BID_P)) tickBuilderMap.get(symbol).setBidPrice(0, jsonObject.getDoubleValue(BID_P));
- if(jsonObject.containsKey(ASK_V)) tickBuilderMap.get(symbol).setAskVolume(0, jsonObject.getIntValue(ASK_V));
- if(jsonObject.containsKey(BID_V)) tickBuilderMap.get(symbol).setBidVolume(0, jsonObject.getIntValue(BID_V));
-
- if(jsonObject.containsKey(MKT_STAT) && jsonObject.getString(MKT_STAT).equals("交易中")) {
- // 交易数据更新
- feEngine.emitEvent(NorthstarEventType.TICK, tickBuilderMap.get(symbol)
- .setActionDay(ldt.toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
- .setActionTime(ldt.toLocalTime().format(DateTimeConstant.T_FORMAT_WITH_MS_INT_FORMATTER))
- .setTradingDay(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), londonTimeZone).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
- .setActionTimestamp(timestamp)
- .setPreClosePrice(jsonObject.getDoubleValue("preClose"))
- .setLastPrice(jsonObject.getDoubleValue("latestPrice"))
- .setHighPrice(jsonObject.getDoubleValue("high"))
- .setLowPrice(jsonObject.getDoubleValue("low"))
- .setOpenPrice(jsonObject.getDoubleValue("open"))
- .setVolume(jsonObject.getLongValue("volume"))
- .build());
- } else if(!jsonObject.containsKey(MKT_STAT)){
- // 盘口数据更新
- feEngine.emitEvent(NorthstarEventType.TICK, tickBuilderMap.get(symbol)
- .setActionDay(ldt.toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
- .setActionTime(ldt.toLocalTime().format(DateTimeConstant.T_FORMAT_WITH_MS_INT_FORMATTER))
- .setTradingDay(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), londonTimeZone).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
- .setActionTimestamp(timestamp)
- .build());
- }
- } catch(Exception e) {
- log.warn("异常数据:{}", jsonObject);
- log.error("", e);
- }
- }
-
- @Override
- public void tradeTickChange(JSONObject jsonObject) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void optionChange(JSONObject jsonObject) {
- lastActive = System.currentTimeMillis();
- }
-
- @Override
- public void futureChange(JSONObject jsonObject) {
- lastActive = System.currentTimeMillis();
- }
-
- @Override
- public void depthQuoteChange(JSONObject jsonObject) {
- }
-
- @Override
- public void subscribeEnd(String id, String subject, JSONObject jsonObject) {
- log.info("成功订阅 [{} {} {}]", id, subject, jsonObject);
- }
-
- @Override
- public void cancelSubscribeEnd(String id, String subject, JSONObject jsonObject) {
- log.info("取消订阅 [{} {} {}]", id, subject, jsonObject);
- }
-
- @Override
- public void getSubscribedSymbolEnd(SubscribedSymbol subscribedSymbol) {
- }
-
- @Override
- public void error(String errorMsg) {
- NoticeField notice = NoticeField.newBuilder()
- .setStatus(CommonStatusEnum.COMS_WARN)
- .setContent(errorMsg)
- .setTimestamp(System.currentTimeMillis())
- .build();
- feEngine.emitEvent(NorthstarEventType.NOTICE, notice);
- }
-
- @Override
- public void error(int id, int errorCode, String errorMsg) {
- log.error("TIGER网关出错 [{} {} {}]", id, errorCode, errorMsg);
- }
-
- @Override
- public void connectionClosed() {
- log.info("TIGER网关断开");
- }
-
- @Override
- public void connectionKickoff(int errorCode, String errorMsg) {
- NoticeField notice = NoticeField.newBuilder()
- .setStatus(CommonStatusEnum.COMS_WARN)
- .setContent(errorMsg)
- .setTimestamp(System.currentTimeMillis())
- .build();
- feEngine.emitEvent(NorthstarEventType.NOTICE, notice);
- }
-
- @Override
- public void connectionAck() {
- log.info("TIGER网关应答");
- }
-
- @Override
- public void connectionAck(int serverSendInterval, int serverReceiveInterval) {
- }
-
- @Override
- public void hearBeat(String heartBeatContent) {
- }
-
- @Override
- public void serverHeartBeatTimeOut(String channelIdAsLongText) {
- log.info("TIGER网关服务响应超时:{}", channelIdAsLongText);
- }
-
- public boolean isActive() {
- return System.currentTimeMillis() - lastActive < 3000;
- }
-
- }
+ this.client = WebSocketClient.getInstance().clientConfig(clientConfig).apiComposeCallback(spi);
+ ApiLogger.setEnabled(true, "logs/");
+ }
+
+ @Override
+ public void connect() {
+ client.connect();
+ connState = ConnectionState.CONNECTED;
+ feEngine.emitEvent(NorthstarEventType.GATEWAY_READY, gd.getGatewayId());
+ }
+
+ @Override
+ public void disconnect() {
+ client.disconnect();
+ connState = ConnectionState.DISCONNECTED;
+ }
+
+ @Override
+ public ConnectionState getConnectionState() {
+ return connState;
+ }
+
+ @Override
+ public boolean getAuthErrorFlag() {
+ return false;
+ }
+
+ @Override
+ public boolean subscribe(Contract contract) {
+ if (contract.productClass() == ProductClassEnum.EQUITY) {
+ client.cancelSubscribeKline(Set.of(contract.symbol()));
+ log.info("TIGER网关订阅合约 {} {}", contract.name(), contract.unifiedSymbol());
+ }
+ // TODO 期货期权暂没实现
+ return true;
+ }
+
+ @Override
+ public boolean unsubscribe(Contract contract) {
+ if (contract.productClass() == ProductClassEnum.EQUITY) {
+ client.cancelSubscribeQuote(Set.of(contract.symbol()));
+ }
+ // TODO 期货期权暂没实现
+ return true;
+ }
+
+ @Override
+ public boolean isActive() {
+ return spi.isActive();
+ }
+
+ @Override
+ public ChannelType channelType() {
+ return ChannelType.TIGER;
+ }
+
+ @Override
+ public GatewayDescription gatewayDescription() {
+ gd.setConnectionState(getConnectionState());
+ return gd;
+ }
+
+ @Override
+ public String gatewayId() {
+ return gd.getGatewayId();
+ }
+
+ class TigerSpi implements ApiComposeCallback {
+
+ static final String MKT_STAT = "marketStatus";
+ static final String ASK_P = "askPrice";
+ static final String ASK_V = "askSize";
+ static final String BID_P = "bidPrice";
+ static final String BID_V = "bidSize";
+
+ private ConcurrentMap tickBuilderMap = new ConcurrentHashMap<>();
+
+ private FastEventEngine feEngine;
+ private IContractManager contractMgr;
+
+ private long lastActive;
+
+ public TigerSpi(FastEventEngine feEngine, IContractManager contractMgr) {
+ this.feEngine = feEngine;
+ this.contractMgr = contractMgr;
+ }
+
+ @Override
+ public void orderStatusChange(OrderStatusData orderStatusData) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void orderTransactionChange(OrderTransactionData orderTransactionData) {
+
+ }
+
+ @Override
+ public void positionChange(PositionData positionData) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void assetChange(AssetData assetData) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void quoteChange(QuoteBasicData quoteBasicData) {
+ lastActive = System.currentTimeMillis();
+ if (log.isTraceEnabled()) {
+ log.trace("数据回报:{}", quoteBasicData);
+ }
+ if (quoteBasicData.getHourTradingTag() != null && !quoteBasicData.getHourTradingTag().isEmpty()) {
+ return; // 忽略盘前盘后数据
+ }
+ try {
+ String symbol = quoteBasicData.getSymbol();
+ long timestamp = quoteBasicData.getTimestamp();
+ LocalDateTime ldt = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
+ ZoneId londonTimeZone = ZoneId.of(ZoneOffset.UTC.getId());
+ tickBuilderMap.computeIfAbsent(symbol, key -> TickField.newBuilder()
+ .setGatewayId(gd.getGatewayId())
+ .addAllAskPrice(List.of(0D))
+ .addAllAskVolume(List.of(0))
+ .addAllBidPrice(List.of(0D))
+ .addAllBidVolume(List.of(0))
+ .setUnifiedSymbol(contractMgr.getContract(ChannelType.TIGER, symbol).contract().unifiedSymbol()));
+
+ // Example: replace with actual fields
+ // Assuming `quoteBasicData` has methods like `getAskPrice`, `getBidPrice`, etc.
+ tickBuilderMap.get(symbol).setAskPrice(0, quoteBasicData.getAvgPrice()); // Example field
+ tickBuilderMap.get(symbol).setBidPrice(0, quoteBasicData.getAvgPrice()); // Example field
+ tickBuilderMap.get(symbol).setAskVolume(0, (int) quoteBasicData.getVolume()); // Example field
+ tickBuilderMap.get(symbol).setBidVolume(0, (int) quoteBasicData.getVolume()); // Example field
+
+ if (quoteBasicData.getMarketStatus() != null && quoteBasicData.getMarketStatus().equals("交易中")) {
+ // 交易数据更新
+ feEngine.emitEvent(NorthstarEventType.TICK, tickBuilderMap.get(symbol)
+ .setActionDay(ldt.toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
+ .setActionTime(ldt.toLocalTime().format(DateTimeConstant.T_FORMAT_WITH_MS_INT_FORMATTER))
+ .setTradingDay(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), londonTimeZone).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
+ .setActionTimestamp(timestamp)
+ .setPreClosePrice(quoteBasicData.getPreClose())
+ .setLastPrice(quoteBasicData.getLatestPrice())
+ .setHighPrice(quoteBasicData.getHigh())
+ .setLowPrice(quoteBasicData.getLow())
+ .setOpenPrice(quoteBasicData.getOpen())
+ .setVolume(quoteBasicData.getVolume())
+ .build());
+ } else if (quoteBasicData.getMarketStatus() == null || quoteBasicData.getMarketStatus().isEmpty()) {
+ // 盘口数据更新
+ feEngine.emitEvent(NorthstarEventType.TICK, tickBuilderMap.get(symbol)
+ .setActionDay(ldt.toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
+ .setActionTime(ldt.toLocalTime().format(DateTimeConstant.T_FORMAT_WITH_MS_INT_FORMATTER))
+ .setTradingDay(LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), londonTimeZone).toLocalDate().format(DateTimeConstant.D_FORMAT_INT_FORMATTER))
+ .setActionTimestamp(timestamp)
+ .build());
+ }
+ } catch (Exception e) {
+ log.warn("异常数据:{}", quoteBasicData);
+ log.error("", e);
+ }
+ }
+
+ @Override
+ public void quoteAskBidChange(QuoteBBOData quoteBBOData) {
+
+ }
+
+
+ @Override
+ public void tradeTickChange(TradeTick tradeTick) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void fullTickChange(TickData tickData) {
+
+ }
+
+ @Override
+ public void optionChange(QuoteBasicData quoteBasicData) {
+ lastActive = System.currentTimeMillis();
+ }
+
+ @Override
+ public void optionAskBidChange(QuoteBBOData quoteBBOData) {
+
+ }
+
+ @Override
+ public void futureChange(QuoteBasicData quoteBasicData) {
+ lastActive = System.currentTimeMillis();
+ }
+
+ @Override
+ public void futureAskBidChange(QuoteBBOData quoteBBOData) {
+
+ }
+
+ @Override
+ public void depthQuoteChange(QuoteDepthData quoteDepthData) {
+ }
+
+ @Override
+ public void klineChange(KlineData klineData) {
+
+ }
+
+ @Override
+ public void stockTopPush(StockTopData stockTopData) {
+
+ }
+
+ @Override
+ public void optionTopPush(OptionTopData optionTopData) {
+
+ }
+
+ @Override
+ public void subscribeEnd(int id, String subject, String jsonObject) {
+ log.info("成功订阅 [{} {} {}]", id, subject, jsonObject);
+ }
+
+ @Override
+ public void cancelSubscribeEnd(int id, String subject, String jsonObject) {
+ log.info("取消订阅 [{} {} {}]", id, subject, jsonObject);
+ }
+
+ @Override
+ public void getSubscribedSymbolEnd(SubscribedSymbol subscribedSymbol) {
+ }
+
+ @Override
+ public void error(String errorMsg) {
+ NoticeField notice = NoticeField.newBuilder()
+ .setStatus(CommonStatusEnum.COMS_WARN)
+ .setContent(errorMsg)
+ .setTimestamp(System.currentTimeMillis())
+ .build();
+ feEngine.emitEvent(NorthstarEventType.NOTICE, notice);
+ }
+
+ @Override
+ public void error(int id, int errorCode, String errorMsg) {
+ log.error("TIGER网关出错 [{} {} {}]", id, errorCode, errorMsg);
+ }
+
+ @Override
+ public void connectionClosed() {
+ log.info("TIGER网关断开");
+ }
+
+ @Override
+ public void connectionKickout(int errorCode, String errorMsg) {
+ NoticeField notice = NoticeField.newBuilder()
+ .setStatus(CommonStatusEnum.COMS_WARN)
+ .setContent(errorMsg)
+ .setTimestamp(System.currentTimeMillis())
+ .build();
+ feEngine.emitEvent(NorthstarEventType.NOTICE, notice);
+ }
+
+ @Override
+ public void connectionAck() {
+ log.info("TIGER网关应答");
+ }
+
+ @Override
+ public void connectionAck(int serverSendInterval, int serverReceiveInterval) {
+ }
+
+ @Override
+ public void hearBeat(String heartBeatContent) {
+ }
+
+ @Override
+ public void serverHeartBeatTimeOut(String channelIdAsLongText) {
+ log.info("TIGER网关服务响应超时:{}", channelIdAsLongText);
+ }
+
+ public boolean isActive() {
+ return System.currentTimeMillis() - lastActive < 3000;
+ }
+
+ }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/TigerTradeGatewayAdapter.java b/src/main/java/org/dromara/northstar/gateway/tiger/TigerTradeGatewayAdapter.java
index 08cf53adc8d45b93a5ba68b2f5e8d15d84f9cab4..b76589f4342b1ffc606af341272f5f166b9afeef 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/TigerTradeGatewayAdapter.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/TigerTradeGatewayAdapter.java
@@ -1,26 +1,5 @@
package org.dromara.northstar.gateway.tiger;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-import org.apache.commons.lang3.StringUtils;
-import org.dromara.northstar.common.constant.ChannelType;
-import org.dromara.northstar.common.constant.ConnectionState;
-import org.dromara.northstar.common.event.FastEventEngine;
-import org.dromara.northstar.common.event.NorthstarEventType;
-import org.dromara.northstar.common.model.GatewayDescription;
-import org.dromara.northstar.gateway.IContractManager;
-import org.dromara.northstar.gateway.TradeGateway;
-
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@@ -33,27 +12,31 @@ import com.tigerbrokers.stock.openapi.client.https.request.trade.TradeOrderReque
import com.tigerbrokers.stock.openapi.client.https.response.TigerHttpResponse;
import com.tigerbrokers.stock.openapi.client.https.response.trade.PrimeAssetResponse;
import com.tigerbrokers.stock.openapi.client.https.response.trade.TradeOrderResponse;
-import com.tigerbrokers.stock.openapi.client.struct.enums.ActionType;
-import com.tigerbrokers.stock.openapi.client.struct.enums.Category;
import com.tigerbrokers.stock.openapi.client.struct.enums.Currency;
-import com.tigerbrokers.stock.openapi.client.struct.enums.Language;
-import com.tigerbrokers.stock.openapi.client.struct.enums.MethodName;
-import com.tigerbrokers.stock.openapi.client.struct.enums.OrderType;
-import com.tigerbrokers.stock.openapi.client.struct.enums.SecType;
+import com.tigerbrokers.stock.openapi.client.struct.enums.*;
import com.tigerbrokers.stock.openapi.client.struct.param.OrderParameter;
import com.tigerbrokers.stock.openapi.client.util.builder.AccountParamBuilder;
import com.tigerbrokers.stock.openapi.client.util.builder.TradeParamBuilder;
-
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.dromara.northstar.common.constant.ChannelType;
+import org.dromara.northstar.common.constant.ConnectionState;
+import org.dromara.northstar.common.event.FastEventEngine;
+import org.dromara.northstar.common.event.NorthstarEventType;
+import org.dromara.northstar.common.model.GatewayDescription;
+import org.dromara.northstar.common.model.core.*;
+import org.dromara.northstar.gateway.IContractManager;
+import org.dromara.northstar.gateway.TradeGateway;
import xyz.redtorch.pb.CoreEnum.CurrencyEnum;
import xyz.redtorch.pb.CoreEnum.PositionDirectionEnum;
import xyz.redtorch.pb.CoreField.AccountField;
-import xyz.redtorch.pb.CoreField.CancelOrderReqField;
-import xyz.redtorch.pb.CoreField.ContractField;
-import xyz.redtorch.pb.CoreField.OrderField;
-import xyz.redtorch.pb.CoreField.PositionField;
-import xyz.redtorch.pb.CoreField.SubmitOrderReqField;
-import xyz.redtorch.pb.CoreField.TradeField;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
/**
* 老虎证券交易网关适配器
@@ -61,247 +44,250 @@ import xyz.redtorch.pb.CoreField.TradeField;
*
*/
@Slf4j
-public class TigerTradeGatewayAdapter implements TradeGateway{
-
- private final FastEventEngine feEngine;
- private final GatewayDescription gd;
- private final TigerGatewaySettings settings;
- private final IContractManager contractMgr;
-
- private final ConcurrentMap orderIdMap = new ConcurrentHashMap<>();
-
- private TigerHttpClient client;
- private OrderTradeQueryProxy proxy;
- private Timer timer;
-
- private ConnectionState connState = ConnectionState.DISCONNECTED;
-
- private Executor exec = Executors.newSingleThreadExecutor();
-
- private Map lastPositions = new HashMap<>();
-
- public TigerTradeGatewayAdapter(FastEventEngine feEngine, GatewayDescription gd, IContractManager contractMgr) {
- this.feEngine = feEngine;
- this.gd = gd;
- this.settings = (TigerGatewaySettings) gd.getSettings();
- this.contractMgr = contractMgr;
- }
-
- @Override
- public synchronized void connect() {
- ClientConfig clientConfig = ClientConfig.DEFAULT_CONFIG;
- clientConfig.tigerId = settings.getTigerId();
+public class TigerTradeGatewayAdapter implements TradeGateway {
+
+ private final FastEventEngine feEngine;
+ private final GatewayDescription gd;
+ private final TigerGatewaySettings settings;
+ private final IContractManager contractMgr;
+
+ private final ConcurrentMap orderIdMap = new ConcurrentHashMap<>();
+
+ private TigerHttpClient client;
+ private OrderTradeQueryProxy proxy;
+ private Timer timer;
+
+ private ConnectionState connState = ConnectionState.DISCONNECTED;
+
+ private Executor exec = Executors.newSingleThreadExecutor();
+
+ private Map lastPositions = new HashMap<>();
+
+ public TigerTradeGatewayAdapter(FastEventEngine feEngine, GatewayDescription gd, IContractManager contractMgr) {
+ this.feEngine = feEngine;
+ this.gd = gd;
+ this.settings = (TigerGatewaySettings) gd.getSettings();
+ this.contractMgr = contractMgr;
+ }
+
+ @Override
+ public synchronized void connect() {
+ ClientConfig clientConfig = ClientConfig.DEFAULT_CONFIG;
+ clientConfig.tigerId = settings.getTigerId();
clientConfig.defaultAccount = settings.getAccountId();
clientConfig.privateKey = settings.getPrivateKey();
clientConfig.license = settings.getLicense();
clientConfig.secretKey = settings.getSecretKey();
clientConfig.language = Language.zh_CN;
- client = TigerHttpClient.getInstance().clientConfig(clientConfig);
- proxy = new OrderTradeQueryProxy(client, contractMgr, gatewayId(), settings.getAccountId());
- List orders = proxy.getDeltaOrder();
- List symbols = orders.stream().map(OrderField::getContract).map(ContractField::getSymbol).distinct().toList();
-
- connState = ConnectionState.CONNECTED;
- feEngine.emitEvent(NorthstarEventType.LOGGED_IN, gatewayId());
-
- orders.forEach(order -> feEngine.emitEvent(NorthstarEventType.ORDER, order));
- symbols.forEach(symbol -> {
- List trades = proxy.getTrades(symbol);
- trades.forEach(trade -> feEngine.emitEvent(NorthstarEventType.TRADE, trade));
- });
-
- timer = new Timer("TIGER_" + gatewayId(), true);
- timer.scheduleAtFixedRate(new TimerTask() {
-
- @Override
- public void run() {
- try {
- queryAccount();
- queryPosition();
- TigerTradeGatewayAdapter.this.doQueryOrderAndTrade();
- } catch(Exception e) {
- log.error("", e);
- }
- }
- }, 3000, 1500);
- }
-
- private void doQueryOrderAndTrade() {
- proxy.getDeltaOrder().forEach(order -> {
- Long id = Long.valueOf(order.getOrderId());
- String originOrderId = orderIdMap.get(id);
- feEngine.emitEvent(NorthstarEventType.ORDER, order.toBuilder().setOriginOrderId(Optional.ofNullable(originOrderId).orElse("")).build());
- if(order.getTradedVolume() > 0) {
- proxy.getDeltaTrade(Long.valueOf(order.getOrderId()))
- .forEach(trade -> feEngine.emitEvent(NorthstarEventType.TRADE, trade.toBuilder().setOriginOrderId(Optional.ofNullable(originOrderId).orElse("")).build()));
- }
- });
- }
-
- @Override
- public synchronized void disconnect() {
- timer.cancel();
- timer = null;
- client = null;
- proxy = null;
- feEngine.emitEvent(NorthstarEventType.LOGGED_OUT, gatewayId());
- connState = ConnectionState.DISCONNECTED;
- }
-
- private void queryAccount() {
- log.trace("查询TIGER账户信息");
- PrimeAssetRequest assetRequest = PrimeAssetRequest.buildPrimeAssetRequest(settings.getAccountId());
- PrimeAssetResponse primeAssetResponse = client.execute(assetRequest);
- if(!primeAssetResponse.isSuccess()) {
- log.warn("查询账户返回异常:{}", primeAssetResponse.getMessage());
- return;
- }
- //查询证券相关资产信息
- PrimeAssetItem.Segment segment = primeAssetResponse.getSegment(Category.S); // 暂不实现期货资产查询
- //查询账号中美元相关资产信息
- if (segment != null) {
- PrimeAssetItem.CurrencyAssets assetByCurrency = segment.getAssetByCurrency(Currency.USD);
- feEngine.emitEvent(NorthstarEventType.ACCOUNT, AccountField.newBuilder()
- .setAccountId(settings.getAccountId())
- .setGatewayId(gatewayId())
- .setAvailable(assetByCurrency.getCashBalance())
- .setMargin(segment.getGrossPositionValue())
- .setBalance(segment.getNetLiquidation())
- .setPositionProfit(segment.getUnrealizedPL())
- .setCloseProfit(segment.getRealizedPL())
- .setCurrency(CurrencyEnum.USD)
- .build());
- }
- }
-
- private void queryPosition() {
- log.trace("查询TIGER持仓信息");
- TigerHttpRequest request = new TigerHttpRequest(MethodName.POSITIONS);
- String bizContent = AccountParamBuilder.instance()
- .account(settings.getAccountId())
- .secType(SecType.STK)
- .buildJson();
-
- request.setBizContent(bizContent);
- TigerHttpResponse response = client.execute(request);
- if(!response.isSuccess()) {
- log.warn("查询持仓返回异常:{}", response.getMessage());
- return;
- }
- // 解析具体字段
- JSONArray positions = JSON.parseObject(response.getData()).getJSONArray("items");
- Map positionMap = new HashMap<>();
- for(int i=0; i {
- feEngine.emitEvent(NorthstarEventType.POSITION, pf.toBuilder().setPosition(0).build());
- });
- lastPositions = positionMap;
- }
-
- @Override
- public ConnectionState getConnectionState() {
- return connState;
- }
-
- @Override
- public boolean getAuthErrorFlag() {
- return false;
- }
-
- @Override
- public String submitOrder(SubmitOrderReqField submitOrderReq) {
- if(connState != ConnectionState.CONNECTED) {
- throw new IllegalStateException("网关未连线");
- }
-
- SecType secType = switch(submitOrderReq.getContract().getProductClass()) {
- case EQUITY -> SecType.STK;
- case FUTURES -> SecType.FUT;
- default -> throw new IllegalArgumentException("Unexpected value: " + submitOrderReq.getContract().getProductClass());
- };
-
- OrderType orderType = switch(submitOrderReq.getOrderPriceType()) {
- case OPT_LimitPrice -> OrderType.LMT;
- case OPT_AnyPrice -> OrderType.MKT;
- default -> throw new IllegalArgumentException("老虎证券仅支持限价与市价两种订单类型");
- };
-
- ActionType actionType = switch(submitOrderReq.getDirection()) {
- case D_Buy -> ActionType.BUY;
- case D_Sell -> ActionType.SELL;
- default -> throw new IllegalArgumentException("Unexpected value: " + submitOrderReq.getDirection());
- };
-
- TradeOrderModel model = new TradeOrderModel();
- model.setAccount(settings.getAccountId());
- model.setSymbol(submitOrderReq.getContract().getSymbol());
- model.setSecType(secType);
- model.setOrderType(orderType);
- model.setAction(actionType);
- model.setLimitPrice(orderType == OrderType.MKT ? null : submitOrderReq.getPrice());
- model.setTotalQuantity(submitOrderReq.getVolume());
- model.setSecretKey(settings.getSecretKey());
- log.info("网关[{}] 下单:{}", gatewayId(), model);
- TradeOrderResponse response = client.execute(TradeOrderRequest.newRequest(model));
- log.info("网关[{}] 下单反馈:{}", gatewayId(), JSON.toJSONString(response));
- Long id = response.getItem().getId();
- orderIdMap.put(id, submitOrderReq.getOriginOrderId());
- exec.execute(this::doQueryOrderAndTrade);
- return id + "";
- }
-
- @Override
- public boolean cancelOrder(CancelOrderReqField cancelOrderReq) {
- if(connState != ConnectionState.CONNECTED) {
- throw new IllegalStateException("网关未连线");
- }
- for(Entry e : orderIdMap.entrySet()) {
- if(StringUtils.isNotEmpty(cancelOrderReq.getOriginOrderId()) && StringUtils.equals(cancelOrderReq.getOriginOrderId(), e.getValue())) {
- Long id = e.getKey();
- OrderParameter params = TradeParamBuilder.instance().account(settings.getAccountId()).id(id).secretKey(settings.getSecretKey()).build();
- String bizContent = JSON.toJSONString(params);
- TigerHttpRequest request = new TigerHttpRequest(MethodName.CANCEL_ORDER);
- request.setBizContent(bizContent);
- log.info("网关[{}] 撤单:{}", gatewayId(), bizContent);
- TigerHttpResponse response = client.execute(request);
- log.info("网关[{}] 撤单反馈:{}", gatewayId(), JSON.toJSONString(response));
- return response.isSuccess();
- }
- }
-
- return false;
- }
-
- @Override
- public GatewayDescription gatewayDescription() {
- gd.setConnectionState(getConnectionState());
- return gd;
- }
-
- @Override
- public String gatewayId() {
- return gd.getGatewayId();
- }
+ client = TigerHttpClient.getInstance().clientConfig(clientConfig);
+ proxy = new OrderTradeQueryProxy(client, contractMgr, gatewayId(), settings.getAccountId());
+ List orders = proxy.getDeltaOrder();
+ List symbols = orders.stream().map(Order::contract).map(Contract::symbol).distinct().toList();
+
+ connState = ConnectionState.CONNECTED;
+ feEngine.emitEvent(NorthstarEventType.LOGGED_IN, gatewayId());
+
+ orders.forEach(order -> feEngine.emitEvent(NorthstarEventType.ORDER, order));
+ symbols.forEach(symbol -> {
+ List trades = proxy.getTrades(symbol);
+ trades.forEach(trade -> feEngine.emitEvent(NorthstarEventType.TRADE, trade));
+ });
+
+ timer = new Timer("TIGER_" + gatewayId(), true);
+ timer.scheduleAtFixedRate(new TimerTask() {
+
+ @Override
+ public void run() {
+ try {
+ queryAccount();
+ queryPosition();
+ TigerTradeGatewayAdapter.this.doQueryOrderAndTrade();
+ } catch (Exception e) {
+ log.error("", e);
+ }
+ }
+ }, 3000, 1500);
+ }
+
+ private void doQueryOrderAndTrade() {
+ proxy.getDeltaOrder().forEach(order -> {
+ Long id = Long.valueOf(order.orderId());
+ String originOrderId = orderIdMap.get(id);
+ feEngine.emitEvent(NorthstarEventType.ORDER, order.toBuilder().originOrderId(Optional.ofNullable(originOrderId).orElse("")).build());
+ if (order.tradedVolume() > 0) {
+ proxy.getDeltaTrade(Long.valueOf(order.orderId()))
+ .forEach(trade -> feEngine.emitEvent(NorthstarEventType.TRADE, trade.toBuilder().originOrderId(Optional.ofNullable(originOrderId).orElse("")).build()));
+ }
+ });
+ }
+
+ @Override
+ public synchronized void disconnect() {
+ timer.cancel();
+ timer = null;
+ client = null;
+ proxy = null;
+ feEngine.emitEvent(NorthstarEventType.LOGGED_OUT, gatewayId());
+ connState = ConnectionState.DISCONNECTED;
+ }
+
+ private void queryAccount() {
+ log.trace("查询TIGER账户信息");
+ PrimeAssetRequest assetRequest = PrimeAssetRequest.buildPrimeAssetRequest(settings.getAccountId());
+ PrimeAssetResponse primeAssetResponse = client.execute(assetRequest);
+ if (!primeAssetResponse.isSuccess()) {
+ log.warn("查询账户返回异常:{}", primeAssetResponse.getMessage());
+ return;
+ }
+ //查询证券相关资产信息
+ PrimeAssetItem.Segment segment = primeAssetResponse.getSegment(Category.S); // 暂不实现期货资产查询
+ //查询账号中美元相关资产信息
+ if (segment != null) {
+ PrimeAssetItem.CurrencyAssets assetByCurrency = segment.getAssetByCurrency(Currency.USD);
+ feEngine.emitEvent(NorthstarEventType.ACCOUNT, AccountField.newBuilder()
+ .setAccountId(settings.getAccountId())
+ .setGatewayId(gatewayId())
+ .setAvailable(assetByCurrency.getCashBalance())
+ .setMargin(segment.getGrossPositionValue())
+ .setBalance(segment.getNetLiquidation())
+ .setPositionProfit(segment.getUnrealizedPL())
+ .setCloseProfit(segment.getRealizedPL())
+ .setCurrency(CurrencyEnum.USD)
+ .build());
+ }
+ }
+
+ private void queryPosition() {
+ log.trace("查询TIGER持仓信息");
+ TigerHttpRequest request = new TigerHttpRequest(MethodName.POSITIONS);
+ String bizContent = AccountParamBuilder.instance()
+ .account(settings.getAccountId())
+ .secType(SecType.STK)
+ .buildJson();
+
+ request.setBizContent(bizContent);
+ TigerHttpResponse response = client.execute(request);
+ if (!response.isSuccess()) {
+ log.warn("查询持仓返回异常:{}", response.getMessage());
+ return;
+ }
+ // 解析具体字段
+ JSONArray positions = JSON.parseObject(response.getData()).getJSONArray("items");
+ Map positionMap = new HashMap<>();
+ for (int i = 0; i < positions.size(); i++) {
+ JSONObject json = positions.getJSONObject(i);
+ Contract contract = contractMgr.getContract(ChannelType.TIGER, json.getString("symbol")).contract();
+ String positionId = String.format("%s@%s@%s", contract.unifiedSymbol(), PositionDirectionEnum.PD_Long, gatewayId());
+ double openPrice = (int) (json.getDoubleValue("averageCost") / contract.priceTick()) * contract.priceTick();
+ Position pos = Position.builder()
+ .gatewayId(gd.getGatewayId())
+ .positionId(settings.getAccountId())
+ .contract(contract)
+ .positionProfit(json.getDoubleValue("unrealizedPnl"))
+ .positionDirection(PositionDirectionEnum.PD_Long)
+ .position(json.getIntValue("position"))
+ //.frozen(frozen)
+ //.tdFrozen(tdFrozen)
+ //.ydFrozen(ydFrozen)
+ .openPrice(openPrice)
+ .openPriceDiff(json.getDoubleValue("latestPrice") - openPrice)
+ .build();
+
+ positionMap.put(positionId, pos);
+ feEngine.emitEvent(NorthstarEventType.POSITION, pos);
+ lastPositions.remove(positionId);
+ }
+
+ lastPositions.values().forEach(pf -> {
+ feEngine.emitEvent(NorthstarEventType.POSITION, pf.toBuilder().position(0).build());
+ });
+ lastPositions = positionMap;
+ }
+
+ @Override
+ public ConnectionState getConnectionState() {
+ return connState;
+ }
+
+ @Override
+ public boolean getAuthErrorFlag() {
+ return false;
+ }
+
+ @Override
+ public String submitOrder(SubmitOrderReq submitOrderReq) {
+ if (connState != ConnectionState.CONNECTED) {
+ throw new IllegalStateException("网关未连线");
+ }
+
+ SecType secType = switch (submitOrderReq.contract().productClass()) {
+ case EQUITY -> SecType.STK;
+ case FUTURES -> SecType.FUT;
+ default ->
+ throw new IllegalArgumentException("Unexpected value: " + submitOrderReq.contract().productClass());
+ };
+
+ OrderType orderType = switch (submitOrderReq.orderPriceType()) {
+ case OPT_LimitPrice -> OrderType.LMT;
+ case OPT_AnyPrice -> OrderType.MKT;
+ default -> throw new IllegalArgumentException("老虎证券仅支持限价与市价两种订单类型");
+ };
+
+ ActionType actionType = switch (submitOrderReq.direction()) {
+ case D_Buy -> ActionType.BUY;
+ case D_Sell -> ActionType.SELL;
+ default -> throw new IllegalArgumentException("Unexpected value: " + submitOrderReq.direction());
+ };
+
+ TradeOrderModel model = new TradeOrderModel();
+ model.setAccount(settings.getAccountId());
+ model.setSymbol(submitOrderReq.contract().symbol());
+ model.setSecType(secType);
+ model.setOrderType(orderType);
+ model.setAction(actionType);
+ model.setLimitPrice(orderType == OrderType.MKT ? null : submitOrderReq.price());
+ model.setTotalQuantity((long) submitOrderReq.volume());
+ model.setSecretKey(settings.getSecretKey());
+ log.info("网关[{}] 下单:{}", gatewayId(), model);
+ TradeOrderResponse response = client.execute(TradeOrderRequest.newRequest(model));
+ log.info("网关[{}] 下单反馈:{}", gatewayId(), JSON.toJSONString(response));
+ Long id = response.getItem().getId();
+ orderIdMap.put(id, submitOrderReq.originOrderId());
+ exec.execute(this::doQueryOrderAndTrade);
+ return id + "";
+ }
+
+ @Override
+ public boolean cancelOrder(String originOrderId) {
+ if (connState != ConnectionState.CONNECTED) {
+ throw new IllegalStateException("网关未连线");
+ }
+ for (Entry e : orderIdMap.entrySet()) {
+ if (StringUtils.isNotEmpty(originOrderId) && StringUtils.equals(originOrderId, e.getValue())) {
+ Long id = e.getKey();
+ OrderParameter params = TradeParamBuilder.instance().account(settings.getAccountId()).id(id).secretKey(settings.getSecretKey()).build();
+ String bizContent = JSON.toJSONString(params);
+ TigerHttpRequest request = new TigerHttpRequest(MethodName.CANCEL_ORDER);
+ request.setBizContent(bizContent);
+ log.info("网关[{}] 撤单:{}", gatewayId(), bizContent);
+ TigerHttpResponse response = client.execute(request);
+ log.info("网关[{}] 撤单反馈:{}", gatewayId(), JSON.toJSONString(response));
+ return response.isSuccess();
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public GatewayDescription gatewayDescription() {
+ gd.setConnectionState(getConnectionState());
+ return gd;
+ }
+
+ @Override
+ public String gatewayId() {
+ return gd.getGatewayId();
+ }
}
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/time/CnStockTradeTime.java b/src/main/java/org/dromara/northstar/gateway/tiger/time/CnStockTradeTime.java
index f0d53bb7b8350df323659b69adda11f6b6500dc0..2ab73ec140886cae8da1febb5ac2f2e100c6d0f5 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/time/CnStockTradeTime.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/time/CnStockTradeTime.java
@@ -1,17 +1,18 @@
package org.dromara.northstar.gateway.tiger.time;
+import org.dromara.northstar.common.model.core.TradeTimeDefinition;
+
import java.time.LocalTime;
import java.util.List;
-import org.dromara.northstar.gateway.TradeTimeDefinition;
-import org.dromara.northstar.gateway.model.PeriodSegment;
/**
* A股连续交易时段
* @author KevinHuangwl
*
*/
-public class CnStockTradeTime implements TradeTimeDefinition{
+/*
+public class CnStockTradeTime implements TradeTimeDefinition {
@Override
public List tradeTimeSegments() {
@@ -21,3 +22,4 @@ public class CnStockTradeTime implements TradeTimeDefinition{
}
}
+*/
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/time/HkStockTradeTime.java b/src/main/java/org/dromara/northstar/gateway/tiger/time/HkStockTradeTime.java
index d22d43d847a24b6cff551245b0f4157f0cf36060..bbe166f17669697b3202e4e64e2ff70e8460080d 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/time/HkStockTradeTime.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/time/HkStockTradeTime.java
@@ -1,17 +1,18 @@
package org.dromara.northstar.gateway.tiger.time;
+import org.dromara.northstar.common.model.core.TradeTimeDefinition;
+
import java.time.LocalTime;
import java.util.List;
-import org.dromara.northstar.gateway.TradeTimeDefinition;
-import org.dromara.northstar.gateway.model.PeriodSegment;
/**
* 港股连续交易时段
* @author KevinHuangwl
*
*/
-public class HkStockTradeTime implements TradeTimeDefinition{
+/*
+public class HkStockTradeTime implements TradeTimeDefinition {
@Override
public List tradeTimeSegments() {
@@ -21,3 +22,4 @@ public class HkStockTradeTime implements TradeTimeDefinition{
}
}
+*/
diff --git a/src/main/java/org/dromara/northstar/gateway/tiger/time/UsStockTradeTime.java b/src/main/java/org/dromara/northstar/gateway/tiger/time/UsStockTradeTime.java
index 8207036fe2948e6a60e5ecebe67a35e4a8ffb92d..6e8ffde80329150ff3dfdd446687c3193d02f2e1 100644
--- a/src/main/java/org/dromara/northstar/gateway/tiger/time/UsStockTradeTime.java
+++ b/src/main/java/org/dromara/northstar/gateway/tiger/time/UsStockTradeTime.java
@@ -6,15 +6,14 @@ import java.time.LocalTime;
import java.time.Month;
import java.util.List;
-import org.dromara.northstar.gateway.TradeTimeDefinition;
-import org.dromara.northstar.gateway.model.PeriodSegment;
-import org.dromara.northstar.gateway.time.DateUtils;
+import org.dromara.northstar.common.model.core.TradeTimeDefinition;
/**
* 美股连续交易时段
* @author KevinHuangwl
*
*/
+/*
public class UsStockTradeTime implements TradeTimeDefinition {
@@ -30,3 +29,4 @@ public class UsStockTradeTime implements TradeTimeDefinition {
}
}
+*/
diff --git a/src/test/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxyTest.java b/src/test/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxyTest.java
index 992ff1b87a89c021c24dd8a555dd73513861837f..77e464a68527eddc6ca3e61d69f329c5e8950476 100644
--- a/src/test/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxyTest.java
+++ b/src/test/java/org/dromara/northstar/gateway/tiger/OrderTradeQueryProxyTest.java
@@ -1,3 +1,4 @@
+/*
package org.dromara.northstar.gateway.tiger;
import static org.assertj.core.api.Assertions.assertThat;
@@ -6,7 +7,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import org.dromara.northstar.gateway.Contract;
+import org.dromara.northstar.common.model.core.Contract;
import org.dromara.northstar.gateway.IContractManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -26,12 +27,12 @@ class OrderTradeQueryProxyTest {
private TestFieldFactory factory = new TestFieldFactory("testGateway");
private TigerHttpClient client = mock(TigerHttpClient.class);
-
+
@BeforeEach
void prepare() {
IContractManager contractMgr = mock(IContractManager.class);
Contract contract = mock(Contract.class);
- when(contract.contractField()).thenReturn(factory.makeContract("rb2205"));
+ when(contract.toContractField()).thenReturn(factory.makeContract("rb2205"));
when(contractMgr.getContract(any(), anyString())).thenReturn(contract);
proxy = new OrderTradeQueryProxy(client, contractMgr, "testGateway", "testAccount");
}
@@ -74,3 +75,4 @@ class OrderTradeQueryProxyTest {
}
}
+*/