掘金终端实现仿真交易
穷人版仿真交易解决方案
掘金仿真交易终端可以提供模拟精准撮合成交。并且提供了python的工具包gmtrade
以供用户与交易终端交互。相当于前文的CTP
用gmtrade
代替。
VNPY对掘金仿真交易的封装在vnpy_gm
包中。
代码解读
掘金交易网关GmGateway
package
from gmtrade.api import (
set_token,
order_volume,
order_cancel,
get_cash,
get_positions,
get_orders,
get_execution_reports,
login,
set_endpoint,
account
)
这里从掘金终端提供的API接口中,导入了一系列与交易有关的函数
order_volume,order_cancel
:按指定量委托,取消委托get_cash,get_positions,get_orders,get_execution_reports
:查询现金余额、持仓、订单、执行反馈
from gmtrade.api.storage import ctx
from gmtrade.api.callback import callback_controller
from gmtrade.csdk.c_sdk import (
c_status_fail,
py_gmi_set_data_callback,
py_gmi_start,
py_gmi_stop
)
GmGateway的实现
class GmGateway(BaseGateway):
"""
VeighNa用于对接掘金量化终端的交易接口。
"""
default_name: str = "GM"
default_setting: Dict[str, str] = {
"Token": "",
"账户ID": ""
}
exchanges: List[str] = list(EXCHANGE_GM2VT.values())
def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
"""构造函数"""
super().__init__(event_engine, gateway_name)
self.md_api: "GmMdApi" = GmMdApi(self)
self.td_api: "GmTdApi" = GmTdApi(self)
self.run_timer: Thread = Thread(target=self.process_timer_event)
def connect(self, setting: dict) -> None:
"""连接交易接口"""
token: str = setting["Token"]
accountid: str = setting["账户ID"]
self.md_api.init()
self.td_api.connect(token, accountid)
self.init_query()
def subscribe(self, req: SubscribeRequest) -> None:
"""订阅行情"""
self.md_api.subscribe(req)
def send_order(self, req: OrderRequest) -> str:
"""委托下单"""
return self.td_api.send_order(req)
def cancel_order(self, req: CancelRequest) -> None:
"""委托撤单"""
self.td_api.cancel_order(req)
def query_account(self) -> None:
"""查询资金"""
self.td_api.query_account()
def query_position(self) -> None:
"""查询持仓"""
self.td_api.query_position()
def process_timer_event(self) -> None:
"""定时事件处理"""
while self.td_api._active and self.md_api._active:
sleep(3)
self.query_position()
self.query_account()
self.md_api.query_realtime_quotes()
def init_query(self) -> None:
"""初始化查询任务"""
self.run_timer.start()
def close(self) -> None:
"""关闭接口"""
self.md_api.close()
self.td_api.close()
前面的文章说到,在BaseGateway
中,已经定义好了与VNPY事件引擎交互的函数,所以在继承模板类之后,没有再定义这类函数。
代码主要是具体定义与网关交互的函数。与网关交互的函数又与每个网关需要用到的行情接口与交易接口相关。故对于每一种交易网关,都需要定义其独特的行情接口与交易接口类。
在CTP中,行情接口与交易接口分别是MdApi
和TdApi
。此处沿用了这一命名方式。但与CTP既支持实时行情,又支持交易不同,使用掘金终端进行仿真交易时,实时行情来自于Tushare。故实施方案为:
- 行情接口:
GmMdApi
封装Tushare - 交易接口:
GmTdApi
封装gmtrade
在GmGateway
的初始化中,可以看到一开始就调用了这两个接口:
self.md_api: "GmMdApi" = GmMdApi(self)
self.td_api: "GmTdApi" = GmTdApi(self)
后续与网关交互的函数,都是对md_api
或是td_api
的函数做进一步封装。
实时行情的获取
重点看process_timer_event
:其逻辑为只要md_api
和td_api
是运行中的,就会不断地每隔3秒调用md_api.query_realtime_quotes()
拿到一个快照数据。
def process_timer_event(self) -> None:
"""定时事件处理"""
while self.td_api._active and self.md_api._active:
sleep(3)
self.query_position()
self.query_account()
self.md_api.query_realtime_quotes()
在初始化中,可以看到有一个专门的线程来执行获取实时行情:
self.run_timer: Thread = Thread(target=self.process_timer_event)
行情接口
行情接口封装了Tushare的获取实时行情、历史行情等功能(此处略去历史行情)。
class GmMdApi:
def __init__(self, gateway: GmGateway) -> None:
"""构造函数"""
self.gateway: GmGateway = gateway
self.gateway_name: str = gateway.gateway_name
self.username: str = SETTINGS["datafeed.username"]
self.password: str = SETTINGS["datafeed.password"]
self._active: bool = False
self.subscribed: set = set()
def subscribe(self, req: SubscribeRequest) -> None:
"""订阅行情"""
if req.symbol in symbol_contract_map:
self.subscribed.add(req.symbol)
关注订阅集合subscribed
,当要订阅合约时,调用GmGateway.subscribe -> GmMdApi.subscribe
,将一个合约放到subscribed
中。
可以看到,这里订阅合约只是在本地储存一个集合,而并不是上传至网关获取权限。
def init(self) -> None:
"""初始化"""
ts.set_token(self.password)
self.pro: DataApi = ts.pro_api()
self._active = True
self.gateway.write_log("数据服务初始化完成")
以上传入了Tushare的Key,对Tushare的接口做了初始化。
def query_realtime_quotes(self) -> None:
"""查询行情数据"""
try:
df: DataFrame = ts.get_realtime_quotes(self.subscribed)
except IOError:
return
if df is not None:
# 处理原始数据中的NaN值
df.fillna(0, inplace=True)
for ix, row in df.iterrows():
dt: str = row["date"].replace("-", "") + " " + row["time"].replace(":", "")
contract: ContractData = symbol_contract_map[row["code"]]
tick: tick = TickData(......)
self.gateway.on_tick(tick)
query_realtime_quotes
调用Tushare接口获取了pd.DataFrame
格式的快照数据。
此处将df
重新整理成VNPY的TickData格式,再调用gateway.on_tick(tick)
,将Tick数据推送至事件引擎。
至此,数据链条就完整了:
实时行情获取 -> 推送至事件引擎 -> CTA引擎处理事件 -> CTA策略接收到实时行情 -> 推送到K线合成器、K线池 -> 策略生成交易信号
def close(self) -> None:
"""关闭连接"""
if self._active:
self._active = False
最后,提供关闭行情接口的函数。
交易接口
交易接口封装了gmtrade关于交易的接口。
class GmTdApi:
def __init__(self, gateway: GmGateway):
"""构造函数"""
super().__init__()
self.gateway: GmGateway = gateway
self.gateway_name: str = gateway.gateway_name
self.inited: bool = False
self._active: bool = False
def onconnected(self) -> None:
"""服务器连接成功回报"""
self.gateway.write_log("交易服务器连接成功")
def ondisconnected(self) -> None:
"""服务器连接断开回报"""
self.gateway.write_log("交易服务器连接断开")
def onRtnOrder(self, order) -> None:
"""生成时间戳"""
type: OrderType = ORDERTYPE_GM2VT.get(order.order_type, None)
if type is None:
return
exchange, symbol = order.symbol.split(".")
order_data: OrderData = OrderData(......)
self.gateway.on_order(order_data)
if order.ord_rej_reason_detail:
self.gateway.write_log(f"委托拒单:{order.ord_rej_reason_detail}")
def onRtnTrade(self, rpt) -> None:
"""生成时间戳"""
if rpt.exec_type != 15:
if rpt.ord_rej_reason_detail:
self.gateway.write_log(rpt.ord_rej_reason_detail)
return
exchange, symbol = rpt.symbol.split(".")
trade: TradeData = TradeData(......)
self.gateway.on_trade(trade)
def on_error(self, code, info) -> None:
"""输出错误信息"""
self.gateway.write_log(f"错误代码:{code},信息:{info}")
def connect(self, token: str, accountid: str) -> None:
"""连接交易接口"""
if not self.inited:
self.inited = True
set_token(token)
set_endpoint()
login(account(accountid))
err: int = self.init_callback()
if err:
self.gateway.write_log(f"交易服务器登陆失败,错误码{err}")
return
self.query_order()
self.query_trade()
else:
self.gateway.write_log("已经初始化,请勿重复操作")
def init_callback(self) -> int:
"""注册回调"""
ctx.inside_file_module = self
ctx.on_execution_report_fun = self.onRtnTrade
ctx.on_order_status_fun = self.onRtnOrder
ctx.on_trade_data_connected_fun = self.onconnected
ctx.on_trade_data_disconnected_fun = self.ondisconnected
ctx.on_error_fun = self.on_error
py_gmi_set_data_callback(callback_controller) # 设置事件处理的回调函数
status: int = py_gmi_start() # type: int
if c_status_fail(status, 'gmi_start'):
self._active = False
return status
else:
self._active = True
return status
以上均为回调函数,即掘金终端作为仿真柜台,会时刻输出状态变化的反馈,如:订单状态、成交信息等。
ctx
是提供给API的唯一上下文实例,仿真柜台的各种反馈消息都通过ctx
传递。
所以这里是定义了一系列函数,从ctx
这里接受信息,进而与仿真柜台对接。
def send_order(self, req: OrderRequest) -> str:
"""委托下单"""
if req.offset not in POSITIONEFFECT_VT2GM:
self.gateway.write_log("请选择开平方向")
return ""
type: int = ORDERTYPE_VT2GM.get(req.type, None)
if type is None:
self.gateway.write_log(f"不支持的委托类型: {req.type.value}")
return ""
exchange: str = EXCHANGE_VT2GM.get(req.exchange, None)
if exchange is None:
self.gateway.write_log(f"不支持的交易所: {req.exchange.value}")
return ""
symbol: str = exchange + "." + req.symbol
order_data: list = order_volume(
symbol=symbol,
volume=int(req.volume),
side=DIRECTION_VT2GM[req.direction],
order_type=type,
price=req.price,
position_effect=POSITIONEFFECT_VT2GM[req.offset],
)
orderid: str = order_data[0].cl_ord_id
order: OrderData = req.create_order_data(orderid, self.gateway_name)
self.gateway.on_order(order)
return order.vt_orderid
def cancel_order(self, req: CancelRequest) -> None:
"""委托撤单"""
cancel_order: dict = {"cl_ord_id": req.orderid}
order_cancel(wait_cancel_orders=cancel_order)
以上是订单的提交与撤单函数
send_order
中,调用order_volume
即是将下单请求报给掘金终端。然后,掘金终端会返回下单请求对应的订单数据order_data
,经整理后,由gateway.on_order(order)
推送至本地的订单管理系统。- 由于网关的订单组织形式与VNPY默认形式也许不同(如:交易所代码,证券代码形式等),才需要做各种各样的映射。
- 订单信息要推送至本地的订单管理系统,才能在UI界面上看到。
cancel_order
中,调用order_cancel
即是将撤单请求报给掘金终端。
def query_position(self) -> None:
"""查询持仓"""
data: list = get_positions()
for d in data:
exchange_, symbol = d.symbol.split(".")
exchange: Exchange = EXCHANGE_GM2VT.get(exchange_, None)
if not exchange:
continue
position: PositionData = PositionData(......)
self.gateway.on_position(position)
def query_account(self) -> None:
"""查询账户资金"""
data = get_cash()
if not data:
self.gateway.write_log("请检查accountid")
return
account: AccountData = AccountData(......)
account.available = round(data.available, 2)
self.gateway.on_account(account)
def query_order(self) -> None:
"""查询委托信息"""
data: list = get_orders()
for d in data:
type: OrderType = ORDERTYPE_GM2VT.get(d.order_type, None)
exchange_, symbol = d.symbol.split(".")
exchange: Exchange = EXCHANGE_GM2VT.get(exchange_, None)
if not type or not exchange:
continue
order: OrderData = OrderData(......)
self.gateway.on_order(order)
self.gateway.write_log("委托信息查询成功")
def query_trade(self) -> None:
"""查询成交信息"""
data: list = get_execution_reports()
for d in data:
exchange_, symbol = d.symbol.split(".")
exchange: Exchange = EXCHANGE_GM2VT.get(exchange_, None)
if not exchange:
continue
trade: TradeData = TradeData(......)
self.gateway.on_trade(trade)
self.gateway.write_log("成交信息查询成功")
def close(self) -> None:
"""关闭连接"""
if self.inited:
self._active = False
py_gmi_stop()
以上均为查询函数,每一种查询都需要调用相应的回调函数,将查询信息推送至VNPY事件引擎,再交由事件引擎处理。目的是将交易信息实时显示在本地VNPY界面上。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix