[update] 兼容各类接口得订单逻辑;升级天勤

This commit is contained in:
msincenselee 2022-01-06 11:36:26 +08:00
parent 55b207f5db
commit 39a231cbdf
10 changed files with 106 additions and 30 deletions

View File

@ -10,6 +10,10 @@ github 链接: https://github.com/msincenselee/vnpy
gitee 链接: https://gitee.com/vnpy2/vnpy gitee 链接: https://gitee.com/vnpy2/vnpy
###Fork版本主要改进如下 ###Fork版本主要改进如下
19、期权CTA引擎
-vnpy.app.cta_option
+ 支持股票ETF期权的CTA策略
18、CTA股票选股引擎 18、CTA股票选股引擎
@ -163,11 +167,13 @@ QQ/Wechat28888502
系列在线课程 系列在线课程
2022 期货CTA课程 http://www.uquant.org/course/49
2021 股票CTA实战课程http://www.uquant.org/course/49 2021 股票CTA实战课程http://www.uquant.org/course/49
2021 期货缠论高级课程http://www.uquant.org/course/48 2021 期货缠论高级课程http://www.uquant.org/course/48
2021 数字货币CTA课程http://www.uquant.org/course/46 2021 数字CTA课程http://www.uquant.org/course/46
2020 期货套利课程http://www.uquant.org/course/43 2020 期货套利课程http://www.uquant.org/course/43

View File

@ -0,0 +1,6 @@
:: 启动脚本
:: 进入盘、目录
c:
cd C:\GitHub\msincenselee_vnpy\prod\Matrix08
:: 启动无界面程序
C:\Users\incen\AppData\Local\conda\conda\envs\py37\python run_service.py

View File

@ -11,7 +11,7 @@ import pandas as pd
from contextlib import closing from contextlib import closing
from datetime import datetime, timedelta from datetime import datetime, timedelta
import argparse import argparse
from tqsdk import TqApi, TqSim from tqsdk import TqApi, TqSim,TqAuth
vnpy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) vnpy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
if vnpy_root not in sys.path: if vnpy_root not in sys.path:
@ -21,7 +21,7 @@ os.environ["VNPY_TESTING"] = "1"
from vnpy.data.tdx.tdx_future_data import get_future_contracts, Exchange from vnpy.data.tdx.tdx_future_data import get_future_contracts, Exchange
from vnpy.trader.utility import get_csv_last_dt, get_underlying_symbol, extract_vt_symbol from vnpy.trader.utility import get_csv_last_dt, get_underlying_symbol, extract_vt_symbol
from vnpy.data.tq.downloader import DataDownloader from vnpy.data.tq.downloader import DataDownloader,get_account_config
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) <= 1: if len(sys.argv) <= 1:
@ -36,9 +36,9 @@ if __name__ == "__main__":
if len(args.symbol) == 0: if len(args.symbol) == 0:
print('下载合约未设定 参数 -s rb2010') print('下载合约未设定 参数 -s rb2010')
os._exit(0) os._exit(0)
auth_dict = get_account_config()
# 开始下载(使用快期的免费行情websocket) # 开始下载(使用快期的免费行情websocket)
api = TqApi(account=TqSim(), url="wss://u.shinnytech.com/t/md/front/mobile") api = TqApi(account=TqSim(), auth=TqAuth(auth_dict['user_name'],auth_dict['password'])) # url="wss://u.shinnytech.com/t/md/front/mobile"
download_tasks = {} download_tasks = {}
begin_date = datetime.strptime(args.begin, '%Y%m%d') begin_date = datetime.strptime(args.begin, '%Y%m%d')
end_date = datetime.strptime(args.end, '%Y%m%d') end_date = datetime.strptime(args.end, '%Y%m%d')

View File

