From 39a231cbdf38360059d4c3201253062bf469cba8 Mon Sep 17 00:00:00 2001 From: msincenselee Date: Thu, 6 Jan 2022 11:36:26 +0800 Subject: [PATCH] =?UTF-8?q?[update]=20=E5=85=BC=E5=AE=B9=E5=90=84=E7=B1=BB?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=BE=97=E8=AE=A2=E5=8D=95=E9=80=BB=E8=BE=91?= =?UTF-8?q?;=E5=8D=87=E7=BA=A7=E5=A4=A9=E5=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++-- prod/Matrix08/win_start.bat | 6 ++++ prod/jobs/refill_tq_future_ticks.py | 8 ++--- vnpy/app/cta_option/engine.py | 18 ++++++++++-- vnpy/app/cta_stock/engine.py | 18 +++++++++--- vnpy/app/cta_strategy_pro/back_testing.py | 31 ++++++++++++++++++-- vnpy/app/cta_strategy_pro/template_spread.py | 20 ++++++------- vnpy/component/cta_line_bar.py | 5 +++- vnpy/data/tq/downloader.py | 18 +++++++++++- vnpy/trader/gateway.py | 2 -- 10 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 prod/Matrix08/win_start.bat diff --git a/README.md b/README.md index 4ad62fd8..19b67069 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ github 链接: https://github.com/msincenselee/vnpy gitee 链接: https://gitee.com/vnpy2/vnpy ###Fork版本主要改进如下 +19、期权CTA引擎 + + -vnpy.app.cta_option + + 支持股票ETF期权的CTA策略 18、CTA股票选股引擎 @@ -162,12 +166,14 @@ gitee 链接: https://gitee.com/vnpy2/vnpy QQ/Wechat:28888502 系列在线课程 - + + 2022 期货CTA课程: http://www.uquant.org/course/49 + 2021 股票CTA实战课程:http://www.uquant.org/course/49 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 diff --git a/prod/Matrix08/win_start.bat b/prod/Matrix08/win_start.bat new file mode 100644 index 00000000..bddd5af0 --- /dev/null +++ b/prod/Matrix08/win_start.bat @@ -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 diff --git a/prod/jobs/refill_tq_future_ticks.py b/prod/jobs/refill_tq_future_ticks.py index 559b9bf9..1896e236 100644 --- a/prod/jobs/refill_tq_future_ticks.py +++ b/prod/jobs/refill_tq_future_ticks.py @@ -11,7 +11,7 @@ import pandas as pd from contextlib import closing from datetime import datetime, timedelta 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__), '..', '..')) 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.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 len(sys.argv) <= 1: @@ -36,9 +36,9 @@ if __name__ == "__main__": if len(args.symbol) == 0: print('下载合约未设定 参数 -s rb2010') os._exit(0) - + auth_dict = get_account_config() # 开始下载(使用快期的免费行情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 = {} begin_date = datetime.strptime(args.begin, '%Y%m%d') end_date = datetime.strptime(args.end, '%Y%m%d') diff --git a/vnpy/app/cta_option/engine.py b/vnpy/app/cta_option/engine.py index c7e3c6de..58e52938 100644 --- a/vnpy/app/cta_option/engine.py +++ b/vnpy/app/cta_option/engine.py @@ -412,6 +412,10 @@ class CtaOptionEngine(BaseEngine): if not strategy: if order.vt_orderid in self.internal_orderids: 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())}') if order.type != OrderType.STOP: 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__)}') return 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. vt_orderids = self.strategy_orderid_map[strategy.strategy_name] if order.vt_orderid in vt_orderids and not order.is_active(): @@ -468,7 +475,7 @@ class CtaOptionEngine(BaseEngine): 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) if trade.direction == Direction.LONG: new_pos = cur_pos + trade.volume @@ -478,13 +485,18 @@ class CtaOptionEngine(BaseEngine): self.write_log(f'成交单:trade:{print_dict(trade.__dict__)}') self.net_pos_holding.update({trade.vt_symbol: new_pos}) 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'成交更新 => 当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}') self.write_log(f'成交更新 => 当前内部订单清单:{self.internal_orderids}') - return + return self.write_log(f'成交更新 =>:{trade.vt_orderid} => 策略:{strategy.strategy_name}') diff --git a/vnpy/app/cta_stock/engine.py b/vnpy/app/cta_stock/engine.py index 7d24e9e9..b28934ee 100644 --- a/vnpy/app/cta_stock/engine.py +++ b/vnpy/app/cta_stock/engine.py @@ -353,6 +353,11 @@ class CtaEngine(BaseEngine): self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}') return 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. vt_orderids = self.strategy_orderid_map[strategy.strategy_name] 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) if not strategy: - self.write_log(f'成交单没有对应的策略设置:trade:{trade.__dict__}') - self.write_log(f'当前策略侦听委托单:{list(self.orderid_strategy_map.keys())}') - 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) - 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 # 取消外部干预策略pos,由策略自行完成更新 diff --git a/vnpy/app/cta_strategy_pro/back_testing.py b/vnpy/app/cta_strategy_pro/back_testing.py index 7c9ad349..935a261a 100644 --- a/vnpy/app/cta_strategy_pro/back_testing.py +++ b/vnpy/app/cta_strategy_pro/back_testing.py @@ -425,6 +425,14 @@ class BackTestingEngine(object): @lru_cache() def get_size(self, vt_symbol: str): """查询合约的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) def set_price(self, vt_symbol: str, price: float): @@ -440,11 +448,16 @@ class BackTestingEngine(object): if rate >= 0.1: self.fix_commission.update({vt_symbol: rate}) + @lru_cache() def get_commission_rate(self, vt_symbol: str): """ 获取保证金比例,缺省万分之一""" if vt_symbol not in self.commission_rate: 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)) def get_fix_commission(self, vt_symbol: str): @@ -887,6 +900,16 @@ class BackTestingEngine(object): self.write_log(f"新增订阅指数合约:{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): self.write_log(u'自动初始化策略') strategy.on_init() @@ -1264,8 +1287,8 @@ class BackTestingEngine(object): target=self.get_price_tick(vt_symbol)) - self.get_price_tick( vt_symbol) # 在当前时间点前发出的卖出委托可能的最优成交价 else: - buy_cross_price = tick.last_price - sell_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 if not tick.bid_price_1 else tick.bid_price_1 buy_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']}) 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) diff --git a/vnpy/app/cta_strategy_pro/template_spread.py b/vnpy/app/cta_strategy_pro/template_spread.py index e19f7ac3..a6dd8d93 100644 --- a/vnpy/app/cta_strategy_pro/template_spread.py +++ b/vnpy/app/cta_strategy_pro/template_spread.py @@ -419,9 +419,9 @@ class CtaSpreadTemplate(CtaTemplate): if order_info is not None: # 委托单记录 =》 找到 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): """更新持仓组件得pos""" @@ -535,14 +535,14 @@ class CtaSpreadTemplate(CtaTemplate): """修正order被拆单得情况""" order_info = self.active_orders.get(order.vt_orderid, None) if order_info: - volume = order_info.get('volume') - traded = order_info.get('traded') + volume = order_info.get('volume') # 原始委托数量 + traded = order_info.get('traded') # 原始委托中,已成交的数量 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}) if traded != order.traded: - self.write_log(f'{order.vt_orderid} {order.vt_symbol} 已成交 :{traded}=>{traded + order.traded}') - order_info.update({'volume': traded + order.traded}) + self.write_log(f'更新未完成订单{order.vt_orderid} {order.vt_symbol} 的已成交数量 :{traded}=>{traded + order.traded}') + order_info.update({'traded': traded + order.traded}) def on_order(self, order: OrderData): """报单更新""" @@ -582,9 +582,9 @@ class CtaSpreadTemplate(CtaTemplate): else: 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: opened_price = grid.snapshot.get('act_open_price', 0) @@ -660,7 +660,7 @@ class CtaSpreadTemplate(CtaTemplate): grid.traded_volume = 0 # 平仓完毕(cover, sell) - if order.offset != Offset.OPEN: + if order_info.get("offset", None) != Offset.OPEN: grid.open_status = False grid.close_status = True diff --git a/vnpy/component/cta_line_bar.py b/vnpy/component/cta_line_bar.py index 842746e7..e72f9de1 100644 --- a/vnpy/component/cta_line_bar.py +++ b/vnpy/component/cta_line_bar.py @@ -2496,7 +2496,10 @@ class CtaLineBar(object): def __count_ama(self): """计算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);     整个周期价格的总体变动:=abs(close-ref(close,n)); diff --git a/vnpy/data/tq/downloader.py b/vnpy/data/tq/downloader.py index 1d6920ed..a32313c6 100644 --- a/vnpy/data/tq/downloader.py +++ b/vnpy/data/tq/downloader.py @@ -6,15 +6,31 @@ # 2. 下载tick时,5档行情都下载 # 3. 五档行情变量调整适合vnpy的命名方式 +import os import csv from datetime import date, datetime from typing import Union, List - +import json from tqsdk.api import TqApi from tqsdk.datetime import _get_trading_day_start_time, _get_trading_day_end_time from tqsdk.diff import _get_obj 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: """ diff --git a/vnpy/trader/gateway.py b/vnpy/trader/gateway.py index 6c79a127..165632ed 100644 --- a/vnpy/trader/gateway.py +++ b/vnpy/trader/gateway.py @@ -792,8 +792,6 @@ class LocalOrderManager: Keep an order buf before pushing it to gateway. """ self.orders[order.orderid] = copy(order) - - self.gateway.on_order(order) def cancel_order(self, req: CancelRequest) -> None: