From b88da16eee7f5993519dc33fc296e5533f16809b Mon Sep 17 00:00:00 2001 From: 1122455801 Date: Mon, 8 Apr 2019 16:52:24 +0800 Subject: [PATCH 1/5] Update install.md --- docs/install.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/install.md b/docs/install.md index 48a78528..358608d0 100644 --- a/docs/install.md +++ b/docs/install.md @@ -3,11 +3,78 @@ ## Windows + + ### 使用VNConda +#### 1.下载VNConda (Python 3.7 64位) + +下载地址如下:[VNConda-2.0.1-Windows-x86_64](https://conda.vnpy.com/VNConda-2.0.1-Windows-x86_64.exe) + +  + + +#### 2.安装VNConda + +注意事项:第4步会提示用户是否把VNConda注册成默认Python环境:若用户存在其他Python环境,则都不要勾选;反之,两个都勾选掉。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/install.bat/install_VNConda.png "enter image title here") + +  + +#### 3.登陆VNStation + +输入账号密码或者微信扫码登陆VNConda。(社区账号通过微信号码可得) + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/install.bat/login_VNConda.png "enter image title here") + +  + +#### 4.使用VNStation +登录后会进入到VNConda的主界面。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/install.bat/login_VNConda_2.png "enter image title here") + +窗口下方有5个选项: +- VN Trade Lite:直接运行VN Trader (只有CTP接口) +- VN Trader Pro:先选择保存相关临时文件的目录,再运行VN Trader (接口任选) +- Jupyter Notebook:先选择保存相关临时文件的目录,再运行Jupyter Notebook +- 提问求助:提出相关问题,管理员会每天定时回复 +- 后台更新:一键更新VN Station + +  +  ### 手动安装 +#### 1.下载并安装最新版Anaconda3.7 64位 + +下载地址如下:[Anaconda Distribution](https://www.anaconda.com/distribution/) + +(更轻量的MiniConda地址:[MiniConda Distribution](https://docs.conda.io/en/latest/miniconda.html)) + +  + +#### 2.下载并解压vnpy + +下载地址如下:[vnpy releases](https://github.com/vnpy/vnpy/releases) + +  + +#### 3.安装vnpy +双击install.bat一键安装vnpy: +- 先安装TA_LIB库和IB API +- 然后安装requirements.txt文件内相关依赖库 +- 最后复制vnpy到Anaconda内 + +  + +#### 4.启动VN Trader +在文件夹tests\trader中找到run.py文件。按住“Shift” + 鼠标右键进入cmd窗口,输入下面命令即可启动VN Trader。 +``` +Python run.py +``` + ## Ubuntu From 178ab572cdf7abb9b7643df3c9d3892548b95bb6 Mon Sep 17 00:00:00 2001 From: 1122455801 Date: Tue, 9 Apr 2019 15:13:01 +0800 Subject: [PATCH 2/5] Update install.md --- docs/install.md | 67 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/docs/install.md b/docs/install.md index 358608d0..50744a87 100644 --- a/docs/install.md +++ b/docs/install.md @@ -24,15 +24,14 @@ #### 3.登陆VNStation -输入账号密码或者微信扫码登陆VNConda。(社区账号通过微信号码可得) +输入账号密码或者微信扫码登陆VNConda。(社区账号通过微信扫码可得) ![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/install.bat/login_VNConda.png "enter image title here")   #### 4.使用VNStation -登录后会进入到VNConda的主界面。 - +登录后会进入到VN Station的主界面。 ![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/install.bat/login_VNConda_2.png "enter image title here") 窗口下方有5个选项: @@ -51,19 +50,19 @@ 下载地址如下:[Anaconda Distribution](https://www.anaconda.com/distribution/) -(更轻量的MiniConda地址:[MiniConda Distribution](https://docs.conda.io/en/latest/miniconda.html)) +(更轻量的Miniconda地址:[MiniConda Distribution](https://docs.conda.io/en/latest/miniconda.html))   #### 2.下载并解压vnpy -下载地址如下:[vnpy releases](https://github.com/vnpy/vnpy/releases) +Windows用户选择zip压缩版本。下载地址如下:[vnpy releases](https://github.com/vnpy/vnpy/releases)   #### 3.安装vnpy 双击install.bat一键安装vnpy: -- 先安装TA_LIB库和IB API +- 先安装ta_lib库和ib api - 然后安装requirements.txt文件内相关依赖库 - 最后复制vnpy到Anaconda内 @@ -72,21 +71,61 @@ #### 4.启动VN Trader 在文件夹tests\trader中找到run.py文件。按住“Shift” + 鼠标右键进入cmd窗口,输入下面命令即可启动VN Trader。 ``` -Python run.py +python run.py ``` +  +  ## Ubuntu -### 安装脚本 -### TA-Lib - -### 中文编码 - -如果是英文系统(如阿里云),请先运行下列命令安装中文编码: +### 1. 下载并安装最新版本的Anaconda或者Miniconda (Python 3.7 64位) +以MiniConda为例,进入已下载好 Miniconda3-latest-Linux-x86_64.sh 所在目录,终端运行如下命令开始安装。 ``` -sudo locale-gen zh_CN.GB18030 +$ bash Miniconda3-latest-Linux-x86_64.sh +``` + +安装过程中可以一直按“Enter”键继续下去,除了以下这点: + +当询问是否把Miniconda设置为Python 默认环境时,把默认的"no"改成“yes”。原因是Ubuntu 18.04已有自带的Python 3.6与Python 2.7。 +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/install.bat/install_Miniconda_ubuntu.png "enter image title here") + + +重启Ubuntu后,打开终端直接输入"python" 然后按“Enter”键: 若出现如下图,则表示成功把Miniconda设置为Python默认环境。 +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/install.bat/Conda_Python_version.png "enter image title here") + +  + +### 2. 下载并解压vnpy +Linux用户选择tar.gz压缩版本。下载地址如下:[vnpy releases](https://github.com/vnpy/vnpy/releases) + +  + +### 3. 安装vnpy +先安装gcc编译器,用于编译C++类接口文件。在终端中输入以下命令即可。 +``` +sudo apt-get install build-essential +``` + + +然后在vnpy根目录打开终端,输入下面命令一键安装vnpy。 +``` +bash install.sh +``` + +安装过程分为4步: +- 下载并安装ta_lib库和numpy +- 安装requirements.txt文件内相关依赖库 +- 安装中文编码(针对英文系统) +- 复制vnpy到Anaconda内(若是在虚拟机上运行,需要把内存调至4g,否则报错) + +  + +### 4.启动VN Trader +在文件夹tests\trader中找到run.py文件。右键进入终端,输入下面命令即可启动VN Trader。 +``` +python run.py ``` From 0cdb1f6a83f748efed09f31c19da7e0b2c2da168 Mon Sep 17 00:00:00 2001 From: 1122455801 Date: Wed, 10 Apr 2019 16:40:08 +0800 Subject: [PATCH 3/5] Update quickstart.md --- docs/quickstart.md | 122 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 6 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 9536f20e..a871f014 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,20 +1,130 @@ # 基本使用 -## 启动VN Trader +## 1.启动VN Trader +### 1.1 VN Station模式 +登陆VN Station后,点击VN Trade Lite快速进入VN Trader(只有CTP接口);或者点击VN Trader Pro先选择如下图的底层接口和上层应用,再进入VN Trader。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/VnTrader_Pro.png "enter image title here") -## 连接接口 + +### 1.2 脚本模式 + +在文件夹tests\trader中找到run.py文件。按住“Shift” + 鼠标右键进入cmd窗口,输入下面命令进入如图VN Trader +``` +python run.py +``` +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/Vntrader.PNG "enter image title here") + +  + +## 2.连接接口 +以SinNow仿真交易账号登陆CTP接口为例:点击菜单栏的“系统”->“连接CTP”后,弹出如上图所示CTP接口的配置对话框,输入以下内容后即可登录: +- 用户名username:111111 (6位纯数字账号) +- 密码password:1111111 (需要修改一次密码用于盘后测试) +- 经纪商编号brokerid:9999 (SimNow默认经纪商编号) +- 交易服务器地址td_address:180.168.146.187:10030 (盘后测试) +- 行情服务器地址md_address:180.168.146.187:10031 (盘后测试) +- auth_code和product_info主要用于19年中的CTP接入验证,目前留空即可 + +注意:若使用期货实盘账户,需要问清楚其brokerid、auth_code和product_info; 并且仿真交易需要另外申请开通。 + +连接成功以后,日志组件会立刻输出陆成功相关信息,同时用户也可以看到账号信息,持仓信息,合约查询等相关信息。 + +  + +## 3.订阅行情 +在交易组件输入交易所和合约代码,并且按“Enter”键即可订阅器行情。如订阅IF股指期货,交易所:CFFEX,名称:IF905;铁矿石期货,交易所:DCE,名称:i1905。 + +此时行情组件会显示最新行情信息;交易组件会显示合约名称,并且在下方显示深度行情报价:如最新价、买一价、卖一价。(数字货币品种可以显示十档行情) + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/subcribe_contract.png "enter image title here") -## 订阅行情 + +  + +## 4.委托交易 +交易组件适用于手动交易。除了在行情订阅中输入的交易所和合约代码以外,还需要填写以下5个字段:委托方向、开平仓类型、委托类型、委托价格和委托数量。(若委托类型为市价单,委托价格可不填。) + +发出委托同时本地缓存委托相关信息,并且显示到委托组件和活动组件,其委托状态为“提交中”,然后等待委托回报。 + +交易所收到用户发送的委托,将其插入中央订单簿来进行撮合成交,并推送委托回报给用户: +- 若委托还未成交,委托组件和活动组件只会更新时间和委托状态这两字段,委托状态变成“未成交”; +- 若委托立刻成交,委托相关信息会从活动组件移除,新增至成交组件,委托状态变成“全部成交”。 -## 委托交易 -## 数据监控 +  + +## 5.数据监控 + +数据监控由以下组件构成,并且附带2个辅助功能:选定以下任一组件,鼠标右键可以选择“调整列宽”(特别适用于屏幕分辨率较低),或者选择“保存数据”(csv格式) + +### 5.1行情组件 +用于对订阅的行情进行实时监控,如下图,监控内容可以分成3类: + +- 合约信息:合约代码、交易所、合约名称 + +- 行情信息:最新价、成交量、开盘价、最高价、最低价、收盘价、买一价、买一量、卖一价、卖一量 + +- 其他信息:数据推送时间、接口 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/subcribe_contract_module.png "enter image title here") -## 应用模块 +### 5.2活动组件 +活动组件用于存放还未成交的委托,如限价单或者没有立刻成交的市价单,委托状态永远是“提交中”。在该组件中鼠标双击任一委托可以完成撤单操作。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/active_order.png "enter image title here") + +### 5.3成交组件 +成交组件用于存放已成交的委托,需要注意3个字段信息:价格、数量、时间。他们都是交易所推送过来的成交信息,而不是委托信息。 + +注意:有些接口会独立推送成交信息,如CTP接口;有些接口则需要从委托信息里面提取成交相关字段,如Tiger接口。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/trade.png "enter image title here") + + + +### 5.4委托组件 +委托组件用于存放用户发出的所有委托信息,其委托状态可以是提交中、已撤销、部分成交、全部成交、拒单等等。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/order.png "enter image title here") + + +### 5.5持仓组件 +持仓组件用于记录其历史持仓。其中需要了解以下字段含义 +- 方向:期货品种具有多空方向;而股票品种方向为“净”持仓。 +- 昨仓:其出现衍生于上期所特有的平今、平昨模式的需要 +- 数量:总持仓,即今仓 + 昨仓 +- 均价:历史成交的平均价格(某些巨型委托,会发生多次部分成交,需要计算平均价格) +- 盈亏:持仓盈亏:多仓情况下,盈利 = 当前价格 - 均价;空仓则反之。 + +若平仓离场,持仓数量清零,浮动盈亏变成实际盈亏从而影响账号余额变化。故以下字段:数量、昨仓、冻结、均价、盈亏均为“0”,如下图。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/query_position.png "enter image title here") + +### 5.6资金组件 +资金组件显示了账号的基础信息,如下图需要注意3个字段信息: +- 可用资金:可以用于委托的现金 +- 冻结:委托操作冻结的金额(与保证金不是一个概念) +- 余额:总资金,即可用资金 + 保证金 + 浮动盈亏 + +注意:若全部平仓,浮动盈亏变成实际盈亏,保证金和浮动盈亏清零,总资金等于可用资金 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/quick_start/query_account.png "enter image title here") + + + +### 5.7日志组件 + + + + +  + +## 6.应用模块 From 58f31bdd18f01a76c392e578ff42c87fbdccf4d7 Mon Sep 17 00:00:00 2001 From: 1122455801 Date: Thu, 11 Apr 2019 16:56:15 +0800 Subject: [PATCH 4/5] Update csv_loader.md --- docs/csv_loader.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/csv_loader.md b/docs/csv_loader.md index e69de29b..5b186a76 100644 --- a/docs/csv_loader.md +++ b/docs/csv_loader.md @@ -0,0 +1,63 @@ +# CSV载入模块 + +CSV载入模块在vnpy根目录下vnpy\app\csv_loader文件夹内,engine.py里面的CsvLoaderEngine类负责载入功能实现。 + +## 1. 初始化 +初始化数据载入相关信息,可以分成3类: + +- CSV文件路径 +- 合约信息:合约代码、交易所、K线周期 +- CSV表头信息:日期时间、开盘价、最高价、最低价、收盘价、成交量 + +``` + self.file_path: str = '' + + self.symbol: str = "" + self.exchange: Exchange = Exchange.SSE + self.interval: Interval = Interval.MINUTE + + self.datetime_head: str = '' + self.open_head: str = '' + self.close_head: str = '' + self.low_head: str = '' + self.high_head: str = '' + self.volume_head: str = '' +``` + +  + +## 2. 数据载入 + +从文件路径中读取CSV文件,然后在每一次迭代中载入数据到数据库中。 +``` + with open(file_path, 'rt') as f: + reader = csv.DictReader(f) + + for item in reader: +``` + +  + +载入数据的方法可以分成2类: +- 直接插入:合约代码、交易所、K线周期、成交量、开盘价、最高价、最低价、收盘价、接口名称 +- 需要处理:日期时间(根据其相应的时间格式,通过strptime()转化成时间元祖)、vt_symbol(合约代码.交易所格式,如rb1905.SHFE) + +注意:db_bar.replace()用于数据更新,即把旧的数据替换成新的。 +``` + db_bar.symbol = symbol + db_bar.exchange = exchange.value + db_bar.datetime = datetime.strptime( + item[datetime_head], datetime_format + ) + db_bar.interval = interval.value + db_bar.volume = item[volume_head] + db_bar.open_price = item[open_head] + db_bar.high_price = item[high_head] + db_bar.low_price = item[low_head] + db_bar.close_price = item[close_head] + db_bar.vt_symbol = vt_symbol + db_bar.gateway_name = "DB" + + db_bar.replace() +``` + From e7c1496edf4e08de9edf4b8a622ff7fbbde18ee6 Mon Sep 17 00:00:00 2001 From: 1122455801 Date: Fri, 12 Apr 2019 16:01:58 +0800 Subject: [PATCH 5/5] Update cta_strategy.md --- docs/cta_strategy.md | 690 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 684 insertions(+), 6 deletions(-) diff --git a/docs/cta_strategy.md b/docs/cta_strategy.md index 62d6106d..4c697367 100644 --- a/docs/cta_strategy.md +++ b/docs/cta_strategy.md @@ -1,23 +1,701 @@ # CTA策略模块 -## 模块构成 +## 1. 模块构成 + +CTA策略模块主要由7部分构成,如下图: + +- base:定义了CTA模块中用到的一些基础设置,如引擎类型(回测/实盘)、回测模式(K线/Tick)、本地停止单的定义以及停止单状态(等待中/已撤销/已触发)。 + +- template:定义了CTA策略模板(包含信号生成和委托管理)、CTA信号(仅负责信号生成)、目标仓位算法(仅负责委托管理,适用于拆分巨型委托,降低冲击成本)。 +- strategies: 官方提供的cta策略示例,包含从最基础的双均线策略,到通道突破类型的布林带策略,到跨时间周期策略,再到把信号生成和委托管理独立开来的多信号策略。 +- backesting:包含回测引擎和参数优化。其中回测引擎定义了数据载入、委托撮合机制、计算与统计相关盈利指标、结果绘图等函数。 +- converter:定义了针对上期所品种平今/平昨模式的委托转换模块;对于其他品种用户也可以通过可选参数lock切换至锁仓模式。 +- engine:定义了CTA策略实盘引擎,其中包括:RQData客户端初始化和数据载入、策略的初始化和启动、推送Tick订阅行情到策略中、挂撤单操作、策略的停止和移除等。 +- ui:基于PyQt5的GUI图形应用。 + +![enter image description here](https://vnpy-community.oss-cn-shanghai.aliyuncs.com/forum_experience/yazhang/cta_strategy/seix_elementos.png "enter image title here") + +  + +## 2. 历史数据 -## 历史数据 +  + +## 3. 策略开发 +CTA策略模板提供完整的信号生成和委托管理功能,用户可以基于该模板自行开发策略。新策略可以放在根目录下vnpy\app\cta_strategy\strategies文件夹内,也可以放在用户运行的文件内(VN Station模式)。注意:策略文件命名是以下划线模式,如boll_channel_strategy.py;而策略类命名采用的是驼峰式,如BollChannelStrategy。 + +下面通过BollChannelStrategy策略示例,来展示策略开发的具体步骤: + +### 3.1 参数设置 + +定义策略参数并且初始化策略变量。策略参数为策略类的公有属性,用户可以通过创建新的实例来调用或者改变策略参数。 + +如针对rb1905品种,用户可以创建基于BollChannelStrategy的策略示例,如RB_BollChannelStrategy,boll_window可以由18改成30。 + +创建策略实例的方法有效地实现了一个策略跑多个品种,并且其策略参数可以通过品种的特征进行调整。 +``` + boll_window = 18 + boll_dev = 3.4 + cci_window = 10 + atr_window = 30 + sl_multiplier = 5.2 + fixed_size = 1 + + boll_up = 0 + boll_down = 0 + cci_value = 0 + atr_value = 0 + + intra_trade_high = 0 + intra_trade_low = 0 + long_stop = 0 + short_stop = 0 +``` + +### 3.2 类的初始化 +初始化分3步: +- 通过super( )的方法继承CTA策略模板,在__init__( )函数传入CTA引擎、策略名称、vt_symbol、参数设置。 +- 调用K线生成模块:通过时间切片来把Tick数据合成1分钟K线数据,然后更大的时间周期数据,如15分钟K线。 +- 调用K线时间序列管理模块:基于K线数据,如1分钟、15分钟,来生成相应的技术指标。 + +``` + def __init__(self, cta_engine, strategy_name, vt_symbol, setting): + """""" + super(BollChannelStrategy, self).__init__( + cta_engine, strategy_name, vt_symbol, setting + ) + + self.bg = BarGenerator(self.on_bar, 15, self.on_15min_bar) + self.am = ArrayManager() +``` + +### 3.3 策略的初始化、启动、停止 +通过“CTA策略”组件的相关功能按钮实现。 + +注意:函数load_bar(10),代表策略初始化需要载入10个交易日的历史数据。该历史数据可以是Tick数据,也可以是K线数据。 + +``` + def on_init(self): + """ + Callback when strategy is inited. + """ + self.write_log("策略初始化") + self.load_bar(10) + + def on_start(self): + """ + Callback when strategy is started. + """ + self.write_log("策略启动") + + def on_stop(self): + """ + Callback when strategy is stopped. + """ + self.write_log("策略停止") +``` +### 3.4 Tick数据回报 +策略订阅某品种合约行情,交易所会推送Tick数据到该策略上。 + +由于BollChannelStrategy是基于15分钟K线来生成交易信号的,故收到Tick数据后,需要用到K线生成模块里面的update_tick函数,通过时间切片的方法,聚合成1分钟K线数据,并且推送到on_bar函数。 + +``` + def on_tick(self, tick: TickData): + """ + Callback of new tick data update. + """ + self.bg.update_tick(tick) +``` + +### 3.5 K线数据回报 + +收到推送过来的1分钟K线数据后,通过K线生成模块里面的update_bar函数,以分钟切片的方法,合成15分钟K线数据,并且推送到on_15min_bar函数。 +``` + def on_bar(self, bar: BarData): + """ + Callback of new bar data update. + """ + self.bg.update_bar(bar) +``` + +### 3.6 15分钟K线数据回报 + +负责CTA信号的生成,由3部分组成: +- 清空未成交委托:为了防止之前下的单子在上一个15分钟没有成交,但是下一个15分钟可能已经调整了价格,就用cancel_all()方法立刻撤销之前未成交的所有委托,保证策略在当前这15分钟开始时的整个状态是清晰和唯一的。 +- 调用K线时间序列管理模块:基于最新的15分钟K线数据来计算相应计算指标,如布林带通道上下轨、CCI指标、ATR指标 +- 信号计算:通过持仓的判断以及结合CCI指标、布林带通道、ATR指标在通道突破点挂出停止单委托(buy/sell),同时设置离场点(short/cover)。 + +注意:CTA策略具有低胜率和高盈亏比的特定:在难以提升胜率的情况下,研究提高策略盈亏比有利于策略盈利水平的上升。 + +``` + def on_15min_bar(self, bar: BarData): + """""" + self.cancel_all() + + am = self.am + am.update_bar(bar) + if not am.inited: + return + + self.boll_up, self.boll_down = am.boll(self.boll_window, self.boll_dev) + self.cci_value = am.cci(self.cci_window) + self.atr_value = am.atr(self.atr_window) + + if self.pos == 0: + self.intra_trade_high = bar.high_price + self.intra_trade_low = bar.low_price + + if self.cci_value > 0: + self.buy(self.boll_up, self.fixed_size, True) + elif self.cci_value < 0: + self.short(self.boll_down, self.fixed_size, True) + + elif self.pos > 0: + self.intra_trade_high = max(self.intra_trade_high, bar.high_price) + self.intra_trade_low = bar.low_price + + self.long_stop = self.intra_trade_high - self.atr_value * self.sl_multiplier + self.sell(self.long_stop, abs(self.pos), True) + + elif self.pos < 0: + self.intra_trade_high = bar.high_price + self.intra_trade_low = min(self.intra_trade_low, bar.low_price) + + self.short_stop = self.intra_trade_low + self.atr_value * self.sl_multiplier + self.cover(self.short_stop, abs(self.pos), True) + + self.put_event() +``` + +### 3.7 委托回报、成交回报、停止单回报 + +在策略中可以直接pass,其具体逻辑应用交给回测/实盘引擎负责。 +``` + def on_order(self, order: OrderData): + """ + Callback of new order data update. + """ + pass + + def on_trade(self, trade: TradeData): + """ + Callback of new trade data update. + """ + self.put_event() + + def on_stop_order(self, stop_order: StopOrder): + """ + Callback of stop order update. + """ + pass +``` -## 策略开发 +  -## 回测研究 +## 4. 回测研究 +backtesting.py定义了回测引擎,下面主要介绍相关功能函数,以及回测引擎应用示例: + +### 4.1 加载策略 + +把CTA策略逻辑,对应合约品种,以及参数设置(可在策略文件外修改)载入到回测引擎中。 +``` + def add_strategy(self, strategy_class: type, setting: dict): + """""" + self.strategy_class = strategy_class + self.strategy = strategy_class( + self, strategy_class.__name__, self.vt_symbol, setting + ) +``` +  + +### 4.2 载入历史数据 + +负责载入对应品种的历史数据,大概有4个步骤: +- 根据数据类型不同,分成K线模式和Tick模式; +- 通过select().where()方法,有条件地从数据库中选取数据,其筛选标准包括:vt_symbol、 回测开始日期、回测结束日期、K线周期(K线模式下); +- order_by(DbBarData.datetime)表示需要按照时间顺序载入数据; +- 载入数据是以迭代方式进行的,数据最终存入self.history_data。 + +``` + def load_data(self): + """""" + self.output("开始加载历史数据") + + if self.mode == BacktestingMode.BAR: + s = ( + DbBarData.select() + .where( + (DbBarData.vt_symbol == self.vt_symbol) + & (DbBarData.interval == self.interval) + & (DbBarData.datetime >= self.start) + & (DbBarData.datetime <= self.end) + ) + .order_by(DbBarData.datetime) + ) + self.history_data = [db_bar.to_bar() for db_bar in s] + else: + s = ( + DbTickData.select() + .where( + (DbTickData.vt_symbol == self.vt_symbol) + & (DbTickData.datetime >= self.start) + & (DbTickData.datetime <= self.end) + ) + .order_by(DbTickData.datetime) + ) + self.history_data = [db_tick.to_tick() for db_tick in s] + + self.output(f"历史数据加载完成,数据量:{len(self.history_data)}") +``` +  + +### 4.3 撮合成交 + +载入CTA策略以及相关历史数据后,策略会根据最新的数据来计算相关指标。若符合条件会生成交易信号,发出具体委托(buy/sell/short/cover),并且在下一根K线成交。 + +根据委托类型的不同,回测引擎提供2种撮合成交机制来尽量模仿真实交易环节: + +- 限价单撮合成交:(以买入方向为例)先确定是否发生成交,成交标准为委托价>= 下一根K线的最低价;然后确定成交价格,成交价格为委托价与下一根K线开盘价的最小值。 + +- 停止单撮合成交:(以买入方向为例)先确定是否发生成交,成交标准为委托价<= 下一根K线的最高价;然后确定成交价格,成交价格为委托价与下一根K线开盘价的最大值。 + +  + +下面展示在引擎中限价单撮合成交的流程: +- 确定会撮合成交的价格; +- 遍历限价单字典中的所有限价单,推送委托进入未成交队列的更新状态; +- 判断成交状态,若出现成交,推送成交数据和委托数据; +- 从字典中删除已成交的限价单。 + +``` + def cross_limit_order(self): + """ + Cross limit order with last bar/tick data. + """ + if self.mode == BacktestingMode.BAR: + long_cross_price = self.bar.low_price + short_cross_price = self.bar.high_price + long_best_price = self.bar.open_price + short_best_price = self.bar.open_price + else: + long_cross_price = self.tick.ask_price_1 + short_cross_price = self.tick.bid_price_1 + long_best_price = long_cross_price + short_best_price = short_cross_price + + for order in list(self.active_limit_orders.values()): + # Push order update with status "not traded" (pending) + if order.status == Status.SUBMITTING: + order.status = Status.NOTTRADED + self.strategy.on_order(order) + + # Check whether limit orders can be filled. + long_cross = ( + order.direction == Direction.LONG + and order.price >= long_cross_price + and long_cross_price > 0 + ) + + short_cross = ( + order.direction == Direction.SHORT + and order.price <= short_cross_price + and short_cross_price > 0 + ) + + if not long_cross and not short_cross: + continue + + # Push order udpate with status "all traded" (filled). + order.traded = order.volume + order.status = Status.ALLTRADED + self.strategy.on_order(order) + + self.active_limit_orders.pop(order.vt_orderid) + + # Push trade update + self.trade_count += 1 + + if long_cross: + trade_price = min(order.price, long_best_price) + pos_change = order.volume + else: + trade_price = max(order.price, short_best_price) + pos_change = -order.volume + + trade = TradeData( + symbol=order.symbol, + exchange=order.exchange, + orderid=order.orderid, + tradeid=str(self.trade_count), + direction=order.direction, + offset=order.offset, + price=trade_price, + volume=order.volume, + time=self.datetime.strftime("%H:%M:%S"), + gateway_name=self.gateway_name, + ) + trade.datetime = self.datetime + + self.strategy.pos += pos_change + self.strategy.on_trade(trade) + + self.trades[trade.vt_tradeid] = trade +``` + +  + +### 4.4 计算策略盈亏情况 + +基于收盘价、当日持仓量、合约规模、滑点、手续费率等计算总盈亏与净盈亏,并且其计算结果以DataFrame格式输出,完成基于逐日盯市盈亏统计。 + +下面展示盈亏情况的计算过程 + +- 浮动盈亏 = 持仓量 \*(当日收盘价 - 昨日收盘价)\* 合约规模 +- 实际盈亏 = 持仓变化量 \* (当时收盘价 - 开仓成交价)\* 合约规模 +- 总盈亏 = 浮动盈亏 + 实际盈亏 +- 净盈亏 = 总盈亏 - 总手续费 - 总滑点 + +``` + def calculate_pnl( + self, + pre_close: float, + start_pos: float, + size: int, + rate: float, + slippage: float, + ): + """""" + self.pre_close = pre_close + + # Holding pnl is the pnl from holding position at day start + self.start_pos = start_pos + self.end_pos = start_pos + self.holding_pnl = self.start_pos * \ + (self.close_price - self.pre_close) * size + + # Trading pnl is the pnl from new trade during the day + self.trade_count = len(self.trades) + + for trade in self.trades: + if trade.direction == Direction.LONG: + pos_change = trade.volume + else: + pos_change = -trade.volume + + turnover = trade.price * trade.volume * size + + self.trading_pnl += pos_change * \ + (self.close_price - trade.price) * size + self.end_pos += pos_change + self.turnover += turnover + self.commission += turnover * rate + self.slippage += trade.volume * size * slippage + + # Net pnl takes account of commission and slippage cost + self.total_pnl = self.trading_pnl + self.holding_pnl + self.net_pnl = self.total_pnl - self.commission - self.slippage +``` +  -## 参数优化 +### 4.5 计算策略统计指标 +calculate_statistics函数是基于逐日盯市盈亏情况(DateFrame格式)来计算衍生指标,如最大回撤、年化收益、盈亏比、夏普比率等。 + +``` + df["balance"] = df["net_pnl"].cumsum() + self.capital + df["return"] = np.log(df["balance"] / df["balance"].shift(1)).fillna(0) + df["highlevel"] = ( + df["balance"].rolling( + min_periods=1, window=len(df), center=False).max() + ) + df["drawdown"] = df["balance"] - df["highlevel"] + df["ddpercent"] = df["drawdown"] / df["highlevel"] * 100 + + # Calculate statistics value + start_date = df.index[0] + end_date = df.index[-1] + + total_days = len(df) + profit_days = len(df[df["net_pnl"] > 0]) + loss_days = len(df[df["net_pnl"] < 0]) + + end_balance = df["balance"].iloc[-1] + max_drawdown = df["drawdown"].min() + max_ddpercent = df["ddpercent"].min() + + total_net_pnl = df["net_pnl"].sum() + daily_net_pnl = total_net_pnl / total_days + + total_commission = df["commission"].sum() + daily_commission = total_commission / total_days + + total_slippage = df["slippage"].sum() + daily_slippage = total_slippage / total_days + + total_turnover = df["turnover"].sum() + daily_turnover = total_turnover / total_days + + total_trade_count = df["trade_count"].sum() + daily_trade_count = total_trade_count / total_days + + total_return = (end_balance / self.capital - 1) * 100 + annual_return = total_return / total_days * 240 + daily_return = df["return"].mean() * 100 + return_std = df["return"].std() * 100 + + if return_std: + sharpe_ratio = daily_return / return_std * np.sqrt(240) + else: + sharpe_ratio = 0 +``` +  + +### 4.6 统计指标绘图 +通过matplotlib绘制4幅图: +- 资金曲线图 +- 资金回撤图 +- 每日盈亏图 +- 每日盈亏分布图 + +``` + def show_chart(self, df: DataFrame = None): + """""" + if not df: + df = self.daily_df + + if df is None: + return + + plt.figure(figsize=(10, 16)) + + balance_plot = plt.subplot(4, 1, 1) + balance_plot.set_title("Balance") + df["balance"].plot(legend=True) + + drawdown_plot = plt.subplot(4, 1, 2) + drawdown_plot.set_title("Drawdown") + drawdown_plot.fill_between(range(len(df)), df["drawdown"].values) + + pnl_plot = plt.subplot(4, 1, 3) + pnl_plot.set_title("Daily Pnl") + df["net_pnl"].plot(kind="bar", legend=False, grid=False, xticks=[]) + + distribution_plot = plt.subplot(4, 1, 4) + distribution_plot.set_title("Daily Pnl Distribution") + df["net_pnl"].hist(bins=50) + + plt.show() +``` + +  + +### 4.7 回测引擎使用示例 + +- 导入回测引擎和CTA策略 +- 设置回测相关参数,如:品种、K线周期、回测开始和结束日期、手续费、滑点、合约规模、起始资金 +- 载入策略和数据到引擎中,运行回测。 +- 计算基于逐日统计盈利情况,计算统计指标,统计指标绘图。 +``` +from vnpy.app.cta_strategy.backtesting import BacktestingEngine +from vnpy.app.cta_strategy.strategies.boll_channel_strategy import ( + BollChannelStrategy, +) +from datetime import datetime -## 实盘运行 +engine = BacktestingEngine() +engine.set_parameters( + vt_symbol="IF88.CFFEX", + interval="1m", + start=datetime(2018, 1, 1), + end=datetime(2019, 1, 1), + rate=3.0/10000, + slippage=0.2, + size=300, + pricetick=0.2, + capital=1_000_000, +) + +engine.add_strategy(AtrRsiStrategy, {}) +engine.load_data() +engine.run_backtesting() +df = engine.calculate_result() +engine.calculate_statistics() +engine.show_chart() +``` + +  + +## 5. 参数优化 +参数优化模块主要由3部分构成: + +### 5.1 参数设置 + +- 设置参数优化区间:如boll_window设置起始值为18,终止值为24,步进为2,这样就得到了[18, 20, 22, 24] 这4个待优化的参数了。 +- 设置优化目标字段:如夏普比率、盈亏比、总收益率等。 +- 随机生成参数对组合:使用迭代工具产生参数对组合,然后把参数对组合打包到一个个字典组成的列表中 + +``` +class OptimizationSetting: + """ + Setting for runnning optimization. + """ + + def __init__(self): + """""" + self.params = {} + self.target_name = "" + + def add_parameter( + self, name: str, start: float, end: float = None, step: float = None + ): + """""" + if not end and not step: + self.params[name] = [start] + return + + if start >= end: + print("参数优化起始点必须小于终止点") + return + + if step <= 0: + print("参数优化步进必须大于0") + return + + value = start + value_list = [] + + while value <= end: + value_list.append(value) + value += step + + self.params[name] = value_list + + def set_target(self, target_name: str): + """""" + self.target_name = target_name + + def generate_setting(self): + """""" + keys = self.params.keys() + values = self.params.values() + products = list(product(*values)) + + settings = [] + for p in products: + setting = dict(zip(keys, p)) + settings.append(setting) + + return settings +``` + +  + +### 5.2 参数对组合回测 + +多进程优化时,每个进程都会运行optimize函数,输出参数对组合以及目标优化字段的结果。其步骤如下: +- 调用回测引擎 +- 输入回测相关设置 +- 输入参数对组合到策略中 +- 运行回测 +- 返回回测结果,包括:参数对组合、目标优化字段数值、策略统计指标 + +``` +def optimize( + target_name: str, + strategy_class: CtaTemplate, + setting: dict, + vt_symbol: str, + interval: Interval, + start: datetime, + rate: float, + slippage: float, + size: float, + pricetick: float, + capital: int, + end: datetime, + mode: BacktestingMode, +): + """ + Function for running in multiprocessing.pool + """ + engine = BacktestingEngine() + engine.set_parameters( + vt_symbol=vt_symbol, + interval=interval, + start=start, + rate=rate, + slippage=slippage, + size=size, + pricetick=pricetick, + capital=capital, + end=end, + mode=mode + ) + + engine.add_strategy(strategy_class, setting) + engine.load_data() + engine.run_backtesting() + engine.calculate_result() + statistics = engine.calculate_statistics() + + target_value = statistics[target_name] + return (str(setting), target_value, statistics) +``` + +  + +### 5.3 多进程优化 + +- 根据CPU的核数来创建进程:若CPU为4核,则创建4个进程 +- 在每个进程都调用apply_async( )的方法运行参数对组合回测,其回测结果添加到results中 (apply_async是异步非阻塞的,即不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。) +- pool.close()与pool.join()用于进程跑完任务后,去关闭进程。 +- 对results的内容通过目标优化字段标准进行排序,输出结果。 + +``` + pool = multiprocessing.Pool(multiprocessing.cpu_count()) + + results = [] + for setting in settings: + result = (pool.apply_async(optimize, ( + target_name, + self.strategy_class, + setting, + self.vt_symbol, + self.interval, + self.start, + self.rate, + self.slippage, + self.size, + self.pricetick, + self.capital, + self.end, + self.mode + ))) + results.append(result) + + pool.close() + pool.join() + + # Sort results and output + result_values = [result.get() for result in results] + result_values.sort(reverse=True, key=lambda result: result[1]) + + for value in result_values: + msg = f"参数:{value[0]}, 目标:{value[1]}" + self.output(msg) + + return result_values +``` + +  + +## 6. 实盘运行