@ -412,6 +412,10 @@ class CtaOptionEngine(BaseEngine):
if not strategy: if not strategy:
if order.vt_orderid in self.internal_orderids: if order.vt_orderid in self.internal_orderids:
self.write_log(f'委托更新 => 内部仓位: {print_dict(order.__dict__)}') self.write_log(f'委托更新 => 内部仓位: {print_dict(order.__dict__)}')
if order.sys_orderid and order.sys_orderid != order.orderid and order.sys_orderid not in self.internal_orderids:
self.write_log(f'添加系统编号 {order.sys_orderid}=> 内部订单')
self.internal_orderids.add(order.sys_orderid)
# self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}') # self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}')
if order.type != OrderType.STOP: if order.type != OrderType.STOP:
if order.status in [Status.ALLTRADED, Status.CANCELLED, Status.REJECTED]: if order.status in [Status.ALLTRADED, Status.CANCELLED, Status.REJECTED]:
@ -425,6 +429,9 @@ class CtaOptionEngine(BaseEngine):
self.write_log(f'委托更新 => 系统账号 => {print_dict(order.__dict__)}') self.write_log(f'委托更新 => 系统账号 => {print_dict(order.__dict__)}')
return return
self.write_log(f'委托更新:{order.vt_orderid} => 策略:{strategy.strategy_name}') self.write_log(f'委托更新:{order.vt_orderid} => 策略:{strategy.strategy_name}')
if len(order.sys_orderid) > 0 and order.sys_orderid not in self.orderid_strategy_map:
self.write_log(f'登记系统委托号 {order.sys_orderid} => 策略:{strategy.strategy_name} 映射')
# Remove vt_orderid if order is no longer active. # Remove vt_orderid if order is no longer active.
vt_orderids = self.strategy_orderid_map[strategy.strategy_name] vt_orderids = self.strategy_orderid_map[strategy.strategy_name]
if order.vt_orderid in vt_orderids and not order.is_active(): if order.vt_orderid in vt_orderids and not order.is_active():
@ -468,7 +475,7 @@ class CtaOptionEngine(BaseEngine):
if not strategy: if not strategy:
# 属于内部单子 # 属于内部单子
if trade.vt_orderid in self.internal_orderids: if trade.vt_orderid in self.internal_orderids or trade.sys_orderid in self.internal_orderids:
cur_pos = self.net_pos_holding.get(trade.vt_symbol, 0) cur_pos = self.net_pos_holding.get(trade.vt_symbol, 0)
if trade.direction == Direction.LONG: if trade.direction == Direction.LONG:
new_pos = cur_pos + trade.volume new_pos = cur_pos + trade.volume
@ -478,13 +485,18 @@ class CtaOptionEngine(BaseEngine):
self.write_log(f'成交单:trade:{print_dict(trade.__dict__)}') self.write_log(f'成交单:trade:{print_dict(trade.__dict__)}')
self.net_pos_holding.update({trade.vt_symbol: new_pos}) self.net_pos_holding.update({trade.vt_symbol: new_pos})
self.save_internal_data() self.save_internal_data()
return
if trade.sys_orderid and trade.sys_orderid in self.orderid_strategy_map:
self.write_log(f'使用系统委托单号{trade.sys_orderid} => 策略')
strategy = self.orderid_strategy_map.get(trade.sys_orderid, None)
# 可能是其他实例得 # 可能是其他实例得
else: if not strategy:
self.write_log(f'成交更新 => 没有对应的策略设置:trade:{trade.__dict__}') self.write_log(f'成交更新 => 没有对应的策略设置:trade:{trade.__dict__}')
self.write_log(f'成交更新 => 当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}') self.write_log(f'成交更新 => 当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}')
self.write_log(f'成交更新 => 当前内部订单清单:{self.internal_orderids}') self.write_log(f'成交更新 => 当前内部订单清单:{self.internal_orderids}')
return return
self.write_log(f'成交更新 =>:{trade.vt_orderid} => 策略:{strategy.strategy_name}') self.write_log(f'成交更新 =>:{trade.vt_orderid} => 策略:{strategy.strategy_name}')

View File

