Merge pull request #1459 from nanoric/oes_fix

Oes fix
This commit is contained in:
vn.py 2019-03-08 22:32:52 +08:00 committed by GitHub
commit cc129b3e2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 388 additions and 435 deletions

View File

@ -11,9 +11,6 @@ ordServer = 1 {td_ord_server}
rptServer = 1 {td_rpt_server} rptServer = 1 {td_rpt_server}
qryServer = 1 {td_qry_server} qryServer = 1 {td_qry_server}
username = {username}
# 密码支持明文和MD5两种格式 (如 txt:XXX 或 md5:XXX..., 不带前缀则默认为明文)
password = {password}
heartBtInt = 30 heartBtInt = 30
# 客户端环境号, 用于区分不同客户端实例上报的委托申报, 取值由客户端自行分配 # 客户端环境号, 用于区分不同客户端实例上报的委托申报, 取值由客户端自行分配
@ -33,7 +30,7 @@ rpt.subcribeEnvId = 0
# 比如想订阅所有委托、成交相关的回报消息,可以使用如下两种方式: # 比如想订阅所有委托、成交相关的回报消息,可以使用如下两种方式:
# - rpt.subcribeRptTypes = 1,4,8 # - rpt.subcribeRptTypes = 1,4,8
# - 或等价的: rpt.subcribeRptTypes = 0x0D # - 或等价的: rpt.subcribeRptTypes = 0x0D
rpt.subcribeRptTypes = 0 rpt.subcribeRptTypes = 1,2,4,8,0x10,0x20,0x40
# 服务器集群的集群类型 (1: 基于复制集的高可用集群, 2: 基于对等节点的服务器集群, 0: 默认为基于复制集的高可用集群) # 服务器集群的集群类型 (1: 基于复制集的高可用集群, 2: 基于对等节点的服务器集群, 0: 默认为基于复制集的高可用集群)
clusterType = 0 clusterType = 0
@ -58,9 +55,6 @@ keepCnt = 9
tcpServer = {md_tcp_server} tcpServer = {md_tcp_server}
qryServer = {md_qry_server} qryServer = {md_qry_server}
username = {username}
# 密码支持明文和MD5两种格式 (如 txt:XXX 或 md5:XXX..., 不带前缀则默认为明文)
password = {password}
heartBtInt = 30 heartBtInt = 30
sse.stock.enable = false sse.stock.enable = false
@ -93,7 +87,7 @@ mktData.tickExpireType = 0
# 0x400:指数行情, 0x800:期权行情) # 0x400:指数行情, 0x800:期权行情)
# 要订阅多个数据种类, 可以用逗号或空格分隔, 或者设置为并集值, 如: # 要订阅多个数据种类, 可以用逗号或空格分隔, 或者设置为并集值, 如:
# "mktData.dataTypes = 1,2,4" 或等价的 "mktData.dataTypes = 0x07" # "mktData.dataTypes = 1,2,4" 或等价的 "mktData.dataTypes = 0x07"
mktData.dataTypes = 0 mktData.dataTypes = 1,2,4,8,0x10
# 请求订阅的行情数据的起始时间 (格式: HHMMSS 或 HHMMSSsss) # 请求订阅的行情数据的起始时间 (格式: HHMMSS 或 HHMMSSsss)
# (-1: 从头开始获取, 0: 从最新位置开始获取实时行情, 大于0: 从指定的起始时间开始获取) # (-1: 从头开始获取, 0: 从最新位置开始获取实时行情, 大于0: 从指定的起始时间开始获取)

View File

@ -3,14 +3,15 @@
""" """
import hashlib import hashlib
import os import os
from threading import Thread from gettext import gettext as _
from threading import Thread, Lock
from vnpy.trader.gateway import BaseGateway from vnpy.trader.gateway import BaseGateway
from vnpy.trader.object import (AccountData, CancelRequest, ContractData, OrderData, OrderRequest, from vnpy.trader.object import (CancelRequest, OrderRequest,
PositionData, SubscribeRequest, TickData, TradeData) SubscribeRequest)
from vnpy.trader.utility import get_file_path from vnpy.trader.utility import get_file_path
from .md import OesMdApi from .oes_md import OesMdApi
from .td import OesTdApi from .oes_td import OesTdApi
from .utils import config_template from .utils import config_template
@ -19,30 +20,12 @@ class OesGateway(BaseGateway):
VN Trader Gateway for BitMEX connection. VN Trader Gateway for BitMEX connection.
""" """
def on_tick(self, tick: TickData):
super().on_tick(tick)
def on_trade(self, trade: TradeData):
super().on_trade(trade)
def on_order(self, order: OrderData):
super().on_order(order)
def on_position(self, position: PositionData):
super().on_position(position)
def on_account(self, account: AccountData):
super().on_account(account)
def on_contract(self, contract: ContractData):
super().on_contract(contract)
default_setting = { default_setting = {
"td_ord_server": "tcp://106.15.58.119:6101", "td_ord_server": "",
"td_rpt_server": "tcp://106.15.58.119:6301", "td_rpt_server": "",
"td_qry_server": "tcp://106.15.58.119:6401", "td_qry_server": "",
"md_tcp_server": "tcp://139.196.228.232:5103", "md_tcp_server": "",
"md_qry_server": "tcp://139.196.228.232:5203", "md_qry_server": "",
"username": "", "username": "",
"password": "", "password": "",
} }
@ -54,15 +37,21 @@ class OesGateway(BaseGateway):
self.md_api = OesMdApi(self) self.md_api = OesMdApi(self)
self.td_api = OesTdApi(self) self.td_api = OesTdApi(self)
def connect(self, setting: dict): self._lock_subscribe = Lock()
return self._connect_async(setting) self._lock_send_order = Lock()
self._lock_cancel_order = Lock()
self._lock_query_position = Lock()
self._lock_query_account = Lock()
def _connect_sync(self, setting: dict): def connect(self, setting: dict):
"""""" """"""
if not setting['password'].startswith("md5:"): if not setting['password'].startswith("md5:"):
setting['password'] = "md5:" + hashlib.md5(setting['password'].encode()).hexdigest() setting['password'] = "md5:" + hashlib.md5(setting['password'].encode()).hexdigest()
config_path = get_file_path("vnoes.ini") username = setting['username']
password = setting['password']
config_path = str(get_file_path("vnoes.ini"))
with open(config_path, "wt") as f: with open(config_path, "wt") as f:
if 'test' in setting: if 'test' in setting:
log_level = 'DEBUG' log_level = 'DEBUG'
@ -72,7 +61,7 @@ class OesGateway(BaseGateway):
log_mode = 'file' log_mode = 'file'
log_dir = get_file_path('oes') log_dir = get_file_path('oes')
log_path = os.path.join(log_dir, 'log.log') log_path = os.path.join(log_dir, 'log.log')
if os.path.exists(log_dir): if not os.path.exists(log_dir):
os.mkdir(log_dir) os.mkdir(log_dir)
content = config_template.format(**setting, content = config_template.format(**setting,
log_level=log_level, log_level=log_level,
@ -80,41 +69,59 @@ class OesGateway(BaseGateway):
log_path=log_path) log_path=log_path)
f.write(content) f.write(content)
self.td_api.connect(str(config_path)) Thread(target=self._connect_md_sync, args=(config_path, username, password)).start()
Thread(target=self._connect_td_sync, args=(config_path, username, password)).start()
def _connect_td_sync(self, config_path, username, password):
self.td_api.config_path = config_path
self.td_api.username = username
self.td_api.password = password
if self.td_api.connect():
self.write_log(_("成功连接到交易服务器"))
self.td_api.query_account() self.td_api.query_account()
self.td_api.query_contracts() self.td_api.query_contracts()
self.write_log("合约信息查询成功")
self.td_api.query_position() self.td_api.query_position()
self.td_api.init_query_orders() self.td_api.query_orders()
self.td_api.start() self.td_api.start()
else:
self.write_log(_("无法连接到交易服务器,请检查你的配置"))
self.md_api.connect(str(config_path)) def _connect_md_sync(self, config_path, username, password):
self.md_api.config_path = config_path
self.md_api.username = username
self.md_api.password = password
if self.md_api.connect():
self.md_api.start() self.md_api.start()
else:
def _connect_async(self, setting: dict): self.write_log(_("无法连接到行情服务器,请检查你的配置"))
Thread(target=self._connect_sync, args=(setting, )).start()
def subscribe(self, req: SubscribeRequest): def subscribe(self, req: SubscribeRequest):
"""""" """"""
with self._lock_subscribe:
self.md_api.subscribe(req) self.md_api.subscribe(req)
def send_order(self, req: OrderRequest): def send_order(self, req: OrderRequest):
"""""" """"""
with self._lock_send_order:
return self.td_api.send_order(req) return self.td_api.send_order(req)
def cancel_order(self, req: CancelRequest): def cancel_order(self, req: CancelRequest):
"""""" """"""
return self.td_api.cancel_order(req) with self._lock_cancel_order:
self.td_api.cancel_order(req)
def query_account(self): def query_account(self):
"""""" """"""
return self.td_api.query_account() with self._lock_query_account:
self.td_api.query_account()
def query_position(self): def query_position(self):
"""""" """"""
return self.query_position() with self._lock_query_position:
self.td_api.query_position()
def close(self): def close(self):
"""""" """"""
self.md_api.stop() self.md_api.stop()
self.td_api.stop() self.td_api.stop()
pass

View File

@ -1,14 +1,16 @@
import time
from datetime import datetime from datetime import datetime
from gettext import gettext as _
from threading import Thread from threading import Thread
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from typing import Any, Callable, Dict from typing import Any, Callable, Dict
from vnpy.api.oes.vnoes import MdsApiClientEnvT, MdsApi_DestoryAll, MdsApi_InitAllByConvention, \ from vnpy.api.oes.vnoes import MdsApiClientEnvT, MdsApi_DestoryAll, MdsApi_InitAllByConvention, \
MdsApi_IsValidQryChannel, MdsApi_IsValidTcpChannel, MdsApi_LogoutAll, \ MdsApi_IsValidQryChannel, MdsApi_IsValidTcpChannel, MdsApi_LogoutAll, MdsApi_SetThreadPassword, \
MdsApi_SubscribeMarketData, MdsApi_WaitOnMsg, MdsL2StockSnapshotBodyT, MdsMktDataRequestEntryT, \ MdsApi_SetThreadUsername, MdsApi_SubscribeMarketData, MdsApi_WaitOnMsg, MdsL2StockSnapshotBodyT, \
MdsMktDataRequestReqT, MdsMktRspMsgBodyT, MdsStockSnapshotBodyT, SGeneralClientChannelT, \ MdsMktDataRequestEntryT, MdsMktDataRequestReqT, MdsMktRspMsgBodyT, MdsStockSnapshotBodyT, \
SMsgHeadT, SPlatform_IsNegEpipe, SPlatform_IsNegEtimeout, cast, eMdsExchangeIdT, \ SGeneralClientChannelT, SMsgHeadT, SPlatform_IsNegEpipe, cast, \
eMdsMktSubscribeFlagT, eMdsMsgTypeT, eMdsSecurityTypeT, eMdsSubscribeDataTypeT, \ eMdsExchangeIdT, eMdsMktSubscribeFlagT, eMdsMsgTypeT, eMdsSecurityTypeT, eMdsSubscribeDataTypeT, \
eMdsSubscribeModeT, eMdsSubscribedTickExpireTypeT, eSMsgProtocolTypeT eMdsSubscribeModeT, eMdsSubscribedTickExpireTypeT, eSMsgProtocolTypeT
from vnpy.trader.constant import Exchange from vnpy.trader.constant import Exchange
@ -25,18 +27,23 @@ EXCHANGE_VT2MDS = {v: k for k, v in EXCHANGE_MDS2VT.items()}
class OesMdMessageLoop: class OesMdMessageLoop:
def __init__(self, gateway: BaseGateway, env: MdsApiClientEnvT): def __init__(self, gateway: BaseGateway, md: "OesMdApi", env: MdsApiClientEnvT):
""""""
self.gateway = gateway self.gateway = gateway
self.env = env self.env = env
self.alive = False
self.th = Thread(target=self.message_loop) self._alive = False
self._md = md
self._th = Thread(target=self._message_loop)
self.message_handlers: Dict[int, Callable[[dict], None]] = { self.message_handlers: Dict[int, Callable[[dict], None]] = {
# tick & orderbook
eMdsMsgTypeT.MDS_MSGTYPE_MARKET_DATA_SNAPSHOT_FULL_REFRESH: self.on_market_full_refresh, eMdsMsgTypeT.MDS_MSGTYPE_MARKET_DATA_SNAPSHOT_FULL_REFRESH: self.on_market_full_refresh,
eMdsMsgTypeT.MDS_MSGTYPE_L2_MARKET_DATA_SNAPSHOT: self.on_l2_market_data_snapshot, eMdsMsgTypeT.MDS_MSGTYPE_L2_MARKET_DATA_SNAPSHOT: self.on_l2_market_data_snapshot,
eMdsMsgTypeT.MDS_MSGTYPE_L2_ORDER: self.on_l2_order, eMdsMsgTypeT.MDS_MSGTYPE_L2_ORDER: self.on_l2_order,
eMdsMsgTypeT.MDS_MSGTYPE_L2_TRADE: self.on_l2_trade, eMdsMsgTypeT.MDS_MSGTYPE_L2_TRADE: self.on_l2_trade,
# others
eMdsMsgTypeT.MDS_MSGTYPE_QRY_SECURITY_STATUS: self.on_security_status, eMdsMsgTypeT.MDS_MSGTYPE_QRY_SECURITY_STATUS: self.on_security_status,
eMdsMsgTypeT.MDS_MSGTYPE_L2_MARKET_DATA_INCREMENTAL: lambda x: 1, eMdsMsgTypeT.MDS_MSGTYPE_L2_MARKET_DATA_INCREMENTAL: lambda x: 1,
eMdsMsgTypeT.MDS_MSGTYPE_L2_BEST_ORDERS_SNAPSHOT: self.on_best_orders_snapshot, eMdsMsgTypeT.MDS_MSGTYPE_L2_BEST_ORDERS_SNAPSHOT: self.on_best_orders_snapshot,
@ -46,15 +53,36 @@ class OesMdMessageLoop:
eMdsMsgTypeT.MDS_MSGTYPE_TRADING_SESSION_STATUS: self.on_trading_session_status, eMdsMsgTypeT.MDS_MSGTYPE_TRADING_SESSION_STATUS: self.on_trading_session_status,
eMdsMsgTypeT.MDS_MSGTYPE_SECURITY_STATUS: self.on_security_status, eMdsMsgTypeT.MDS_MSGTYPE_SECURITY_STATUS: self.on_security_status,
eMdsMsgTypeT.MDS_MSGTYPE_MARKET_DATA_REQUEST: self.on_market_data_request, eMdsMsgTypeT.MDS_MSGTYPE_MARKET_DATA_REQUEST: self.on_market_data_request,
eMdsMsgTypeT.MDS_MSGTYPE_HEARTBEAT: lambda x: 1, eMdsMsgTypeT.MDS_MSGTYPE_HEARTBEAT: lambda x: 1,
} }
self.last_tick: Dict[str, TickData] = {} self.last_tick: Dict[str, TickData] = {}
self.symbol_to_exchange: Dict[str, Exchange] = {} self.symbol_to_exchange: Dict[str, Exchange] = {}
def register_symbol(self, symbol: str, exchange: Exchange): def register_symbol(self, symbol: str, exchange: Exchange):
""""""
self.symbol_to_exchange[symbol] = exchange self.symbol_to_exchange[symbol] = exchange
def get_last_tick(self, symbol): def start(self):
""""""
self._alive = True
self._th.start()
def stop(self):
""""""
self._alive = False
def join(self):
""""""
self._th.join()
def reconnect(self):
""""""
self.gateway.write_log(_("正在尝试重新连接到行情服务器。"))
return self._md.connect()
def _get_last_tick(self, symbol):
""""""
try: try:
return self.last_tick[symbol] return self.last_tick[symbol]
except KeyError: except KeyError:
@ -62,22 +90,15 @@ class OesMdMessageLoop:
gateway_name=self.gateway.gateway_name, gateway_name=self.gateway.gateway_name,
symbol=symbol, symbol=symbol,
exchange=self.symbol_to_exchange[symbol], exchange=self.symbol_to_exchange[symbol],
# todo: use cache of something else to resolve exchange
datetime=datetime.utcnow() datetime=datetime.utcnow()
) )
self.last_tick[symbol] = tick self.last_tick[symbol] = tick
return tick return tick
def start(self): def _on_message(self, session_info: SGeneralClientChannelT,
self.alive = True
self.th.start()
def join(self):
self.th.join()
def on_message(self, session_info: SGeneralClientChannelT,
head: SMsgHeadT, head: SMsgHeadT,
body: Any): body: Any):
""""""
if session_info.protocolType == eSMsgProtocolTypeT.SMSG_PROTO_BINARY: if session_info.protocolType == eSMsgProtocolTypeT.SMSG_PROTO_BINARY:
b = cast.toMdsMktRspMsgBodyT(body) b = cast.toMdsMktRspMsgBodyT(body)
if head.msgId in self.message_handlers: if head.msgId in self.message_handlers:
@ -91,31 +112,30 @@ class OesMdMessageLoop:
self.gateway.write_log(f"unknown prototype : {session_info.protocolType}") self.gateway.write_log(f"unknown prototype : {session_info.protocolType}")
return 1 return 1
def message_loop(self): def _message_loop(self):
""""""
tcp_channel = self.env.tcpChannel tcp_channel = self.env.tcpChannel
timeout_ms = 1000 timeout_ms = 1000
is_timeout = SPlatform_IsNegEtimeout # is_timeout = SPlatform_IsNegEtimeout
is_disconnected = SPlatform_IsNegEpipe is_disconnected = SPlatform_IsNegEpipe
while self.alive: while self._alive:
ret = MdsApi_WaitOnMsg(tcp_channel, ret = MdsApi_WaitOnMsg(tcp_channel,
timeout_ms, timeout_ms,
self.on_message) self._on_message)
if ret < 0: if ret < 0:
if is_timeout(ret): # if is_timeout(ret):
pass # pass # just no message
if is_disconnected(ret): if is_disconnected(ret):
# todo: handle disconnected self.gateway.write_log(_("与行情服务器的连接已断开。"))
self.alive = False while self._alive and not self.reconnect():
break time.sleep(1)
pass
return return
def on_l2_market_data_snapshot(self, d: MdsMktRspMsgBodyT): def on_l2_market_data_snapshot(self, d: MdsMktRspMsgBodyT):
""""""
data: MdsL2StockSnapshotBodyT = d.mktDataSnapshot.l2Stock data: MdsL2StockSnapshotBodyT = d.mktDataSnapshot.l2Stock
symbol = str(data.SecurityID) symbol = str(data.SecurityID)
tick = self.get_last_tick(symbol) tick = self._get_last_tick(symbol)
tick.limit_up = data.HighPx / 10000
tick.limit_down = data.LowPx / 10000
tick.open_price = data.OpenPx / 10000 tick.open_price = data.OpenPx / 10000
tick.pre_close = data.ClosePx / 10000 tick.pre_close = data.ClosePx / 10000
tick.high_price = data.HighPx / 10000 tick.high_price = data.HighPx / 10000
@ -128,11 +148,10 @@ class OesMdMessageLoop:
self.gateway.on_tick(tick) self.gateway.on_tick(tick)
def on_market_full_refresh(self, d: MdsMktRspMsgBodyT): def on_market_full_refresh(self, d: MdsMktRspMsgBodyT):
""""""
data: MdsStockSnapshotBodyT = d.mktDataSnapshot.stock data: MdsStockSnapshotBodyT = d.mktDataSnapshot.stock
symbol = data.SecurityID symbol = data.SecurityID
tick = self.get_last_tick(symbol) tick = self._get_last_tick(symbol)
tick.limit_up = data.HighPx / 10000
tick.limit_down = data.LowPx / 10000
tick.open_price = data.OpenPx / 10000 tick.open_price = data.OpenPx / 10000
tick.pre_close = data.ClosePx / 10000 tick.pre_close = data.ClosePx / 10000
tick.high_price = data.HighPx / 10000 tick.high_price = data.HighPx / 10000
@ -143,76 +162,96 @@ class OesMdMessageLoop:
for i in range(5): for i in range(5):
tick.__dict__['ask_price_' + str(i + 1)] = data.OfferLevels[i].Price / 10000 tick.__dict__['ask_price_' + str(i + 1)] = data.OfferLevels[i].Price / 10000
self.gateway.on_tick(tick) self.gateway.on_tick(tick)
pass
def on_l2_trade(self, d: MdsMktRspMsgBodyT): def on_l2_trade(self, d: MdsMktRspMsgBodyT):
""""""
data = d.trade data = d.trade
symbol = data.SecurityID symbol = data.SecurityID
tick = self.get_last_tick(symbol) tick = self._get_last_tick(symbol)
tick.datetime = datetime.utcnow() tick.datetime = datetime.utcnow()
tick.volume = data.TradeQty tick.volume = data.TradeQty
tick.last_price = data.TradePrice / 10000 tick.last_price = data.TradePrice / 10000
self.gateway.on_tick(tick) self.gateway.on_tick(tick)
def on_market_data_request(self, d: MdsMktRspMsgBodyT): def on_market_data_request(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def on_trading_session_status(self, d: MdsMktRspMsgBodyT): def on_trading_session_status(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def on_l2_market_overview(self, d: MdsMktRspMsgBodyT): def on_l2_market_overview(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def on_index_snapshot_full_refresh(self, d: MdsMktRspMsgBodyT): def on_index_snapshot_full_refresh(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def on_option_snapshot_ful_refresh(self, d: MdsMktRspMsgBodyT): def on_option_snapshot_ful_refresh(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def on_best_orders_snapshot(self, d: MdsMktRspMsgBodyT): def on_best_orders_snapshot(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def on_l2_order(self, d: MdsMktRspMsgBodyT): def on_l2_order(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def on_security_status(self, d: MdsMktRspMsgBodyT): def on_security_status(self, d: MdsMktRspMsgBodyT):
""""""
pass pass
def stop(self):
self.alive = False
class OesMdApi: class OesMdApi:
def __init__(self, gateway: BaseGateway): def __init__(self, gateway: BaseGateway):
""""""
self.gateway = gateway self.gateway = gateway
self.env = MdsApiClientEnvT() self.config_path: str = ''
self.message_loop = OesMdMessageLoop(gateway, self.env) self.username: str = ''
self.password: str = ''
def connect(self, config_path: str): self._env = MdsApiClientEnvT()
if not MdsApi_InitAllByConvention(self.env, config_path): self._message_loop = OesMdMessageLoop(gateway, self, self._env)
pass
if not MdsApi_IsValidTcpChannel(self.env.tcpChannel): def connect(self) -> bool:
pass """"""
if not MdsApi_IsValidQryChannel(self.env.qryChannel): """Connect to trading server.
pass :note set config_path before calling this function
"""
MdsApi_SetThreadUsername(self.username)
MdsApi_SetThreadPassword(self.password)
config_path = self.config_path
if not MdsApi_InitAllByConvention(self._env, config_path):
return False
if not MdsApi_IsValidTcpChannel(self._env.tcpChannel):
return False
if not MdsApi_IsValidQryChannel(self._env.qryChannel):
return False
return True
def start(self): def start(self):
self.message_loop.start() """"""
self._message_loop.start()
def stop(self): def stop(self):
self.message_loop.stop() """"""
if not MdsApi_LogoutAll(self.env, True): self._message_loop.stop()
pass # doc for this function is error MdsApi_LogoutAll(self._env, True)
if not MdsApi_DestoryAll(self.env): MdsApi_DestoryAll(self._env)
pass # doc for this function is error
def join(self): def join(self):
self.message_loop.join() """"""
self._message_loop.join()
# why isn't arg a ContractData? # why isn't arg a ContractData?
def subscribe(self, req: SubscribeRequest): def subscribe(self, req: SubscribeRequest):
""""""
mds_req = MdsMktDataRequestReqT() mds_req = MdsMktDataRequestReqT()
entry = MdsMktDataRequestEntryT() entry = MdsMktDataRequestEntryT()
mds_req.subMode = eMdsSubscribeModeT.MDS_SUB_MODE_APPEND mds_req.subMode = eMdsSubscribeModeT.MDS_SUB_MODE_APPEND
@ -240,12 +279,11 @@ class OesMdApi:
entry.securityType = eMdsSecurityTypeT.MDS_SECURITY_TYPE_STOCK # todo: option and others entry.securityType = eMdsSecurityTypeT.MDS_SECURITY_TYPE_STOCK # todo: option and others
entry.instrId = int(req.symbol) entry.instrId = int(req.symbol)
self.message_loop.register_symbol(req.symbol, req.exchange) self._message_loop.register_symbol(req.symbol, req.exchange)
ret = MdsApi_SubscribeMarketData( ret = MdsApi_SubscribeMarketData(
self.env.tcpChannel, self._env.tcpChannel,
mds_req, mds_req,
entry) entry)
if not ret: if not ret:
self.gateway.write_log( self.gateway.write_log(
f"MdsApi_SubscribeByString failed with {ret}:{error_to_str(ret)}") f"MdsApi_SubscribeByString failed with {ret}:{error_to_str(ret)}")
pass

View File

@ -1,19 +1,20 @@
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime, timedelta, timezone
from threading import Thread from gettext import gettext as _
from threading import Lock, Thread
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from typing import Any, Callable, Dict, Tuple from typing import Any, Callable, Dict
from vnpy.api.oes.vnoes import OesApiClientEnvT, OesApi_DestoryAll, OesApi_InitAllByConvention, \ from vnpy.api.oes.vnoes import OesApiClientEnvT, OesApi_DestoryAll, OesApi_InitAllByConvention, \
OesApi_IsValidOrdChannel, OesApi_IsValidQryChannel, OesApi_IsValidRptChannel, OesApi_LogoutAll, \ OesApi_IsValidOrdChannel, OesApi_IsValidQryChannel, OesApi_IsValidRptChannel, OesApi_LogoutAll, \
OesApi_QueryCashAsset, OesApi_QueryEtf, OesApi_QueryIssue, OesApi_QueryOptHolding, \ OesApi_QueryCashAsset, OesApi_QueryOptHolding, OesApi_QueryOption, OesApi_QueryOrder, \
OesApi_QueryOption, OesApi_QueryOrder, OesApi_QueryStkHolding, OesApi_QueryStock, \ OesApi_QueryStkHolding, OesApi_QueryStock, OesApi_SendOrderCancelReq, OesApi_SendOrderReq, \
OesApi_SendOrderCancelReq, OesApi_SendOrderReq, OesApi_WaitReportMsg, OesOrdCancelReqT, \ OesApi_SetThreadPassword, OesApi_SetThreadUsername, OesApi_WaitReportMsg, OesOrdCancelReqT, \
OesOrdCnfmT, OesOrdRejectT, OesOrdReqT, OesQryCashAssetFilterT, OesQryCursorT, OesQryEtfFilterT, \ OesOrdCnfmT, OesOrdRejectT, OesOrdReqT, OesQryCashAssetFilterT, OesQryCursorT, \
OesQryIssueFilterT, OesQryOptionFilterT, OesQryOrdFilterT, OesQryStkHoldingFilterT, \ OesQryOptionFilterT, OesQryOrdFilterT, OesQryStkHoldingFilterT, OesQryStockFilterT, \
OesQryStockFilterT, OesRspMsgBodyT, OesStockBaseInfoT, OesTrdCnfmT, SGeneralClientChannelT, \ OesRspMsgBodyT, OesStockBaseInfoT, OesTrdCnfmT, SGeneralClientChannelT, SMSG_PROTO_BINARY, \
SMSG_PROTO_BINARY, SMsgHeadT, SPlatform_IsNegEpipe, SPlatform_IsNegEtimeout, cast, \ SMsgHeadT, SPlatform_IsNegEpipe, cast, eOesBuySellTypeT, eOesMarketIdT, \
eOesBuySellTypeT, eOesMarketIdT, eOesMsgTypeT, eOesOrdStatusT, eOesOrdTypeShT, eOesOrdTypeSzT eOesMsgTypeT, eOesOrdStatusT, eOesOrdTypeShT, eOesOrdTypeSzT
from vnpy.gateway.oes.error_code import error_to_str from vnpy.gateway.oes.error_code import error_to_str
from vnpy.trader.constant import Direction, Exchange, Offset, PriceType, Product, Status from vnpy.trader.constant import Direction, Exchange, Offset, PriceType, Product, Status
@ -84,87 +85,28 @@ STATUS_OES2VT = {
eOesOrdStatusT.OES_ORD_STATUS_INVALID_SZ_TRY_AGAIN: Status.REJECTED, eOesOrdStatusT.OES_ORD_STATUS_INVALID_SZ_TRY_AGAIN: Status.REJECTED,
} }
bjtz = timezone(timedelta(hours=8))
@dataclass @dataclass
class InternalOrder: class InternalOrder:
order_id: int = None order_id: int = None
vt_order: OrderData = None vt_order: OrderData = None
req_data: OesOrdReqT = None
rpt_data: OesOrdCnfmT = None
class OrderManager: def parse_oes_datetime(date: int, time: int):
"""convert oes datetime to python datetime"""
# YYYYMMDD
year = int(date / 10000)
month = int((date % 10000) / 100)
day = int(date % 100)
def __init__(self): # HHMMSSsss
self.last_order_id = 100000000 hour = int(time / 10000000)
self._orders: Dict[int, InternalOrder] = {} minute = int((time % 10000000) / 100000)
sec = int((time % 100000) / 1000)
# key tuple: seqNo, ordId, envId, userInfo mill = int(time % 1000)
self._remote_created_orders: Dict[Tuple[int, int, int, int], InternalOrder] = {} return datetime(year, month, day, hour, minute, sec, mill * 1000, tzinfo=bjtz)
@staticmethod
def hash_remote_order(data):
key = (data.origClSeqNo, data.origClOrdId, data.origClEnvId, data.userInfo)
return key
@staticmethod
def hash_remote_trade(data: OesTrdCnfmT):
key = (data.clSeqNo, data.clOrdId, data.clEnvId, data.userInfo)
return key
def new_local_id(self):
id = self.last_order_id
self.last_order_id += 1
return id
def new_remote_id(self):
id = self.last_order_id
self.last_order_id += 1
return id
def save_local_created(self, order_id: int, order: OrderData, oes_req: OesOrdReqT):
self._orders[order_id] = InternalOrder(
order_id=order_id,
vt_order=order,
req_data=oes_req
)
def save_remote_created(self, order_id: int, vt_order: OrderData, data: OesOrdCnfmT):
internal_order = InternalOrder(
order_id=order_id,
vt_order=vt_order,
rpt_data=data
)
self._orders[order_id] = internal_order
key = self.hash_remote_order(data)
self._remote_created_orders[key] = internal_order
def get_from_order_id(self, id: int):
return self._orders[id]
def get_remote_created_order_from_oes_data(self, data):
"""
:return: internal_order if succeed else None, will check only remote created order
"""
try:
key = self.hash_remote_order(data)
except AttributeError:
key = self.hash_remote_trade(data)
try:
return self._remote_created_orders[key]
except KeyError:
return None
def get_from_oes_data(self, data):
try:
key = self.hash_remote_order(data)
except AttributeError:
key = self.hash_remote_trade(data)
try:
return self._remote_created_orders[key]
except KeyError:
order_id = key[3]
return self._orders[order_id]
class OesTdMessageLoop: class OesTdMessageLoop:
@ -172,38 +114,52 @@ class OesTdMessageLoop:
def __init__(self, def __init__(self,
gateway: BaseGateway, gateway: BaseGateway,
env: OesApiClientEnvT, env: OesApiClientEnvT,
order_manager: OrderManager,
td: "OesTdApi" td: "OesTdApi"
): ):
""""""
self.gateway = gateway self.gateway = gateway
self.env = env
self.order_manager = order_manager
self.td = td
self.alive = False self._env = env
self.th = Thread(target=self.message_loop) self._td = td
self._alive = False
self._th = Thread(target=self._message_loop)
self.message_handlers: Dict[int, Callable[[dict], None]] = { self.message_handlers: Dict[int, Callable[[dict], None]] = {
eOesMsgTypeT.OESMSG_RPT_BUSINESS_REJECT: self.on_reject, eOesMsgTypeT.OESMSG_RPT_BUSINESS_REJECT: self.on_order_rejected,
eOesMsgTypeT.OESMSG_RPT_ORDER_INSERT: self.on_order_inserted, eOesMsgTypeT.OESMSG_RPT_ORDER_INSERT: self.on_order_inserted,
eOesMsgTypeT.OESMSG_RPT_ORDER_REPORT: self.on_order_report, eOesMsgTypeT.OESMSG_RPT_ORDER_REPORT: self.on_order_report,
eOesMsgTypeT.OESMSG_RPT_TRADE_REPORT: self.on_trade_report, eOesMsgTypeT.OESMSG_RPT_TRADE_REPORT: self.on_trade_report,
eOesMsgTypeT.OESMSG_RPT_STOCK_HOLDING_VARIATION: self.on_stock_holding, eOesMsgTypeT.OESMSG_RPT_STOCK_HOLDING_VARIATION: self.on_stock_holding,
eOesMsgTypeT.OESMSG_RPT_OPTION_HOLDING_VARIATION: self.on_option_holding, eOesMsgTypeT.OESMSG_RPT_OPTION_HOLDING_VARIATION: self.on_option_holding,
eOesMsgTypeT.OESMSG_RPT_CASH_ASSET_VARIATION: self.on_cash, eOesMsgTypeT.OESMSG_RPT_CASH_ASSET_VARIATION: self.on_cash,
eOesMsgTypeT.OESMSG_SESS_HEARTBEAT: lambda x: x,
eOesMsgTypeT.OESMSG_RPT_REPORT_SYNCHRONIZATION: lambda x: 1,
eOesMsgTypeT.OESMSG_SESS_HEARTBEAT: lambda x: 1,
} }
def start(self): def start(self):
self.alive = True """"""
self.th.start() self._alive = True
self._th.start()
def stop(self):
""""""
self._alive = False
def join(self): def join(self):
self.th.join() """"""
self._th.join()
def on_message(self, session_info: SGeneralClientChannelT, def reconnect(self):
""""""
self.gateway.write_log(_("正在尝试重新连接到交易服务器。"))
self._td.connect()
def _on_message(self, session_info: SGeneralClientChannelT,
head: SMsgHeadT, head: SMsgHeadT,
body: Any): body: Any):
""""""
if session_info.protocolType == SMSG_PROTO_BINARY: if session_info.protocolType == SMSG_PROTO_BINARY:
b = cast.toOesRspMsgBodyT(body) b = cast.toOesRspMsgBodyT(body)
if head.msgId in self.message_handlers: if head.msgId in self.message_handlers:
@ -215,31 +171,32 @@ class OesTdMessageLoop:
self.gateway.write_log(f"unknown prototype : {session_info.protocolType}") self.gateway.write_log(f"unknown prototype : {session_info.protocolType}")
return 1 return 1
def message_loop(self): def _message_loop(self):
rtp_channel = self.env.rptChannel """"""
rpt_channel = self._env.rptChannel
timeout_ms = 1000 timeout_ms = 1000
is_timeout = SPlatform_IsNegEtimeout
is_disconnected = SPlatform_IsNegEpipe is_disconnected = SPlatform_IsNegEpipe
while self.alive: while self._alive:
ret = OesApi_WaitReportMsg(rtp_channel, ret = OesApi_WaitReportMsg(rpt_channel,
timeout_ms, timeout_ms,
self.on_message) self._on_message)
if ret < 0: if ret < 0:
if is_timeout(ret): # if is_timeout(ret):
pass # pass # just no message
if is_disconnected(ret): if is_disconnected(ret):
# todo: handle disconnected self.gateway.write_log(_("与交易服务器的连接已断开。"))
self.alive = False while self._alive and not self.reconnect():
break
pass pass
return return
def on_reject(self, d: OesRspMsgBodyT): def on_order_rejected(self, d: OesRspMsgBodyT):
""""""
error_code = d.rptMsg.rptHead.ordRejReason error_code = d.rptMsg.rptHead.ordRejReason
error_string = error_to_str(error_code) error_string = error_to_str(error_code)
data: OesOrdRejectT = d.rptMsg.rptBody.ordRejectRsp data: OesOrdRejectT = d.rptMsg.rptBody.ordRejectRsp
i = self.order_manager.get_from_oes_data(data) if not data.origClSeqNo:
i = self._td.get_order(data.clSeqNo)
vt_order = i.vt_order vt_order = i.vt_order
if vt_order == Status.ALLTRADED: if vt_order == Status.ALLTRADED:
@ -250,32 +207,47 @@ class OesTdMessageLoop:
self.gateway.on_order(vt_order) self.gateway.on_order(vt_order)
self.gateway.write_log( self.gateway.write_log(
f"Order: {vt_order.vt_symbol}-{vt_order.vt_orderid} Code: {error_code} Rejected: {error_string}") f"Order: {vt_order.vt_symbol}-{vt_order.vt_orderid} Code: {error_code} Rejected: {error_string}")
else:
self.gateway.write_log(f"撤单失败,订单号: {data.origClSeqNo}。原因:{error_string}")
def on_order_inserted(self, d: OesRspMsgBodyT): def on_order_inserted(self, d: OesRspMsgBodyT):
""""""
data = d.rptMsg.rptBody.ordInsertRsp data = d.rptMsg.rptBody.ordInsertRsp
i = self.order_manager.get_from_oes_data(data) if not data.origClSeqNo:
i = self._td.get_order(data.clSeqNo)
else:
i = self._td.get_order(data.origClSeqNo)
vt_order = i.vt_order vt_order = i.vt_order
vt_order.status = STATUS_OES2VT[data.ordStatus] vt_order.status = STATUS_OES2VT[data.ordStatus]
vt_order.volume = data.ordQty - data.canceledQty vt_order.volume = data.ordQty
vt_order.traded = data.cumQty vt_order.traded = data.cumQty
vt_order.time = parse_oes_datetime(data.ordDate, data.ordTime)
self.gateway.on_order(vt_order) self.gateway.on_order(vt_order)
def on_order_report(self, d: OesRspMsgBodyT): def on_order_report(self, d: OesRspMsgBodyT):
""""""
data: OesOrdCnfmT = d.rptMsg.rptBody.ordCnfm data: OesOrdCnfmT = d.rptMsg.rptBody.ordCnfm
i = self.order_manager.get_from_oes_data(data) if not data.origClSeqNo:
i = self._td.get_order(data.clSeqNo)
else:
i = self._td.get_order(data.origClSeqNo)
vt_order = i.vt_order vt_order = i.vt_order
vt_order.status = STATUS_OES2VT[data.ordStatus] vt_order.status = STATUS_OES2VT[data.ordStatus]
vt_order.volume = data.ordQty - data.canceledQty vt_order.volume = data.ordQty
vt_order.traded = data.cumQty vt_order.traded = data.cumQty
vt_order.time = parse_oes_datetime(data.ordDate, data.ordCnfmTime)
self.gateway.on_order(vt_order) self.gateway.on_order(vt_order)
def on_trade_report(self, d: OesRspMsgBodyT): def on_trade_report(self, d: OesRspMsgBodyT):
""""""
data: OesTrdCnfmT = d.rptMsg.rptBody.trdCnfm data: OesTrdCnfmT = d.rptMsg.rptBody.trdCnfm
i = self.order_manager.get_from_oes_data(data) i = self._td.get_order(data.clSeqNo)
vt_order = i.vt_order vt_order = i.vt_order
# vt_order.status = STATUS_OES2VT[data.ordStatus] # vt_order.status = STATUS_OES2VT[data.ordStatus]
@ -289,25 +261,20 @@ class OesTdMessageLoop:
offset=vt_order.offset, offset=vt_order.offset,
price=data.trdPrice / 10000, price=data.trdPrice / 10000,
volume=data.trdQty, volume=data.trdQty,
time=datetime.utcnow().isoformat() # strict time=parse_oes_datetime(data.trdDate, data.trdTime)
) )
vt_order.status = STATUS_OES2VT[data.ordStatus]
vt_order.traded = data.cumQty
vt_order.time = parse_oes_datetime(data.trdDate, data.trdTime)
self.gateway.on_trade(trade) self.gateway.on_trade(trade)
self.gateway.on_order(vt_order)
# hack :
# Sometimes order_report is not received after a trade is received.
# (only trade msg but no order msg)
# This cause a problem that vt_order.traded stay 0 after a trade, which is a error state.
# So we have to query new status of order for every receiving of trade.
# BUT
# Oes have no async call to query order only.
# And calling sync function here will slow down vnpy.
# So we queue it into another thread.
self.td.schedule_query_order(i)
def on_option_holding(self, d: OesRspMsgBodyT): def on_option_holding(self, d: OesRspMsgBodyT):
""""""
pass pass
def on_stock_holding(self, d: OesRspMsgBodyT): def on_stock_holding(self, d: OesRspMsgBodyT):
""""""
data = d.rptMsg.rptBody.stkHoldingRpt data = d.rptMsg.rptBody.stkHoldingRpt
position = PositionData( position = PositionData(
gateway_name=self.gateway.gateway_name, gateway_name=self.gateway.gateway_name,
@ -315,21 +282,22 @@ class OesTdMessageLoop:
exchange=EXCHANGE_OES2VT[data.mktId], exchange=EXCHANGE_OES2VT[data.mktId],
direction=Direction.NET, direction=Direction.NET,
volume=data.sumHld, volume=data.sumHld,
frozen=data.lockHld, frozen=data.lockHld, # todo: to verify
price=data.costPrice / 10000, price=data.costPrice / 10000,
# pnl=data.costPrice - data.originalCostAmt, # pnl=data.costPrice - data.originalCostAmt,
pnl=0, # todo: oes只提供日初持仓价格信息不提供最初持仓价格信息所以pnl只有当日的 pnl=0,
yd_volume=data.originalHld, yd_volume=data.originalHld,
) )
self.gateway.on_position(position) self.gateway.on_position(position)
def on_cash(self, d: OesRspMsgBodyT): def on_cash(self, d: OesRspMsgBodyT):
""""""
data = d.rptMsg.rptBody.cashAssetRpt data = d.rptMsg.rptBody.cashAssetRpt
balance = data.currentTotalBal balance = data.currentTotalBal
availiable = data.currentAvailableBal availiable = data.currentAvailableBal
# drawable = data.currentDrawableBal # drawable = data.currentDrawableBal
account_id = data.custId account_id = data.cashAcctId
account = AccountData( account = AccountData(
gateway_name=self.gateway.gateway_name, gateway_name=self.gateway.gateway_name,
accountid=account_id, accountid=account_id,
@ -339,59 +307,74 @@ class OesTdMessageLoop:
self.gateway.on_account(account) self.gateway.on_account(account)
return 1 return 1
def stop(self):
self.alive = False
class OesTdApi: class OesTdApi:
def __init__(self, gateway: BaseGateway): def __init__(self, gateway: BaseGateway):
""""""
self.config_path: str = None
self.username: str = ''
self.password: str = ''
self.gateway = gateway self.gateway = gateway
self.env = OesApiClientEnvT()
self.order_manager = OrderManager() self._env = OesApiClientEnvT()
self.message_loop = OesTdMessageLoop(gateway,
self.env, self._message_loop = OesTdMessageLoop(gateway,
self.order_manager, self._env,
self) self)
self.account_id = None self._last_seq_lock = Lock()
self.last_seq_index = 1 # 0 has special manning for oes self._last_seq_index = 1000000 # 0 has special manning for oes
def connect(self, config_path: str): self._orders: Dict[int, InternalOrder] = {}
if not OesApi_InitAllByConvention(self.env, config_path, -1, self.last_seq_index):
pass
self.last_seq_index = self.env.ordChannel.lastOutMsgSeq + 1
if not OesApi_IsValidOrdChannel(self.env.ordChannel): def connect(self):
pass """Connect to trading server.
if not OesApi_IsValidQryChannel(self.env.qryChannel): :note set config_path before calling this function
pass """
if not OesApi_IsValidRptChannel(self.env.rptChannel): OesApi_SetThreadUsername(self.username)
pass OesApi_SetThreadPassword(self.password)
config_path = self.config_path
if not OesApi_InitAllByConvention(self._env, config_path, -1, self._last_seq_index):
return False
self._last_seq_index = max(self._last_seq_index, self._env.ordChannel.lastOutMsgSeq + 1)
if not OesApi_IsValidOrdChannel(self._env.ordChannel):
return False
if not OesApi_IsValidQryChannel(self._env.qryChannel):
return False
if not OesApi_IsValidRptChannel(self._env.rptChannel):
return False
return True
def start(self): def start(self):
self.message_loop.start() """"""
self._message_loop.start()
def stop(self): def stop(self):
self.message_loop.stop() """"""
if not OesApi_LogoutAll(self.env, True): self._message_loop.stop()
pass # doc for this function is error OesApi_LogoutAll(self._env, True)
if not OesApi_DestoryAll(self.env): OesApi_DestoryAll(self._env)
pass # doc for this function is error
def join(self): def join(self):
self.message_loop.join() """"""
self._message_loop.join()
def query_account(self) -> bool: def _get_new_seq_index(self):
return self.query_cash_asset() """"""
with self._last_seq_lock:
index = self._last_seq_index
self._last_seq_index += 1
return index
def query_cash_asset(self) -> bool: def query_account(self):
ret = OesApi_QueryCashAsset(self.env.qryChannel, """"""
OesApi_QueryCashAsset(self._env.qryChannel,
OesQryCashAssetFilterT(), OesQryCashAssetFilterT(),
self.on_query_asset self.on_query_asset
) )
return ret >= 0
def on_query_asset(self, def on_query_asset(self,
session_info: SGeneralClientChannelT, session_info: SGeneralClientChannelT,
@ -399,28 +382,25 @@ class OesTdApi:
body: Any, body: Any,
cursor: OesQryCursorT, cursor: OesQryCursorT,
): ):
""""""
data = cast.toOesCashAssetItemT(body) data = cast.toOesCashAssetItemT(body)
balance = data.currentTotalBal / 10000 balance = data.currentTotalBal / 10000
availiable = data.currentAvailableBal / 10000 availiable = data.currentAvailableBal / 10000
# drawable = data.currentDrawableBal # drawable = data.currentDrawableBal
account_id = data.custId account_id = data.cashAcctId
account = AccountData( account = AccountData(
gateway_name=self.gateway.gateway_name, gateway_name=self.gateway.gateway_name,
accountid=account_id, accountid=account_id,
balance=balance, balance=balance,
frozen=balance - availiable, frozen=balance - availiable,
) )
self.account_id = account_id
self.gateway.on_account(account) self.gateway.on_account(account)
return 1 return 1
def query_stock(self, ) -> bool: def query_stock(self, ) -> bool:
# Thread(target=self._query_stock, ).start() """"""
return self._query_stock()
def _query_stock(self, ) -> bool:
f = OesQryStockFilterT() f = OesQryStockFilterT()
ret = OesApi_QueryStock(self.env.qryChannel, f, self.on_query_stock) ret = OesApi_QueryStock(self._env.qryChannel, f, self.on_query_stock)
return ret >= 0 return ret >= 0
def on_query_stock(self, def on_query_stock(self,
@ -429,6 +409,7 @@ class OesTdApi:
body: Any, body: Any,
cursor: OesQryCursorT, cursor: OesQryCursorT,
): ):
""""""
data: OesStockBaseInfoT = cast.toOesStockItemT(body) data: OesStockBaseInfoT = cast.toOesStockItemT(body)
contract = ContractData( contract = ContractData(
gateway_name=self.gateway.gateway_name, gateway_name=self.gateway.gateway_name,
@ -443,8 +424,9 @@ class OesTdApi:
return 1 return 1
def query_option(self) -> bool: def query_option(self) -> bool:
""""""
f = OesQryOptionFilterT() f = OesQryOptionFilterT()
ret = OesApi_QueryOption(self.env.qryChannel, ret = OesApi_QueryOption(self._env.qryChannel,
f, f,
self.on_query_option self.on_query_option
) )
@ -456,6 +438,7 @@ class OesTdApi:
body: Any, body: Any,
cursor: OesQryCursorT, cursor: OesQryCursorT,
): ):
""""""
data = cast.toOesOptionItemT(body) data = cast.toOesOptionItemT(body)
contract = ContractData( contract = ContractData(
gateway_name=self.gateway.gateway_name, gateway_name=self.gateway.gateway_name,
@ -469,63 +452,10 @@ class OesTdApi:
self.gateway.on_contract(contract) self.gateway.on_contract(contract)
return 1 return 1
def query_issue(self) -> bool:
f = OesQryIssueFilterT()
ret = OesApi_QueryIssue(self.env.qryChannel,
f,
self.on_query_issue
)
return ret >= 0
def on_query_issue(self,
session_info: SGeneralClientChannelT,
head: SMsgHeadT,
body: Any,
cursor: OesQryCursorT,
):
data = cast.toOesIssueItemT(body)
contract = ContractData(
gateway_name=self.gateway.gateway_name,
symbol=data.securityId,
exchange=EXCHANGE_OES2VT[data.mktId],
name=data.securityName,
product=PRODUCT_OES2VT[data.mktId],
size=data.qtyUnit,
pricetick=1,
)
self.gateway.on_contract(contract)
return 1
def query_etf(self) -> bool:
f = OesQryEtfFilterT()
ret = OesApi_QueryEtf(self.env.qryChannel,
f,
self.on_query_etf
)
return ret >= 0
def on_query_etf(self,
session_info: SGeneralClientChannelT,
head: SMsgHeadT,
body: Any,
cursor: OesQryCursorT,
):
data = cast.toOesEtfItemT(body)
contract = ContractData(
gateway_name=self.gateway.gateway_name,
symbol=data.securityId,
exchange=EXCHANGE_OES2VT[data.mktId],
name=data.securityId,
product=PRODUCT_OES2VT[data.mktId],
size=data.creRdmUnit, # todo: to verify! creRdmUnit : 每个篮子 (最小申购、赎回单位) 对应的ETF份数
pricetick=1,
)
self.gateway.on_contract(contract)
return 1
def query_stock_holding(self) -> bool: def query_stock_holding(self) -> bool:
""""""
f = OesQryStkHoldingFilterT() f = OesQryStkHoldingFilterT()
ret = OesApi_QueryStkHolding(self.env.qryChannel, ret = OesApi_QueryStkHolding(self._env.qryChannel,
f, f,
self.on_query_stock_holding self.on_query_stock_holding
) )
@ -537,6 +467,7 @@ class OesTdApi:
body: Any, body: Any,
cursor: OesQryCursorT, cursor: OesQryCursorT,
): ):
""""""
data = cast.toOesStkHoldingItemT(body) data = cast.toOesStkHoldingItemT(body)
position = PositionData( position = PositionData(
@ -555,10 +486,11 @@ class OesTdApi:
return 1 return 1
def query_option_holding(self) -> bool: def query_option_holding(self) -> bool:
""""""
f = OesQryStkHoldingFilterT() f = OesQryStkHoldingFilterT()
f.mktId = eOesMarketIdT.OES_MKT_ID_UNDEFINE f.mktId = eOesMarketIdT.OES_MKT_ID_UNDEFINE
f.userInfo = 0 f.userInfo = 0
ret = OesApi_QueryOptHolding(self.env.qryChannel, ret = OesApi_QueryOptHolding(self._env.qryChannel,
f, f,
self.on_query_holding self.on_query_holding
) )
@ -570,6 +502,7 @@ class OesTdApi:
body: Any, body: Any,
cursor: OesQryCursorT, cursor: OesQryCursorT,
): ):
""""""
data = cast.toOesOptHoldingItemT(body) data = cast.toOesOptHoldingItemT(body)
# 权利 # 权利
@ -605,19 +538,20 @@ class OesTdApi:
return 1 return 1
def query_contracts(self): def query_contracts(self):
""""""
self.query_stock() self.query_stock()
# self.query_option() # self.query_option()
# self.query_issue() # self.query_issue()
pass
def query_position(self): def query_position(self):
""""""
self.query_stock_holding() self.query_stock_holding()
self.query_option_holding() self.query_option_holding()
def send_order(self, vt_req: OrderRequest): def send_order(self, vt_req: OrderRequest):
seq_id = self.last_seq_index """"""
self.last_seq_index += 1 # note: thread un-safe here, conflict with on_query_order seq_id = self._get_new_seq_index()
order_id = self.order_manager.new_local_id() order_id = seq_id
oes_req = OesOrdReqT() oes_req = OesOrdReqT()
oes_req.clSeqNo = seq_id oes_req.clSeqNo = seq_id
@ -628,16 +562,17 @@ class OesTdApi:
oes_req.securityId = vt_req.symbol oes_req.securityId = vt_req.symbol
oes_req.ordQty = int(vt_req.volume) oes_req.ordQty = int(vt_req.volume)
oes_req.ordPrice = int(vt_req.price * 10000) oes_req.ordPrice = int(vt_req.price * 10000)
oes_req.userInfo = order_id oes_req.origClOrdId = order_id
ret = OesApi_SendOrderReq(self.env.ordChannel,
oes_req
)
order = vt_req.create_order_data(str(order_id), self.gateway.gateway_name) order = vt_req.create_order_data(str(order_id), self.gateway.gateway_name)
order.direction = Direction.NET # fix direction into NET: stock only order.direction = Direction.NET # fix direction into NET: stock only
self.save_order(order_id, order)
ret = OesApi_SendOrderReq(self._env.ordChannel,
oes_req
)
if ret >= 0: if ret >= 0:
self.order_manager.save_local_created(order_id, order, oes_req)
self.gateway.on_order(order) self.gateway.on_order(order)
else: else:
self.gateway.write_log("Failed to send_order!") self.gateway.write_log("Failed to send_order!")
@ -645,58 +580,26 @@ class OesTdApi:
return order.vt_orderid return order.vt_orderid
def cancel_order(self, vt_req: CancelRequest): def cancel_order(self, vt_req: CancelRequest):
seq_id = self.last_seq_index """"""
self.last_seq_index += 1 # note: thread un-safe here seq_id = self._get_new_seq_index()
oes_req = OesOrdCancelReqT() oes_req = OesOrdCancelReqT()
order_id = int(vt_req.orderid) order_id = int(vt_req.orderid)
internal_order = self.order_manager.get_from_order_id(order_id) oes_req.mktId = EXCHANGE_VT2OES[vt_req.exchange]
if internal_order.rpt_data:
data = internal_order.rpt_data
# oes_req.origClSeqNo = self.local_id_to_sys_id[int(order_id)]
oes_req.origClOrdId = data.clOrdId
oes_req.origClSeqNo = data.clSeqNo
oes_req.origClEnvId = data.origClEnvId
oes_req.mktId = data.mktId
# oes_req.invAcctId = data.invAcctId
# oes_req.mktId = data.mktId
# oes_req.securityId = data.securityId
else:
data = internal_order.req_data
oes_req.origClSeqNo = data.clSeqNo
oes_req.mktId = internal_order.req_data.mktId
oes_req.clSeqNo = seq_id oes_req.clSeqNo = seq_id
oes_req.origClSeqNo = order_id
oes_req.invAcctId = "" oes_req.invAcctId = ""
oes_req.securityId = vt_req.symbol oes_req.securityId = vt_req.symbol
oes_req.userInfo = order_id OesApi_SendOrderCancelReq(self._env.ordChannel,
ret = OesApi_SendOrderCancelReq(self.env.ordChannel,
oes_req) oes_req)
if ret >= 0:
pass
else:
pass
return
def schedule_query_order(self, internal_order: InternalOrder) -> Thread:
th = Thread(target=self.query_order, args=(internal_order,))
th.start()
return th
def query_order(self, internal_order: InternalOrder) -> bool: def query_order(self, internal_order: InternalOrder) -> bool:
""""""
f = OesQryOrdFilterT() f = OesQryOrdFilterT()
if internal_order.req_data: f.mktId = EXCHANGE_VT2OES[internal_order.vt_order.exchange]
f.clSeqNo = internal_order.req_data.clSeqNo f.clSeqNo = internal_order.order_id
f.mktId = internal_order.req_data.mktId ret = OesApi_QueryOrder(self._env.qryChannel,
f.invAcctId = internal_order.req_data.invAcctId
else:
f.clSeqNo = internal_order.rpt_data.origClSeqNo
f.clOrdId = internal_order.rpt_data.origClOrdId
f.clEnvId = internal_order.rpt_data.origClEnvId
f.mktId = internal_order.rpt_data.mktId
f.invAcctId = internal_order.rpt_data.invAcctId
ret = OesApi_QueryOrder(self.env.qryChannel,
f, f,
self.on_query_order self.on_query_order
) )
@ -707,8 +610,10 @@ class OesTdApi:
head: SMsgHeadT, head: SMsgHeadT,
body: Any, body: Any,
cursor: OesQryCursorT): cursor: OesQryCursorT):
""""""
data: OesOrdCnfmT = cast.toOesOrdItemT(body) data: OesOrdCnfmT = cast.toOesOrdItemT(body)
i = self.order_manager.get_from_oes_data(data)
i = self.get_order(data.clSeqNo)
vt_order = i.vt_order vt_order = i.vt_order
vt_order.status = STATUS_OES2VT[data.ordStatus] vt_order.status = STATUS_OES2VT[data.ordStatus]
vt_order.volume = data.ordQty - data.canceledQty vt_order.volume = data.ordQty - data.canceledQty
@ -716,28 +621,33 @@ class OesTdApi:
self.gateway.on_order(vt_order) self.gateway.on_order(vt_order)
return 1 return 1
def init_query_orders(self) -> bool: def query_orders(self) -> bool:
""" """"""
:note: this function can be called only before calling send_order
:return:
"""
f = OesQryOrdFilterT() f = OesQryOrdFilterT()
ret = OesApi_QueryOrder(self.env.qryChannel, ret = OesApi_QueryOrder(self._env.qryChannel,
f, f,
self.on_init_query_orders self.on_query_orders
) )
return ret >= 0 return ret >= 0
def on_init_query_orders(self, def on_query_orders(self,
session_info: SGeneralClientChannelT, session_info: SGeneralClientChannelT,
head: SMsgHeadT, head: SMsgHeadT,
body: Any, body: Any,
cursor: OesQryCursorT, cursor: OesQryCursorT,
): ):
""""""
data: OesOrdCnfmT = cast.toOesOrdItemT(body) data: OesOrdCnfmT = cast.toOesOrdItemT(body)
i = self.order_manager.get_remote_created_order_from_oes_data(data) try:
if not i: i = self.get_order(data.clSeqNo)
order_id = self.order_manager.new_remote_id() vt_order = i.vt_order
vt_order.status = STATUS_OES2VT[data.ordStatus]
vt_order.volume = data.ordQty - data.canceledQty
vt_order.traded = data.cumQty
self.gateway.on_order(vt_order)
except KeyError:
# order_id = self.order_manager.new_remote_id()
order_id = data.clSeqNo
if data.bsType == eOesBuySellTypeT.OES_BS_TYPE_BUY: if data.bsType == eOesBuySellTypeT.OES_BS_TYPE_BUY:
offset = Offset.OPEN offset = Offset.OPEN
@ -748,7 +658,7 @@ class OesTdApi:
gateway_name=self.gateway.gateway_name, gateway_name=self.gateway.gateway_name,
symbol=data.securityId, symbol=data.securityId,
exchange=EXCHANGE_OES2VT[data.mktId], exchange=EXCHANGE_OES2VT[data.mktId],
orderid=order_id if order_id else data.userInfo, # generated id orderid=order_id if order_id else data.origClSeqNo, # generated id
direction=Direction.NET, direction=Direction.NET,
offset=offset, offset=offset,
price=data.ordPrice / 10000, price=data.ordPrice / 10000,
@ -759,13 +669,17 @@ class OesTdApi:
# this time should be generated automatically or by a static function # this time should be generated automatically or by a static function
time=datetime.utcnow().isoformat(), time=datetime.utcnow().isoformat(),
) )
self.order_manager.save_remote_created(order_id, vt_order, data) self.save_order(order_id, vt_order)
self.gateway.on_order(vt_order)
return 1
else:
vt_order = i.vt_order
vt_order.status = STATUS_OES2VT[data.ordStatus]
vt_order.volume = data.ordQty - data.canceledQty
vt_order.traded = data.cumQty
self.gateway.on_order(vt_order) self.gateway.on_order(vt_order)
return 1 return 1
def save_order(self, order_id: int, order: OrderData):
""""""
self._orders[order_id] = InternalOrder(
order_id=order_id,
vt_order=order,
)
def get_order(self, order_id: int):
""""""
return self._orders[order_id]