VeighNa量化社区
你的开源社区量化交易平台
KeKe's Avatar
Member
离线
187 帖子
声望: 51

数据库模块原理

 

做量化,不管在研究策略还是实盘交易的过程中,都离不开数据。更准确地说,是都不离开从数据库读取中精确的数据。

 

高质量的数据,有利于保证模型分析和策略开发时最终产出结果的准确性,并且也能帮助避免在策略实盘初始化过程中由于异常数据导致的无谓损失。

 
在数据的获取方面,vn.py内部集成了以下常用的数据源:

 

  • 国内期货股票:米筐推出的RQData数据服务
  • 海外市场:盈透证券(IB)

 

其中,RQData数据源是vn.py通过对米筐提供的rqdatac库再次封装调用实现的,具体实现逻辑包含在RqdataClient类中。

 

而后两者,则是通过对应交易接口Gateway类的query_history函数,来实现历史数据下载的功能。

 

除了使用以上的集成数据功能来在线下载数据外,vn.py的CsvLoader模块还提供了从csv文件读取数据,并自动插入到数据库中的功能,方便用户更加灵活的使用来自其他地方的数据。
 

多渠道的数据获取和维护,在另一方面也凸显出数据库管理的重要性,所以vn.py选择将所有数据库相关的逻辑代码,全部整合在一个模块中进行实现,这就是数据库模块:

 
description

 

该模块位于vnpy\trader\database目录下,其中database.py中的BaseDatabaseManager类定义了数据插入和读取相关的标准接口。为了更直观的展示,我们使用了框架图来辅助说明:

 
description

 

数据库模块内部的调用过程,整体来说可以分成4个步骤:

 

  1. import模块时,自动运行的init.py文件,读取用户目录下.vntrader/vt_setting.json中的数据库配置信息(可以VN Trader的全局配置对话框中进行修改),然后调用同一文件夹下initialize.py中的init函数,来判断数据库驱动driver的类型。
  2. 在settings中保存着数据库driver的类型,根据不同的类型分别调用同一文件中的init_sql或者init_nosql。vn.py官方支持的数据库包括:SQLite、MySQL、PostgreSQL和MongoDB,其中除了MongoDB属于nosql类外,其余三个都属于sql类。
  3. vn.py安装后默认使用的SQLite数据库(轻量方便),以其为例:SQLite的driver类型是sql,初始化应该调用init_sql函数,在init_sql函数内部从database_sql.py中调用真正的init函数。
  4. 最后在database_sql.py中,会通过peewee的ORM功能,自动生成两张表(DbTickData和DbBarData)并添加到SqlManager中。

 
在介绍database_sql.py逻辑之前,我们需要简单讲一下peewee库:peewee是一个轻量级的对象-关系映射(Object-Relational Mapping,简称ORM)框架,用统一的形式对SQL数据库进行管理,开放上层接口给用户使用。

 
peewee的用法比较简单:

 

  • 通过继承peewee提供的model类来实现数据类,每一个数据类代表一个表(table);
  • 数据类的每个实例可以视为一条记录(row);
  • 数据类的每个field字段可以视为一列(column)。

 

所以,我们可以用peewee库提供的数据库引擎类实例化,建立与数据文件的连接;然后定义一个model类用于表示表;最后将model类添加到数据库引擎类(即生成数据表)。

 

此时,db即可表示一个与数据文件连接着的数据引擎实例,上面添加到db的model类即可表示db中的一张张表,并且可以取出来继续单独使用。

 

那么在database_sql.py中,整个逻辑过程如下:
 

  1. 在init中调用peewee的Database Engine(SQLiteDatabase)生成实例,表示与数据库文件建立连接,将该实例对象称为db。
  2. 调用init_models函数生成model类同时将model类添加到db中,然后将两张表返回(DbTickData和DbBarData)。
  3. 最后,将这两张表(类)添加到SqlManager中,生成统一的DatabaseManager,并提供给外界调用。

description

 

 

数据库具体配置

 
上面大致介绍了DataManager在VN Trader启动时的初始化过程,对于没有太多数据库使用经验的读者来说可能看的云里雾里(其实对于作者来说以上语言也十分的绕口不好读)。

 

但是不用担心,要把一辆自动挡汽车开起来并不需要知道具体的发动机和变速箱工作原理,只要分得清楚油门刹车,会打方向盘就行。接下来我们进入实操阶段的内容,具体讲解不同数据库该如何配置。
 
 

SQLite

 

SQLite是vn.py默认的数据库,无需用户做任何配置即可直接使用。作为轻量级的文件数据库,SQLite只有数据库驱动而没有服务器程序,且所有Python标准库都自带,无需用户另外安装。

 

除了SQLite以外,其他的数据库都需要我们自行安装,并在VN Trader的全局配置对话框中设置相关参数。
 

 

MySQL

 

首先在MySQL官网下载Windows版本安装包【MySQL Installer for Windows】:

 

description

 

下载完成后得到msi格式的安装包,双击打开后选择【Full】模式,安装MySQL完整版,然后一直点击【Next】按钮即可完成安装。
 

description

 

安装过程中将会自动从网站下载相关组件,先点击【Execute】按钮来补全,再点击【Next】按钮。

 

安装过程中将会要求我们输入3次密码,这里为了方便演示,我们将密码设置为1001(请在自己安装的过程中使用更加复杂安全的密码)。
 
description

 

安装完毕后会自动打开MySQL的图形管理工具MySQL WorkBench,点击菜单栏【Database】->【Connect to Database】:

 
description

 

在弹出的对话框中,直接选择默认数据库Local Instance MySQL,然后点击【OK】按钮连接上我们的MySQL数据库服务器。

 

description
 

在自动打开的数据库管理界面中,点击下图中菜单栏红色方框的按钮,来创建新的数据库。在【Name】选择我们输入“vnpy”,然后点击下方的【Apply】按钮确认。

 

description

 

在之后弹出的数据库脚本执行确认对话框中,同样点击【Apply】即可,这样我们就完成了在MySQL WorkBench的所有操作。

 
description

 

现在我们需要启动VN Trader,点击菜单栏的【配置】后,设置数据库相关字段:

 

  • driver要改成mysql;
  • database改成vnpy;
  • host为本地IP,即localhost或者127.0.0.1;
  • port为MySQL的默认端口3306;
  • user用户名为root
  • password密码则是之前我们设置的1001。

 

"database.driver": "mysql"
"database.database": "vnpy"
"database.host": "localhost"
"database.port": 3306,
"database.user": "root"
"database.password": "1001"

 

上表中的双引号都无需输入,保存完成配置修改后,我们需要重启VN Trader来启用新的数据库配置。重启后,在打开VN Trader的过程中若无报错提示,则说明MySQL数据库配置成功。

 
 

PostgreSQL

 
PostgreSQL官网下载安装包:

description

 

运行安装文件,同样一路点击【Next】按钮即可完成安装,在安装途中需要输入密码(1001):

 
description
 

同时记住PostgreSQL的默认端口为5432:
 
description

 

安装完毕后,若弹出Stack Builder界面直接点击【取消】就可以了,它一般用来安装其他补充组件:
 
description

 

与MySQL一键安装完服务器和客户端不同,PostgreSQL需要用户自行安装图形管理工具。

 

这里我们选择pgAdmin,首先从pgAdmin官网下载最新的exe格式安装包:

 
description

 

安装过程同样是一路【Next】,完成后在浏览器中会自动打开pgAdmin管理界面,这里我们要输入之前设置的数据库密码(1001)进入管理界面:

 

description
 

在管理界面中,点击【Database】->【Create】->【Database】会弹出【Create-Datebase】窗口:

 

description
 

这里我们选择创建的数据库名称为database.db,当然你也可以选择其他任意的名称:

 
description

 

点击【Save】按钮完成新数据库创建后,发现它处于未连接状态:

 
description

 

鼠标点击一下即可自动完成连接:

 
description

 

然后我们需要检查一下PostgreSQL的登录用户名,点击【Login/Group Roles】可以发现下面8个都是Group,只有最后一个是User,User的名称是“postgres”:
 
description

 

到这里我们就已经获取到了所有相关的数据库信息,参考之前的MySQL配置过程在VN Trader中进行设置即可:

 

driver要改成postgresql;
database改成database.db;
host为本地IP,即localhost或者127.0.0.1;
port为5432;
user用户名为postgres
password密码为1001。

 

"database.driver": "postgresql"
"database.database": "database.db"
"database.host": "localhost"
"database.port": 5432
"database.user": "postgres"
"database.password": "1001"

 

同样,修改完后记得重启VN Trader。

 
 

MongoDB

 

MongoDB官网下载安装包:

 

description

 

运行安装包,点击【Complete】按钮来安装完整版,一路点击【Next】:

 

description

 

在安装过程中的最后阶段,会自动帮我们装上图形管理工具MongoDB Compass,并且在完成后自动运行。我们只需点击【CONNECT】按钮即可连接上MongoDB数据库服务器:

 
description

 

点击【CREATE DATABASE】按钮创建数据库:

 
description

 

在对话框中Database和Collection Name均填写“vnpy”,然后点击下方的【CREATE DATABASE】完成数据库的创建:

 
description

 

最后在VN Trader中完成配置:

 

  • driver要改成mongodb;
  • database改成vnpy;
  • host依旧是localhost
  • port端口改成27017;
  • 用户名user、密码password、认证authentication_source均留空

 

"database.driver": "mongodb"
"database.database": "vnpy"
"database.host": "localhost"
"database.port": 27017
"database.user": ""
"database.password": ""
"database.authentication_source": ""

 
最后,别忘记重启~~~

 

《vn.py全实战进阶》课程全新上线,一共50节内容覆盖从策略设计开发、参数回测优化,到最终实盘自动交易的完整CTA量化业务流程,目前已经更新到第十三集,详细内容请戳课程上线:《vn.py全实战进阶》!

 
了解更多知识,请关注vn.py社区公众号。
description

什么是对数化处理

 

我们平时看到的K线图几乎都是采用普通坐标 ,而有一种叫作对数坐标的K线图大部分人可能没了解过。

 

在介绍对数坐标下的K线图之前,我们先思考一个问题:以下两种情形,情形1的涨幅大还是情形2的涨幅大?

 

  • 情形1:从100点涨到1300点
  • 情形2:从1000点涨到6000点

 

从绝对数值来看,情形1涨了1200点(1200=1300-100),情形2涨了5000点(5000=6000-1000)。情形2涨的绝对幅度大。

 

但如果换个新的思路呢?以收益率的角度看,结果完全反过来了:情形1涨了12倍(12=(1300-100)/100),情形2涨了5倍(5=(6000-1000)/1000)。情形1的收益率更大。

 

从实际情况,我们也应该更看重价格收益率而非价格涨幅度。回归现实世界中的例子,情形1对应的是中国股市刚开始时期上证指数行情(91-92年),若投入1000元,那么期末收益为12000元。情形2对应的是06-07年牛市行情,同样投入1000元,期末收益为5000元。

 

所以,从收益率上就凸显了对数化坐标的优势:

 

  1. 易于处理不同价量变化的关系:贵州茅台从8000涨到16000的收益率与中国平安从60涨到120的收益率是一样的。
  2. 对数收益率考虑到复利因数,故曲线相对平滑,平滑的时间序列更利于直接观察收益率和进行数据分析。
  3. 对数收益率具有可加性,它的均值可以正确反映出的真实收益率:如一个投资产品,今年涨10%,明年跌10%,从算术平均角度看是不赚不亏,但是其产品净值以下降到0.99(0.99= 11.10.9)。而对数收益率可以解决这个痛点。

 

下面2幅图分别对应上证指数的标准坐标和对数坐标。显然,对数坐标能更容易挖掘到盈利点。

 

description

(标准坐标)

 

description

(对数坐标)

 
 

对数化指标的适用领域

 

CTA策略大致可以分成2类:

 

1)趋势突破类

 

行情一旦有突破迹象(即行情还未走远或者正式确立)就下单成交,但是实际上大部分的突破都是假的,只有少部分行情能走到一波比较强的趋势。所以趋势突破类策略具有胜率低的特点(一般预测准确率< 40%)。

 

策略的盈利主要依赖对止盈止损的控制:如亏损的交易止损设为10%,而少部分盈利的交易止盈设为500%。若交易次数足够多(满足大数定律和中心极限定理),那么少部分能捕捉到大趋势的盈利,足以覆盖大部分假突破导致的风险,从而让策略整体盈利。

 

若胜率在30%到40%,那么盈亏比需要控制在2以上,从而使整个策略是盈利的。举个例子来简单说明一下:
 

  • 胜率40%,代表40%预测方向对的,60%预测方向错误的;
  • 盈亏比2,代表在对的交易上,盈利是2,在错误的交易上,亏损是1;
  • 综合起来,总体盈利为0.8(0.8=40% x 2),总体亏损为0.6(0.6=60% x 1),那么利润为0.2(0.2=0.8-0.6)

 

低胜率与高盈亏比是趋势突破类策略的两大特征,在统计学上表现出尖峰肥尾的特点,尖峰代表亏损的交易比较多,但亏损数额都不大,肥尾则说明少部分成功交易所带来的盈利是巨大的。
 

description

 

趋势突破类中的突破通常指的是通道突破,如突破布林带通道的上轨做多,突破布林带通道下轨做空。
 

  • 布林带通道上轨 = 收盘价均线 + N x 收盘价标准差
  • 布林带通道下轨 = 收盘价均线 - N x 收盘价标准差

 

由于布林带通道的构成因素是基于标准坐标的,属于价位指标。对数化的效果反而不好,所以对数化的技术不适用于趋势突破类策略。

 
 

2)趋势跟踪类

 

行情已经突破并且走了有一段距离(即行情正式确立)才下单成交。因为行情已经确立,所以策略预测的成功率会比较高;但由于行情已经走出一段距离才下单追上去,盈利空间大幅度减少,甚至会遇到行情的反转。

 

所以,趋势跟踪类策略的特点恰好与趋势突破类相反:胜率高,盈亏比低。它所依赖的不是基于绝对价位的通道类突破,而是一些非价位指标,如RSI指标高于66时候做多,RSI指标低于34时做空等。

 

对数化处理非价位指标,可以进一步提升趋势跟踪类策略的盈利空间,下面通过vn.py里面的AtrRsi策略来展示对数化的效果。
 
 

以AtrRsiStrategy为例

 

策略的原理

 

行情能走出大趋势的充分条件是波动率增大,即当前波动率突破历史平均波动率(ATR>ATR均值)。在波动率变大,市场参与者增多或者多空双方开始发力的时候,我们可以判断在一定时间内:

 

  • 若收盘价的平均涨幅要大于跌幅(如RSI>66),说明多头已取得上风,并且多头趋势还会持续下去,可以去做多。
  • 若收盘价的平均跌幅幅要大于涨幅(如RSI<34),说明空头已取得上风,并且空头趋势还将持续下去,可以去做空。

 

然后我们看看原始的策略效果如何?

 

description

 

策略的夏普比率是0.8,收益回撤比是11.25。

 

尝试对数化非价位指标

 

vnpy\vnpy\trader目录下的utility.py文件是负责定义技术指标的。这些技术指标都是基于talib库来实现的,在log字段填True,就可以对数化我们需要的非价位指标了。

    def atr(self, n, log=False, array=False):
        """
        Average True Range (ATR).
        """
        if log:
            result = talib.ATR(np.log(self.high), np.log(self.low), np.log(self.close), n)
        else:
            result = talib.ATR(self.high, self.low, self.close, n)

        if array:
            return result
        return result[-1]

    def rsi(self, n, log=False, array=False):
        """
        Relative Strenght Index (RSI).
        """
        if log:
            result = talib.RSI(self.close, n)
        else:
            result = talib.RSI(np.log(self.close), n)
        if array:
            return result
        return result[-1]

 

在新的AtrRsi策略上,通过对数化处理的ATR和RSI指标,我们看看回测效果。

 
description

 

夏普比率是0.82,收益回撤比为11.48。对数化非价位指标对策略有影响,但是效果甚微。

 
 
进一步验证:参数优化

 

为了进一步验证对数化非价位指标对策略有没有效果,我们用到了参数优化:优化目标是最大化收益回撤比,优化的参数分别是atr_length、atr_ma_length、rsi_length。
 

setting = OptimizationSetting()
setting.set_target("return_drawdown_ratio")
setting.add_parameter("atr_length", 18,24, 2)
setting.add_parameter("atr_ma_length",8, 12,2)
setting.add_parameter("rsi_length",3,7,2)
engine.run_optimization(setting)

 

我们先把对数化技术指标的策略称之为实验组,原始策略称之为对照组。优化完毕后,实验组和对照组最优参数都相同,均为:

 

atr_length=18

atr_ma_length=12,

rsi_length=5

 

但是,实验组的收益回撤比整体要高于对照组的。
 
description

(实验组优化结果)

 
description

(对照组优化结果)
 

选取最优参数,我们再跑一下策略回测,就可以看到对数化非价位指标的确适用于趋势跟踪类策略的了。

 
description

(实验组最优参数回测)

 
description

(对照组最优参数回测)

 

《vn.py全实战进阶》课程全新上线,一共50节内容覆盖从策略设计开发、参数回测优化,到最终实盘自动交易的完整CTA量化业务流程,目前已经更新到第八集,详细内容请戳课程上线:《vn.py全实战进阶》!

 
了解更多知识,请关注vn.py社区公众号。

description

策略原理

 

双均线策略作为最常见最基础的CTA策略,也就是常说的金叉死叉信号组合得到的策略,常用于捕捉一段大趋势。它的思想很简单,由一个短周期均线和一个长周期均线组成,短周期均线代表近期的走势,长周期均线则是较长时间的走势:

 

  • 当短周期均线从下往上突破长周期均线,也就意味着当前时间段具有上涨趋势,突破点也就是常说的金叉,意味着多头信号;
  • 当短周期均线从上向下突破短周期信号,则意味着当前时间段具有下降趋势,突破点也就是常说的死叉,意味着空头信号。

 

 

源码分析

 

下面就以vn.py项目中的双均线策略的源码为例,进行策略交易逻辑以及内部代码实现的解析。

 

1、创建策略实例

 

首先需要记住一点,所有vn.py框架中的CTA策略类(项目自带的或者用户开发的),都是基于CTA策略模板类(CtaTemplate)来实现的子类。策略类与模板类的关系如同抽象之于具体:照着汽车的设计图和技术规格,人类就能造出各种各样的汽车。

 

同理,CTA策略模板定义了一系列底层的交易函数和策略的逻辑范式,根据这种规则,我们可以快速实现出自己想要的策略。
 

class DoubleMaStrategy(CtaTemplate):
    author = "用Python的交易员"

    fast_window = 10
    slow_window = 20

    fast_ma0 = 0.0
    fast_ma1 = 0.0

    slow_ma0 = 0.0
    slow_ma1 = 0.0

    parameters = ["fast_window", "slow_window"]
    variables = ["fast_ma0", "fast_ma1", "slow_ma0", "slow_ma1"]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
    """"""
        super(DoubleMaStrategy, self).__init__(
            cta_engine, strategy_name, vt_symbol, setting
        )

        self.bg = BarGenerator(self.on_bar)
        self.am = ArrayManager()

 

首先我们需要设置策略的参数和变量,两者都从属于策略类,不同的是策略参数是固定的(由交易员从外部指定),而策略变量则在交易的过程中随着策略的状态变化,所以策略变量一开始只需要初始化为对应的基础类型,例如:整数设为0,浮点数设为0.0,而字符串则设为""。

 

策略参数列表parameters中,需要写入策略的参数名称字符串,基于该列表中的内容,策略引擎会自动从缓存的策略配置json文件中读取策略配置,图形界面则会自动提供用户在创建策略实例时配置策略参数的对话框。

 

策略变量列表variables中,则需要写入策略的变量名称字符串,基于其中的内容,图形界面会自动渲染显示(调用put_event函数时更新),策略引擎会在用户停止策略、收到成交回报时、调用sync_data函数时,将变量数据写入硬盘中的缓存json文件,用于程序重启后策略状态的恢复。

 

策略类的构造函数init,需要传递cta_engine、strategy_name、vt_symbol、setting四个参数,分别对应CTA引擎对象、策略名称字符串、标的代码字符串、设置信息字典。注意其中的CTA引擎,可以是实盘引擎或者回测引擎,这样就可以很方便的实现一套代码同时跑回测和实盘了。以上参数均由策略引擎在使用策略类创建策略实例时自动传入,用户本质上无需关心。

 

在构造函数中,我们还创建了一个BarGenerator实例,并传入了on_bar的1分钟K线回调函数,用于实现将tick数据(TickData)自动合成为分钟级别K线数据(BarData)。除此之外,ArrayManager实例则用于缓存BarGenerator合成出来的K线数据,将其转化为便于向量化计算的时间序列数据结构,并在内部支持使用talib来计算指标。
 

 

2、状态变量初始化
 

注意这里的策略状态变量初始化,并不是指上一步中创建策略实例时的初始化函数init中的逻辑。当用户在VN Trader的CTA策略模块界面上,点击【添加策略】按钮,并在弹出的窗口中设置好策略实例名称、合约代码、策略参数,实际上是完成了策略实例的创建。

 

此时策略实例中的变量状态,依旧是0或者""这样的原始数据。用户需要点击策略管理界面上的【初始化】按钮,来调用策略中的on_init函数,完成加载历史数据回放给策略初始化其中的变量状态的操作。
 

def on_init(self):
    """
    Callback when strategy is inited.
    """
    self.write_log("策略初始化")
    self.load_bar(10)

 

从上面的代码中可以看到,用户在调用这个on_init函数后,会在CTA策略管理界面的日志组件中输出信息“策略初始化“,随后调用父类CtaTemplate提供的load_bar函数用于加载历史数据,CTA策略引擎会负责将数据推送给策略完成变量状态的初始化计算。

 

注意这里我们load_bar时,传入的参数是10,对应也就是加载10天的1分钟K线数据数据。在回测时,10天指的是10个交易日,而在实盘时,10天则是指的是自然日,因此加载的天数宁可多一些也不要太少。load_bar函数的实现如下:

 

def load_bar(
    self,
    days: int,
    interval: Interval = Interval.MINUTE,
    callback: Callable = None,
):
    """
    Load historical bar data for initializing strategy.
    """
    if not callback:
        callback = self.on_bar       #设置回调函数
    self.cta_engine.load_bar(self.vt_symbol, days, interval, callback)

 

CtaTemplate在这里调用了CtaEngine的load_bar函数来完成历史数据的加载回放。查看CtaEngine中对于load_bar函数的实现后,我们可以看到历史数据加载的两种模式:首先尝试使用RQData API从远端服务器拉取,前提是需要配置好RQData账号,同时该合约的行情数据在RQData上可以找到(主要是国内期货),若获取失败则会尝试在本地数据库中进行查找(默认为位于.vntrader文件夹下的sqlite数据库)。
 

def load_bar(
    self, 
    vt_symbol: str, 
    days: int, 
    interval: Interval,
    callback: Callable[[BarData], None]
):
    """"""
    symbol, exchange = extract_vt_symbol(vt_symbol)
    end = datetime.now()
    start = end - timedelta(days)

    # Query bars from RQData by default, if not found, load from database.
    bars = self.query_bar_from_rq(symbol, exchange, interval, start, end)
    if not bars:
        bars = database_manager.load_bar_data(
              symbol=symbol,
              exchange=exchange,
              interval=interval,
              start=start,
              end=end,
        )

    for bar in bars:
        callback(bar)

 

从上述代码中可以看出,通过datetime模块获取当前时间作为end,然后减去10天的时间作为start进行查询。将得到的所有bar数据通过第一步load_bar中设定的回调函数on_bar进行调用,这样就实现了将加载的K线数据推送给CTA策略。

 
 

3、启动自动交易

 

完成策略变量的初始化之后,就可以启动策略的自动交易功能了。点击图形界面的【启动策略】按钮后,CTA引擎会自动调用策略中的on_start函数,同时将策略的trading控制变量设置为True,界面上的日志组件中就会出现相应的策略启动日志信息。

 

def on_start(self):

    """
    Callback when strategy is started.
    """
    self.write_log("策略启动")
    self.put_event()

 

注意这里必须调用put_event函数,来通知图形界面刷新策略状态相关的显示(变量),如果不调用则界面不会更新。

 
 

4、接收Tick推送

 

启动自动交易后,CTP接口会以每0.5秒一次的频率推送Tick数据,再由VN Trader内部的事件引擎分发推送到我们的策略中,策略中的Tick数据处理函数如下:

 

def on_tick(self, tick: TickData):
    """
    Callback of new tick data update.
    """
    self.bg.update_tick(tick)

 

因为是较为简单的双均线策略,交易逻辑都在K线时间周期上执行,所以在接收到Tick数据后,通过调用策略实例所属的bg对象(BarGenerator)的update_tick,来实现Tick自动合成1分钟K线数据:

 

def update_tick(self, tick: TickData):
    """
    Update new tick data into generator.
    """
    new_minute = False

    # Filter tick data with 0 last price
    if not tick.last_price:
        return

    if not self.bar:
        new_minute = True
    elif self.bar.datetime.minute != tick.datetime.minute:
        self.bar.datetime = self.bar.datetime.replace(
                second=0, microsecond=0
        )
        self.on_bar(self.bar)
        new_minute = True

    if new_minute:
        self.bar = BarData(
            symbol=tick.symbol,
            exchange=tick.exchange,
            interval=Interval.MINUTE,
            datetime=tick.datetime,
            gateway_name=tick.gateway_name,
            open_price=tick.last_price,
            high_price=tick.last_price,
            low_price=tick.last_price,
            close_price=tick.last_price,
            open_interest=tick.open_interest
        )
    else:
        self.bar.high_price = max(self.bar.high_price, tick.last_price)
        self.bar.low_price = min(self.bar.low_price, tick.last_price)
        self.bar.close_price = tick.last_price
        self.bar.open_interest = tick.open_interest
        self.bar.datetime = tick.datetime

    if self.last_tick:
        volume_change = tick.volume - self.last_tick.volume
        self.bar.volume += max(volume_change, 0)

    self.last_tick = tick

 

update_tick函数内部主要是通过检查当前的Tick数据与上一笔Tick数据是否是属于同一分钟,来判断是否有新的1分钟K线生成,如果没有就会继续进行累加更新当前K线的信息。

 

这里意味着只有当T+1分钟的第一个Tick接收到了之后,T分钟的Bar数据才会生成。在创建bg对象的时候,我们传入了on_bar作为K线合成完毕的回调函数,所以在当新的1分钟K线生成后,就会通过on_bar函数推送到策略中。
 
 

5、核心交易逻辑

 

每个策略中最至关重要的就是策略的核心交易逻辑:

 

  • 如果策略逻辑是基于Tick数据的,则在on_tick函数中实现相关的交易逻辑;
  • 如果策略逻辑是基于K线的,如这里我们的双均线策略,则在on_bar函数中实现相关的交易逻辑。

 

def on_bar(self, bar: BarData):
    """Callback of new bar data update."""
    am = self.am
    am.update_bar(bar)
    if not am.inited:
        return

    fast_ma = am.sma(self.fast_window, array=True)
    self.fast_ma0 = fast_ma[-1]
    self.fast_ma1 = fast_ma[-2]

    slow_ma = am.sma(self.slow_window, array=True)
    self.slow_ma0 = slow_ma[-1]
    self.slow_ma1 = slow_ma[-2]

    cross_over = self.fast_ma0 > self.slow_ma0 and self.fast_ma1 < self.slow_ma1
    cross_below = self.fast_ma0 < self.slow_ma0 and self.fast_ma1 > self.slow_ma1

    if cross_over:
        if self.pos == 0:
            self.buy(bar.close_price, 1)
        elif self.pos < 0:
          self.cover(bar.close_price, 1)
          self.buy(bar.close_price, 1)

    elif cross_below:
        if self.pos == 0:
            self.short(bar.close_price, 1)
        elif self.pos > 0:
            self.sell(bar.close_price, 1)
            self.short(bar.close_price, 1)

    self.put_event()

 

在接收到K线数据,即bar对象的推送后,我们需要将该bar数据放入am(ArrayManager)时间序列容器中进行更新,当有了至少100个bar数据后am对象才初始化完毕(inited变为True)。

 

这里需要注意,如果在初始化策略状态变量时,没有足够的历史数据来让am初始化完毕,则在自动交易启动后,需要至少收到100个的bar数据来填充am容器,直到am初始化完毕后,才会执行后面的交易逻辑代码。

 

之后调用封装在ArrayManager内部的talib库,用于计算最新窗口内的技术指标,对应我们双均线策略中的也就是10窗口的MA和20窗口的MA指标。

 

注意这里的am.sma实际上是对talib中的SMA函数的进一步封装,本质上是在计算bar数据的收盘价的算术平均:

 

    am = self.am
    am.update_bar(bar)
    if not am.inited:
        return

    fast_ma = am.sma(self.fast_window, array=True)
    self.fast_ma0 = fast_ma[-1]
    self.fast_ma1 = fast_ma[-2]

    slow_ma = am.sma(self.slow_window, array=True)
    self.slow_ma0 = slow_ma[-1]
    self.slow_ma1 = slow_ma[-2]

 

然后通过判断是否出现金叉死叉来决定是否触发交易逻辑:

 

    cross_over = self.fast_ma0 > self.slow_ma0 and self.fast_ma1 < self.slow_ma1
    cross_below = self.fast_ma0 < self.slow_ma0 and self.fast_ma1 > self.slow_ma1

 

  • 当出现了金叉:如果没有持仓时,则直接买入开仓;或者持有空头,则先平空再买入开仓。
  • 当出现了死叉:如果没有持仓时,则直接卖出开仓;或者是持有多头,则先平多再卖出开仓。

 

具体的委托指令已由CTA策略模板封装好了,在on_bar函数里面直接调用即可:

 

  • buy:买入开仓( Direction:多,Offset:开)
  • sell:卖出平仓( Direction:空,Offset:平)
  • short:卖出开仓( Direction:空,Offset:开)
  • cover:买入平仓( Direction:多,Offset:平)

 

此处需要注意,国内期货有开平仓的概念,例如买入操作要区分为买入开仓和买入平仓;但股票和外盘期货都是净持仓模式,没有开仓和平仓概念,所以只需使用买入(buy) 和卖出(sell) 这两个指令就可以了。

 

if cross_over:
    if self.pos == 0:
        self.buy(bar.close_price, 1)
    elif self.pos < 0:
        self.cover(bar.close_price, 1)
        self.buy(bar.close_price, 1)

elif cross_below:
    if self.pos == 0:
        self.short(bar.close_price, 1)
    elif self.pos > 0:
        self.sell(bar.close_price, 1)
        self.short(bar.close_price, 1)

self.put_event()

 
 

6、委托回报

 

on_order是委托回调函数,当我们发出一个交易委托后,这个委托每当有状态变化时,我们都会收到该委托最新的数据推送,这条数据就是委托回报。

 

其中比较重要信息的是status委托状态(包括:拒单、未成交、部分成交、完全成交、已撤单),我们可以基于委托状态实现更加细粒度的交易委托控制(算法交易)。

 

这里我们的双均线策略由于逻辑较为简单,所以在on_order中没有任何操作:

 

def on_order(self, order: OrderData):
    """
    Callback of new order data update.
    """
    pass

 

同样对于on_trader(成交回报函数)以及on_stop_order(停止单回报函数)也没有任何操作。

 

 

7、停止自动交易

 

当每日的交易时段结束后(国内期货一般是下午三点收盘后),需要点击CTA策略界面的【停止】按钮来停止策略的自动交易。
 

此时CTA策略引擎会将策略的交易状态变量trading设为False,撤销该策略之前发出的所有活动状态的委托,以及将策略variables列表中的参数写入到缓存json文件中,最后调用策略的on_stop回调函数执行用户定义的逻辑:

 

def on_stop(self):
    """
    Callback when strategy is stopped.
    """
    self.write_log("策略停止")
    self.put_event()

 
 

CTA交易流程梳理

 

最后,用我制作的这个思维导图,以双均线策略为例来梳理一下vn.py对于策略实现以及执行的流程:

 

description

 

《vn.py全实战进阶》课程全新上线,一共50节内容覆盖从策略设计开发、参数回测优化,到最终实盘自动交易的完整CTA量化业务流程,目前已经更新到第八集,详细内容请戳课程上线:《vn.py全实战进阶》!
 
了解更多知识,请关注vn.py社区公众号。
description

上一篇社区精选中,主要解决了如何搭建支持远程桌面的Ubuntu量化交易服务器。有了系统环境,那么本篇的内容就是如何运行和使用vn.py量化平台了。

 

安装vn.py

 

首先打开vn.py项目的GitHub发布页面

 

这里包含了vn.py所有发布的正式版本,推荐使用最新版本(左侧会有个Latest release的绿色文字框提示)。点击最新版本下方的的Source Code (zip)链接,来进行下载。
 

下载完毕后,进入文件目录\root\Downloads,解压zip格式的安装文件。

 
description

 

然后进入解压文件目录\root\Downloads\vnpy-xxx(其中xxx是下载的vn.py版本号),在终端中运行安装命令:

 

bash install.sh

 

接下来安装脚本会自动进行vn.py以及相关依赖库的安装任务。

 
description

 

安装完毕后,尝试启动图形化交易界面VN Trader。进入文件目录\root\Downloads\vnpy-xxx\examples\vn_trader,其中的run.py文件就是我们启动VN Trader的程序入口。
 

通常用户可以根据自己的需求,自行在run.py文件中加载需要使用的底层接口和上层策略应用。这里我们只展示CTP接口的连接,如果需要使用别的接口可以使用VSCode编辑文件自行添加。

 
description

 

在当前的vn_trader目录下,右键打开终端运行命令,即可启动图形化交易界面VN Trader:

 

python run.py

 

注意:如果启动VN Trader时报错说缺少了pyqtgraph和zmq库,直接用pip工具安装即可,在终端中运行命令:

 

pip install pyqtgraph pyzmq

 
description

 

进入VN Trader后,点击菜单栏【系统】->【连接CTP】会弹出CTP账号配置选项。填好账号信息后,点击下方的【连接】按钮即可登陆CTP进行交易。

 
description

 

点击“连接”按钮后,左下角的日志信息区域会输出相关的初始化日志信息,看到“合约信息获取成功”的日志后,我们就可以订阅行情推送以及执行委托交易了。

 
description

 

 

交易接口支持

 

目前2.0版本的vn.py,在Windows系统下可以使用所有的交易接口,而在Ubuntu系统下则只能使用其中的一部分,具体情况如下:

 

C/C++类接口:CTP、OES

这类原生API接口提供的SDK文件中通常包含:头文件、动态链接库、静态链接库(Windows下)。动态链接库在Windows下为dll文件,而Linux下则为so文件。

 

理论上,所有提供了so格式动态链接库的C/C++类交易接口,都能支持在Ubuntu上运行,如下图所示的CTP:

 
description

 

目前由于开发力量上的限制,对于C/C++类接口,vn.py在Ubuntu上只支持CTP和OES两个用户量最大的接口,后续随着2.0版本的功能模块逐步移植完毕,会提供其他接口的支持:TAP、FEMAS、XTP等。
 

Python类接口:IB、TIGER、FUTU
 

IB(盈透证券)、TIGER(老虎证券)、FUTU(证券)这三个接口,使用的是其官方提供的纯Python SDK,直接进行接口函数的对接开发。得益于Python本身的跨平台解析性语言特点,这类接口在Ubuntu系统下也能直接使用。

 

 

编译CTP

 

注意:如果只是想要在Ubuntu下使用vn.py做量化,这段内容并不是必须掌握的知识。

 

对于C++接口的具体编译过程感兴趣的用户(vn.py社区的成员就是这么好学~),可以照着下面的步骤尝试在Ubuntu环境下编译CTP接口。

 

首先在桌面上创建一个如下结构的目录,其中包含ctpapi文件夹(包含ctp文件夹和init.py)、setup.py、MANIFEST.in。

 
description

 