@ -353,6 +353,11 @@ class CtaEngine(BaseEngine):
self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}') self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}')
return return
self.write_log(f'委托更新:{order.vt_orderid} => 策略:{strategy.strategy_name}') self.write_log(f'委托更新:{order.vt_orderid} => 策略:{strategy.strategy_name}')
if len(order.sys_orderid) > 0 and order.sys_orderid not in self.orderid_strategy_map:
self.write_log(f'登记系统委托号 {order.sys_orderid} => 策略:{strategy.strategy_name} 映射')
self.orderid_strategy_map.update({order.sys_orderid: strategy})
# Remove vt_orderid if order is no longer active. # Remove vt_orderid if order is no longer active.
vt_orderids = self.strategy_orderid_map[strategy.strategy_name] vt_orderids = self.strategy_orderid_map[strategy.strategy_name]
if order.vt_orderid in vt_orderids and not order.is_active(): if order.vt_orderid in vt_orderids and not order.is_active():
@ -388,11 +393,16 @@ class CtaEngine(BaseEngine):
strategy = self.orderid_strategy_map.get(trade.vt_orderid, None) strategy = self.orderid_strategy_map.get(trade.vt_orderid, None)
if not strategy: if not strategy:
self.write_log(f'成交单没有对应的策略设置:trade:{trade.__dict__}') if trade.sys_orderid and trade.sys_orderid in self.orderid_strategy_map:
self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}') self.write_log(f'使用系统委托单号{trade.sys_orderid} => 策略')
return strategy = self.orderid_strategy_map.get(trade.sys_orderid, None)
self.write_log(f'成交更新:{trade.vt_orderid} => 策略:{strategy.strategy_name}') if not strategy:
self.write_log(f'成交单没有对应的策略设置:trade:{trade.__dict__}')
self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}')
return
self.write_log(f'成交更新,本地委托{trade.vt_orderid},系统委托{trade.sys_orderid} => 策略:{strategy.strategy_name}')
# Update strategy pos before calling on_trade method # Update strategy pos before calling on_trade method
# 取消外部干预策略pos由策略自行完成更新 # 取消外部干预策略pos由策略自行完成更新

View File

@ -425,6 +425,14 @@ class BackTestingEngine(object):
@lru_cache() @lru_cache()
def get_size(self, vt_symbol: str): def get_size(self, vt_symbol: str):
"""查询合约的size""" """查询合约的size"""
if vt_symbol not in self.size:
symbol, exchange = extract_vt_symbol(vt_symbol)
if symbol in self.size:
return self.size.get(symbol)
else:
underly_symbol = get_underlying_symbol(symbol).upper()
return self.size.get(f'{underly_symbol}99',10)
return self.size.get(vt_symbol, 10) return self.size.get(vt_symbol, 10)
def set_price(self, vt_symbol: str, price: float): def set_price(self, vt_symbol: str, price: float):
@ -440,11 +448,16 @@ class BackTestingEngine(object):
if rate >= 0.1: if rate >= 0.1:
self.fix_commission.update({vt_symbol: rate}) self.fix_commission.update({vt_symbol: rate})
@lru_cache()
def get_commission_rate(self, vt_symbol: str): def get_commission_rate(self, vt_symbol: str):
""" 获取保证金比例,缺省万分之一""" """ 获取保证金比例,缺省万分之一"""
if vt_symbol not in self.commission_rate: if vt_symbol not in self.commission_rate:
symbol, exchange = extract_vt_symbol(vt_symbol) symbol, exchange = extract_vt_symbol(vt_symbol)
return self.commission_rate.get(symbol, float(0.0001)) if symbol in self.commission_rate:
return self.commission_rate.get(symbol)
else:
underly_symbol = get_underlying_symbol(symbol).upper()
return self.commission_rate.get(f'{underly_symbol}99', float(0.0001))
return self.commission_rate.get(vt_symbol, float(0.0001)) return self.commission_rate.get(vt_symbol, float(0.0001))
def get_fix_commission(self, vt_symbol: str): def get_fix_commission(self, vt_symbol: str):
@ -887,6 +900,16 @@ class BackTestingEngine(object):
self.write_log(f"新增订阅指数合约:{setting['idx_symbol']}") self.write_log(f"新增订阅指数合约:{setting['idx_symbol']}")
self.subscribe_symbol(strategy_name=strategy_name, vt_symbol=setting['idx_symbol']) self.subscribe_symbol(strategy_name=strategy_name, vt_symbol=setting['idx_symbol'])
# 如果act_vt_symbol不再列表中需要订阅
if 'act_vt_symbol' in setting.keys() and setting['act_vt_symbol'] not in self.symbol_strategy_map.keys():
self.write_log(f"新增订阅act_vt_symbol合约:{setting['act_vt_symbol']}")
self.subscribe_symbol(strategy_name=strategy_name, vt_symbol=setting['act_vt_symbol'])
# 如果pas_vt_symbol不再列表中需要订阅
if 'pas_vt_symbol' in setting.keys() and setting['pas_vt_symbol'] not in self.symbol_strategy_map.keys():
self.write_log(f"新增订阅pas_vt_symbol合约:{setting['pas_vt_symbol']}")
self.subscribe_symbol(strategy_name=strategy_name, vt_symbol=setting['pas_vt_symbol'])
if strategy_setting.get('auto_init', False): if strategy_setting.get('auto_init', False):
self.write_log(u'自动初始化策略') self.write_log(u'自动初始化策略')
strategy.on_init() strategy.on_init()
@ -1264,8 +1287,8 @@ class BackTestingEngine(object):
target=self.get_price_tick(vt_symbol)) - self.get_price_tick( target=self.get_price_tick(vt_symbol)) - self.get_price_tick(
vt_symbol) # 在当前时间点前发出的卖出委托可能的最优成交价 vt_symbol) # 在当前时间点前发出的卖出委托可能的最优成交价
else: else:
buy_cross_price = tick.last_price buy_cross_price = tick.last_price if not tick.ask_price_1 else tick.ask_price_1
sell_cross_price = tick.last_price sell_cross_price = tick.last_price if not tick.bid_price_1 else tick.bid_price_1
buy_best_cross_price = tick.last_price buy_best_cross_price = tick.last_price
sell_best_cross_price = tick.last_price sell_best_cross_price = tick.last_price
@ -2317,6 +2340,8 @@ class BackTestingEngine(object):
result_info.update({u'Sharpe Ratio': d['sharpe']}) result_info.update({u'Sharpe Ratio': d['sharpe']})
self.output(u'Sharpe Ratio\t%s' % format_number(d['sharpe'])) self.output(u'Sharpe Ratio\t%s' % format_number(d['sharpe']))
result_file = os.path.abspath(os.path.join(self.get_logs_path(), '{}_result.csv'.format(self.test_name)))
self.append_data(result_file, result_info)
# 保存回测结果/交易记录/日线统计 至数据库 # 保存回测结果/交易记录/日线统计 至数据库
self.save_result_to_mongo(result_info) self.save_result_to_mongo(result_info)

View File

@ -419,9 +419,9 @@ class CtaSpreadTemplate(CtaTemplate):
if order_info is not None: if order_info is not None:
# 委托单记录 =》 找到 Grid # 委托单记录 =》 找到 Grid
grid = order_info.get('grid') grid = order_info.get('grid')
if grid and order_info.get('offset', None) == Offset.OPEN: if grid :
# 更新平均开仓/平仓得价格,数量 # 更新平均开仓/平仓得价格,数量
self.update_grid_trade(trade, grid) self.update_grid_trade(order_info.get('offset', None), trade, grid)
def update_pos(self, price, volume, operation, dt): def update_pos(self, price, volume, operation, dt):
"""更新持仓组件得pos""" """更新持仓组件得pos"""
@ -535,14 +535,14 @@ class CtaSpreadTemplate(CtaTemplate):
"""修正order被拆单得情况""" """修正order被拆单得情况"""
order_info = self.active_orders.get(order.vt_orderid, None) order_info = self.active_orders.get(order.vt_orderid, None)
if order_info: if order_info:
volume = order_info.get('volume') volume = order_info.get('volume') # 原始委托数量
traded = order_info.get('traded') traded = order_info.get('traded') # 原始委托中,已成交的数量
if volume != order.volume: if volume != order.volume:
self.write_log(f'调整{order.vt_orderid} {order.vt_symbol} 委托:{volume}=>{order.volume}') self.write_log(f'更新未完成订单{order.vt_orderid} {order.vt_symbol} 委托数量:{volume}=>{order.volume}')
order_info.update({'volume': order.volume}) order_info.update({'volume': order.volume})
if traded != order.traded: if traded != order.traded:
self.write_log(f'{order.vt_orderid} {order.vt_symbol} 已成交 :{traded}=>{traded + order.traded}') self.write_log(f'更新未完成订单{order.vt_orderid} {order.vt_symbol} 已成交数量 :{traded}=>{traded + order.traded}')
order_info.update({'volume': traded + order.traded}) order_info.update({'traded': traded + order.traded})
def on_order(self, order: OrderData): def on_order(self, order: OrderData):
"""报单更新""" """报单更新"""
@ -582,9 +582,9 @@ class CtaSpreadTemplate(CtaTemplate):
else: else:
self.write_error(u'委托单{}不在策略的未完成订单列表中:{}'.format(order.vt_orderid, self.active_orders)) self.write_error(u'委托单{}不在策略的未完成订单列表中:{}'.format(order.vt_orderid, self.active_orders))
def update_grid_trade(self, trade: TradeData, grid: CtaGrid): def update_grid_trade(self, offset: Offset, trade: TradeData, grid: CtaGrid):
"""更新网格内,主动腿/被动腿得开平仓信息""" """更新网格内,主动腿/被动腿得开平仓信息"""
if trade.offset == Offset.OPEN: if offset == Offset.OPEN:
# 更新开仓均价/数量 # 更新开仓均价/数量
if trade.vt_symbol == self.act_vt_symbol: if trade.vt_symbol == self.act_vt_symbol:
opened_price = grid.snapshot.get('act_open_price', 0) opened_price = grid.snapshot.get('act_open_price', 0)
@ -660,7 +660,7 @@ class CtaSpreadTemplate(CtaTemplate):
grid.traded_volume = 0 grid.traded_volume = 0
# 平仓完毕cover sell # 平仓完毕cover sell
if order.offset != Offset.OPEN: if order_info.get("offset", None) != Offset.OPEN:
grid.open_status = False grid.open_status = False
grid.close_status = True grid.close_status = True

View File

@ -2496,7 +2496,10 @@ class CtaLineBar(object):
def __count_ama(self): def __count_ama(self):
"""计算K线的卡夫曼自适应AMA1 """计算K线的卡夫曼自适应AMA1
如何测量价格变动的速率 如何测量价格变动的速率
    采用的方法是在一定的周期内计算每个周期价格的变动的累加用整个周期的总体价格变动除以每个周期价格变动的累加我们采用这个数字作为价格变化的速率如果股票持续上涨或下跌那么变动的速率就是1如果股票在一定周期内涨跌的幅度为0那么价格的变动速率就是0变动速率为1对应的最快速的均线2日的EMA变动速率为0 则对应最慢速的均线30日EMA     采用的方法是在一定的周期内计算每个周期价格的变动的累加用整个周期的总体价格变动除以每个周期价格变动的累加
我们采用这个数字作为价格变化的速率如果股票持续上涨或下跌那么变动的速率就是1
如果股票在一定周期内涨跌的幅度为0那么价格的变动速率就是0变动速率为1
对应的最快速的均线2日的EMA变动速率为0 则对应最慢速的均线30日EMA
    以通达信软件的公式为例其他软件也可以用     以通达信软件的公式为例其他软件也可以用
    每个周期价格变动的累加:=sum(abs(close-ref(close,1)),n);     每个周期价格变动的累加:=sum(abs(close-ref(close,1)),n);
    整个周期价格的总体变动:=abs(close-ref(close,n));     整个周期价格的总体变动:=abs(close-ref(close,n));

View File

@ -6,15 +6,31 @@
# 2. 下载tick时5档行情都下载 # 2. 下载tick时5档行情都下载
# 3. 五档行情变量调整适合vnpy的命名方式 # 3. 五档行情变量调整适合vnpy的命名方式
import os
import csv import csv
from datetime import date, datetime from datetime import date, datetime
from typing import Union, List from typing import Union, List
import json
from tqsdk.api import TqApi from tqsdk.api import TqApi
from tqsdk.datetime import _get_trading_day_start_time, _get_trading_day_end_time from tqsdk.datetime import _get_trading_day_start_time, _get_trading_day_end_time
from tqsdk.diff import _get_obj from tqsdk.diff import _get_obj
from tqsdk.utils import _generate_uuid from tqsdk.utils import _generate_uuid
def get_account_config():
"""
获取本地账号配置
:return:
"""
config_file_name = os.path.abspath(os.path.join(os.path.dirname(__file__), 'auth_account.json'))
if os.path.exists(config_file_name):
try:
with open(config_file_name, mode="r", encoding="UTF-8") as f:
data = json.load(f)
return data
except Exception as ex:
pass
return {}
class DataDownloader: class DataDownloader:
""" """

View File

@ -792,8 +792,6 @@ class LocalOrderManager:
Keep an order buf before pushing it to gateway. Keep an order buf before pushing it to gateway.
""" """
self.orders[order.orderid] = copy(order) self.orders[order.orderid] = copy(order)
self.gateway.on_order(order) self.gateway.on_order(order)
def cancel_order(self, req: CancelRequest) -> None: def cancel_order(self, req: CancelRequest) -> None: