diff --git a/examples/vn_trader/run.py b/examples/vn_trader/run.py index 5ae053bf..0c884a48 100644 --- a/examples/vn_trader/run.py +++ b/examples/vn_trader/run.py @@ -11,21 +11,22 @@ from vnpy.trader.ui import MainWindow, create_qapp # from vnpy.gateway.ctp import CtpGateway # from vnpy.gateway.ctptest import CtptestGateway # from vnpy.gateway.mini import MiniGateway -from vnpy.gateway.sopt import SoptGateway +# from vnpy.gateway.sopt import SoptGateway # from vnpy.gateway.minitest import MinitestGateway # from vnpy.gateway.femas import FemasGateway # from vnpy.gateway.tiger import TigerGateway # from vnpy.gateway.oes import OesGateway -from vnpy.gateway.okex import OkexGateway +# from vnpy.gateway.okex import OkexGateway # from vnpy.gateway.huobi import HuobiGateway -from vnpy.gateway.bitfinex import BitfinexGateway +# from vnpy.gateway.bitfinex import BitfinexGateway # from vnpy.gateway.onetoken import OnetokenGateway -from vnpy.gateway.okexf import OkexfGateway +# from vnpy.gateway.okexf import OkexfGateway # from vnpy.gateway.xtp import XtpGateway # from vnpy.gateway.hbdm import HbdmGateway # from vnpy.gateway.tap import TapGateway # from vnpy.gateway.tora import ToraGateway # from vnpy.gateway.alpaca import AlpacaGateway +from vnpy.gateway.da import DaGateway from vnpy.app.cta_strategy import CtaStrategyApp # from vnpy.app.csv_loader import CsvLoaderApp @@ -49,7 +50,7 @@ def main(): # main_engine.add_gateway(CtpGateway) # main_engine.add_gateway(CtptestGateway) # main_engine.add_gateway(MiniGateway) - main_engine.add_gateway(SoptGateway) + # # main_engine.add_gateway(MinitestGateway) # main_engine.add_gateway(FemasGateway) # main_engine.add_gateway(IbGateway) @@ -67,6 +68,7 @@ def main(): # main_engine.add_gateway(TapGateway) # main_engine.add_gateway(ToraGateway) # main_engine.add_gateway(AlpacaGateway) + main_engine.add_gateway(DaGateway) main_engine.add_app(CtaStrategyApp) main_engine.add_app(CtaBacktesterApp) diff --git a/vnpy/api/da/Config/Live.ini b/vnpy/api/da/Config/Live.ini deleted file mode 100644 index 6091ebf7..00000000 --- a/vnpy/api/da/Config/Live.ini +++ /dev/null @@ -1,2 +0,0 @@ -[A] -LocalID=13499 diff --git a/vnpy/api/da/Config/Option.ini b/vnpy/api/da/Config/Option.ini deleted file mode 100644 index fa5aae87..00000000 --- a/vnpy/api/da/Config/Option.ini +++ /dev/null @@ -1,5 +0,0 @@ -[Generel] -SUB=HKEX,00001.HK; -SHOW= -ApiLog=1 -Stock=1 diff --git a/vnpy/api/da/__init__.py b/vnpy/api/da/__init__.py new file mode 100644 index 00000000..b1480612 --- /dev/null +++ b/vnpy/api/da/__init__.py @@ -0,0 +1,4 @@ +from .vndamarket import MarketApi +from .vndafuture import FutureApi +from .vndastock import StockApi +from .da_constant import * diff --git a/vnpy/api/da/da_constant.py b/vnpy/api/da/da_constant.py new file mode 100644 index 00000000..5a86f65b --- /dev/null +++ b/vnpy/api/da/da_constant.py @@ -0,0 +1,15 @@ +DA_STR_SIZE = 256 +MAX_BROKER = 4096 +DAAPI_VERSION = "1.12" +OS_NAME = "Windows" +OS_NAME = "Linux" +MAX_SUB_COUNT = 20 +DAF_TYPE_Stock = 'S' +DAF_TYPE_Future = 'D' +DAF_TYPE_Unknown = 0 +DAF_SUB_Append = '1' +DAF_SUB_Replace = '2' +DAF_SUB_Unsubcribe = '3' +DAF_SUB_UnsubcribeAll = '4' +DAF_Market_Full = 'Z' +DAF_Market_Fill = 'Y' diff --git a/vnpy/api/da/vnda/vndafuture/vndafuture.cpp b/vnpy/api/da/vnda/vndafuture/vndafuture.cpp index f7db0c1a..d3a61802 100644 Binary files a/vnpy/api/da/vnda/vndafuture/vndafuture.cpp and b/vnpy/api/da/vnda/vndafuture/vndafuture.cpp differ diff --git a/vnpy/api/da/vnda/vndamarket/vndamarket.cpp b/vnpy/api/da/vnda/vndamarket/vndamarket.cpp index bc75a6e6..0a7697f5 100644 Binary files a/vnpy/api/da/vnda/vndamarket/vndamarket.cpp and b/vnpy/api/da/vnda/vndamarket/vndamarket.cpp differ diff --git a/vnpy/api/da/vnda/vndamarket/vndamarket.h b/vnpy/api/da/vnda/vndamarket/vndamarket.h index 8b6e6a6c..483d5cfe 100644 --- a/vnpy/api/da/vnda/vndamarket/vndamarket.h +++ b/vnpy/api/da/vnda/vndamarket/vndamarket.h @@ -20,16 +20,12 @@ using namespace std; class MarketApi : public IMarketEvent { private: - CMarketApi* api; //API对象 - -public: - MarketApi() - { - }; + CMarketApi *api; //API对象 - ~MarketApi() - { - }; +public: + MarketApi(){}; + + ~MarketApi(){}; //------------------------------------------------------------------------------------- //API回调函数 @@ -39,7 +35,7 @@ public: void OnFrontDisconnected(int iReason); void OnHeartBeatWarning(int iTimeLapse); - void OnRspRawData(const char* rawData); + void OnRspRawData(const char *rawData); void OnRspUserLogin(CMarketRspInfoField *pRspInfo, int iRequestID, bool bIsLast); void OnRspTradeDate(CMarketRspTradeDateField *pRspTradeDate, CMarketRspInfoField *pRspInfo, int iRequestID, bool bIsLast); void OnRspBrokerData(CMarketRspBrokerDataField *pRspBrokerData, CMarketRspInfoField *pRspInfo, int iRequestID, bool bIsLast); @@ -49,24 +45,25 @@ public: //Python回调函数 //------------------------------------------------------------------------------------- - virtual void onFrontConnected() {}; + virtual void onFrontConnected(){}; - virtual void onFrontDisconnected(int reqid) {}; + virtual void onFrontDisconnected(int reqid){}; - virtual void onHeartBeatWarning(int reqid) {}; + virtual void onHeartBeatWarning(int reqid){}; - virtual void onRspRawData(string data) {}; + virtual void onRspRawData(string data){}; - virtual void onRspUserLogin(const dict &error, int reqid, bool last) {}; + virtual void onRspUserLogin(const dict &error, int reqid, bool last){}; - virtual void onRspTradeDate(const dict &data, const dict &error, int reqid, bool last) {}; + virtual void onRspTradeDate(const dict &data, const dict &error, int reqid, bool last){}; - virtual void onRspBrokerData(const dict &data, const dict &error, int reqid, bool last) {}; + virtual void onRspBrokerData(const dict &data, const dict &error, int reqid, bool last){}; - virtual void onRspMarketData(const dict &data, const dict &error, int reqid, bool last) {}; + virtual void onRspMarketData(const dict &data, const dict &error, int reqid, bool last){}; //------------------------------------------------------------------------------------- //req:主动函数的请求字典 + //------------------------------------------------------------------------------------- string getVersion(); diff --git a/vnpy/api/da/vnda/vndastock/vndastock.cpp b/vnpy/api/da/vnda/vndastock/vndastock.cpp index 3d7d2186..8090fa19 100644 Binary files a/vnpy/api/da/vnda/vndastock/vndastock.cpp and b/vnpy/api/da/vnda/vndastock/vndastock.cpp differ diff --git a/vnpy/api/da/vndafuture.lib b/vnpy/api/da/vndafuture.lib new file mode 100644 index 00000000..86943f50 Binary files /dev/null and b/vnpy/api/da/vndafuture.lib differ diff --git a/vnpy/api/da/vndafuture.pyd b/vnpy/api/da/vndafuture.pyd index c606212d..0bae339f 100644 Binary files a/vnpy/api/da/vndafuture.pyd and b/vnpy/api/da/vndafuture.pyd differ diff --git a/vnpy/api/da/vndamarket.pyd b/vnpy/api/da/vndamarket.pyd index 3d7f4f7c..04913401 100644 Binary files a/vnpy/api/da/vndamarket.pyd and b/vnpy/api/da/vndamarket.pyd differ diff --git a/vnpy/api/da/vndastock.lib b/vnpy/api/da/vndastock.lib new file mode 100644 index 00000000..0e434a01 Binary files /dev/null and b/vnpy/api/da/vndastock.lib differ diff --git a/vnpy/api/da/vndastock.pyd b/vnpy/api/da/vndastock.pyd index c1ec857d..f3544efc 100644 Binary files a/vnpy/api/da/vndastock.pyd and b/vnpy/api/da/vndastock.pyd differ diff --git a/vnpy/gateway/da/__init__.py b/vnpy/gateway/da/__init__.py new file mode 100644 index 00000000..7ce6b057 --- /dev/null +++ b/vnpy/gateway/da/__init__.py @@ -0,0 +1 @@ +from .da_gateway import DaGateway diff --git a/vnpy/gateway/da/da_gateway.py b/vnpy/gateway/da/da_gateway.py new file mode 100644 index 00000000..08aedcf5 --- /dev/null +++ b/vnpy/gateway/da/da_gateway.py @@ -0,0 +1,773 @@ +""" +""" + +from datetime import datetime +from threading import Thread + +import wmi + +from vnpy.api.da import ( + MarketApi, + FutureApi, + DAF_SUB_Append, + DAF_TYPE_Future +) +from vnpy.trader.constant import ( + Direction, + Offset, + Exchange, + OrderType, + Product, + Status, + OptionType +) +from vnpy.trader.gateway import BaseGateway +from vnpy.trader.object import ( + TickData, + OrderData, + TradeData, + PositionData, + AccountData, + ContractData, + OrderRequest, + CancelRequest, + SubscribeRequest, +) +from vnpy.trader.utility import get_folder_path +from vnpy.trader.event import EVENT_TIMER + + +# STATUS_DA2VT = { +# THOST_FTDC_OAS_Submitted: Status.SUBMITTING, +# THOST_FTDC_OAS_Accepted: Status.SUBMITTING, +# THOST_FTDC_OAS_Rejected: Status.REJECTED, +# THOST_FTDC_OST_NoTradeQueueing: Status.NOTTRADED, +# THOST_FTDC_OST_PartTradedQueueing: Status.PARTTRADED, +# THOST_FTDC_OST_AllTraded: Status.ALLTRADED, +# THOST_FTDC_OST_Canceled: Status.CANCELLED +# } + +# DIRECTION_VT2DA = { +# Direction.LONG: THOST_FTDC_D_Buy, +# Direction.SHORT: THOST_FTDC_D_Sell +# } +# DIRECTION_DA2VT = {v: k for k, v in DIRECTION_VT2DA.items()} +# DIRECTION_DA2VT[THOST_FTDC_PD_Long] = Direction.LONG +# DIRECTION_DA2VT[THOST_FTDC_PD_Short] = Direction.SHORT + +# ORDERTYPE_VT2DA = { +# OrderType.LIMIT: THOST_FTDC_OPT_LimitPrice, +# OrderType.MARKET: THOST_FTDC_OPT_AnyPrice +# } +# ORDERTYPE_DA2VT = {v: k for k, v in ORDERTYPE_VT2DA.items()} + +# OFFSET_VT2DA = { +# Offset.OPEN: THOST_FTDC_OF_Open, +# Offset.CLOSE: THOST_FTDC_OFEN_Close, +# Offset.CLOSETODAY: THOST_FTDC_OFEN_CloseToday, +# Offset.CLOSEYESTERDAY: THOST_FTDC_OFEN_CloseYesterday, +# } +# OFFSET_DA2VT = {v: k for k, v in OFFSET_VT2DA.items()} + +EXCHANGE_DA2VT = { + "APEX": Exchange.APEX, + # "SGXQ": Exchange.SGX, + # "NYMEX": Exchange.NYMEX, + # "LME": Exchange.LME, + # "CME_CBT": Exchange.CBOT, + # "HKEX": Exchange.HKFE, + # "CME": Exchange.CME, + # "TOCOM": Exchange.TOCOM, + # "KRX": Exchange.KRX, + # "ICE": Exchange.ICE +} +EXCHANGE_VT2DA = {v: k for k, v in EXCHANGE_DA2VT.items()} + +PRODUCT_DA2VT = { + "F": Product.FUTURES, + "O": Product.OPTION, +} + +# OPTIONTYPE_DA2VT = { +# THOST_FTDC_CP_CallOptions: OptionType.CALL, +# THOST_FTDC_CP_PutOptions: OptionType.PUT +# } + + +symbol_exchange_map = {} +symbol_name_map = {} +symbol_size_map = {} + + +class DaGateway(BaseGateway): + """ + VN Trader Gateway for DA . + """ + + default_setting = { + "鐢ㄦ埛鍚": "", + "瀵嗙爜": "", + "浜ゆ槗鏈嶅姟鍣": "", + "琛屾儏鏈嶅姟鍣": "", + "鎺堟潈鐮": "" + } + + exchanges = list(EXCHANGE_DA2VT.values()) + + def __init__(self, event_engine): + """Constructor""" + super().__init__(event_engine, "DA") + + self.future_api = DaFutureApi(self) + self.market_api = DaMarketApi(self) + + def connect(self, setting: dict): + """""" + userid = setting["鐢ㄦ埛鍚"] + password = setting["瀵嗙爜"] + future_address = setting["浜ゆ槗鏈嶅姟鍣"] + market_address = setting["琛屾儏鏈嶅姟鍣"] + auth_code = setting["鎺堟潈鐮"] + + if not future_address.startswith("tcp://"): + future_address = "tcp://" + future_address + if not market_address.startswith("tcp://"): + market_address = "tcp://" + market_address + + self.future_api.connect(future_address, userid, password, auth_code) + self.market_api.connect(market_address, userid, password, auth_code) + + def subscribe(self, req: SubscribeRequest): + """""" + self.market_api.subscribe(req) + + def send_order(self, req: OrderRequest): + """""" + return self.future_api.send_order(req) + + def cancel_order(self, req: CancelRequest): + """""" + self.future_api.cancel_order(req) + + def query_account(self): + """""" + pass + + def query_position(self): + """""" + pass + + def close(self): + """""" + self.future_api.close() + self.market_api.close() + + def write_error(self, msg: str, error: dict): + """""" + error_id = error["ErrorID"] + error_msg = error["ErrorMsg"] + msg = f"{msg}锛屼唬鐮侊細{error_id}锛屼俊鎭細{error_msg}" + self.write_log(msg) + + +class DaMarketApi(MarketApi): + """""" + + def __init__(self, gateway): + """Constructor""" + super().__init__() + + self.gateway = gateway + self.gateway_name = gateway.gateway_name + + self.reqid = 0 + + self.connect_status = False + self.login_status = False + self.subscribed = {} + self.mac_address = get_mac_address() + + self.userid = "" + self.password = "" + self.auth_code = "" + + def onFrontConnected(self): + """ + Callback when front server is connected. + """ + self.gateway.write_log("琛屾儏鏈嶅姟鍣ㄨ繛鎺ユ垚鍔") + self.login() + + def onFrontDisconnected(self, reason: int): + """ + Callback when front server is disconnected. + """ + self.login_status = False + self.gateway.write_log(f"琛屾儏鏈嶅姟鍣ㄨ繛鎺ユ柇寮锛屽師鍥爗reason}") + + def onRspUserLogin(self, error: dict, reqid: int, last: bool): + """ + Callback when user is logged in. + """ + if not error["ErrorID"]: + self.login_status = True + self.gateway.write_log("琛屾儏鏈嶅姟鍣ㄧ櫥褰曟垚鍔") + + for req in self.subscribed.values(): + self.subscribe(req) + else: + self.gateway.write_error("琛屾儏鏈嶅姟鍣ㄧ櫥褰曞け璐", error) + + def onRspMarketData(self, data: dict, error: dict, reqid: int, last: bool): + """ + Callback of tick data update. + """ + print('on rsp', data, error) + symbol = data["TreatyCode"] + exchange = EXCHANGE_DA2VT.get(data["ExchangeCode"], None) + if not exchange: + return + + timestamp = f"{data['TradeDay']} {data['Time']}" + + tick = TickData( + symbol=symbol, + exchange=exchange, + datetime=datetime.strptime(timestamp, "%Y%m%d %H:%M:%S.%f"), + name=symbol_name_map[symbol], + volume=int(data["FilledNum"]), + open_interest=int(data["HoldNum"]), + limit_up=float(data["LimitUpPrice"]), + limit_down=float(data["LimitDownPrice"]), + last_price=float(data["CurrPrice"]), + open_price=float(data["Open"]), + high_price=float(data["High"]), + low_price=float(data["Low"]), + pre_close=float(data["PreSettlementPrice"]), + bid_price_1=float(data["BuyPrice"]), + bid_price_2=float(data["BuyPrice2"]), + bid_price_3=float(data["BuyPrice3"]), + bid_price_4=float(data["BuyPrice4"]), + bid_price_5=float(data["BuyPrice5"]), + ask_price_1=float(data["SalePrice"]), + ask_price_2=float(data["SalePrice2"]), + ask_price_3=float(data["SalePrice3"]), + ask_price_4=float(data["SalePrice4"]), + ask_price_5=float(data["SalePrice5"]), + bid_volume_1=int(data["BuyNumber"]), + bid_volume_2=int(data["BuyNumber2"]), + bid_volume_3=int(data["BuyNumber3"]), + bid_volume_4=int(data["BuyNumber4"]), + bid_volume_5=int(data["BuyNumber5"]), + ask_volume_1=int(data["SaleNumber"]), + ask_volume_2=int(data["SaleNumber2"]), + ask_volume_3=int(data["SaleNumber3"]), + ask_volume_4=int(data["SaleNumber4"]), + ask_volume_5=int(data["SaleNumber5"]), + gateway_name=self.gateway_name + ) + self.gateway.on_tick(tick) + + def connect(self, address: str, userid: str, password: str, auth_code: str): + """ + Start connection to server. + """ + self.userid = userid + self.password = password + self.auth_code = auth_code + + # If not connected, then start connection first. + if not self.connect_status: + # path = get_folder_path(self.gateway_name.lower()) + self.createMarketApi(True, "market_log.txt") + + self.registerNameServer(address) + self.init() + + self.connect_status = True + # If already connected, then login immediately. + elif not self.login_status: + self.login() + + def login(self): + """ + Login onto server. + """ + req = { + "UserId": self.userid, + "UserPwd": self.password, + "AuthorCode": self.auth_code, + "MacAddress": self.mac_address, + "ComputerName": "Dev Server", + "SoftwareName": "vn.py", + "SoftwareVersion": "2.0", + } + + self.reqid += 1 + self.reqUserLogin(req, self.reqid) + + def subscribe(self, req: SubscribeRequest): + """ + Subscribe to tick data update. + """ + if self.login_status: + da_exchange = EXCHANGE_VT2DA.get(req.exchange, "") + if not da_exchange: + self.gateway.write_log(f"涓嶆敮鎸佺殑浜ゆ槗鎵{req.exchange.value}") + return + + da_code = f"{da_exchange},{req.symbol}" + + da_req = { + "MarketType": DAF_TYPE_Future, + "SubscMode": DAF_SUB_Append, + "MarketCount": 1, + "MarketTrcode": da_code, + } + self.reqid += 1 + i = self.reqMarketData(da_req, self.reqid) + print(i, da_req) + + self.subscribed[req.symbol] = req + + def close(self): + """ + Close the connection. + """ + if self.connect_status: + self.exit() + + +class DaFutureApi(FutureApi): + """""" + + def __init__(self, gateway): + """Constructor""" + super().__init__() + + self.gateway = gateway + self.gateway_name = gateway.gateway_name + + self.reqid = 0 + self.order_ref = 0 + + self.connect_status = False + self.login_status = False + self.auth_staus = False + self.login_failed = False + + self.userid = "" + self.password = "" + self.auth_code = "" + self.mac_address = get_mac_address() + + def onFrontConnected(self): + """""" + self.gateway.write_log("浜ゆ槗鏈嶅姟鍣ㄨ繛鎺ユ垚鍔") + self.login() + + def onFrontDisconnected(self, reason: int): + """""" + self.login_status = False + self.gateway.write_log(f"浜ゆ槗鏈嶅姟鍣ㄨ繛鎺ユ柇寮锛屽師鍥爗reason}") + + def onRspUserLogin(self, error: dict, reqid: int, last: bool): + """""" + if not error["ErrorID"]: + self.login_status = True + self.gateway.write_log("浜ゆ槗鏈嶅姟鍣ㄧ櫥褰曟垚鍔") + + # 鏌ヨ鍙氦鏄撳悎绾 + for exchange in EXCHANGE_DA2VT.values(): + self.query_contract(exchange) + else: + self.login_failed = True + self.gateway.write_error("浜ゆ槗鏈嶅姟鍣ㄧ櫥褰曞け璐", error) + + def onRspNeedVerify(self, firstLogin: bool, hasSetQA: bool): + """""" + print("on rsp need verify", firstLogin, hasSetQA) + + def onRspOrderInsert(self, data: dict, error: dict, reqid: int, last: bool): + """""" + order_ref = data["OrderRef"] + orderid = f"{self.frontid}_{self.sessionid}_{order_ref}" + + symbol = data["InstrumentID"] + exchange = symbol_exchange_map[symbol] + + order = OrderData( + symbol=symbol, + exchange=exchange, + orderid=orderid, + direction=DIRECTION_DA2VT[data["Direction"]], + offset=OFFSET_DA2VT.get(data["CombOffsetFlag"], Offset.NONE), + price=data["LimitPrice"], + volume=data["VolumeTotalOriginal"], + status=Status.REJECTED, + gateway_name=self.gateway_name + ) + self.gateway.on_order(order) + + self.gateway.write_error("浜ゆ槗濮旀墭澶辫触", error) + + def onRspOrderAction(self, data: dict, error: dict, reqid: int, last: bool): + """""" + self.gateway.write_error("浜ゆ槗鎾ゅ崟澶辫触", error) + + def onRspQueryMaxOrderVolume(self, data: dict, error: dict, reqid: int, last: bool): + """""" + pass + + def onRspSettlementInfoConfirm(self, data: dict, error: dict, reqid: int, last: bool): + """ + Callback of settlment info confimation. + """ + self.gateway.write_log("缁撶畻淇℃伅纭鎴愬姛") + + self.reqid += 1 + self.reqQryInstrument({}, self.reqid) + + def onRspQryInvestorPosition(self, data: dict, error: dict, reqid: int, last: bool): + """""" + if not data: + return + + # Get buffered position object + key = f"{data['InstrumentID'], data['PosiDirection']}" + position = self.positions.get(key, None) + if not position: + position = PositionData( + symbol=data["InstrumentID"], + exchange=symbol_exchange_map[data["InstrumentID"]], + direction=DIRECTION_DA2VT[data["PosiDirection"]], + gateway_name=self.gateway_name + ) + self.positions[key] = position + + # For SHFE position data update + if position.exchange == Exchange.SHFE: + if data["YdPosition"] and not data["TodayPosition"]: + position.yd_volume = data["Position"] + # For other exchange position data update + else: + position.yd_volume = data["Position"] - data["TodayPosition"] + + # Get contract size (spread contract has no size value) + size = symbol_size_map.get(position.symbol, 0) + + # Calculate previous position cost + cost = position.price * position.volume * size + + # Update new position volume + position.volume += data["Position"] + position.pnl += data["PositionProfit"] + + # Calculate average position price + if position.volume and size: + cost += data["PositionCost"] + position.price = cost / (position.volume * size) + + # Get frozen volume + if position.direction == Direction.LONG: + position.frozen += data["ShortFrozen"] + else: + position.frozen += data["LongFrozen"] + + if last: + for position in self.positions.values(): + self.gateway.on_position(position) + + self.positions.clear() + + def onRspQryTradingAccount(self, data: dict, error: dict, reqid: int, last: bool): + """""" + if "AccountID" not in data: + return + + account = AccountData( + accountid=data["AccountID"], + balance=data["Balance"], + frozen=data["FrozenMargin"] + data["FrozenCash"] + data["FrozenCommission"], + gateway_name=self.gateway_name + ) + account.available = data["Available"] + + self.gateway.on_account(account) + + def onRspQryExchange(self, data: dict, error: dict, reqid: int, last: bool): + """ + Callback of instrument query. + """ + print(data) + + def onRspQryInstrument(self, data: dict, error: dict, reqid: int, last: bool): + """ + Callback of instrument query. + """ + product = PRODUCT_DA2VT.get(data["CommodityType"], None) + if product: + contract = ContractData( + symbol=data["CommodityCode"], + exchange=EXCHANGE_DA2VT[data["ExchangeNo"]], + name=data["ContractFName"], + product=product, + size=data["ProductDot"] / data["UpperTick"], + pricetick=data["UpperTick"], + gateway_name=self.gateway_name + ) + + self.gateway.on_contract(contract) + + if last: + self.gateway.write_log("鍚堢害淇℃伅鏌ヨ鎴愬姛") + + def onRtnOrder(self, data: dict): + """ + Callback of order status update. + """ + symbol = data["InstrumentID"] + exchange = symbol_exchange_map.get(symbol, "") + if not exchange: + self.order_data.append(data) + return + + frontid = data["FrontID"] + sessionid = data["SessionID"] + order_ref = data["OrderRef"] + orderid = f"{frontid}_{sessionid}_{order_ref}" + + order = OrderData( + symbol=symbol, + exchange=exchange, + orderid=orderid, + type=ORDERTYPE_DA2VT[data["OrderPriceType"]], + direction=DIRECTION_DA2VT[data["Direction"]], + offset=OFFSET_DA2VT[data["CombOffsetFlag"]], + price=data["LimitPrice"], + volume=data["VolumeTotalOriginal"], + traded=data["VolumeTraded"], + status=STATUS_DA2VT[data["OrderStatus"]], + time=data["InsertTime"], + gateway_name=self.gateway_name + ) + self.gateway.on_order(order) + + self.sysid_orderid_map[data["OrderSysID"]] = orderid + + def onRtnTrade(self, data: dict): + """ + Callback of trade status update. + """ + symbol = data["InstrumentID"] + exchange = symbol_exchange_map.get(symbol, "") + if not exchange: + self.trade_data.append(data) + return + + orderid = self.sysid_orderid_map[data["OrderSysID"]] + + trade = TradeData( + symbol=symbol, + exchange=exchange, + orderid=orderid, + tradeid=data["TradeID"], + direction=DIRECTION_DA2VT[data["Direction"]], + offset=OFFSET_DA2VT[data["OffsetFlag"]], + price=data["Price"], + volume=data["Volume"], + time=data["TradeTime"], + gateway_name=self.gateway_name + ) + self.gateway.on_trade(trade) + + def connect( + self, + address: str, + userid: str, + password: str, + auth_code: str + ): + """ + Start connection to server. + """ + self.userid = userid + self.password = password + self.auth_code = auth_code + + if not self.connect_status: + #path = get_folder_path(self.gateway_name.lower()) + self.createFutureApi(True, "future_log.txt") + + self.registerNameServer(address) + self.init() + + self.connect_status = True + else: + self.login() + + def authenticate(self): + """ + Authenticate with auth_code and appid. + """ + req = { + "UserID": self.userid, + "BrokerID": self.brokerid, + "AuthCode": self.auth_code, + "AppID": self.appid + } + + if self.product_info: + req["UserProductInfo"] = self.product_info + + self.reqid += 1 + self.reqAuthenticate(req, self.reqid) + + def login(self): + """ + Login onto server. + """ + req = { + "UserId": self.userid, + "UserPwd": self.password, + "AuthorCode": self.auth_code, + "MacAddress": self.mac_address, + "ComputerName": "Dev Server", + "SoftwareName": "vn.py", + "SoftwareVersion": "2.0", + } + + self.reqid += 1 + n = self.reqUserLogin(req, self.reqid) + print("login", n) + + def send_order(self, req: OrderRequest): + """ + Send new order. + """ + self.order_ref += 1 + + da_req = { + "InstrumentID": req.symbol, + "ExchangeID": req.exchange.value, + "LimitPrice": req.price, + "VolumeTotalOriginal": int(req.volume), + "OrderPriceType": ORDERTYPE_VT2DA.get(req.type, ""), + "Direction": DIRECTION_VT2DA.get(req.direction, ""), + "CombOffsetFlag": OFFSET_VT2DA.get(req.offset, ""), + "OrderRef": str(self.order_ref), + "InvestorID": self.userid, + "UserID": self.userid, + "BrokerID": self.brokerid, + "CombHedgeFlag": THOST_FTDC_HF_Speculation, + "ContingentCondition": THOST_FTDC_CC_Immediately, + "ForceCloseReason": THOST_FTDC_FCC_NotForceClose, + "IsAutoSuspend": 0, + "TimeCondition": THOST_FTDC_TC_GFD, + "VolumeCondition": THOST_FTDC_VC_AV, + "MinVolume": 1 + } + + if req.type == OrderType.FAK: + da_req["OrderPriceType"] = THOST_FTDC_OPT_LimitPrice + da_req["TimeCondition"] = THOST_FTDC_TC_IOC + da_req["VolumeCondition"] = THOST_FTDC_VC_AV + elif req.type == OrderType.FOK: + da_req["OrderPriceType"] = THOST_FTDC_OPT_LimitPrice + da_req["TimeCondition"] = THOST_FTDC_TC_IOC + da_req["VolumeCondition"] = THOST_FTDC_VC_CV + + self.reqid += 1 + self.reqOrderInsert(da_req, self.reqid) + + orderid = f"{self.frontid}_{self.sessionid}_{self.order_ref}" + order = req.create_order_data(orderid, self.gateway_name) + self.gateway.on_order(order) + + return order.vt_orderid + + def cancel_order(self, req: CancelRequest): + """ + Cancel existing order. + """ + frontid, sessionid, order_ref = req.orderid.split("_") + + da_req = { + "InstrumentID": req.symbol, + "ExchangeID": req.exchange.value, + "OrderRef": order_ref, + "FrontID": int(frontid), + "SessionID": int(sessionid), + "ActionFlag": THOST_FTDC_AF_Delete, + "BrokerID": self.brokerid, + "InvestorID": self.userid + } + + self.reqid += 1 + self.reqOrderAction(da_req, self.reqid) + + def query_account(self): + """ + Query account balance data. + """ + self.reqid += 1 + self.reqQryTradingAccount({}, self.reqid) + + def query_position(self): + """ + Query position holding data. + """ + if not symbol_exchange_map: + return + + req = { + "BrokerID": self.brokerid, + "InvestorID": self.userid + } + + self.reqid += 1 + self.reqQryInvestorPosition(req, self.reqid) + + def query_contract(self, exchange, page=1): + """ + Query contract data. + """ + da_exchange = EXCHANGE_VT2DA[exchange] + + req = { + "PageIndex": page, + "ExchangeNo": da_exchange + } + + self.reqid += 1 + self.reqQryInstrument(req, self.reqid) + + def close(self): + """""" + if self.connect_status: + self.exit() + + +def get_network_interface(): + """""" + c = wmi.WMI() + interfaces = c.Win32_NetworkAdapterConfiguration(IPEnabled=1) + if not interfaces: + return None + + # Use interface with default ip gateway + for interface in interfaces: + if interface.DefaultIPGateway: + return interface + + # Otherwise use last one + return interface + + +def get_mac_address(): + """""" + interface = get_network_interface() + if not interface: + return "" + + return interface.MACAddress \ No newline at end of file