创建好后,需要对红色方框标识的3个文件进行操作:setup.py和MANIFEST.in需要写入新的代码,而ctp文件夹需要放入新的文件。

 

setup.py是C++ API封装代码的编译的主入口文件,运行后即可生成Linux环境下的动态链接库so文件,或者用于Window环境下的dll文件。具体内容如下:
 

import platform
from setuptools import Extension, setup

dir_path = "ctpapi"

if platform.uname().system == "Windows":
    compiler_flags = [
"/MP", "/std:c++17",  # standard
"/O2", "/Ob2", "/Oi", "/Ot", "/Oy", "/GL",  # Optimization
"/wd4819"  # 936 code page
    ]
    extra_link_args = []
else:
    compiler_flags = [
"-std=c++17",  # standard
"-O3",  # Optimization
"-Wno-delete-incomplete", "-Wno-sign-compare", "-pthread"
    ]
    extra_link_args = ["-lstdc++"]

vnctpmd = Extension(
# 指定 vnctpmd 的位置
"ctpapi.ctp.vnctpmd",
    [
f"{dir_path}/ctp/vnctp/vnctpmd/vnctpmd.cpp",
    ],
# 编译需要的头文件
    include_dirs=[
f"{dir_path}/ctp/include",
f"{dir_path}/ctp/vnctp",
    ],
# 指定为c plus plus
    language="cpp",
    define_macros=[],
    undef_macros=[],
# 依赖目录
    library_dirs=[f"{dir_path}/ctp/libs", f"{dir_path}/ctp"],
# 依赖项
    libraries=["thostmduserapi_se", "thosttraderapi_se", ],
    extra_compile_args=compiler_flags,
    extra_link_args=extra_link_args,
    depends=[],
    runtime_library_dirs=["$ORIGIN"],
)
vnctptd = Extension(
"ctpapi.ctp.vnctptd",
    [
f"{dir_path}/ctp/vnctp/vnctptd/vnctptd.cpp",
    ],
    include_dirs=[
f"{dir_path}/ctp/include",
f"{dir_path}/ctp/vnctp",
    ],
    define_macros=[],
    undef_macros=[],
    library_dirs=[f"{dir_path}/ctp/libs", f"{dir_path}/ctp"],
    libraries=["thostmduserapi_se", "thosttraderapi_se"],
    extra_compile_args=compiler_flags,
    extra_link_args=extra_link_args,
    runtime_library_dirs=["$ORIGIN"],
    depends=[],
    language="cpp",
)

if platform.system() == "Windows":
# use pre-built pyd for windows ( support python 3.7 only )

    ext_modules = []
# if you really want to build it . please check your environment (没测试过)
# ext_modules = [vnctptd, vnctpmd]
elif platform.system() == "Darwin":
    ext_modules = []
else:
    ext_modules = [vnctptd, vnctpmd]

pkgs = ['ctpapi', 'ctpapi.ctp']
install_requires = []
setup(
    name='ctpapi',
    version='1.0',
    description="good luck",
    author='somewheve',
    author_email='####',
    license="MIT",
    packages=pkgs,
    install_requires=install_requires,
    platforms=["Windows", "Linux", "Mac OS-X"],
    package_dir={'ctpapi': 'ctpapi/'},
    package_data={'ctpapi': ['ctp/*', ]},
    ext_modules=ext_modules,
    classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.7',
    ]
)

 

MANIFEST.in用于指明所有需要导入的文件,其代码如下:
 

# include MANIFEST.in
include README.md
recursive-include ctpapi/ctp *

 

对于原本空空如也的ctp文件夹,我们进行以下复制操作:

 

  1. 复制/root/Downloads/vnpy-xxx/vnpy/api目录下的ctp文件,
  2. 粘贴到/root/Desktop/ctpapi/ctpapi目录下。

 
对MANIFEST.in、setup.py、ctp目录处理完毕后,就可以开始进行编译了:

 

  1. 切换到与setup.py同级的目录/root/Desktop/ctpapi;
  2. 在该目录下进入终端,然后输入命令python setup.py build开始编译;
  3. 编译完毕后会生成新的文件夹build。

 
description
 

打开build文件夹,在build/ctpapi/build/lib.linux-x86_64-3.7/ctpapi/ctp里面,可以看到两个so文件:vnctpmd.cpython-37m-x86_64-linux-gnu.so和 vnctptd.cpython-37m-x86_64-linux-gnu.so,这两个Linux下的动态链接库就是已经编译完成的CTP API封装,可以直接在Python中加载使用了。

 
description

 

编译好后,为了检验有效性,可以试试看能否在Python解释器中导入vnctpmd和vnctptd两个模块:
 

  1. 在桌面上创建新的文件夹ctpso;
  2. 然后把root/ctpapi/build/lib.linux-x86_64-3.7目录下的ctpapi文件夹复制,并粘贴到新文件夹ctpso里面;
  3. 在ctpso目录下进入终端,启动Python解释器,运行下面命令:
    from ctpapi.ctp import vnctpmd
    from ctpapi.ctp import vnctptd
     
  4. 若无报错(正常载入),则说明我们的编译已经成功了!

 
description

 
了解更多知识,请关注vn.py社区公众号。
description

对于vn.py的初学者以及绝大部分用户来说,Windows操作系统可能是比较好的选择,性能满足需求而且也几乎没有额外的学习成本。但不可否的是,Linux操作系统在系统资源占用、扩展服务开发、跨进程通讯延时等方面,有着明显的优势。

 

社区内也一直不乏用户希望尝试学习使用Linux,常见的两种形式包括:

 

  • 虚拟机:VMWare、Hyper-V等,运行于本地电脑,傻瓜式安装适合体验学习
  • 云服务器:阿里云、腾讯云等,需要自行安装配置,运行于服务端更适合实际应用

 

本篇教程就主要针对如何在阿里云服务器上搭建一套完整的Linux量化交易系统环境来讲解。Linux版本上选择了vn.py官方支持的Ubuntu 18.04 LTS 64位版本,如果要使用Debian、CentOS等可以自行尝试,整体大同小异。
 

主要用到的工具包括MobaXterm(远程连接客户端)、Xubuntu-destop(服务器图形界面)、vnc4server(远程桌面服务)等,尽管安装配置的过程有些繁琐,但只要跟着图文说明一步步去做,100%能成功。

 

在开始安装工作前,请先准备好1台阿里云的服务器(也可以选择AWS、腾讯云等):

 

  • 节点选择:华东2(国内期货、证券)或者香港(外盘)
  • 机器配置:2核以上CPU,4G以上内存,共享计算型(不要选突发实例)
  • 操作系统:Ubuntu 18.04 LTS 64位
  • 网络带宽:按需付费,20M以上带宽,分配公网IP
  • 登录方式:用户名和密码(不要选密钥)

 

购买好后请记录下该服务器的公网IP,下面连接要用。

 

 

安装MobaXterm

 
MobaXterm是一款增强型远程连接工具,可以轻松地调用远端Linux服务器上的各项功能命令。接下来将会用到MobaXterm的SSH和VNC功能:

 

  • SSH,可以理解为命令行终端,类似黑框框的CMD
  • VNC,则可以理解为远程图形化桌面,类似常规Windows桌面

 

首先,需要从官网下载MobaXterm:

https://mobaxterm.mobatek.net/download-home-edition.html

 

下载完成后解压安装包,直接双击exe文件进行安装。

 

安装完成后,双击桌面图标启动MobaXterm。在主界面中单击导航栏左边第一个【Session】进入连接页面。

 
description

 

或者也可以点击顶部菜单栏【Sessions】->【New Session】按钮。

 
description

 

在弹出的新页面Session Settings中,单击导航栏最左边的【SSH】按钮。然后在Basic SSH settings中输入云服务器的公网IP和账号。

 
description

 

其中默认账号为root,输入root账号之前记得把左边小方框勾选上,端口号保留默认的22即可,然后点击最下方的【OK】按钮。

 
description

 

之后会自动弹出一个新的连接页面,第一次连接时右侧终端会提示输入云服务器的密码,注意在输入时,界面上并不会有任何反应(不会显示密码)。

 
description

 

输入完按回车键后,若密码正确则会弹出一个小窗口提示是否保存密码,可以点击【Yes】按钮。

 
description

 

看到下图中显示的内容,就说明阿里云的Ubuntu服务器已经连接成功了。左边显示的是云服务器上的文件夹目录,右边的黑框是命令操作界面。

 

description

 

到这里,我们就完成了使用MobaXterm远程连接云服务器的步骤。当然,这种连接是基于SSH,只能通过命令行终端的方式来调用服务器上的各项功能。

 

为了更方便的管理连接,需要进行一下重命名:点击最左侧的【Session】选项,找到刚刚创建的SSH连接,鼠标右键选定该连接,选择【Rename session】。

 
description

 

弹出Session settings界面,其中的Session name可以修改为任意名称,这里我们将其重命名为DEV_1,完成后点击左下方的【OK】保存。

 

description

 

此时在左侧的Sessions标签中,我们可以看到该连接的名称已经改为了DEV_1。

 

description

 

同理重复上面的操作,输入相同的云服务器的公网IP和账号,创建第二个SSH连接(命名为DEV_2),这样我们就能同时使用2个终端了,同理你也可以创建更多的终端连接。

 
description

 
 

更新软件仓库

 
在Windows下安装软件,通常只需要准备好安装包的exe文件,然后一路点击【下一步】即可完成。但Linux并非如此,Linux系统的发行版大多自带了软件包管理器,如Ubuntu和Debian的APT。

 

对于大部分常用的软件,用户都可以直接从官方提供的仓库中下载安装需要的软件,而不必自己去每个网站下载,这也是Linux与Windows在使用上的一大区别。

 

所以在Ubuntu系统中,本地保存了一个软件包安装源列表,列表里面都是一些网址信息,标识着某个源服务器上有哪些软件可以安装使用。所以为了能够正常安装最新版本的软件,首先需要更新软件包管理器里的这些列表。

 
description

 

只需要在Ubuntu终端中,运行输入命令

sudo apt-get update

就会如上图所示,自动遍历访问源列表中的每个网址,并读取最新的软件信息,保存在本地系统中。

 
 

安装Xubuntu-destop

 
Xfce是一款针对Linux/UNIX系统的现代化轻量级开源桌面环境,优点是内存消耗小,且系统资源占用率很低,Linux之父Linus Torvalds日常使用的桌面环境就是Xfce。
 

Xfce只是纯粹的桌面环境,但我们在日常使用操作系统时,还需要许多其他常用的工具,如多国语言支持、浏览器、输入法工具等。Xubuntu-desktop就是一套整合了Xfce桌面环境和其他常用图形界面软件的安装包。

 

安装方法也相对简单,在终端中运行命令:

 

sudo apt-get install xubuntu-desktop

 
description

 

在国内当前的网络环境下,Xubuntu-desktop的下载和安装可能耗时在20-60分钟的样子,期间可以在终端中看到类似上图所示的安装信息。

 

 

搭建VNC服务

 
VNC是一款基于RFB协议的屏幕画面分享及远程操作软件,主要特色在于跨平台性:我们可以用Windows电脑来控制Linux系统,反之亦然。

 

首先需要安装VNC服务器,在终端下运行命令:

 

sudo apt-get install vnc4server

 

安装完毕后,在终端运行命令
 

vncserver

 
用来启动VNC服务器。首次运行时需要设置VNC远程连接的密码,长度至少是6位,并且二次输入来确认(注意该密码不是Ubuntu账户的密码)。

 

description

 

VNC服务器启动好后,可以在日志输出文件名中看到其默认端口是1,上图所示红色方框标识“:1”。

 

启动了远程桌面服务器后,下一步是配置xstartup文件,来设置远程桌面使用Xfce的桌面环境。用文本编辑器nano打开xstarup文件,在终端运行命令:

 

nano ~/.vnc/xstartup

 

可以看到如下内容:

 
description

 

需要在最后一行 "x-window-manager &" 前面添加一个"#"注释掉这行,同时在文件最后加入一段新的配置信息:

 

session-manager & xfdesktop & xfce4-panel &
xfce4-menu-plugin &
xfsettingsd &
xfconfd &
xfwm4 &

 
description

 

修改完毕后,按住“Ctrl”和“Y”键来退出nano编辑器,注意会询问是否要保存当前修改,请按“Y”进行保存。

 

配置完xstartup后,我们需要重启VNC服务器:先把默认的1号端口服务杀掉,然后新的服务我们改为使用9号端口(因为1号端口容易被扫描攻击),同时设置远程桌面的分辨率为1920x1080(根据你的本地显示器分辨率来设),运行下列命令:

 

vncserver -kill :1
vncserver -geometry 1920x1080 :9

 

description

 

 

连接VNC桌面

 

回到MobaXterm主界面,单击主页最上边的【Sessions】->【new Session】弹出【Sessions settings】界面,这一次选择【VNC】连接。

 

其中【IP address】为阿里云公网IP,【Port】为VNC连接端口,在上一段中我们选择了在9号端口启动,故从需要把Port端口由默认的5900调整为5909。

 
description

 

同时在下面的【Bookmark settings】界面对该VNC连接进行重命名为“VNC”,点击左下方的【OK】按钮,开始连接VNC远程桌面,成功后即可看到如下图所示的Xfce图形界面:

 
description

 

 

安装IBus中文输入法

 
作为中国人总归免不了要输入中文,这里我们选择安装IBus中文拼音输入法,在终端中运行命令:
 

sudo apt install ibus-pinyin

 

安装完成后,还需要先配置下Ubuntu系统的中文语言包:
 

  1. 在桌面顶部的菜单栏,点击左上方【Applications】->【Settings】->【Language Support】按钮;
  2. 第一次会出现提示语言未全部安装,然后点击确认自动安装,成功后会弹出【Language Support】界面;
  3. 完成后点击下方的【Install/Remove Languages】按钮,会弹出新的【Installed Languages】界面,勾选【Chinese(Simplified)】,即简体中文,然后点击下方的【Apply】按钮;
  4. 最后在【Keyboard input method system】选项选择【IBus】。

 
description

 

设置完中文语言包后,进入IBus输入法设置:

 

  1. 在菜单栏左上方点击【Applications】->【Settings】->【IBus Preferences】选项;
  2. 第一次会提示IBus-Daemon尚未启动,点击【Yes】按钮进行安装,成功后菜单栏右上角出现一个语言图标,并且弹出【IBus Preferences】界面;

description
 

  1. 进入【Input Method】界面,点击【Add】来添加中文输入法:选择【Chinese】->【Pinyin】;

description

 

  1. 鼠标右键点击菜单栏上的语言图标【Restart】按钮来重启;

description

 

  1. 然后再次左键点击,点击【Chines-Pinyin】就可以输入了。此时语言图标也变成“拼”字;

description

 

  1. 打开一个编辑器或浏览器,可以看到我们此时输入的已经是中文拼音。

description
 

 

安装Visual Studio Code

 
既然要写程序那就还需要一套IDE工具,同样这里我们使用vn.py官方推荐的Visual Studio Code(VSCode),作为微软出品的轻量级代码编辑器,在Linux下的安装和使用也同样非常方便。

 

打开Visual Studio Code的官网:https://code.visualstudio.com/

 
description

 

在官网首页点击下载.deb版本:

 
description

 

Ubuntu下的deb格式安装包,需要用使用dpkg命令来安装。进入下载文件所在的目录/root/Downloads,鼠标在空白处右键点击【Open Terminal Here】进入终端:

 
description

 

输入下面命令安装:
 

sudo dpkg -i code_1.37.0-1565227985_amd64.deb

 
description
 

安装完成后会发现VSCode不能正常启动,因为Xfce和VSCode的默认设置存在兼容性问题,需要手动配置,在终端中运行下面命令:

 

sudo sed -i 's/BIG-REQUESTS/_IG-REQUESTS/' /usr/lib/x86_64-linux-gnu/libxcb.so.1

 

再次尝试启动VSCode,已经可以正常打开运行,注意上述命令输入运行后界面没有任何输出。

 
 

安装Python3.7

 

Ubuntu 18.04系统中内置了Python 2.7和3.6,并且输入python命令时默认启动的是Python2.7。

 
description

 

但v2.0版本的vn.py则是基于Python 3.7,如果安装新的Python 3.7版本,则需要把新安装的Python 3.7设置为系统默认的Python环境,同时将pip命令也指向Python 3.7的版本。
 

手动进行上述命令的重新定向容易导致各种问题,所以这里我们选择使用Minconda(Python 3.7 64位)。作为有名的Python科学计算发行版本Anaconda的轻量小弟,安装完成后会自动设置其内部的Python 3.7为系统默认环境。
 

打开Minconda官网,选择【Linux】系统的Python 3.7【64-bit】版本下载:

 
description

 

下载完成后,进入文件所在目录/root/Downloads可以看到.sh格式的Miniconda安装包,鼠标在空白处右键,点击【Open Teminal Here】进入终端,然后输入命令进行安装:

 

bash Miniconda3-latest-Linux-x86_64.sh

 
description

 

安装完毕后,在终端运行Python,可以看到当前的版本已经变成Python 3.7:

 
description

 

折腾至此,终于完成我们的目标:支持远程图形桌面的阿里云Ubuntu量化交易服务器~~未来Linux使用熟练后,也同样可以选择关闭图形界面,以纯命令行的形式来运行vn.py的自动交易功能。
 

了解更多知识,请关注vn.py社区公众号。
description

新的vn.py社区论坛已经上线差不多大半年的时间,许多社区用户都贡献了非常高质量的量化交易相关内容。接下来我们计划每周整理一篇论坛中的精华文章,制作为一个《vn.py社区精选》系列。

 

每篇文章我们会先争得作者的转载同意,同时支付200元的稿费。稿费金额数字不大,更多是对每位作者为vn.py社区做出贡献的一份感谢,也欢迎大家在论坛上更多分享自己的使用经验!

 

 

为什么要加密?

 

从执行方式上,编程语言可分为2类,编译型语言解释型语言

 

  • 编译型语言(如C++等),在程序执行之前,会先通过编译器对程序源代码执行进行编译,将其转变为机器语言后(如.dll 或者.exe),再由机器语言负责最后的运行操作;
  • 解释型语言(如Python等),则省去了编译的过程,而是选择在程序运行的时候,通过解释器对程序逐行做出解释,然后直接运行。

 

尽管在分类上属于解释型语言,Python在实际运行时为了提高效率,同样会先从源代码(py文件)编译为字节码文件(pyc文件),而后在运行时通过解释器再来解释为机器指令执行。第二次运行该Python文件时,解释器会在硬盘中寻找到事先编译过的pyc文件,若找到则直接载入,否则就重新生成pyc文件。

 

但无论是py文件还是pyc文件,都有极高的风险泄露源代码:
 

  • py文件:Python程序的可读源代码;
  • pyc文件:作为字节码,可以通过某些工具(如uncompyle6)还原为.py文件。
     

无论是谁,都不会希望自己辛辛苦苦开发的量化策略被任何第三方窃取,因此自然而然就产生了对策略文件进行加密的需求:对py文件加密,生成可以正常加载运行,但无法被反编译的pyd文件(在Linux上为.so文件)。

 

 

解决方案Cython

 
作为Python语言的子集,Cython主要被用来解决Python代码中的运行效率瓶颈问题,如numpy底层的矩阵运算加速,期权的实时定价模型等等。

 

除了加速功能外,Cython也提供了一整套Python语言的静态编译器,可以将Python源代码转换成C源代码,再编译成pyd二进制文件(本质上是dll文件)。

 

尝试用VSCode打开一个编译生成的pyd文件:

 

description

 

可以看到内容全都是不可读的二进制乱码,从而实现了我们需要的代码加密功能。

 

 

一步步学加密

 

尽管听起来有点复杂,Cython的实际操作却非常非常简单,装好工具后只需要一条命令就能完成所有编译工作,所以完全不用紧张,照着下面的傻瓜教程一步步操作就好。

 
 

第一步

 

安装Visual StudioComunity 2017,下载地址:

https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/

 

安装时请勾选“使用C++的桌面开发”,如下图所示:
 
description

 

 

第二步

 

在Python环境中安装Cython,打开cmd后输入运行pip install cython即可:

 

description

 
 

第三步

 
创建一个新的文件夹Demo,把需要加密的策略(如demo_strategy)复制到该文件夹下:

 
description

 

 

第四步

 

在Demo文件夹下,按住“Shift”+ 鼠标右键,点击“在此处打开命令窗口(或者Powershell)”进入cmd,输入以下命令来进行编译:

 

cythonize -i demo_strategy.py

 

随后Cython工具会自动执行C代码的生成和编译工作,输出类似下图中的一系列日志信息:

 

description

 
 

第五步

 
编译完成后,Demo文件夹下会多出2个新的文件,其中就有已加密的策略文件demo_strategy.cp37-win_amd64.pyd:

 
description

 

 

第六步

 
在操作系统的用户目录下(如C:\Users\Administrator\),创建strategies文件夹,用于存放用户自己开发的的策略文件。将上一步生成的demo_strategy.cp37-win_amd64.pyd,放到此处即可运行:
 

description

 

 

第七步

 
启动VN Trader后,进入CTA策略模块即可看到加密后的DemoStrategy策略已经正常识别并加载到了系统中:

 
description

 

了解更多知识,请关注vn.py社区公众号。

description

跑完了历史数据回测和优化,得到了一个不错的回测资金曲线,最后就可以准备开始实盘交易了。在教程2-5中我们已经接触了真实账户和仿真账户的概念,这里强调一个原则:

 

所有量化策略在开始真金白银交易之前,都应该经过仿真账户的充分测试,毕竟每个人交易的本金都来之不易,一定要有十分负责的态度。

 
本篇教程我们继续以股指期货为例,其他产品的量化策略实盘也基本都一样。首先启动VN Trader Pro,加载CTP接口以及CTA策略模块(CtaStrategy),或者也可以直接运行VN Trader Lite。

 

进入VN Trader主界面后,连接登录CTP接口,等待日志组件中看到“合约信息查询成功”的信息输出。

 

 

加载一个实例

 
随后,点击菜单栏的“功能”->“CTA策略”,或者左侧导航栏里的国际象棋棋子的图标,看到CTA策略实盘交易的窗口:

 
description

 

此时在右下方的日志监控组件中,可以看到“RQData数据接口初始化成功”的信息,如果没有的话请照着上一篇教程里的方法,配置RQData数据服务。

 

接下来要基于之前开发好的策略模板(类),来添加策略的实例(对象),点击右上角的策略下拉框,找到DemoStrategy:

 
description

 

点击添加策略按钮,出现策略实盘参数配置的对话框:

 
description

 

每个参数字段,后面的<>括号中说明了该字段对应的数据类型,注意必须根据要求填写,否则实例创建会出错。

 
首先我们要给策略实例一个名字,也就是strategy_name字段,注意每个实例的名称必须唯一,不能重复。

 
然后需要指定策略具体要交易的合约,通过本地代码vt_symbol来指定(合约代码 + 交易所名称)。注意在上一篇教程中我们回测使用的是RQData提供的股指连续合约数据IF88.CFFEX,该合约只是为了方便回测而人工合成的数据,在实盘交易系统中并不存在。在实盘中,需要指定具体的交易合约,一般选择该期货品种当前流动性最好的月份,比如写本文时是2019年8月3日,此时的股指主力合约为IF1908.CFFEX。

 

fast_window和slow_window是策略里写在parameters列表中的参数名,这里我们填入上一篇教程中优化后的结果18和90。
 

点击“添加”按钮后,在左侧的策略监控组件中,就能看到该策略实例了:

 
description

 

顶部按钮用于控制和管理策略实例,第一行表格显示了策略内部的参数信息,第二行表格则显示了策略运行过程中的变量信息(变量名需要写在策略的variables列表中)。inited字段表示当前策略的初始化状态(是否已经完成了历史数据回放),trading字段表示策略当前是否开始交易。

 

注意上方显示的所有变量信息,需要在策略中调用put_event函数,界面上才会进行数据刷新。有时用户会发现自己写的策略,无论跑多久这些变量信息都不动,这种情况请检查策略中是否漏掉了对put_event函数的调用。

 

 

策略初始化

 

点击监控组件顶部的“初始化”按钮,此时内部的CTA策略引擎会先调用策略的on_init函数,运行用户定义的逻辑,随后按照顺序完成以下三步任务。

 
 

获取历史数据

 
首先是载入该合约最新的历史数据,具体载入数据的长度,通过策略内部的load_bar函数的参数控制。数据载入后会以逐根K线(或者Tick)的方式推送给策略,实现内部变量的初始化计算,比如缓存K线序列、计算技术指标等。

 

在载入时,CTA策略引擎会优先通过RQData来获取历史数据,RQData的数据服务提供盘中K线更新,因此即使在9点45分才启动策略,也能获取到之前从9点30开盘到9点45分之间的K线数据提供给策略进行初始化计算,而不用担心数据缺失的问题。

 

如果RQData不支持该合约(比如外盘期货),CTA策略引擎则会尝试使用交易接口进行获取。对于IB接口来说,交易的服务端系统提供了相应的历史数据下载功能。

 

最后,如果交易接口也获取不到,那么CTA策略引擎会访问本地数据库来加载历史数据。这种情况下,用户需要自己来保证数据库中的数据完整性(满足需求),比如通过DataRecorder录制,使用CsvLoader从CSV文件载入,或者使用其他数据服务(比如万得宏汇等)来更新。

 
 

载入缓存变量

 

量化策略在每天实盘运行的过程中,有些变量纯粹只和行情数据相关的,这类变量通过上一步的加载历史数据回放就能得到正确的数值。另一类变量则可能是和交易状态相关的,如策略的持仓、移动止损的最高价跟踪等,这类变量需要缓存在硬盘上(退出程序时),第二天回放完历史数据后再读取还原,才能保证和之前交易状态的一致性。

 

在CtaStrategy中这一步骤对于用户来说是几乎无感的,每次关闭程序时会自动将每个策略的variables列表对应的变量写入json文件(缓存在.vntrader目录下的cta_strategy_data.json中),并在下一次策略初始化时自动载入。

 

注意在某些情况下,可能缓存的数据出现了偏差(比如手动平仓了),此时可以通过手动修改json文件来调整

 

 

订阅行情推送

 

最后是获取该策略所交易合约的信息(基于vt_symbol),并订阅该合约的实时行情推送,如果找不到该合约的信息,比如没有登录接口或者vt_symbol写错了,则会在日志模块中输出相应的报错信息。

 

注意对于IB接口来说,因为登录时无法自动获取所有的合约信息,只有在用户手动订阅行情时才能获取,因此需要在主界面上先行手动订阅该合约行情,然后再点击“初始化”按钮。

 

description

 

以上三个步骤全部完成后,策略的inited状态变为True,且变量也都有了对应的数值(不再为0),则说明初始化已经完成。

 
 

策略的启动

 

完成策略初始化后(inited状态为True时),才可以点击“启动”按钮启动策略的自动交易功能:

 

description
 

当trading状态为True时,策略内部的交易请求类函数(buy/sell/short/cover/cancel_order等),以及信息输出类函数(write_log/send_email等),才会真正执行并发出对应的请求指令到底层接口中(真正执行交易)。

 

在上一步策略初始化的过程中,尽管策略同样在接收(历史)数据,并调用对应的功能行数,但因为trading状态为False,所以并不会有任何真正的委托下单操作或者日志信息输出。

 

 

策略的停止

 
到了市场收盘时间,或者盘中遇到紧急情况时,点击“停止”按钮即可停止策略的自动交易。

 

CTA策略引擎会自动将该策略之前发出的所有活动委托全部撤销(保证在策略停止后不会有失去控制的委托存在),同时执行上面提到过的变量缓存操作。

 

这两步都完成后,策略的trading状态会变为False,此时可以放心的关闭程序了。

 
在CTA策略的实盘交易过程中,正常情况应该让策略在整个交易时段中都自动运行,尽量不要有额外的暂停重启类操作。对于国内期货市场来说,应该在夜盘时段开始前,启动策略的自动交易,然后直到第二天下午收盘后,再关闭自动交易,中间的夜盘收盘属于同一交易日内,无需停止策略。

 

 

编辑和移除

 

跑量化策略的过程中,有时可能需要调整策略的参数,点击策略监控组件上的“编辑”按钮,即可在弹出的参数编辑对话框中任意修改参数:

 

description

 

点击确定按钮后相应的修改会立即更新在参数表格中,注意策略实例的交易合约代码无法修改,同时修改完后也不会重新执行初始化操作。
 

为了安全起见,请一定要在trading状态为False时(自动交易停止),才进行参数的编辑操作。

 

想要删除某个策略实例时,点击“移除”按钮,CTA策略引擎会自动完成该策略实例的对象销毁和内存释放,并在GUI图形界面上移除其监控组件,此时该策略的名称(strategy_name)也可以再次使用。注意只能移除trading状态为False的策略实例,如果策略已经启动了自动交易功能需要先停止。

 
 

其他

 

在每天的实盘交易中,如果存在比较多的策略实例,可以通过右上角的“全部初始化”、“全部启动”“全部停止”三个按钮,来一次性对所有的策略实例进行相应的操作管理,避免开盘前点几十下“启动”,收盘后点几十下“停止”的重复劳动。

 

当日志监控组件中的信息条数过多时,可以点击右上角的“清空日志”按钮来清空其中已有的信息。

 

最后还剩没提到的就是右上方区域的停止单(Stop Order)监控组件,该组件用来跟踪所有CTA引擎内的本地停止单状态变化,具体用法请参考官网文档

 
实盘交易中除了每天机械的启动和关闭策略外,更重要的是对每天策略运行交易结果的跟踪和总结:逻辑运行和回测是否一致、实盘成交和回测假设的滑点偏差有多少等等,并用这些观察到的结果数据,去修正回测研究中用到的参数假设,从而才能实现自己策略研究水平的迭代进步。
 
了解更多知识,请关注vn.py社区公众号。
description

策略已经写好了,下一步就是历史回测:把历史上的价格数据(K线或者Tick),推送给策略去运行交易逻辑,并把策略产生的交易记录下来,最后分析这些回测的交易记录,从而来判断该策略的潜在盈利能力。

 

在开始之前,先来讲几个量化策略研究中(不管是否用vn.py),需要记住的几条重要原则:

 

  • 所有量化程序的回测功能,永远都只能尽量接近实盘交易中的各项细节,而无法做到100%一样,关键点在于误差的大小(是否能容忍);
  • 回测效果好的策略,并不能代表实盘交易就一定盈利,可能存在交易成本误差、参数过度拟合、逻辑有未来函数或者市场特征变化(Regime Switch)等原因;
  • 回测效果烂的策略,实盘交易基本可以保证会更烂,绝对不要有侥幸心理。

 
 

准备历史数据

 

要跑历史数据回测,第一步自然就是要先准备好历史数据。这里我们以国内期货数据为例,使用米筐的RQData来下载获取。

 

RQData目前提供30天的免费试用权限,网站申请非常方便。前往RQData主页

 

description
 

找到上图中的“免费试用”按钮,点击进去后:

 

description

 

根据自己的实际情况填写相应的注册信息,邀请码点击那个白色小问号图标,可以看到没有邀请码情况下的默认输入值(当前是ClOR,注意l是小写的L,而不是大写的i),点击“登录并申请”按钮后,会看到登录框:

 

description

 

选择“验证码登录”,输入手机号验证码后点击“确认登录”按钮,回到上一步的界面,但注意此时底部按钮显示的文字已变为“立即申请试用”:

 

description
 

点击上述按钮后完成试用申请,注意此时有可能出现验证码超时或者其他的错误信息,根据提示重新填写再点击按钮即可。申请成功后会自动弹出开始下载[make.bat]文件(该文件中即包含了申请的试用账号和密码),以及[RQDATA使用说明.pdf]。

 

使用VS Code打开make.bat文件:

 

description

 

记住其中的name(用户名)以及password(密码),注意密码是一串长达344个字符的密钥,上图中仅截取了很短一部分(别想偷懒,哈哈)。

 

然后运行VN Station,点击VN Trader Pro,在右侧的上层应用中加载CtaBacktester(CTA回测模块)后启动,在主界面顶部的菜单栏,找到“配置”按钮:

 

description

 

点击后打开VN Trader的全局配置对话框:

 

description

 

将之前已经准备好的RQData用户名和密码,分别填入到rqdata.username和rqdata.password两个字段中,然后点击“确定”按钮,弹出提示重启的对话框。
 

此时即可关闭VN Trader并重启,点击菜单栏“功能”->“CTA回测”,启动接下来我们要用到的CTA策略回测图形界面:

 

description

 

如果上一步的RQData账号密码配置正确,此时可以在中间底部的日志输出框中看到“RQData数据接口初始化成功”的信息。如果没有就说明配置有问题,回去重来吧。

 

窗口左上方的一系列编辑框和下拉框,用来控制和管理我们的回测功能。在本地代码编辑框中输入IF88.CFFEX,K线周期选择1m(即1分钟K线),然后选择要下载数据的开始日期和结束日期,点击“下载数据”按钮。

 

此时CtaBacktester模块就会自动从RQData服务器下载历史数据,并完成数据结构转化后插入到VN Trader的数据库中(默认使用SQLite,数据文件位于.vntrader目录下的database.db),下载完成后同样会在日志输出框中看到相应信息:

 

description

 

 

运行历史回测

 

有了历史数据后,我们就可以开始跑历史回测。在左上角的交易策略下拉框里,应该已经能找到上一篇教程我们编写的DemoStrategy,选中后开始配置回测参数。

 

注意这里我们使用的是中金所股指期货的IF合约,回测时的参数要设置为股指期货所对应的属性。手续费率编辑框中输入0.000025(万0.25),交易滑点输入0.2(即单边成交1跳的滑点成本),合约乘数为300(300元每点),价格跳动也是0.2(股指期货最小价格变动),回测资金我们使用100万。

 

点击“开始回测”按钮,弹出参数配置对话框:

 

description

 

这里显示的fast_window和slow_window就是之前我们添加到parameters列表中的参数名称,这里我们直接使用默认数值,点击“确定”按钮后,我们的回测引擎就会自动开始执行策略回测的整个流程:加载数据、数据回放、模拟撮合、计算每日盈亏、统计指标、以及最后画出图表:

 

description

 

不出之前的意料,双均线策略的效果差的一塌糊涂,右侧图表的4个子图中:

 

description

 

  • 子图1:资金变化曲线,笔直向下说明稳定亏损
  • 子图2:最大回撤曲线,越来越大说明策略亏损越来越多
  • 子图3:每日盈亏统计,红绿分布平均,但绿色密度更大(亏损)
  • 子图4:盈亏的概率分布图,尖峰在0轴左侧(中位数日期发生亏损)

 

然后在中间顶部的表格中,可以看到回测相关的一些统计数据:

 

description

 

这策略干了什么事情能亏这么多钱呢,总有很多人会抱着不信邪的态度,此时点击左侧的“成交记录”按钮,可以看到回测过程中的每一笔成交记录:

 

description

 

还不信邪,可以点击“委托记录”按钮查看这些成交具体是由哪些委托触发的:

 

description

 

可以通过“成交记录”中的每条成交对应的委托号,在“委托记录”中找到策略下出的委托。细心的人可能已经发现上面两张图中,某一笔委托的价格和其对应成交的价格并不一致,这是因为我们在策略下单时使用了超价5元(为了保证成交),而回测仿真撮合时则是取了T+1时刻的最优成交价(也是实盘中最可能拿到的价格)。
 

在“每日盈亏”窗口中,可以看到以逐日盯市规则(期货结算规则),将每日的持仓和成交映射到当日收盘价后的当日整体盈亏情况:

 

description

 

最后,如果还是觉得死活不相信双均线策略怎么可能这么差,点击“K线图表按钮”,可以看到整个回测数据对应的K线图表:

 

description

 

上图中的黄色向上箭头代表多头成交(buy/cover),蓝色向下箭头则代表了空头成交(sell/short),可以通过键盘和鼠标拖动和缩放图表,看到自己想要的部分。

 

到了这里,是不是已经有点相信了“双均线策略就是垃圾”的说法?实际上从公平角度讲,以上看到的回测信息,并不能充分证明双均线信号的无效性,如果我们:

 

  • 将手续费和滑点都调为0,这样不考虑交易成本影响
  • 然后在回测时缩小fast_window到3,增大slow_window到80,即让长周期均线变得更加平稳

 

出来的结果则变成了:

 

description

 

从这张图上看,在不考虑交易成本时,双均线的来回穿插作为一种信号,可能还是有一定的预测效果(尽管也好不到哪里去)。

 
 

策略参数优化

 

实盘用可能是没希望了,但不妨碍我们想来折腾一下,看看在股指1分钟数据上到底怎样的均线组合能起到最好的效果,毕竟之前的3和80两个参数纯粹只是拍脑袋的结果。

 

假设我们想要看看fast_window,从2到20(步进2),slow_window,从20到100(步进10),参数分别两两组合出来的回测效果,看看能不能找到更好的均线组合。

 

比较傻的方法就是人工操作,每次将两个参数输入到回测的参数对话框里,然后运行等结果,再把结果记录在Excel表格里最后用来做排序比较。但对于这种机械重复的劳动,电脑比起人的效率要高得多得多,在本质上我们就是分别遍历两个参数的各种可能排列组合,然后针对每组组合,跑完回测并记录其中的关键结果,也就是所谓的“参数优化”。
 

CtaBacktester模块已经内置了策略参数优化的功能,点击左侧下方的“参数优化”按钮:

 

description

 

在弹出的对话框中,我们把之前的参数想法输入进去:

 

  • 目标函数就选择最简单的总收益率
  • fast_window,开始数值为2,结束数值为20,每次步进为2(即2、4、6、8、10...)
  • slow_window,开始数值为20,结束数值为100,每次步进为10(即20、30、40、50、60...)

 

点击“多进程优化”,使用暴力穷举算法(Brute-Force Algorithm),同时运行多个并行的Python进程,充分利用CPU的核心数量来加快优化速度。

 

优化完成后,日志信息中会有相应的提示,同时左下角的“优化结果”按钮会亮起:

 

description

 

点击后看到每组参数组合,所对应的目标函数结果:

 

description

 

效果最好的是fast_window为18,slow_window为90,带入到策略回测中运行后:

 

description

 

参数优化大法好!!!

 

可能在前面铺垫了那么多的情况下(过度拟合风险、双均线信号普通、移除了滑点手续费),你不见得还会脑子里蹦出这么一句话,但不可否认参数优化后的效果提升非常明显。

在本篇教程的最后,希望提醒大家的是:尽管看起来一路点点鼠标就能搞出个漂亮的资金曲线了,但实际上量化策略回测和优化过程中充满了各种各样的地雷。

 

到目前为止我们所讲述的只是最最基础的操作方法,还远没有涉及到实践经验的内容,这块要么大家用自己的真金白银在交易中慢慢积累,另一个成本更低的选择当然就是关注我们后续的进阶教程了!

 

了解更多知识,请关注vn.py社区公众号。
description

快速入门系列已经到了第6篇,终于要开始接触编程实操的内容了。这篇教程中的内容,假设你对于Python语言的开发已经有了一定的基础掌握:

 

  • 了解Python的数据结果
  • 理解面向对象编程的概念
  • 能使用控制语句来表述逻辑

 

description

 

如果没有也没关系,推荐一本快速入门的书《笨方法学Python3》(Learn Python the Hard Way),也是我自己多年前从完全0基础的小白第一次学习Python的敲门砖,50多堂课从头到尾敲一遍,只要不偷懒包敲包会,不会来找我。

 
 

准备IDE工具

 

用电脑写文章通常需要Word,同样写程序代码也需要专用的工具,这种工具就叫做IDE,全称Integrated Development Environment,中文名叫做“集成开发环境”。

 

IDE提供了一整套包括代码编写、调试运行、版本控制、管理界面等等写程序时所必须的工具环境,接下来教程里我们选择使用的是Visual Studio Code(简称VS Code),由微软推出的编程专用开源编辑器(然后被热心的开源社区加上各种插件打造成了超级IDE)。

 
前往VSCode首页,点击Download绿色图标下载后,一路傻瓜安装,运行后看到下述界面:

 

description
 

 

在左侧导航栏顶部的5个按钮中,点击最下方的按钮进入安装扩展插件的页面,在搜索框中输入Python回车,找到由微软官方推出的Python插件:

 
description

 

点击上图红框中的绿色安装按钮,会自动在后台完成插件的下载安装启动,至此就已经准备好了我们的IDE。

 

尽管这里我们推荐使用VS Code,但如果你已经有了常用的IDE工具,不管是PyCharm、WingIDE、Vim还是Visual Studio,都可以用来完成后续的策略代码开发工作,所以直接用就好。假设过程中遇到了某些难以解决的问题,可以再换回到VS Code,本系列教程中的操作保证都完全可用。
 

 

创建策略文件

 

首先要接触的是一个用户目录的概念,即任何操作系统默认用来存放当前登录的用户运行程序时缓存文件的目录,假设你的登录用户名为client,那么:

 

  • Windows下:C:\Users\client\
  • Linux或Mac下:/home/client/

 
上述即为最常用的用户目录路径,注意只是常用情况,如果你的系统做了特殊配置修改可能不同。

 

VN Trader默认的运行时目录即为操作系统用户目录,启动后会在用户目录下创建.vntrader文件夹用于保存配置和临时文件(有时遇到奇怪的bug,删除该文件夹后重启就能解决)。

 

同时CTA策略模块(CtaStrategyApp)在启动后,也会扫描位于VN Trader运行时目录下的strategies文件夹来加载用户自定义的策略文件,以Windows为例:C:\Users\client\strategies。注意strategies文件夹默认是不存在的,需要用户自行创建。

 

进入strategies目录后,新建我们的第一个策略文件:demo_strategy.py,然后用VS Code打开。
 

 

定义策略类

 
新建的策略文件打开后,内部空空如也,此时我们开始往其中添加代码,遵循行业传统,这里也同样选择用傻瓜的双均线策略作为演示:

 

from vnpy.app.cta_strategy import (
    CtaTemplate,
    StopOrder,
    TickData,
    BarData,
    TradeData,
    OrderData,
    BarGenerator,
    ArrayManager,
)


class DemoStrategy(CtaTemplate):
    """演示用的简单双均线"""

    # 策略作者
    author = "Smart Trader"

    # 定义参数
    fast_window = 10
    slow_window = 20

    # 定义变量
    fast_ma0 = 0.0
    fast_ma1 = 0.0
    slow_ma0 = 0.0
    slow_ma1 = 0.0

    # 添加参数和变量名到对应的列表
    parameters = ["fast_window", "slow_window"]
    variables = ["fast_ma0", "fast_ma1", "slow_ma0", "slow_ma1"]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)

        # K线合成器:从Tick合成分钟K线用
        self.bg = BarGenerator(self.on_bar)

        # 时间序列容器:计算技术指标用
        self.am = ArrayManager()

    def on_init(self):
        """
        当策略被初始化时调用该函数。
        """
        # 输出个日志信息,下同
        self.write_log("策略初始化")

        # 加载10天的历史数据用于初始化回放
        self.load_bar(10)

    def on_start(self):
        """
        当策略被启动时调用该函数。
        """
        self.write_log("策略启动")

        # 通知图形界面更新(策略最新状态)
        # 不调用该函数则界面不会变化
        self.put_event()

    def on_stop(self):
        """
        当策略被停止时调用该函数。
        """
        self.write_log("策略停止")

        self.put_event()

    def on_tick(self, tick: TickData):
        """
        通过该函数收到Tick推送。
        """
        self.bg.update_tick(tick)

    def on_bar(self, bar: BarData):
        """
        通过该函数收到新的1分钟K线推送。
        """
        am = self.am

        # 更新K线到时间序列容器中
        am.update_bar(bar)

        # 若缓存的K线数量尚不够计算技术指标,则直接返回
        if not am.inited:
            return

        # 计算快速均线
        fast_ma = am.sma(self.fast_window, array=True)
        self.fast_ma0 = fast_ma[-1]     # T时刻数值
        self.fast_ma1 = fast_ma[-2]     # T-1时刻数值

        # 计算慢速均线
        slow_ma = am.sma(self.slow_window, array=True)
        self.slow_ma0 = slow_ma[-1]
        self.slow_ma1 = slow_ma[-2]

        # 判断是否金叉
        cross_over = (self.fast_ma0 > self.slow_ma0 and
                      self.fast_ma1 < self.slow_ma1)

        # 判断是否死叉
        cross_below = (self.fast_ma0 < self.slow_ma0 and
                       self.fast_ma1 > self.slow_ma1)

        # 如果发生了金叉
        if cross_over:
            # 为了保证成交,在K线收盘价上加5发出限价单
            price = bar.close_price + 5

            # 当前无仓位,则直接开多
            if self.pos == 0:
                self.buy(price, 1)
            # 当前持有空头仓位,则先平空,再开多
            elif self.pos < 0:
                self.cover(price, 1)
                self.buy(price, 1)

        # 如果发生了死叉
        elif cross_below:
            price = bar.close_price - 5

            # 当前无仓位,则直接开空
            if self.pos == 0:
                self.short(price, 1)
            # 当前持有空头仓位,则先平多,再开空
            elif self.pos > 0:
                self.sell(price, 1)
                self.short(price, 1)

        self.put_event()

    def on_order(self, order: OrderData):
        """
        通过该函数收到委托状态更新推送。
        """
        pass

    def on_trade(self, trade: TradeData):
        """
        通过该函数收到成交推送。
        """
        # 成交后策略逻辑仓位发生变化,需要通知界面更新。
        self.put_event()

    def on_stop_order(self, stop_order: StopOrder):
        """
        通过该函数收到本地停止单推送。
        """
        pass

 

在文件头部我们的一系列import中,最重要的就是CtaTemplate,这是我们开发CTA策略所用的策略模板基类,策略模板提供了一系列以on_开头的回调函数,用于接受事件推送,以及其他主动函数用于执行操作(委托、撤单、记录日志等)。

 

所有开发的策略类,都必须继承CtaTemplate基类,然后在需要的回调函数中实现策略逻辑,即当某件事情发生时我们需要执行的对应操作:比如当收到1分钟K线推送时,我们需要计算均线指标,然后判断是否要执行交易。

 
 

设置参数变量

 
所有的量化交易策略必然都会涉及到参数和变量这两个和数值相关的概念。

 

参数是策略内部的逻辑算法中用来控制结果输出的一些数值,在策略类中需要定义出这些参数的默认数值:

 

# 定义参数
fast_window = 10
slow_window = 20

 

定义完后,还需要参数的名称(字符串)添加到parameters列表中:

 

parameters = ["fast_window", "slow_window"]

 
这一步操作是为了让系统内的策略引擎,得以知道该策略有哪些参数,并在初始化策略时弹出相应的对话框让用户填写,或者在命令行模式下直接将配置字典中对应的key的value赋值到策略变量上。

 

变量则是策略内部的逻辑算法在执行过程中用来缓存中间状态的一些数值,在策略类中同样需要定义出这些变量的默认数值:

 

# 定义变量
fast_ma0 = 0.0
fast_ma1 = 0.0
slow_ma0 = 0.0
slow_ma1 = 0.0

 

定义完后,还需要变量的名称(字符串)添加到variables列表中:

 

variables = ["fast_ma0", "fast_ma1", "slow_ma0", "slow_ma1"]

 

和参数类似,这一步操作是为了让系统内的策略引擎,得以知道该策略有哪些变量,并在GUI图形界面上更新策略状态时将这些变量的最新数值显示出来,同时在保存策略运行状态到缓存文件中时将这些变量写入进去(实盘中每天关闭策略时会自动缓存)。

 

需要注意的是:

 

  1. 无论变量还是参数,都必须定义在策略类中,而非策略类的init函数中;
  2. 参数和变量,均只支持Python中的四种基础数据类型:str、int、float、bool,使用其他类型会导致各种出错(尤其注意不要用list、dict等容器);
  3. 如果在策略逻辑中,确实需要使用list、dict之类的容器用于数据缓存,请在init函数中创建这些容器

 

 

交易逻辑实现

 

上面已经提到,在vn.py的CTA策略模块中,所有的策略逻辑都是由事件来驱动的。对于事件,举例来说:

 

  • 策略被执行初始化操作,会收到on_init函数的调用,此时可以加载历史数据,来进行技术指标的初始化运算
  • 新一根1分钟K线走完时,会收到on_bar函数的调用,参数为该根K线对象的BarData
  • 策略发出的委托,状态发生变化时,会收到on_order函数的调用,参数为该委托的最新状态OrderData

 

对于最简单的双均线策略的DemoStrategy来说,我们不用关注委托状态变化和成交推送之类的细节,只需要在收到K线推送时(on_bar函数中)执行交易相关的逻辑判断即可。

 

每次新的一根K线走完时,策略会通过on_bar函数收到该根K线的数据推送。注意此时收到的数据只有该K线,但大部分技术指标计算时都需要过去N个周期的历史数据。

 

所以为了计算均线技术指标,我们需要使用一个叫做时间序列容器ArrayManager的对象,用于实现了K线历史的缓存和技术指标计算,该对象的创建,在策略的init函数中:

 

# 时间序列容器:计算技术指标用
self.am = ArrayManager()

 

在on_bar函数的逻辑中,第一步需要将K线对象推送到该时间序列容器中:

 

# 纯粹为了后续可以少写一些self.
am = self.am

# 更新K线到时间序列容器中
am.update_bar(bar)

# 若缓存的K线数量尚不够计算技术指标,则直接返回
if not am.inited:
    return

 

为了满足技术指标计算的需求,我们通常需要最少N根K线的缓存(N默认为100),在推送进ArrayManager对象的数据不足N之前,是无法计算出需要的技术指标的,对于缓存数据是否已经足够的判断,通过am.inited变量可以很方便的判断,在inited变为True之前,都应该只是缓存数据而不进行任何其他操作。

 

当缓存的数据量满足需求后,我们可以很方便的通过am.sma函数来计算均线指标的数值:

 

# 计算快速均线
fast_ma = am.sma(self.fast_window, array=True)
self.fast_ma0 = fast_ma[-1]     # T时刻数值
self.fast_ma1 = fast_ma[-2]     # T-1时刻数值

# 计算慢速均线
slow_ma = am.sma(self.slow_window, array=True)
self.slow_ma0 = slow_ma[-1]
self.slow_ma1 = slow_ma[-2]

 

注意这里我们传入了可选参数array=True,因此返回的fast_ma为最新移动平均线的数组,其中最新一个周期(T时刻)的移动均线ma数值可以通过-1下标获取,上一个周期(T-1时刻)的ma数值可以通过-2下标获取。

 

有了快慢两根均线在T时刻和T-1时刻的数值后,我们就可以进行双均线策略的核心逻辑判断,即是否发生了均线金叉或者死叉:

 

# 判断是否金叉
cross_over = (self.fast_ma0 > self.slow_ma0 and
              self.fast_ma1 < self.slow_ma1)

# 判断是否死叉
cross_below = (self.fast_ma0 < self.slow_ma0 and
               self.fast_ma1 > self.slow_ma1)

 
所谓的均线金叉,是指T-1时刻的快速均线fast_ma1低于慢速均线slow_ma1,而T时刻时快速均线fast_ma0大于或等于慢速均线slow_ma10,实现了上穿的行为(即金叉)。均线死叉则是相反的情形。

 

当金叉或者死叉发生后,则需要执行相应的交易操作:

 

# 如果发生了金叉
if cross_over:
    # 为了保证成交,在K线收盘价上加5发出限价单
    price = bar.close_price + 5

    # 当前无仓位,则直接开多
    if self.pos == 0:
        self.buy(price, 1)
    # 当前持有空头仓位,则先平空,再开多
    elif self.pos < 0:
        self.cover(price, 1)
        self.buy(price, 1)

# 如果发生了死叉
elif cross_below:
    price = bar.close_price - 5

    # 当前无仓位,则直接开空
    if self.pos == 0:
        self.short(price, 1)
    # 当前持有空头仓位,则先平多,再开空
    elif self.pos > 0:
        self.sell(price, 1)
        self.short(price, 1)

 
对于简单双均线策略来说,用于处于持仓的状态中,金叉后拿多仓,死叉后拿空仓。

 

所以当金叉发生时,我们需要检查当前持仓的情况。如果没有持仓(self.pos == 0),说明此时策略刚开始交易,则应该直接执行多头开仓操作(buy)。如果此时已经持有空头仓位(self.pos , 0),则应该先执行空头平仓操作(cover)然后同时立即执行多头开仓操作(buy)。为了保证成交(简化策略),我们在下单时选择了加价的方式来实现(多头+5,空头-5)。
 

注意尽管这里我们选择使用双均线策略来做演示,但在实践经验中简单均线类的策略效果往往非常差,千万不要拿来跑实盘,也不建议在此基础上进行扩展开发,毕竟在豆腐渣工程上建什么都还是豆腐渣......

 

 

实盘K线合成

 

DemoStrategy的双均线交易逻辑,可以通过超价买卖的方式来保证成交,从而忽略撤单、委托更新、成交推送之类更加细节的事件驱动逻辑。
 

但在实盘交易时,任何交易系统都只会推送最新的Tick更新数据,而不会有完整的K线推送,因此用户需要自行在本地完成Tick到K线的合成逻辑。
 

这块vn.py也提供了完善的K线合成工具BarGenerator,用户只需在策略的init函数中创建实例:

 

# K线合成器:从Tick合成分钟K线用
self.bg = BarGenerator(self.on_bar)

 
其中BarGenerator对象创建时,传入的参数(self.on_bar)是当1分钟K线走完时触发的回调函数。

 

在实盘策略收到最新的Tick推送时,我们只需要将TickData更新到BarGenerator中:

 

def on_tick(self, tick: TickData):
    """
    通过该函数收到Tick推送。
    """
    self.bg.update_tick(tick)

 
当BarGenerator发现某根K线走完时,会将过去1分钟内的Tick数据合成的1分钟K线推送给策略,自动调用策略的on_bar函数,执行在上一节中讲解的交易逻辑。

 
了解更多知识,请关注vn.py社区公众号。
description

由于一些历史原因,A股证券市场在几年前就关闭了类似期货这样的开放式API接入(几乎0门槛,只需要一定开发技术),现阶段可行的方案主要是“三方采购”的接入模式,其门槛大致为:

 

  • 专业投资者认证:自然人需要不低于300万金融资产,且具有1年以上投资经历(证券、期货、基金、黄金、外汇均可),法人则包括各类持牌机构(券商、期货、公募、私募等);
  • 券商三方系统采购:基于券商提供的API接口完成交易程序的开发后,由券商进行安全合规性检查,通过后与客户签署采购协议(该程序成为券商官方提供的程序之一,尽管只提供给该客户自己使用);
  • 券商内网托管运行:完成采购的交易程序,在实盘交易时必须运行在托管于券商内网的服务器上(独立或者虚拟均可),客户在盘中只能通过远程桌面或者其他控制客户端来访问管理,禁止以任何方式向外网发送行情和交易的数据。

 

监管的核心目标(或者说红线)是杜绝配资的可能。

 
中泰证券自主开发的XTP量化极速证券柜台,无论速度性能还是每日交易量,都是目前国内领先的证券柜台之一,同时其“三方采购”流程比较标准化,本篇教程就主要针对XTP进行讲解。

 
 

准备账号

 

XTP目前提供三类账户:

 

  • 现货账户:股票、ETF、债券、回购等
  • 两融账户:融资融券
  • 期权账户:ETF期权

 

测试上我们就用简单的现货账户,前往中泰证券XTP的主页,点击右上角的“注册”按钮申请测试账号:

 

description

 
注册完成后,回到主页点击右上角的“登录”按钮进行登录,完成后再点击右上角的“测试账号”,进入申请页面:
 

description

 

这里请根据实际情况进行填写,提交申请后一个工作日,将会收到测试环境的账号、密码、服务器等信息的邮件。

 

 

接口登录

 
接下来照着入门1中的方法,启动VN Trader Pro,只加载“XTP”接口。

 
进入主界面后,点击菜单栏的“系统”->“连接XTP”,看到对话框:

 
description

 

各个字段的填写如下:

 

  • 账号:测试账号(邮件收到)
  • 密码:测试密码(邮件收到)
  • 客户号:填1(若同时多点登录,则每个点需要不同的客户号)
  • 行情地址:120.27.164.138(可能会变,以邮件收到的为准)
  • 行情端口:6002(可能会变,以邮件收到的为准)
  • 交易地址:120.27.164.69(可能会变,以邮件收到的为准)
  • 交易端口:6002(可能会变,以邮件收到的为准)
  • 行情协议:选TCP,实盘环境中可选UDP行情
  • 授权码:交易程序的授权编码(邮件收到)

 

XTP的测试环境部署在阿里云上,可以直接通过互联网连接,无需把机器放到托管环境内。

 
以上都填好后,点击“连接”按钮开始登录XTP服务器以及相关初始化操作,在右下角的日志监控组件中,可以看到初始化相关的日志信息输出:

 
description

 

登录初始化过程中有任何异常情况,日志信息中都会看到相应的文字输出,可以根据内容自行排查。看到两个交易所的合约信息查询成功这条日志后,说明已经成功完成了初始化操作。

 

 

查看合约

 

点击菜单栏“帮助”->“查询合约”,或者左侧功能导航栏的倒数第二个放大镜按钮,打开合约查询对话框:

 
description

 

点击右上角的查询按钮,显示当前VN Trader内部已连接的交易接口(XTP)的上支持的所有可交易合约。

 

几个需要关注的字段:

 

  • 合约代码symbol:该合约在某家交易所的唯一标识
  • 交易所代码exchanage:该交易所在VN Trader内的唯一标识
  • 本地代码vt_symbol:由合约代码以及交易所代码共同组成,代表该合约在VN Trader内的唯一标识符,需要交易所代码是因为跨交易所的代码可能存在重复,比如000001在上交所代表的是上证指数,在深交所代表的则是平安银行;
  • 价格跳动pricetick:意味着交易委托时价格的最小变动单位,如果精度不对则会造成委托失败

 

 

订阅行情

 

在上一步中找到自己想要订阅行情的合约信息后,则可以在VN Trader界面左上角的交易组件框中,选择交易所、接口后,在代码框中输入合约代码后回车,即可订阅行情。

 

当收到最新行情Tick推送时,会显示在下方的深度报价中,国内证券市场Level 1行情的Tick推送的更新频率是每3秒1笔。

 

description
 

所有已订阅的行情信息,都会显示在右侧顶部的行情监控组件中,方便后续快速执行手动交易:

 
 

交易下单

 

知道最新行情的价格在哪里后,就可以进行买卖下单:

 
description

 

  • 选择交易方向:要买(多)还是要卖(空)
  • 开平留空不用选(两融和期权交易则需要选)
  • 选择价格类型:XTP接口支持限价、市价
  • 输入价格和数量后,点击“委托”按钮即可发出交易请求

 

 

委托成交

 
委托请求提交后,则会返回相应的委托回报信息,显示在委托组件中,显示当前这笔委托请求的最新状态:
 

description

 

注意委托组件分为两个:

 

  • “活动”:只显示当前处于可撤状态(提交中、未成交、部分成交)的委托信息
  • “委托”:显示所有的委托信息(包括可撤委托)

 

两个组件中,对于处于可撤状态的委托,均可双击该笔委托的单元格来实现撤单的功能(鼠标放置其上时会有文字提示)。或者也可以通过交易组件上的单击“全撤”按钮,来实现一键全撤VN Trader内当前所有可撤委托。

 
description

 

当委托发生成交后,VN Trader会收到成交推送的数据,并显示在成交监控组件中,用户可以通过每笔成交的委托号来实现和对应委托的映射。注意在实盘中,每笔委托可能和多笔反向来自其他投资者的委托发生成交,即一笔委托对应有多笔成交记录。

 
 

资金持仓

 

委托成交后,XTP账户的资金情况将会发生变化:

 

description

 

VN Trader底部中间的资金监控组件的数据,默认以每4秒一次的频率查询刷新,所以某一时间点你看到数据可能并非最新情况。

 

description

 

实盘交易

 

当你已经对XTP的仿真测试环境足够熟悉后,可能已经做好了使用XTP柜台进行实盘交易的准备,接下来的步骤是:

 

  1. 选择某家营业部开立XTP实盘账户;
  2. 完成专业投资者身份认证;
  3. 联系客户经理,进行“三方采购”流程;
  4. 将你开发完成的程序(或者直接用vn.py)提交给中泰进行检测;
  5. 检测通过后,中泰会提供实盘账号信息,以及内网托管服务器环境(一般是虚拟机,也可自行购买实体机器);
  6. 开始实盘交易吧!

 

了解更多知识,请关注vn.py社区公众号
description

IB,全称Interactive Brokers,中文名盈透证券,是全球公认第一的全品种零售经纪商,提供的金融品种包括:

 

  • 股票:美股、港股、欧股、A股(沪港通)等
  • 期货:CME、LME、HKFE、SGX等(没有国内期货)
  • 期权:上述股票和期货交易所的所有期权品种
  • 固收:常规交易所的债券以及非常规银行间的债券(打电话)
  • 外汇:纯ECN交易模式的杠杆外汇(银行间做市商)
  • 贵金属现货:和外汇一样的银行间杠杆市场

 

加上已有十多年历史早已成为行业标准的交易接口IB API,基本上说起外盘交易通道,IB就是当仁不让的第一选择了。

 

但需要额外强调的是,针对某些特定的交易市场或者需求,IB未必是最终的选择,但一定是比较方便的。

 
 

准备账号

 

和其他接口不同的是,IB一共有三套账户体系:

 

  • Live Trading:实盘账户
  • Paper Trading:仿真账户
  • Demo Trading:演示账户

 
其中Paper Trading和Live Trading高度类似,提供接近实盘交易行情以及撮合成交(像CTP的第一套环境),而Demo Trading则只提供历史行情的回放,满足交易接口测试的需求(像CTP的第二套7X24小时环境)。

 

接下来的内容,我们以Demo Trading为例来讲解,首先前往IB官网下载TWS(Trader Work Station),一般下载最新版本的TWS Latest。

 

安装后启动,看到以下界面:

 

description

 

点击底部的“Return to the demo”按钮,打开演示账户测试界面:

 

description

 
输入邮箱地址后,点击“Try Demo”按钮,即可登录进入演示账户的TWS界面:

 

description

 

点击右上角的齿轮图标(从右往左导数第五个),打开TWS配置对话框,在General页面,将“Current Language”设为English后,关闭并重启TWS。
 
description

 

然后进入到“API”->“Settings”来进行API接入相关的配置:

 

  • 勾选“Enable ActiveX and Socket Clients”,允许外部程序接入TWS;
  • 勾选“Download open orders on connection”,在外部程序连上TWS时获取所有未成交委托信息;
  • 记录你的端口号“Socket Port”(我这里是7497),将用于后续从VN Trader发起到TWS的连接;
  • 点击“OK”按钮,保存配置

 

 

接口登录

 

和其他大多数API接口直连经纪商服务器的模式不同,IB接口连接的是位于本地的交易客户端TWS,再由TWS负责连接到IB的服务器,因此在启动VN Trader前需要先启动TWS完成账户登录。

 

接下来照着入门1中的方法,启动VN Trader Pro,只加载“盈透证券”接口。

 

进入主界面后,点击菜单栏的“系统”->“连接IB”,看到对话框:

 
description

 

各个字段的填写如下:

 

  • TWS地址:127.0.0.1(本机IP地址)
  • TWS端口:7497(之前步骤)
  • 客户号:填写1即可(除非你有多个进程连接TWS)

 

需要强调的是,和CTP等其他API从本地交易程序直连远端服务器的模式不同,IB API的连接是从本地交易程序(如VN Trader),到本地的客户端TWS,背后由TWS负责维护从本地到IB服务器的连接。

 

以上都填好后,点击“连接”按钮开始登录CTP服务器以及相关初始化操作,在右下角的日志监控组件中,可以看到初始化相关的日志信息输出:
 

description

 

登录初始化过程中有任何异常情况,日志信息中都会看到相应的文字输出,可以根据内容自行排查。看到“服务器时间”这条日志后,说明已经成功完成了初始化操作。

 
后续依旧会有一系列的信息通知,这些主要和IB账户相关的数据权限配置有关(说白了就是告诉你那些实时行情你没花钱买,现在获取不到),Demo Trading没有特别好的解决办法,推荐还是入金后申请Paper Trading。

 
 

查看合约

 

由于盈透证券支持的交易所以及合约资源实在太多,IB API无法提供和CTP接口类似的全合约查询功能。用户需要前往盈透证券的官方网站,在其交易产品列表页面,自行寻找相关的合约信息
 

注意在VN Trader中,针对IB接口所使用的交易合约代码,是该合约在IB系统内的唯一标识符Conid,全称Contract Identifier,注意Conid为一串纯数字,不要和Symbol或者Description Name搞混。

 
 

订阅行情

 

在上一步中找到自己想要订阅行情的合约信息后,则可以在VN Trader界面左上角的交易组件框中,选择交易所、接口后,在代码框中输入合约代码后回车,即可订阅行情。

 

当收到最新行情Tick推送时,会显示在下方的深度报价中,IB接口的Tick推送的最高更新频率是每秒4笔。

 
description

 

订阅行情后,IB接口也会自动查询该合约的相关信息,此时再打开“帮助”->“合约查询”组件,即可看到该合约:

 
description

 

所有已订阅的行情信息,都会显示在右侧顶部的行情监控组件中,方便后续快速执行手动交易:

 
description

 

需要注意IB上的行情数据,除少量免费提供外(外汇、贵金属),其他大部分都需要在IB官网的后台管理系统中付费购买后,才能在VN Trader中订阅使用。如果行情订阅失败(Conid填错、没有购买等),在左下角的日志组件中也会有相应的内容输出,方便排查。

 

 

交易下单

 

知道最新行情的价格在哪里后,就可以进行买卖下单:

 
description

 

  • 选择交易方向:要买(多)还是要卖(空)
  • 开平留空不用选
  • 选择价格类型:IB接口支持限价、市价
  • 输入价格和数量后,点击“委托”按钮即可发出交易请求

 

 

委托成交

 

委托请求提交后,则会返回相应的委托回报信息,显示在委托组件中,显示当前这笔委托请求的最新状态:

 
description

 

注意委托组件分为两个:

 

  • “活动”:只显示当前处于可撤状态(提交中、未成交、部分成交)的委托信息
  • “委托”:显示所有的委托信息(包括可撤委托)

 

两个组件中,对于处于可撤状态的委托,均可双击该笔委托的单元格来实现撤单的功能(鼠标放置其上时会有文字提示)。或者也可以通过交易组件上的单击“全撤”按钮,来实现一键全撤VN Trader内当前所有可撤委托。

 

当委托发生成交后,VN Trader会收到成交推送的数据,并显示在成交监控组件中,用户可以通过每笔成交的委托号来实现和对应委托的映射。注意在实盘中,每笔委托可能和多笔反向来自其他投资者的委托发生成交,即一笔委托对应有多笔成交记录。

 
 

资金持仓

 
委托成交后,账户的资金情况将会发生变化,IB针对每个币种的资金提供独立的账号,可以通过账号名后缀来判断,注意其中的BASE是你的IB账户的基础币种账号。

 
description
 

IB接口提供实时推送更新的资金和持仓数据,在持仓方向上也采取净仓模式来计算:

 
description

 

 

实盘交易

 
当你已经对IB的Demo Trading测试环境足够熟悉后,可以开始用Paper Trading来进行仿真交易,或者使用Live Trading进行实盘交易,只需要在TWS登录的时候选择对应的账号即可。

 

除了TWS外,IB针对API交易还提供了一套更加轻量级的客户端IB Gateway,只有简单的GUI界面用于显示日志,在资源占用和延时性能方面更有优势。
 

了解更多知识,请关注vn.py社区公众号

description

装好了运行环境,下一步就可以直接上手开始交易了。

 

目前vn.py已经支持了市面上几乎所有金融产品的交易:期货、股票、期权、外汇。针对每种产品,可能又有多个不同的交易接口可以选择,有的门槛低,有的速度快,有的功能强大。

 

考虑到这个系列是入门教程,接下来的三篇将会选择常见的接口来讲解如何使用vn.py:

 

  • 国内期货:上期技术CTP接口
  • 海外市场:盈透证券IB接口
  • A股证券:中泰证券XTP接口

 
 

准备账号

 

上期技术官方运营了一套期货仿真交易环境SimNow,提供和实盘环境一致的行情以及交易撮合规则,现在已经是做各种CTP测试交易的首选了。

 

打开SimNow官网,点击右上角的“注册账号”,填写一些基础信息完成注册。整体流程十分傻瓜,但有隐藏的两个坑需要注意:

 

  1. 手机号请使用移动或者联通的号码,其他运营商很可能收不到验证码短信(亲测小米卡就死活收不到)
  2. 注册时间,请选择白天的期货交易时段,即上午9:00-11:30,下午13:00-15:00,其他时段注册系统可能各种抽风:图片验证码死活不对、注册按钮点不了、网页刷不出来......(同样亲测多次想砸电脑)

 

如果不幸遇到以上两点以外的坑,请尝试通过下述电话或者QQ联系客服解决:

 

description

 

注册完成后,回到SimNow首页点击点右上角的“投资者登录”,输入手机号和密码登录进去:

 

description

 

请牢记上图中的investorId,这才是你的SimNow环境的CTP用户名,而不是登录网站用的手机号!同时CTP密码则就是你登录网站用的密码。

 

接下来需要修改一次密码才能使用API进行交易(没错,刚注册就要改),修改有两种办法:

 

  1. SimNow官网首页右上角,点击“忘记密码”,在页面上通过手机验证码来修改,注意需要修改为一个新的密码;
  2. 下载SimNow环境的快期客户端,用investorId和密码登录进去后,在客户端中修改为新的密码,客户端下载页面:http://www.simnow.com.cn/static/softwareOthersDownload.action。

 

一种办法不行,就换另一种,两种都不行就联系客服小姐姐吧。

 

尽管写下来字数还挺多的,实际操作可能也就5分钟的事情,全部弄完后就可以准备开始交易了。

 
 

接口登录

 

接下来照着入门1中的方法,启动VN Trader Pro,只加载CTP接口就行(注意不要加载CTP测试接口)。

 

进入主界面后,点击菜单栏的“系统”->“连接CTP”,看到对话框:

 

description

 

各个字段的填写如下:

 

  • 用户名:SimNow的investorId
  • 密码:SimNow的密码
  • 经纪商代码:9999
  • 交易服务器:180.168.146.187:10101
  • 行情服务器:180.168.146.187:10111
  • 产品名称:simnow_client_test
  • 授权编码:0000000000000000(16个0)
  • 产品信息:留空不用填

 

其中交易和行情服务器,一共有三组选择,前两组只能在交易时段登录(周一到周五,日盘和夜盘时段),提供和实盘环境一致的行情和撮合:

选择1(对应SimNow第一套第二组)

  • 交易服务器:180.168.146.187:10101
  • 行情服务器:180.168.146.187:10111

 

选择2(对应SimNow第一套第三组)

  • 交易服务器:218.202.237.33:10102
  • 行情服务器:218.202.237.33:10112

 

第三组则是只能在非交易时段登录,提供最近交易时段行情的回放和撮合:

 

**选择3(对应SimNow第二套)
 

  • 交易服务器:180.168.146.187:10130
  • 行情服务器:180.168.146.187:10131

 

其他介绍信息可以查看:http://www.simnow.com.cn/product.action

 
以上都填好后,点击“连接”按钮开始登录CTP服务器以及相关初始化操作,在右下角的日志监控组件中,可以看到初始化相关的日志信息输出:

 
description

 

登录初始化过程中有任何异常情况,日志信息中都会看到相应的文字输出,可以根据内容自行排查。看到“合约信息查询成功”这条日志后,说明已经成功完成了初始化操作。

 

 

查看合约

 

点击菜单栏“帮助”->“查询合约”,或者左侧功能导航栏的导数第二个放大镜按钮,打开合约查询对话框:

 

description

 

点击右上角的查询按钮,显示当前VN Trader内部已连接的交易接口(CTP)的上支持的所有可交易合约。

 

几个需要关注的字段:

 

  • 合约代码symbol:该合约在某家交易所的唯一标识
  • 交易所代码exchanage:该交易所在VN Trader内的唯一标识
  • 本地代码vt_symbol:由合约代码以及交易所代码共同组成,代表该合约在VN Trader内的唯一标识符,需要交易所代码是因为跨交易所的代码可能存在重复,比如000001在上交所代表的是上证指数,在深交所代表的则是平安银行;
  • 价格跳动pricetick:意味着交易委托时价格的最小变动单位,如果精度不对则会造成委托失败

 
 

订阅行情

 

在上一步中找到自己想要订阅行情的合约信息后(或者你本来就知道),则可以在VN Trader界面左上角的交易组件框中,选择交易所、接口后,在代码框中输入合约代码后回车,即可订阅行情。

 

当收到最新行情Tick推送时,会显示在下方的深度报价中,Tick推送的最高更新频率是每秒2笔,如果没有变化变则可能1笔推送都没有。

 

注意国内期货普遍只提供1档买卖价,部分期货公司的上期所和能源交易所品种可以获取到5档买卖价。

 

description

 

注意每个交易所的合约命名规则有所区别:

 

  • 中金所CFFEX:字母部分大写,年份数字为2位,举例IF1908
  • 上期所SHFE:字母部分小写,年份数字为2位,举例rb1910
  • 能源交易所INE:字母部分小写,年份数字为2位,举例sc1910
  • 大商所DCE:字母部分小写,年份数字为2位,举例m1911
  • 郑商所CZCE:字母部分大写,年份数字为1位,举例TA910

 

如果订阅行情时,日志监控输出说找不到合约信息,那么请先检查是否搞对名命名规则。

 

description

 

所有已订阅的行情信息,都会显示在右侧顶部的行情监控组件中,方便后续快速执行手动交易。

 
 

交易下单

 
知道最新行情的价格在哪里后,就可以进行买卖下单:

 
description

 

  1. 选择交易方向:要买(多)还是要卖(空)
  2. 选择交易开平:要开仓还是平仓,对于上期所合约则需要具体选择是平今还是平昨(选错则无法平仓会被拒单)
  3. 选择价格类型:CTP接口支持限价、市价、FAK(Fill-and-Kill)、FOK(Fill-or-Kill)四种委托类型,注意SimNow环境不支持市价单!!!
  4. 输入价格和数量后,点击“委托”按钮即可发出交易请求

 

 

委托成交

 

委托请求提交后,则会返回相应的委托回报信息,显示在委托组件中,显示当前这笔委托请求的最新状态:

 

注意委托组件分为两个:

  • “活动”:只显示当前处于可撤状态(提交中、未成交、部分成交)的委托信息
  • “委托”:显示所有的委托信息(包括可撤委托)

两个组件中,对于处于可撤状态的委托,均可双击该笔委托的单元格来实现撤单的功能(鼠标放置其上时会有文字提示)。或者也可以通过交易组件上的单击“全撤”按钮,来实现一键全撤VN Trader内当前所有可撤委托。

 

description

 

当委托发生成交后,VN Trader会收到成交推送的数据,并显示在成交监控组件中,用户可以通过每笔成交的委托号来实现和对应委托的映射。注意在实盘中,每笔委托可能和多笔反向来自其他投资者的委托发生成交,即一笔委托对应有多笔成交记录。

 
 

资金持仓

 

委托成交后,CTP账户的资金情况将会发生变化,可用资金将会减少,同时整体余额将基于“逐日盯市”的规则变动。

 
description
 

VN Trader中底部中间的资金监控组件的数据,默认以每6秒一次的频率查询刷新,所以某一时间点你看到数据可能并非最新情况。

 

description

 

持仓信息同样也采用6秒刷新的频率,注意对于国内的期货市场,多头和空头的持仓情况分开计算。因此在某一合约上,如上图的rb1910,可能既有多头持仓(上图8手),也有空头持仓(上图3手),双向持仓均会存在各自的保证金占用。

 

同时由于上期所(包括能源交易中心)今昨仓分离的规则,平仓时需要分别发出对应的委托指令,如想要平掉上图中rb1910的8手多头持仓,则需要分别发出平昨7手的指令,加上平今1手的指令。而其他三家交易所则不受此影响,直接选择平仓指令8手即可。

 
 

实盘交易

 

当你已经对SimNow的仿真测试环境足够熟悉后,可能已经做好了使用CTP柜台进行实盘交易的准备。

 

对于CTP实盘交易:

 

  • 用户名和密码,就是你开户后直接拿到的信息
  • 经纪商编号和交易行情服务器地址,可以联系你的客户经理获取
  • 产品名称和授权编码,则需要完成穿透式认证获取了

 

具体的穿透式认证方法请参考这篇:看完这篇,彻底搞定期货穿透式CTP API接入。

 
了解更多知识,请关注vn.py社区公众号。
description

尽管之前在知乎和华尔街见闻上都推出过一些教程,但随着vn.py进入2.0时代,过去的内容可能已经有点落后于项目发展了,接下来将会通过vn.py官方服务号(vnpy-community)逐渐更新一套2.0上的快速入门教程。

 
 

安装VN Studio

 

运行vn.py,第一步需要准备Python环境。再也不用像1.0时代需要折腾半天安装Anaconda、三方模块、MongoDB数据库等等,2.0只有一个步骤安装由vn.py核心团队针对量化交易开发的Python发行版,VN Studio。

 

description

 

打开官网首页,正中央左边的金色按钮就是最新版本VN Studio的下载链接,写本文的时候最新版本是2.0.6,后续随着版本更新可能会变为2.0.7、2.0.8等等,总之认准金色按钮就行。

 

description

 

下载完成后双击运行,会看到一个很常见的软件安装界面,安装目录推荐选择默认的C:\vnstudio,后续我们的教程都会以此目录作为VN Studio的路径,当然也可以根据自己的需求安装到其他目录,然后一路“下一步”完成傻瓜式安装,Done!

 
 

运行VN Station

 

安装完成后,回到桌面上就能看到VN Station的快捷方式(就是这个帅气的黑马头像),注意如果桌面背景偏暗可能看不清,请睁大眼睛仔细查看。

 

description

 

社区有人提了说能不能换个图标颜色解决下,无奈换来换去都不如黑色帅,只能暂时作罢(可能我们垃圾的美术和P图水平才是主要原因)。

 

description

 

双击启动后,将会看到VN Station的登录框。对于首次使用的用户,请点击微信登录后,扫描二维码注册账号,请牢记用户名和密码(同样也用于登录社区论坛,后续使用可以直接输入用户名和密码登录,勾上“保存”勾选框更加方便~)

 

description

 

登录后看到的就是VN Station主界面了,上方区域显示的是目前社区论坛最新的置顶精华主题(目前注册人数刚破4500,每日精华做不到,每周两三篇还是有的),下方的五个按钮则是VN Station提供的量化相关功能按钮:

 

  • VN Trader Lite:一键启动针对国内期货CTA策略的轻量版VN Trader
  • VN Trader Lite:VN Trader Pro:支持灵活配置加载交易接口和策略模块的专业版VN Trader
  • Jupyter Notebook:启动Jupyter Notebook交互式研究环境,
  • 提问求助:打开浏览器访问社区论坛的“提问求助”板块,掉坑了快速提问
  • 更新:傻瓜式更新vn.py和VN Station,按钮平时点不了,只在有更新时才会亮起

 

 

启动VN Trader

 

由于VN Trader Lite是一键式启动无需配置,我们这里就只讲VN Trader Pro。

 

description

 

点击按钮后弹出的第一个对话框,是选择VN Trader运行时目录,这里默认是当前操作系统的用户目录(User Path),比如我这里就是C:\Users\Administrator。

 

在2.0中对Python源代码和运行时文件进行了分离,VN Trader运行过程中所有产生的配置文件、临时文件、数据文件(使用SQLite数据库),都会放置在运行时目录下的.vntrader文件夹中。

 

当VN Trader启动时,会检查当前目录是否存在.vntrader文件夹,若有就直接使用当前目录作为运行时目录,找不到则会使用默认的用户目录(并在其中创建.vntrader文件夹)。

 

大多数情况下,使用操作系统默认用户目录就是最便捷的方案,直接在上述窗口中直接点击右下角的“选择文件夹”按钮,开始配置VN Trader:

 

description

 

在左侧选择需要的底层交易接口,“介绍”一栏中可以看到每个接口所支持的交易品种。注意部分接口存在冲突不能同时使用,下方的说明信息中有写。

 

在右侧选择需要的上层应用模块,同样在“介绍”一栏中可以看到该模块所提供的具体功能。各个上层应用之间并不存在冲突的情况,所以新手不妨全部加载了一个个看看,后续确定自己的交易策略后再按需加载。

 

description

 

点击“启动”按钮后,稍等几秒就会看到上图所示的VN Trader主界面,下面就可以连接登录交易接口,开始执行交易了!

 
 

FAQ

 

vn.py/VN Studio/VN Station/VN Trader,都是干啥的?

 

  • vn.py:开源量化交易框架,以下所有功能的核心底层,注意只有它名字是小写的~
  • VN Studio:针对量化交易专门打包的Python发行版,包含了Python解释器以及一系列量化交易常用的三方库,完整支持vn.py
  • VN Station:用于管理VN Trader以及其他Python量化交易应用的图形化管理工具(帮你省去写脚本或者用命令行的麻烦)
  • VN Trader:vn.py框架中的开箱即用专业量化交易平台,灵活加载各类交易接口(期货、股票、期权、外汇),支持诸多量化交易用(CTA策略、算法交易、脚本策略、行情录制、RPC服务等等)

 
 

VN Studio支持哪些操作系统?

 

VN Studio目前仅提供Windows版本,尽管vn.py是全平台通用的(Windows/Linux/Mac),但Linux/Mac下的安装可以通过脚本一键完成(后续教程将会提供),所以暂时没有提供VN Studio的计划。

 
了解更多知识,请关注vn.py社区公众号
description

在onbar里面,先缓存日K线高开低收,在用列表缓存N日K线。若列表长度>=N, 从开始策略信号逻辑部分

对的。

附:策略信号的bug已修复,夏普从原来1.64提高到2

好问题。的确忘了更新缓存,我去改一下哈

R-Breaker是一种中高频的日内交易策略,这个策略也长期被Future Truth杂志评为最赚钱的策略之一。R-Breaker策略结合了趋势和反转两种交易方式,所以交易机会相对较多,比较适合日内1分钟K线或者5分钟K线级别的数据。

 
 

R-Breaker策略逻辑

 

R-Breaker的策略逻辑由以下4部分构成:

1)计算6个目标价位

根据昨日的开高低收价位计算出今日的6个目标价位,按照价格高低依次是:

  • 突破买入价(Bbreak)
  • 观察卖出价(Ssetup)
  • 反转卖出价(Senter)
  • 反转买入价(Benter)
  • 观察买入价(Bsetup)
  • 突破卖出价(Sbreak)

 

他们的计算方法如下:(其中a、b、c、d为策略参数)

  • 观察卖出价(Ssetup)= High + a * (Close – Low)
  • 观察买入(Bsetup)= Low – a * (High – Close)
  • 反转卖出价(Senter)= b / 2 * (High + Low) – c * Low
  • 反转买入价(Benter)= b / 2 * (High + Low) – c * High
  • 突破卖出价(Sbreak)= Ssetup - d * (Ssetup – Bsetup)
  • 突破买入价(Bbreak)= Bsetup + d * (Ssetup – Bsetup)

 

description

2)设计委托逻辑

趋势策略情况:

  • 若价格>突破买入价,开仓做多;
  • 若价格<突破卖出价,开仓做空;

 

反转策略情况:

  • 若日最高价>观察卖出价,然后下跌导致价格<反转卖出价,开仓做空或者反手(先平仓再反向开仓)做空;
  • 若日最低价<观察买入价,然后上涨导致价格>反转买入价,开仓做多或者反手(先平仓再反向开仓)做多;

 

3)设定相应的止盈止损。

 

4)日内策略要求收盘前平仓。

 

上面是原版R-Breaker策略逻辑,但是使用RQData从2010年至今(即2019年10月)的1分钟沪深300股指期货主力连续合约(IF88)测试,效果并不理想。

 
 

策略逻辑优化

 

实际上R-Breaker策略可以拆分成趋势策略和反转策略。下面分别对这对2种策略逻辑进行优化:

1)趋势策略:

  • 若当前x分钟的最高价>观察卖出价,认为它具有上升趋势,在突破买入价挂上买入开仓的停止单;
  • 若当前x分钟的最低价<观察买入价,认为它具有下跌趋势,在突破卖出价挂上买入开仓的停止单;
  • 开仓后,使用固定百分比移动止损离场;
  • 增加过滤条件:为防止横盘行情导致不断的开平仓,日内每次开仓买入开仓(卖出开仓)委托的价位都比上一次更高(更低);
  • 收盘前,必须平调所持有的仓位。

 

2)反转策略:

  • 若当前x分钟的最高价>观察卖出价,认为它已经到了当日阻力位,可能发生行情反转,在反转卖出价挂上卖出开仓的停止单;
  • 若当前x分钟的最低价>观察买入价,认为它已经到了当日支撑位,可能发生行情反转,在反转买入价挂上买入开仓的停止单;
  • 开仓后,使用固定百分比移动止损离场;
  • 收盘前,必须平调所持有的仓位。

 

其代码实现逻辑如下:

self.tend_high, self.tend_low = am.donchian(self.donchian_window)

if bar.datetime.time() < self.exit_time:

    if self.pos == 0:
        self.intra_trade_low = bar.low_price
        self.intra_trade_high = bar.high_price

        # Trend Condition
        if self.tend_high > self.sell_setup:
            long_entry = max(self.buy_break, self.day_high)
            self.buy(long_entry, self.fixed_size, stop=True)

            self.short(self.sell_enter, self.multiplier * self.fixed_size, stop=True)

        elif self.tend_low < self.buy_setup:
            short_entry = min(self.sell_break, self.day_low)
            self.short(short_entry, self.fixed_size, stop=True)

            self.buy(self.buy_enter, self.multiplier * self.fixed_size, stop=True)

    elif self.pos > 0:
        self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
        long_stop = self.intra_trade_high * (1 - self.trailing_long / 100)
        self.sell(long_stop, abs(self.pos), stop=True)

    elif self.pos < 0:
        self.intra_trade_low = min(self.intra_trade_low, bar.low_price)
        short_stop = self.intra_trade_low * (1 + self.trailing_short / 100)
        self.cover(short_stop, abs(self.pos), stop=True)

# Close existing position
else:
    if self.pos > 0:
        self.sell(bar.close_price * 0.99, abs(self.pos))
    elif self.pos < 0:
        self.cover(bar.close_price * 1.01, abs(self.pos))

 
 

策略效果

 

同样使用10年的1分钟IF88数据进行回测。不过,在展示强化版R-Breaker策略效果前,先分别展示一下拆分后的趋势策略和反转策略。

1)趋势策略:

  • 趋势策略夏普比率1.96,日均成交2.6笔,资金曲线是整体上扬的;
  • 但是在2017~2018年的盘整阶段,具有较大并且持续时间较长的回撤;
  • 这凸显出趋势类策略自身无法规避的缺点:在趋势行情中盈利,意味着震荡行情必然亏损。

description

 

2)反转策略

  • 反转策略夏普比率0.75,日均成交0.4笔,资金曲线缓慢上扬;
  • 但是在2017~2018年的盘整阶段,资金曲线上扬最快,而且这个阶段是最平滑的;
  • 这凸显出反转类策略优点:尽管在趋势行情亏损,在震荡行情必然能盈利。

description

 

综合对比2种策略的日均成交笔数和资金曲线,我们可以知道:

  • 由于趋势策略日均交易笔数较多(2.6笔),它主要负责贡献R-Breaker策略的alpha;
  • 趋势策略的亏损也是主要导致R-Breaker策略亏损的原因,但这时候的亏损由反转策略的盈利来填补。

由于趋势策略和反转策略是互斥的,在某些方面呈现出此消彼长的特点。那么,根据投资组合理论,可以把反转策略看作是看跌期权,买入一定规模的看跌期权来对消非系统性风险,那么组合的收益会更加稳健,即夏普比率更高。

由于趋势策略和反转策略日均成交手数比是2.6:0.4,若它们都只委托1手的话,反转策略的对冲效果微乎其微。

为了方便演示,我们设置趋势策略每次交易1手;反转策略则是3手。然后合成R-Breaker策略。发现夏普比率提高到2,资金曲线整体上扬,而且没有较大且持续时间较长的回撤。

description

 
 

结论

 

R-Breaker策略成功之处在于它并不是纯粹的趋势类策略,它属于复合型策略,它的alpha由2部分构成:趋势策略alpha;反转策略alpha。

这类复合型策略可以看作是轻量级的投资组合,因为它的交易标的只有一个:沪深300股指期货的主力合约。

更复杂的话,可以交易多个标的,如在商品期货做虚拟钢厂套利(同时交易螺纹钢、铁矿石、焦炭),在IF股指期货上做日内CTA策略。考虑到市场容量不同,价差套利能分配更多的资金。这样在价差套利提供稳定收益率基础上,CTA策略能在行情好的时候贡献更多alpha(高盈亏比特征导致的)。

从上面例子可以看出,一个合理的投资组合,往往比单个策略具有更高的夏普比率。因为夏普比率=超额收益/风险。夏普比率高意味着资金曲线非常平滑;这也意味着我们可以有效控制使用杠杆的风险。

当某个投资组合策略夏普足够高,而且策略资金容量允许,交易成本能有效控制等情况下,就可以通过杠杆来提升组合收益了。例如向银行贷款或者发放债券,这时候交易团队是债务人角色,即在承担更大风险同时,追求更到收益。债权人享受利息收益(类无风险收益)。

向公众发产品是另一种增加杠杆的方式,但此时投资组合风险已经转移到了客户这方,交易团队可以享受着管理费收益(类无风险收益)。根据目标客户的不同:

  • 私募基金面向高净值客户,这些客户群体风险承受能力较高;并且私募监管比较放松,能使用的衍生品较多,有提升业绩的自由度。故私募基金除了管理费,更追求业绩提成。
  • 公募基金面向普通群众,他们风险承受能力较低;并且公募监管非常严格,投资约束非常多,提升业绩难度较大。但是公募牌照的稀缺性必然导致该行业是盈利的。如万亿级别的管理规模,其管理费的收益,也不是一般的自营交易公司或者私募基金比得上的。

 
 

附录

 

最后附上策略源代码:

from datetime import time
from vnpy.app.cta_strategy import (
    CtaTemplate,
    StopOrder,
    TickData,
    BarData,
    TradeData,
    OrderData,
    BarGenerator,
    ArrayManager
)
​
​
class RBreakStrategy(CtaTemplate):
    """"""
    author = "KeKe"
​
    setup_coef = 0.25
    break_coef = 0.2
    enter_coef_1 = 1.07
    enter_coef_2 = 0.07
    fixed_size = 1
    donchian_window = 30
​
    trailing_long = 0.4
    trailing_short = 0.4
    multiplier = 3
​
    buy_break = 0   # 突破买入价
    sell_setup = 0  # 观察卖出价
    sell_enter = 0  # 反转卖出价
    buy_enter = 0   # 反转买入价
    buy_setup = 0   # 观察买入价
    sell_break = 0  # 突破卖出价
​
    intra_trade_high = 0
    intra_trade_low = 0
​
    day_high = 0
    day_open = 0
    day_close = 0
    day_low = 0
    tend_high = 0
    tend_low = 0
​
    exit_time = time(hour=14, minute=55)
​
    parameters = ["setup_coef", "break_coef", "enter_coef_1", "enter_coef_2", "fixed_size", "donchian_window"]
    variables = ["buy_break", "sell_setup", "sell_enter", "buy_enter", "buy_setup", "sell_break"]
​
    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super(RBreakStrategy, self).__init__(
            cta_engine, strategy_name, vt_symbol, setting
        )
​
        self.bg = BarGenerator(self.on_bar)
        self.am = ArrayManager()
        self.bars = []
​
    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("策略停止")
​
    def on_tick(self, tick: TickData):
        """
        Callback of new tick data update.
        """
        self.bg.update_tick(tick)
​
    def on_bar(self, bar: BarData):
        """
        Callback of new bar data update.
        """
        self.cancel_all()
​
        am = self.am
        am.update_bar(bar)
        if not am.inited:
            return
​
        self.bars.append(bar)
        if len(self.bars) <= 2:
            return
        else:
            self.bars.pop(0)
        last_bar = self.bars[-2]
​
        # New Day
        if last_bar.datetime.date() != bar.datetime.date():
            if self.day_open:
​
                self.buy_setup = self.day_low - self.setup_coef * (self.day_high - self.day_close)  # 观察买入价
                self.sell_setup = self.day_high + self.setup_coef * (self.day_close - self.day_low)  # 观察卖出价
​
                self.buy_enter = (self.enter_coef_1 / 2) * (self.day_high + self.day_low) - self.enter_coef_2 * self.day_high  # 反转买入价
                self.sell_enter = (self.enter_coef_1 / 2) * (self.day_high + self.day_low) - self.enter_coef_2 * self.day_low  # 反转卖出价
​
                self.buy_break = self.buy_setup + self.break_coef * (self.sell_setup - self.buy_setup)  # 突破买入价
                self.sell_break = self.sell_setup - self.break_coef * (self.sell_setup - self.buy_setup)  # 突破卖出价
​
            self.day_open = bar.open_price
            self.day_high = bar.high_price
            self.day_close = bar.close_price
            self.day_low = bar.low_price
​
        # Today
        else:
            self.day_high = max(self.day_high, bar.high_price)
            self.day_low = min(self.day_low, bar.low_price)
            self.day_close = bar.close_price
​
        if not self.sell_setup:
            return
​
        self.tend_high, self.tend_low = am.donchian(self.donchian_window)
​
        if bar.datetime.time() < self.exit_time:
​
            if self.pos == 0:
                self.intra_trade_low = bar.low_price
                self.intra_trade_high = bar.high_price
​
                if self.tend_high > self.sell_setup:
                    long_entry = max(self.buy_break, self.day_high)
                    self.buy(long_entry, self.fixed_size, stop=True)
​
                    self.short(self.sell_enter, self.multiplier * self.fixed_size, stop=True)
​
                elif self.tend_low < self.buy_setup:
                    short_entry = min(self.sell_break, self.day_low)
                    self.short(short_entry, self.fixed_size, stop=True)
​
                    self.buy(self.buy_enter, self.multiplier * self.fixed_size, stop=True)
​
            elif self.pos > 0:
                self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
                long_stop = self.intra_trade_high * (1 - self.trailing_long / 100)
                self.sell(long_stop, abs(self.pos), stop=True)
​
            elif self.pos < 0:
                self.intra_trade_low = min(self.intra_trade_low, bar.low_price)
                short_stop = self.intra_trade_low * (1 + self.trailing_short / 100)
                self.cover(short_stop, abs(self.pos), stop=True)
​
        # Close existing position
        else:
            if self.pos > 0:
                self.sell(bar.close_price * 0.99, abs(self.pos))
            elif self.pos < 0:
                self.cover(bar.close_price * 1.01, abs(self.pos))
​
       self.put_event()
​
    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

CSV格式数据示例如下

description

 

表头及第一行数据示例

 

交易日,合约代码,交易所代码,合约在交易所的代码,最新价,上次结算价,昨收盘,昨持仓量,今开盘,最高价,最低价,数量,成交金额,持仓量,今收盘,本次结算价,涨停板价,跌停板价,昨虚实度,今虚实度,最后修改时间,最后修改毫秒,申买价一,申买量一,申卖价一,申卖量一,申买价二,申买量二,申卖价二,申卖量二,申买价三,申买量三,申卖价三,申卖量三,申买价四,申买量四,申卖价四,申卖量四,申买价五,申买量五,申卖价五,申卖量五,当日均价,业务日期
20190102,ru1905,,,11280.0000,11290.0000,11305.0000,322472,11280.0000,11280.0000,11280.0000,246,27748800.0000,322468,0.0000,0.0000,12080.0000,10495.0000,0,0,08:59:00,500,11280.0000,10,11290.0000,10,0.0000,0,0.0000,0,0.0000,0,0.0000,0,0.0000,0,0.0000,0,0.0000,0,0.0000,0,112800.0000,20190102

 

可以发现几个问题:

  • 表头是中文的
  • datetime需要由3列数据合成
  • 在非交易时间可能出现垃圾数据,需要剔除(不包含集合竞价发出的那一个Tick数据)

 

基于csv格式的特点,开发载入tick数据到数据库的脚本,脚本功能如下:

  1. 在同一文件夹下,用for循环读取csv文件并载入到数据库
  2. 合成时间字符串,并且最终转换为datetime格式
  3. 通过datetime来判断非交易时间段,剔除垃圾数据的载入

脚本实现代码如下:

import os 
import csv
from datetime import datetime, time

from vnpy.trader.constant import Exchange
from vnpy.trader.database import database_manager
from vnpy.trader.object import TickData


def run_load_csv():
    """
    遍历同一文件夹内所有csv文件,并且载入到数据库中
    """
    for file in os.listdir("."): 
        if not file.endswith(".csv"): 
            continue

        print("载入文件:", file)
        csv_load(file)


def csv_load(file):
    """
    读取csv文件内容,并写入到数据库中    
    """
    with open(file, "r") as f:
        reader = csv.DictReader(f)

        ticks = []
        start = None
        count = 0

        for item in reader:

            # generate datetime
            date = item["交易日"]
            second = item["最后修改时间"]
            millisecond = item["最后修改毫秒"]

            standard_time = date + " " + second + "." + millisecond
            dt = datetime.strptime(standard_time, "%Y%m%d %H:%M:%S.%f")

            # filter
            if dt.time() > time(15, 1) and dt.time() < time(20, 59):
                continue

            tick = TickData(
                symbol="RU88",
                datetime=dt,
                exchange=Exchange.SHFE,
                last_price=float(item["最新价"]),
                volume=float(item["数量"]),
                bid_price_1=float(item["申买价一"]),
                bid_volume_1=float(item["申买量一"]),
                ask_price_1=float(item["申卖价一"]),
                ask_volume_1=float(item["申卖量一"]), 
                gateway_name="DB",       
            )
            ticks.append(tick)

            # do some statistics
            count += 1
            if not start:
                start = tick.datetime

        end = tick.datetime
        database_manager.save_tick_data(ticks)

        print("插入数据", start, "-", end, "总数量:", count)      


if __name__ == "__main__":
    run_load_csv()

 

效果展示

 

description

description

本教程使用MobaXterm、Xubuntu-destop、vnc4server来搭建阿里云ubuntu18.04。尽管过程有些复杂,但跟着图文教程一步一步去做,肯定能成功的。

第一步 安装MobaXterm

MobaXterm是一款增强型远程连接工具,可以轻松地来使用Linux上的GNUUnix命令。这样一来,我们可以不用安装虚拟机来搭建虚拟环境,然后只要通过MobaXterm就可以使用大多数的Linux命令。本教程主要使用MobaXterm的SSH和VNC功能:SSH可以想象成Ubuntu的终端(无图形界面),VNC是Ubuntu的图形操作界面。

首先,从官网下载MobaXterm (https://mobaxterm.mobatek.net/download-home-edition.html)

下载完成后解压安装包,直接双击.exe文件进行安装。

安装完成后,启动MobaXterm,在主界面中单击导航栏左边第一个【Session】进入连接页面。(或者单击点击菜单栏【Sessions】->【new Session】按钮也行)


 

在弹出的新页面Session Settings中,单击导航栏最左边的【SSH】按钮。然后在Basic SSH Settings中输入云服务器的公网IP和账号。其中默认账号是root,输入root账号之前记得把左边小方框的√打上,端口号保留默认的22。然后点击最下方的【OK】按钮。


 

之后会弹出一个新页面:第一次连接,左边的黑框会提示输入云服务器的密码(密码输入输入界面不会有任何反应)。输入完按回车键后,若密码正确,会弹出一个小窗口提示是否保存密码,可以点击【Yes】按钮。


 

出现下图就表示阿里云Ubuntu连接成功:左边是云服务器的文件夹,右边的黑框是命令操作界面。到这里,就完成了使用MobaXterm远程连接云服务器了。当然,这种连接是基于SSH连接的,只能使用阿里云Ubuntu的终端功能,图形化界面还需要另外搭建。

 

为了更好管理界面,需要进行一下重命名:鼠标点击最左边的【Session】选项,显示刚刚创建的SSH连接,鼠标选定该连接,右键选择【Rename session】 会弹出Session settings界面,在里面的Session name可输入新的名字,如DEV_1。
输入完毕,钮点击左下方的【OK】即可改名成功。



 

同理按照上面的操作,输入相同的云服务器的公网IP和账号,创建第二个SSH连接,命名为DEV_2。这样我们就能同时使用2个终端了。

 

第二步 更新软件仓库

Ubuntu系统在安装软件前,需要更新其软件仓储列表。这也是于Windows系统的一大差异。

在Windows下安装软件,我们只需要有.exe文件,然后一直双击【下一步】即可完成。但Linux并非如此:每个Linux的发行版,比如Ubuntu,都会维护一个自己的软件仓库,我们常用的几乎所有软件都在这里面。这里面的软件绝对安全,而且绝对的能正常安装。

故在Ubuntu下,我们需要维护一个源列表,源列表里面都是一些网址信息,这每一条网址就是一个源,这个地址指向的数据标识着这台源服务器上有哪些软件可以安装使用。

所以,为了能够正常安装软件,需要更新软件包管理器里里面的软件列表。在Ubuntu终端输入命令sudo apt-get update,会访问源列表里的每个网址,并读取软件列表,然后保存在本地电脑。

 

第三步 安装Xubuntu-destop

Xfce是一款针对Linux系统的现代化轻型开源桌面环境。其最大的优点是内存消耗小系统资源占用率很低。但是Xfce用起来并不方便,需要另外安装其他包,如支持中文显示,安装火狐浏览器等。

而Xubuntu-destop则免去这个麻烦,它整合了Xfce桌面环境和其他支持包,让用户搭建起来更加方面。安装方法也相对简单,在终端中输入命令sudo apt-get install xubuntu-desktop即可。

 

第四步 安装vnc4server

VNC是一款基于RFB协议的屏幕画面分享及远程操作软件。它最大的特色在于跨平台性,即我们可以用Windows电脑控制Linux系统或苹果的Mac OS,反之亦然。

首先,安装VNC服务器:在终端下输入命令sudo apt-get install vnc4server

 

安装完毕后,在终端输入vncserver运行服务器,首次运行需要设置密码(长度至少是6位)并且二次输入来确认。
VNC连接好后可以看到其默认端口是1(红色方框标识“:1”)。

然而,尽管连接上VNC,不代表客户能够立刻实现图形化界面操作,还需要配置xstartup文件和配置MobaXterm的VNC设置。

首先,用文本编辑器nano打开xstarup文件,在终端输入命令nano ~/.vnc/xstartup,可以看到如下内容。

需要在最后一行 "x-window-manager &" 前面添加一个"#",以注释不再需要的配置。然后在文件最后加入一段配置信息:

sesion-manager & xfdesktop & xfce4-panel &
xfce4-menu-plugin &
xfsettingsd &
xfconfd &
xfwm4 &

修改完毕后,需要保存退出,nano保存退出的方法相对简单:按住“ctrl”和“x”键即可。

 
配置完xstarup后,还需要配置端口信息:先把默认的1号端口会话杀掉,然后生成新的会话,我们改成9号端口(因为1号端口容易被攻击),然后设置图形界面的分辨率位1920x1080。

注意:杀掉原先会话,建立新的会话,在每次启动VNC图形界面前都要做。

vncserver -kill :1
vncserver -geometry 1920x1080 :9

现在回到MobaXterm主界面,单击主页最上边的【Sessions】->【new Session】弹出【Sessions settings】界面,这一次选择【VNC】连接。
【IP address】为阿里云公网IP,【Port】为VNC连接端口,vncserver -geometry 1920x1080 :9意为在9号端口启动,故从默认的5900调整为5909。

在下面的【Bookmark settings】界面对该VNC连接进行重命名为“VNC”。输入完毕,钮点击左下方的【OK】按会弹出基于Xfce图形化界面。


 

第五步 安装IBus中文输入法

IBus中文输入法是Ubutnu常用的中文输入法。安装方法也比较简单,在终端中输入命令sudo apt install ibus-pinyin即可。

使用中文输入法之前需要配置中文语言包:

  • 在菜单栏左上方点击【Applications】->【Settings】->【Language Support】按钮;
  • 第一次会出现提示语言未全部安装,然后点击确认自动安装,成功后会弹出【Language Support】界面;
  • 完成后点击下方的【Install/Remove Languages】按钮,会弹出新的【Installed Languages】界面,勾选【Chinese(Simplified)】,即简体中文,然后点击下方的【Apply】按钮;
  • 最后在【Keyboard input method system】选项选择【IBus】。

 

设置完中文语言包后,进入IBus输入法设置:

  • 在菜单栏左上方点击【Applications】->【Settings】->【IBus Preferences】选项;
  • 第一次会提示IBus-Daemon尚未启动,点击【Yes】按钮进行安装,成功后菜单栏右上角出现一个语言图标,并且弹出【IBus Preferences】界面;
  • 进入【Input Method】界面,点击【Add】来添加中文输入法:选择【Chinese】->【Pinyin】
  • 鼠标右键点击菜单栏上的语言图标【Restart】按钮来重启
  • 然后再次左键点击,点击【Chines-Pinyin】就可以输入了。此时语言图标也变成“拼”字。





 

第六步 安装Vscode

Vscode是微软出品的轻量级代码编辑器,安装和使用起来非常方便。

首先通过火狐浏览器打开百度,搜索“vscode”,第一个就是官网地址。在官网首页安装点击下载.deb版本。

Ubuntu下的Vscode安装包是.deb格式的,需要用使用dpkg命令来安装。进入下载文件所在的目录/root/Downloads,鼠标在空白处右键点击【Open Terminal Here】进入终端,输入下面命令安装Vscode。

sudo dpkg -i code_1.37.0-1565227985_amd64.deb


 

安装完Vscode之后,发现不能正常启动,因为Xfce和Vscode的兼容性问题,在终端中输入下面命令即可正常运行。(命令输入后界面没有任何反应)

sudo sed -i 's/BIG-REQUESTS/_IG-REQUESTS/' /usr/lib/x86_64-linux-gnu/libxcb.so.1

 

第七步 安装Python3.7

阿里云ubuntu18.04 已经安装了Python2.7以及Python3.6,并且默认启动的是Python2.7。

 

而vn.py是基于python3.7。故面临着一个问题:需要把新安装的Python3.7设置位系统默认的Python环境,并且pip3安装库需要对应Python3.7,而不是原来的3.6版本。

这样的Python版本管理起来非常复杂,所以我们使用了MiniConda(Python3.7 64位),它是Anaconda的简化版。安装MiniConda后,会自动设置其Python3.7为系统默认环境,而且提供了conda install的命令代替的pip3来安装其他库。

Miniconda安装也是非常简单的,首先在百度上搜索“miniconda”,第一个就是官网下载地址。打开官网页面后,选择【Linux】系统的Python3.7【64-bit】版本来下载。下载完成后,进入文件所在目录/root/Downloads可以看到.sh格式的Miniconda安装包。鼠标在空白处右击点击【Open Teminal Here】进入终端,然后输入命令bash Miniconda3-latest-Linux-x86_64.sh进行安装。



 

安装完毕后重启MobaXterm后,终端输入Pyhton可以看到Python默认版本已经变成Python3.7了

 

载入json文件报错,先检查cta_strategy_data.json的文件,看看数据结构或者类型是否出现错误;还是报错的话,建议删除该json文件

© 2015-2022 微信 18391752892
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】