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

报错没有plotly, 应该是因为2.1.4图形显示从matplotlib换成了plotly。可以打开cmd执行pip install plotly命令手动安装试试看启动还报不报错。

 

1.原因应该是你用的sl_multiplier太小了,以至于价格太接近close_price,所以总是触到停止单定的价格,继而发出委托。

自己不确定的时候其实可以print看看。
 
description
 
图上没有改你的参数,只是加上了trailing_percent(拿的原策略里的0.8)算出的long_stop进行对比,可以看到,你的atr1算出的long_stop一直比trailing_percent算出的更贴近close_price,所以交易笔数多是正常的。
 
此外,策略跑起来是没有问题的,但是有现成的atr建议不用再加个不一样的window来计算atr。这样增加了复杂度,但不一定能对策略起到实际的提升效果。而且如果之后走到优化,因为本来atrrsi就有很多参数了,再增加新的参数不仅会消耗更多时间也会带来更大的过拟合的风险。如果要改atr止损就像下面一样简单改动就可以了。
 

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


class AtrRsiStrategy_百分比修改为atr(CtaTemplate):
    """"""

    author = "用Python的交易员"

    atr_length = 22
    atr_ma_length = 10
    rsi_length = 5
    rsi_entry = 16
    atr_multiplier = 3
    fixed_size = 1

    atr_value = 0
    atr_ma = 0
    rsi_value = 0
    rsi_buy = 0
    rsi_sell = 0
    intra_trade_high = 0
    intra_trade_low = 0

    parameters = [
        "atr_length",
        "atr_ma_length",
        "rsi_length",
        "rsi_entry",
        "atr_multiplier",
        "fixed_size"
    ]
    variables = [
        "atr_value",
        "atr_ma",
        "rsi_value",
        "rsi_buy",
        "rsi_sell",
        "intra_trade_high",
        "intra_trade_low"
    ]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)
        self.bg = BarGenerator(self.on_bar)
        self.am = ArrayManager()

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

        self.rsi_buy = 50 + self.rsi_entry
        self.rsi_sell = 50 - self.rsi_entry

        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

        atr_array = am.atr(self.atr_length, array=True)
        self.atr_value = atr_array[-1]
        self.atr_ma = atr_array[-self.atr_ma_length:].mean()
        self.rsi_value = am.rsi(self.rsi_length)

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

            if self.atr_value > self.atr_ma:
                if self.rsi_value > self.rsi_buy:
                    self.buy(bar.close_price + 5, self.fixed_size)
                elif self.rsi_value < self.rsi_sell:
                    self.short(bar.close_price - 5, self.fixed_size)

        elif self.pos > 0:
            self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
            self.intra_trade_low = bar.low_price

            long_stop = self.intra_trade_high - self.atr_value * self.atr_multiplier
            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)
            self.intra_trade_high = bar.high_price

            short_stop = self.intra_trade_low + self.atr_value * self.atr_multiplier
            self.cover(short_stop, abs(self.pos), stop=True)

        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

 

2.可以相应调整参数,比如说atr_window和对应的乘数(e.g. 增大)。要是觉得交易次数过多,也可以考虑一下不同的品种进行尝试。
 

发布于vn.py社区公众号【vnpy-community】

 

原文作者:用Python的交易员 | 发布时间:2020-05-30
 

更新日期:2020-5-30
 
针对许多初学者在刚开始使用vn.py时,容易遇到的各种常见问题,整理了这份FAQ文档,后续也会持续保持更新。

 

CTP期货接口


【Q】已经连接登录了CTP接口,但是在VN Trader主界面,左上角的编辑框中输入合约代码后,为何回车无法订阅行情?

【A】请检查合约代码是否输入正确,国内4家期货交易所的合约命名规则有所区别,vn.py内部全部采用官方命名,举例来说:

  • 中金所:IF2003
  • 上期所:au2003
  • 大商所:m2003
  • 郑商所:TA003

请注意以上命名中的英文字母大小写,以及年月的数字。


【Q】连接SimNow的CTP服务器,主界面左下角日志区域没有任何输出信息,或者出现4097错误?

【A】可能有以下几个原因,请按顺序排查:

  1. SimNow提供两套环境,其中第一套仿真**实盘环境只能在交易时段使用(9:00-15:00),第二套仿真测试环境**只能在非交易时段使用(所有其他时间);
  2. 第一套环境中又分为三组服务器,vn.py只能连接第二和第三组服务器(支持穿透式验证的版本),不能连接第一组;
  3. 由于运维方面的原因,SimNow服务器有时会出现停机维护的情况(2019年之后尤其频繁),此时只能等待到下一个交易日再尝试。

 

RQData数据服务


【Q】RQData中的连续合约数据,提供88、888、99等多种类型,做CTA策略回测应该用哪个?

【A】首先是数据的区别,以股指IF合约为例:

  • IF88:简单将IF股指期货每个主力月份的量价数据进行拼接,未做任何平滑处理,在主力合约换月时数据会有跳空的现象,因此该数据只适合日内CTA策略的回测(收盘无持仓)
  • IF888:在IF88数据的基础上,当主力合约换月时记录换月的价差金额,并对之前的历史数据全部加上或者减去该价差,进行平滑处理,因此该数据适合隔夜CTA策略的回测(收盘有持仓)
  • IF99:由所有可交易品种的数据,以累计持仓量进行加权平均后,得出的指数数据,由于其在现实中不可交易(交易所并不提供指数合约),因此不推荐使用该数据进行回测,可用于某些数据模型的研究

具体细节可以参考米筐官方的RQData文档页面

 
CTA策略交易


【Q】我自己开发的策略,应该放到什么目录?

【A】CtaStrategy和CtaBacktester两个模块,在启动时都会自动扫描加载VN Trader运行时目录(主界面窗口顶部标题栏的路径)下的strategies目录中的策略文件。

默认情况下,运行时目录是当前操作系统的用户目录,假设你的用户名为abc:

  • Windows系统

    • 用户目录为c:\users\abc;
    • 策略应该放在c:\users\strategies中
  • Linux/Mac系统

    • 用户目录为/home/abc
    • 策略应该放在/home/abc/strategies中

 

历史数据和数据库


【Q】vn.py支持哪些数据库?对于用户来说应该怎么选择?

【A】目前一共支持四套数据库:SQLite、MySQL、PostgreSQL以及MongoDB。其中SQLite、MySQL和PostgreSQL属于SQL类数据库,MongoDB属于NoSQL类数据库。

从各自的特点看:

  • SQLite:采用单一本地文件来保存数据,用户无需安装任何软件即可使用,也是vn.py默认使用的数据库,推荐绝大部分刚上手的用户使用(无需做任何配置);
  • MySQL:互联网时代使用广泛的数据库,需要额外安装运行MySQL数据库服务(可以选择本机或者独立服务器部署),推荐需要同时运行多个VN Trader进程访问数据库,且有一定SQL经验的用户使用;
  • MongoDB:基于类似Json格式储存的文件型数据库,其查询语言比起SQL更加适合交易员的胃口(易上手),同时速度性能比起MySQL有一定优势,推荐需要同时运行多个VN Trader进程访问数据库,且不会SQL的用户使用
  • PostgreSQL:功能强大的开源数据库之一,比起MySQL支持更复杂的查询语法和数据格式,但同样也需要大量的时间来学习和配置,只推荐对其已有丰富使用经验的用户使用。

关于数据库的具体配置方法,请参考官网文档


【Q】手头已有从其他来源(淘宝购买、软件导出等)获取的CSV格式的K线数据,如何导入到vn.py中用于策略历史回测分析?

【A】注意:最新版本中已将之前的CsvLoader模块的功能,合并到了DataManager模块中。

操作流程如下:

  1. 在VN Station中启动VN Trader Pro时,勾选加载DataManager应用模块;
  2. 打开VN Trader主界面后,点击顶部菜单栏的【功能】->【数据管理】,打开DataManager窗口;
  3. 点击右上角的【导入数据】按钮,弹出对话框【从CSV导入数据】;
  4. 点击【选择文件】按钮,在弹出的对话框中选择要导入的CSV文件路径;
  5. 在【合约信息】栏目下,配置要把数据放到其中的目标数据库,注意周期只支持:MINUTE(1分钟线)、HOUR(1小时线)、DAILY(1日线)、WEEKLY(1周线);
  6. 在【表头信息】栏目下,根据CSV文件内的表头进行匹配,注意如果是英文大小写必须完全一致;
  7. 在【格式信息】栏目下,对日期时间戳的字符串格式进行匹配,大部分情况下注意分隔符(如年月日用“-”还是“/”)以及日期时间之间是否有空格就够了;
  8. 点击【确定】按钮执行导入操作,导入过程中界面会假死,通常耗费几秒到十几分钟的时间(视乎数据量);
  9. 完成后点击右上角的【刷新】按钮,即可查询数据库中当前该合约的数据范围情况。

 
 

2020年的第二期vn.py小班课还剩最后一个名额!

  • 两天共计10小时的密集提高课程

  • 8套高端CTA策略源代码分享

    • DoubleChannel
    • KeltnerBandit
    • RsiMomentum
    • SuperTurtle
    • TrendThrust
    • Cinco
    • Cuatro
    • SuperCombo
  • 动态风险仓位管理模型

  • 策略内嵌复杂算法交易

详情请戳 2020年第二期小班课:CTA策略开发

螺纹钢手续费率是0.01%,应该填0.0001。如果是每手几元,可以用手续费除以每手价格得到一个大概的数值填上去。
关于交易滑点,就是最小变动价位*预计多少跳。

发布于vn.py社区公众号【vnpy-community】

 

原文作者:用Python的交易员 | 发布时间:2020-06-12

 

提示:如果只对封面3D图实现感兴趣的同学,可以直接跳转到文末的第三段内容~
 

当前的情况

 

对于CTA策略的回测研究,vn.py提供了两套工具:CtaBacktester图形界面

和CtaStrategy回测引擎。

其中CtaBacktester采用了基于PyQt的图形界面,适合刚开始上手的初学者,十分简洁易用,点点鼠标就能快速完成【数据下载入库】和【历史数据回测】功能:

 
description
 

CtaStrategy回测引擎作为CtaBacktester图形界面背后的计算引擎,也可以在Jupyter Notebook中直接使用。在CLI命令交互的模式下,用户操作的自由度大大提升,可以实现更加复杂的分析脚本,如【策略组合回测】、【主力切换回测】、【滚动窗口优化】等等:

 
description
 
尽管功能更加强大,但截止目前最新版本(2.1.3.1),回测引擎基于Matplotlib和Seaborn实现的绘图展示却一直备受大家吐槽:

 
description
 

整体图表极端单调的白蓝配色,第三张每日盈亏子图一旦数据太多就会无法显示,底部X轴坐标密集成了黑色长条,除了能右键另存为图片外几乎毫无交互功能......妥妥体现出了我们vn.py钢铁直男团队的审美。

 

更好的选择

 

长期在社区论坛被这么吐槽下去也不是个事,好在Python大数据分析的生态比起六年前vn.py刚起步的时候早就今非昔比,做了点功课后快速选定了新的方向:Plotly。

仅看能绘制的图表类型,Plotly对比Matplotlib来说可能优势未必那么明显(仅限于vn.py应用范围),但在绘图性能和交互式功能方面,Plotly基于Javascript在浏览器中动态绘制的图表,就吊打Matplotlib的静态图片几条大街了。

下面我们就试着用Plotly在Jupyter Notebook中绘制出上面的策略统计图表,首先是安装plotly.py,打开cmd直接运行:
 

pip install plotly

 
然后启动Jupyter Notebook来运行历史数据回测,并获取到回测的统计结果数据,当然在运行前请先确保已经准备好了历史数据(没有的同学可以申请RQData试用下载:点击链接),代码如下:
 

# 加载相关模块
from datetime import datetime

from vnpy.app.cta_strategy.backtesting import BacktestingEngine, OptimizationSetting
from vnpy.app.cta_strategy.strategies.atr_rsi_strategy import AtrRsiStrateg

# 运行策略回测
engine = BacktestingEngine()
engine.set_parameters(
    vt_symbol="IF888.CFFEX",
    interval="1m",
    start=datetime(2010, 1, 1),
    end=datetime(2020, 7, 30),
    rate=0.3/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()

 
到这里,我们已经准备好了绘图所需的全部数据,保存在一个pandas.DataFrame数据结构df中,下一步就可以开始尝试用Plotly来画图了,首先加载Plotly相关模块:
 

import plotly.graph_objects as go
from plotly.subplots import make_subplots

 
其中的go模块包含了Plotly中的绘图组件(折线图、柱状图、面积图、散点图等等),而make_subplots函数则用于创建带子图的绘图区域。真正的绘图代码则十分简单,只有短短几行:
 

# 创建一个4行、1列的带子图绘图区域,并分别给子图加上标题
fig = make_subplots(rows=4, cols=1, subplot_titles=["Balance", "Drawdown", "Daily Pnl", "Pnl Distribution"], vertical_spacing=0.06)

# 第一张:账户净值子图,用折线图来绘制
fig.add_trace(go.Line(x=df.index, y=df["balance"], name="Balance"), row=1, col=1)

# 第二张:最大回撤子图,用面积图来绘制
fig.add_trace(go.Scatter(x=df.index, y=df["drawdown"], fillcolor="red", fill='tozeroy', line={"width": 0.5, "color": "red"}, name="Drawdown"), row=2, col=1)

# 第三张:每日盈亏子图,用柱状图来绘制
fig.add_trace(go.Bar(y=df["net_pnl"], name="Daily Pnl"), row=3, col=1)

# 第四张:盈亏分布子图,用直方图来绘制
fig.add_trace(go.Histogram(x=df["net_pnl"], nbinsx=100, name="Days"), row=4, col=1)

# 把图表放大些,默认小了点
fig.update_layout(height=1000, width=1000)

# 将绘制完的图表,正式显示出来
fig.show()

 
不必经历Matplotlib绘图的漫长等待,几乎一瞬间,结果就显示了出来:

 
description
 

就算是钢铁直男也得承认,比起之前的Matplotlib图表,这个真的好看太多。

首先作为能偷懒一定要偷懒的优秀开发者,我们是绝对不会没事跑去设置颜色的,四张子图中除了面积图必须设底色不然不显示外,另外三张子图的显示都是默认自动选择的颜色和效果。

另外还有个优点在上述截图上没法体现,将鼠标移动到子图上任意位置时,会自动显示当前X轴位置对应的Y轴数据细节,连日期时间戳也完全清楚,再也不用把df给print出来后一行行去翻数据了。

 

3D参数曲面

 

有了这么个方便的图表工具后,接下来自然要更进一步发挥作用。参数优化是所有量化策略开发过程中极为重要的步骤,通过调整参数的数值实现策略模型对历史数据的最优匹配,来更好的把握未来实盘交易中的行情。

关于参数优化的数学原理、详细步骤、注意事项,足足可以写上一个系列的文章来讲解,在这里我们不去做额外的深入,先用这段代码快速跑出一个优化结果:
 

# 创建优化配置
setting = OptimizationSetting()

# 选择夏普比率作为目标函数
setting.set_target("sharpe_ratio")            

# 选择优化rsi_length和atr_length两个参数
setting.add_parameter("rsi_length", 2, 11, 1)  
setting.add_parameter("atr_length", 10, 30, 2)

# 通过多进程穷举算法来执行优化
result = engine.run_optimization(setting)

 
采用文本形式输出的结果如下:

 

description
 
或者在CtaBacktester组件中通过图形界面执行优化,也可以得到类似的显示:

 

description
 
所有的参数组合,都已经根据目标函数的结果从高到低排列。比起单纯选择排在第一位的参数组合,有经验的同学会更加倾向于选择位于【参数平原】的结果,即其附近的结果都相对更好的组合。

尽管数字都已经显示出来了,但是对于我们人脑来说想要从其中找到这个平原,难度还是太大了点。更直观的方式还是通过图表来判断,2个自变量,1个因变量,正好可以组合为3D曲面。

首先是将上一步中保存下来的优化结果result列表中的数据,调整其格式生成X、Y、Z三条坐标轴上的数据点:
 

# 直接取出X、Y轴
x = setting.params["atr_length"]
y = setting.params["rsi_length"]

# 通过映射的方式取出Z轴
z_dict = {}
for param_str, target, statistics in result:
    param = eval(param_str)
    z_dict[(param["atr_length"], param["rsi_length"])] = target

z = []
for x_value in x:
    z_buf = []
    for y_value in y:
        z_value = z_dict[(x_value, y_value)]
        z_buf.append(z_value)
    z.append(z_buf)

 
准备好了数据,又到了用上Plotly的时刻,简单的三行代码(update_layout中因为参数过长做了换行处理):
 

fig = go.Figure(data=[go.Surface(z=z, x=x, y=y)])
fig.update_layout(
    title='优化结果', autosize=False,
    width=600, height=600,
    scene={
        "xaxis": {"title": "atr_length"},
        "yaxis": {"title": "rsi_length"},
        "zaxis": {"title": setting.target_name},
    },
    margin={"l": 65, "r": 50, "b": 65, "t": 90}
)
fig.show()

 
同样几乎瞬间,就在Jupyter中看到了显示出来的参数优化结果的3D曲面图:

 

description
 
其中平面的X、Y轴分别显示的是两个参数rsi_length和atr_length,而垂直的Y轴则显示的优化目标sharpe_ratio的数值,三者共同构成了3D曲面图,同时【参数平原】也变得一目了然,真的就是选择曲面上“最高的平原区域”。

尽管截图中没法体现出来,该3D图表还可以非常方便的在三条轴线上进行360度旋转,以及拉近和拉远操作,并且当鼠标移动到曲面上时也可以直接看到该位置对应的具体数据内容,如下图所示:
 

description

 

最终的结论

也不多说啥了,接下来的v2.1.4版本让大家直接全都用上!

 
 

涨价通知

 

感谢vn.py社区用户的支持。

【vn.py全实战进阶 - CTA策略】课程销量已经达到778份。

销量超过800后将会进行一次涨价,涨价后的价格为499(目前是399)。

最后22份名额,感兴趣的朋友请抓紧时间吧。

购买请直接扫描下方二维码:
 

description

发布于vn.py社区公众号【vnpy-community】

 

原文作者:木头哥 | 发布时间:2020-6-12
 

作为Python开发的开源项目,vn.py本身具有非常好的跨平台通用性,毕竟Python几乎可以在所有主流操作系统上运行。但对于Linux系统,官方团队只提供了对Ubuntu 18.04版本的支持(主要就是安装脚本)。

本人一直用的是CentOS的服务器,折腾了几天,终于在上面把vnpy跑起来了。没有记录折腾的细节,只是记录了下正常的操作步骤,欢迎大家一起交流。

以下内容全部基于CentOS 7.6版本,首先准备好一个全新安装的系统,然后跟着一步步操作即可。

 

安装Python环境

 

# 在anaconda.com官网下载安装包,这里需要下载linux版本的x86_64位的包
bash Anaconda3-2019.07-Linux-x86_64.sh
# 如果不修改安装路径的话,按照默认设置即可。
# 安装过anaconda后,需要将/root/.bashrc中的关于anaconda的部分屏蔽掉,不然后面vncserver无法正常启动。

 

安装Mate桌面

 

yum groups install "X Window System" -y
yum install epel-release -y
yum groups install "MATE Desktop" -y
systemctl set-default graphical.target

 

安装VNC Server

 

yum install tigervnc-server -y

# 替换User为root,增加显示分辨率参数设置
sed -r -i "s/^(ExecStart.*)<USER>(.*%i)/\1root\2 -geometry 1920x1200 -depth 16/" /lib/systemd/system/vncserver@.service
sed -r -i "s/^(PIDFile.*)home\/<USER>(.*pid)/\1root\2/" /lib/systemd/system/vncserver@.service

mv /lib/systemd/system/vncserver@.service /lib/systemd/system/vncserver@:1.service
systemctl daemon-reload
vncpasswd
systemctl start vncserver@:1.service
systemctl enable vncserver@:1.service

# 屏蔽默认桌面,启动mate桌面
sed -r -i "s@^/etc/X11/xinit/xinitrc$@# &@" /root/.vnc/xstartup
echo "/usr/bin/mate-session &" >> /root/.vnc/xstartup

# 其它操作
# 禁用selinux
sed -r -i "s/^(SELINUX=).*/\1disabled/" /etc/selinux/config
# 关闭防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service

reboot

# 如果是云服务器,需要确保开放了TCP 5901端口

 

安装VS Code

 

rpm --import https://packages.microsoft.com/keys/microsoft.asc
sh -c 'echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\nenabled=1\ngpgcheck=1\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/vscode.repo'
yum check-update
yum install code -y

 

升级GCC版本

注意GCC必须要使用9.1.0以上的版本,否则在编译vnpy的时候,会报-std=c++17相关的错误,另外GCC编译的时间很长,估计得几个小时。
 

yum install gcc gcc-c++ bzip2 m4  gmp-devel.x86_64  -y

wget https://mirrors.ustc.edu.cn/gnu/gcc/gcc-9.1.0/gcc-9.1.0.tar.gz
tar xvf gcc-9.1.0.tar.gz
cd gcc-9.1.0/
./contrib/download_prerequisites

cd gmp;mkdir temp;cd temp
../configure --prefix=/usr/local/gmp-6.1.0
make && make install

cd ../../mpfr;mkdir temp;cd temp
../configure --prefix=/usr/local/mpfr-3.1.4 --with-gmp=/usr/local/gmp-6.1.0
make && make install

cd ../../mpc;mkdir temp;cd temp
../configure --prefix=/usr/local/mpc-1.0.3 --with-gmp=/usr/local/gmp-6.1.0 --with-mpfr=/usr/local/mpfr-3.1.4
make && make install

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/mpc-1.0.3/lib:/usr/local/gmp-6.1.0/lib:/usr/local/mpfr-3.1.4/lib

cd ../..;mkdir temp;cd temp
../configure --disable-multilib --enable-languages=c,c++ --with-gmp=/usr/local/gmp-6.1.0 --with-mpfr=/usr/local/mpfr-3.1.4 --with-mpc=/usr/local/mpc-1.0.3
make -j4 && make install

 
我的服务器是4核的,就在make后面加了-j4,大家可根据自己的情况调整,缩短编译时间。

 

安装vn.py

最后终于可以安装vn.py了,在此过程中会自动编译Linux上支持的交易接口,如CTP/OES等。
 

# 切换到python环境
. ~/miniconda3/bin/activate
yum install postgresql-devel* libxkbcommon-x11 -y

# 下载vnpy的最新源码包并解压后执行
cd vnpy
bash install.sh

 

发布于vn.py社区公众号【vnpy-community】

 

原文作者:李怡然 | 发布时间:2020-05-11

 

如何将本地CSV文件格式的历史数据导入到数据库中,是vn.py社区论坛上提问最多的问题之一。本文的主要目的是帮助初学者解决数据入库问题,以便可以快速开始量化策略的开发研究。

本文的内容主要分为三大部分:

  • 第一部分介绍了在vn.py中使用MongoDB数据库所需要进行的配置(只打算使用vn.py默认SQLite数据库的用户,可以简单了解一下);
  • 第二部分介绍了数据入库的基本流程(适用于vn.py支持的所有数据库);
  • 最后一部分则是具体的实现:分别将数据导入MongoDB和SQLite数据库(适用于vn.py支持的所有数据库)。

在正文开始之前,还需要提醒大家:将数据导入数据库之前,务必要确保这些数据已经是被清洗过的干净的数据。如果将没有被清洗过质量差的数据,直接入库进行回测,可能会导致各种问题,影响策略开发的效率。因此,建议大家使用高质量的数据源。

 

配置数据库

 
vn.py 中默认使用 SQLite 数据库。因此,如果需要使用 MongoDB 数据库,则需要修改 vn.py 的全局配置。具体流程如下:

  1. 找到 vt_setting.json 文件:位于C:\Users\你的用户名.vntrader 目录下;
  2. 对vt_setting.json 文件中的相关字段进行修改,包括:database.driver、database.database、database.host、database.port。

下图是我自己设置的数据库配置信息:

 
description

 
上图中的两个 "mongodb" 可能让人会有些困扰。实际上第一个"mongodb"是告诉 vn.py 我们使用的数据库类型是 MongoDB 数据库而不是默认的 SQLite 数据库。第二个 "mongodb" 则是告诉 vn.py 回测所需要的数据储存在 MongoDB 数据库中一个叫做 "mongodb" 的 database 中。这样说可能有些绕口,请看下图:

 
description
 
上图是 MongoDB 数据库的官方图形界面客户端MongoDB Compass。我们可以清楚的看到在该 MongoDB 数据库中一共有四个 database, 分别是 admin,config,local, mongodb。从2.0开始,vn.py采用ORM/ODM的方式来管理历史数据,因此所有的K线数据都被保存在db_bar_data这一集合中,所有Tick数据都被保存在db_tick_data这一集合中。
 

数据入库基本流程

 
在配置好 MongoDB 数据库后,我们可以正式开始讨论数据入库操作,vn.py 提供了很多工具使数据入库这个过程变得简单快捷,以下是是数据入库的基本流程:

  1. 先确定要入库的数据是 Tick 还是 Bar (K线) 类型数据;
  2. 将需要入库的数据转化成 vn.py 定义的 TickData 或 BarData 数据类型;
  3. 使用 vn.py 提供的数据入库工具函数 database_manager.save_tick_data 或 database_manager.save_bar_data 将相应的 TickData 或 BarData 入库 。

以上三步操作中的难点(或者说复杂点)集中在第二步,即如何将将本地CSV文件中的数据格式转换成vn.py 定义的 TickData 或 BarData,大部分情况下只需对数据的时间戳进行转化处理。另外,如果数据本身的质量不高,比如数据的整个时间戳格式前后不一致,那需要先对数据的时间戳格式进行统一。
 

数据入库具体实现

 
在了解了数据入库的基本流程之后,我们来实现一次数据入库的过程。首先,来看一看我们要入库的数据:

 
description

 
从上图可以看出,C 列储存的是表示时间的数据且 C2 和 C3 的时间间隔是1分钟。所以,要入库的数据是1分钟的 Bar (K线)数据类型。下面我们进行第二步:将需要入库的数据转化成 vn.py 定义的 BarData。

首先,我们先来认识一下 vn.py 中的BarData:

 
description

 
从上图中可以看出,BarData 一共有11个属性。其中,BarData.vt_symbol 会在 BarData 实例化的时候自动生成。另外,需要指出的是BarData.exchange 和 BarData.inteval 的数据类型分别是 vn.py 中定义好的枚举常量 Exchange 和 Inteval 而 BarData.datetime 则是 Python 标准库 datetime 中的 datetime 数据类型。

Exchange 枚举值的定义:

 
description

 
Interval 枚举值的定义:
 

description

 
在认识了vn.py 中的 BarData 之后,我们开始着手将需要入库的数据转化成 BarData类型数据。再来重温一下,需要入库数据的格式:

 
description
 

通过和上文 BarData 的数据结构对比,我们有以下几个发现:

  1. csv文件中的 合约代码 时间 开 高 低 收 成交量 持仓量 和 BarData中的 symbol datetime open_price high_price low_price close_price volume open_interest 一一对应(从名称就就可以看出)。
  2. csv文件中 市场代码 没办法和 BarData 中的 exchange 对应。因为csv文件中 市场代码 都是 SC ,而在上图Exchange 数据结构代码截图中找不到和 SC 对应的枚举常量的绑定值。从合约代码 ag1608(沪银1608) 可以推断出这里的 SC 指的就是上海期货交易所,对应的枚举常量是 Exchang.SHFE。
  3. csv文件中缺少了和 BarData 中的 interval 相对应的数据。上文我们已经发现了 csv文件中储存的是1分钟的BarData,对应的枚举常量是 Interval.MINUTE。

基于上面的发现,很自然的,我们需要进行如下的操作:

  1. 将csv文件中 市场代码的 SC 替换成 Exchang.SHFE
  2. 增加一列数据,且该列数据的所有值都是 Interval.MINUTE

一般情况下,使用Python 的 pandas 库可以方便的完成上面的操作。如果数据的质量较差,比如数据的分隔符设置存在问题,会使得pd.read_csv函数没办法正确的读取.csv文件。这时则需要使用Python的 csv库。本文的数据入库过程统一使用 pandas 来完成。具体操作,如下:
 

from vnpy.trader.constant import (Exchange, Interval)
import pandas as pd
# 读取需要入库的csv文件,该文件是用gbk编码
imported_data = pd.read_csv('需要入库的数据的绝对路径',encoding='gbk')

# 将csv文件中 `市场代码`的 SC 替换成 Exchange.SHFE SHFE
imported_data['市场代码'] = Exchange.SHFE

# 增加一列数据 `inteval`,且该列数据的所有值都是 Interval.MINUTE
imported_data['interval'] = Interval.MINUTE

 
接下来,我们还需要对每列数据的数据类型进行修改,确保和 BarData 中各个属性的数据类型一致。BarData中属性的数据类型可以分为三大类:float 类, datetime 类 和 自定义枚举类 (Interval 和 Exchange)。因为,上面已经修改过了Interval 和 Exchange,下面只需要修改 float 和 datetime 类。

修改 float类代码:
 

# 明确需要是float数据类型的列
float_columns = ['开', '高', '低', '收', '成交量', '持仓量']

for col in float_columns:
  imported_data[col] = imported_data[col].astype('float')

 
修改 datatime类代码:
 

# 明确时间戳的格式
# %Y/%m/%d %H:%M:%S 代表着你的csv数据中的时间戳必须是 2020/05/01 08:32:30 格式
datetime_format = '%Y%m%d %H:%M:%S'

imported_data['时间'] = pd.to_datetime(imported_data['时间'],format=datetime_format)

 
下一步,我们还需要对列名进行修改:
 

# 因为没有用到 成交额 这一列的数据,所以该列列名不变
imported_data.columns=['exchange','symbol','datetime','open','high','low','close','volume','成交额','open_interest','interval']

 
另外,因为该csv文件储存的是ag的主力连续数据,即多张ag合约的拼接。因此,symbol列中有多个不同到期日的ag合约代码,这里需要将合约代码统一为ag88:
 

imported_data['symbol'] ='ag88'

 
最后,我们使用 vn.py 封装好的 database_manager.save_bar_data 将数据入库:
 

# 导入 database_manager 模块
from vnpy.trader.database import database_manager
from vnpy.trader.object import (BarData,TickData)
# 封装函数
def move_df_to_mongodb(imported_data:pd.DataFrame,collection_name:str):
    bars = []
    start = None
    count = 0

    for row in imported_data.itertuples():

        bar = BarData(

              symbol=row.symbol,
              exchange=row.exchange,
              datetime=row.datetime,
              interval=row.interval,
              volume=row.volume,
              open_price=row.open,
              high_price=row.high,
              low_price=row.low,
              close_price=row.close,
              open_interest=row.open_interest,
              gateway_name="DB",

        )


        bars.append(bar)

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

    # insert into database
    database_manager.save_bar_data(bars)
    print(f"Insert Bar: {count} from {start} - {end}")

 

如果想要将数据储存储存在 SQLite 数据库中也很简单,只需要两步就可以完成。

首先创建一个sqlite数据库连接对象:
 

from vnpy.trader.database.initialize import init_sql
from vnpy.trader.database.database import Driver

settings={
    "database": "database.db",
    "host": "localhost",
    "port": 3306,
    "user": "root",
    "password": "",
    "authentication_source": "admin"
}
sqlite_manager = init_sql(driver=Driver.SQLITE, settings=settings)

 
然后使用sqlite数据库连接对象将数据入库:
 

# 替换函数 move_df_to_mongodb 的倒数第二行
sqlite_manager.save_bar_data(bars)

 

如果在进行Sqlite数据入库的时候,出现peewee.InterfaceError: Error binding parameter 2 - probably unsupported type错误,解决方法如下:

  1. 找到imported_data['时间'] = pd.to_datetime(imported_data['时间'],format=datetime_format)代码所在行
  2. 在该行代码下键入imported_data['时间'] = imported_data['时间'].dt.strftime('%Y%m%d %H:%M:%S')

详细的Debug过程记录在sqlite数据入库Debug. 将该文件夹内的内容下载到本地同一个位置,运行Jupyter Notebook就可以复现整个过程.

 

总结

 

本文尝试从数据库配置,数据入库基本流程,数据入库具体实现,三部分来帮助vn.py新用户解决编写python脚本实现数据入库这个难点。借助vn.py的database_manager模块,用户基本上可以无缝切换SQLite,MongoDB等vn.py支持的数据库来读取和存入数据。希望这篇文章能帮助大家快速进入量化策略的研究和开发。

 

最后附上完整代码

 

from vnpy.trader.constant import (Exchange, Interval)
import pandas as pd
from vnpy.trader.database import database_manager
from vnpy.trader.object import (BarData,TickData)

# 封装函数
def move_df_to_mongodb(imported_data:pd.DataFrame,collection_name:str):
    bars = []
    start = None
    count = 0

    for row in imported_data.itertuples():

        bar = BarData(
              symbol=row.symbol,
              exchange=row.exchange,
              datetime=row.datetime,
              interval=row.interval,
              volume=row.volume,
              open_price=row.open,
              high_price=row.high,
              low_price=row.low,
              close_price=row.close,
              open_interest=row.open_interest,
              gateway_name="DB",
        )
        bars.append(bar)

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

    # insert into database
    database_manager.save_bar_data(bars, collection_name)
    print(f'Insert Bar: {count} from {start} - {end}')

if __name__ == "__main__":
    # 读取需要入库的csv文件,该文件是用gbk编码
    imported_data = pd.read_csv('D:/1分钟数据压缩包/FutAC_Min1_Std_2016/ag主力连续.csv',encoding='gbk')
    # 将csv文件中 `市场代码`的 SC 替换成 Exchange.SHFE SHFE
    imported_data['市场代码'] = Exchange.SHFE
    # 增加一列数据 `inteval`,且该列数据的所有值都是 Interval.MINUTE
    imported_data['interval'] = Interval.MINUTE
    # 明确需要是float数据类型的列
    float_columns = ['开', '高', '低', '收', '成交量', '持仓量']
    for col in float_columns:
      imported_data[col] = imported_data[col].astype('float')
    # 明确时间戳的格式
    # %Y/%m/%d %H:%M:%S 代表着你的csv数据中的时间戳必须是 2020/05/01 08:32:30 格式
    datetime_format = '%Y%m%d %H:%M:%S'
    imported_data['时间'] = pd.to_datetime(imported_data['时间'],format=datetime_format)
    # 因为没有用到 成交额 这一列的数据,所以该列列名不变
    imported_data.columns = ['exchange','symbol','datetime','open','high','low','close','volume','成交额','open_interest','interval']
    imported_data['symbol'] ='ag88'
    move_df_to_mongodb(imported_data,'ag88')

 
 

2020年社区第四次线上活动:把你的苹果Mac变成量化利器!

内容:
  1. 环境搭建
    a. Python语言环境
    b. 安装vn.py框架
    c. 适合量化的IDE
    d. 交易对接:以IB TWS为例

  2. 策略回测
    a. 数据解决方案
    b. 开发CTA策略
    c. 历史数据回测
    d. 策略参数优化

  3. 自动交易
    a. CTA实盘模块
    b. 策略生命周期
    c. 运维注意事项

  4. QA

报名请扫描下方二维码:

 
description

请问是回测还是优化?一般的策略回测的话应该不需要一两天的时间的。如果是优化,可以先用遗传算法找出大概的参数平原范围之后,再用穷举算法来优化,这样可以节约不少时间。

因为成交记录为空了,自然就没有图形输出了。成交记录为空有很多可能的原因,下次可以注意一下,如果显示“历史数据加载完成,数据量:0”,有可能是没有该品种的数据,或者是填错了交易所。如果显示有数据而成交记录为空,则可能是数据太少不够初始化,或者策略的条件太苛刻以至于一直没有发出委托。

看不到完整代码,请问是使用的自己编写的DualThrustStrategy还是cta_strategy附带的呢?附带的DualThrustStrategy里好像没有trailing_percent这个参数。

策略内通过self.write_log函数就能输出日志信息。

get_order:根据vt_orderid查询委托单的详细信息。

0:账户的资金,交易品种的手续费,合约乘数等不需要在策略中填写,回测过程中在图形界面设定就好了。可用资金只能在实盘中查看,但是在vn.py框架中不推荐这么做。

1-2:on_trade可以收到委托的成交通知,其中:
trade.direction:买卖方向
trade.offset:开平方向
trade.volume:成交数量
trade.price:成交价格

3-4:交易信号与持仓方向需要自己在策略中去写。

5:拿AtrRsiStrategy中第99行代码举例:

elif self.pos > 0:

    self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
    # COND:最新K线的最高价高于持仓周期的最高价
    # bar.high_price = X ; self.intra_trade_high = 上一个X

    self.intra_trade_low = bar.low_price

    long_stop = self.intra_trade_high * \
        (1 - self.trailing_percent / 100)
    self.sell(long_stop, abs(self.pos), stop=True)

6-7:如想求N天最高,最低值,可直接调用am中的唐奇安指标。如想求收盘价,可参考此贴

发布于vn.py社区公众号【vnpy-community】

 

原文作者:上弦之月 | 发布时间:2020-03-20

 

随着量化交易在国内金融市场越来越普及,CTA策略之间的竞争也变得越发激烈,除了体现在策略的核心交易信号方面外,也同样体现在策略的实盘委托执行中。

 

大部分vn.py官方提供的CTA策略样例中,在K线回调函数on_bar内采用的都是两步操作:

 

  1. 调用cancel_all函数,全撤之前上一根K线已经挂出的委托
  2. 检查核心交易信号,发出新一轮的委托挂单

 

这种简单粗暴的写法,更多是出于简化策略执行中的状态机控制,帮助vn.py初学者的降低学习难度,但由于较多的重复操作,在实盘中的运行效果未必能达到最佳。

 

好在策略模板CtaTemplate中委托函数(buy/sell/short/cover)可以直接返回委托号信息,以及on_order/on_trade回调函数会推送委托和成交状态变化,结合少量的底层代码改造,我们就可以实现更加精细的Tick级别委托挂撤单管理,让策略的核心交易信号和委托执行算法更加有机地结合起来。

 

扩展OrderData对象

 

找到vn.py源代码所在的路径,使用VN Studio的情况下,应该位于C:\vnstudio\Lib\site-packages\vnpy,进入到目录vnpy\trader下找到object.py 文件

 

首先需要对OrderData类进行扩展,主要表现在:

 

  1. 类的初始化除了time属性外,还增加了date和cancel_time属性

  2. __post_init__函数,增加了未成交量untraded和委托的具体日期时间datetime。

     

其中应该注意的是,由于每个行情API接口推送的时间格式不太相同,所以基于:

 

  • date格式是"%Y-%m-%d"还是"%Y%m%d";
  • time有无微秒级别数据;

 

要用到4种不同的处理方法得到datetime。

 

@dataclass
class OrderData(BaseData):    
    """    
    Order data contains information for tracking lastest status    
    of a specific order.    
    """

    symbol: str    
    exchange: Exchange    
    orderid: str

    type: OrderType = OrderType.LIMIT    
    direction: Direction = ""    
    offset: Offset = Offset.NONE    
    price: float = 0    
    volume: float = 0    
    traded: float = 0    
    status: Status = Status.SUBMITTING

    date: str = ""    
    time: str = ""    
    cancel_time: str = ""

    def __post_init__(self):   
        """"""        
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"     
        self.vt_orderid = f"{self.gateway_name}.{self.orderid}"

        self.untraded = self.volume - self.traded

        # With millisecond    
        if self.date and "." in self.time:   
            if "-"in self.date:          
                self.datetime = datetime.strptime(" ".join([self.date, self.time]), "%Y-%m-%d %H:%M:%S.%f")    
            else:    
                self.datetime = datetime.strptime(" ".join([self.date, self.time]), "%Y%m%d %H:%M:%S.%f")   
        # Without millisecond        
        elif self.date:            
            if "-" in self.date:       
                self.datetime = datetime.strptime(" ".join([self.date, self.time]), "%Y-%m-%d %H:%M:%S")        
            else:               
                self.datetime = datetime.strptime(" ".join([self.date, self.time]), "%Y%m%d %H:%M:%S")

    def is_active(self): 
        """       
        Check if the order is active.      
        """     
        if self.status in ACTIVE_STATUSES:        
            return True       
        else:            
            return False

    def create_cancel_request(self):    
        """       
        Create cancel request object from order.  
        """      
        req = CancelRequest(            
            orderid=self.orderid, symbol=self.symbol, exchange=self.exchange 
        )       
        return req

 

扩展持仓明细查询

 

修改PositionHolding对象

 

进入目录vnpy\trader打开converter.py文件,这一次我们对PositionHolding类进行修改,主要改动如下:

 

  1. __init__类的初始化,新加入long_pnl,long_price,short_pnl,short_price这4个属性。即增加了持仓盈亏以及开仓均价;
  2. update_position函数新增缓存持仓盈亏和开仓均价;
  3. update_order函数首先修改了活动委托的定义,把【委托提交中】剔除,即在update_order函数只处理未成交或者部分成交状态的委托。

 

class PositionHolding:    
    """"""

    def __init__(self, contract: ContractData):   
        """"""      
        self.vt_symbol = contract.vt_symbol   
        self.exchange = contract.exchange

        self.active_orders = {}          

        self.long_pos = 0    
        self.long_pnl = 0     
        self.long_price = 0   
        self.long_yd = 0      
        self.long_td = 0

        self.short_pos = 0        
        self.short_pnl = 0        
        self.short_price = 0      
        self.short_yd = 0       
        self.short_td = 0

        self.long_pos_frozen = 0        
        self.long_yd_frozen = 0      
        self.long_td_frozen = 0

        self.short_pos_frozen = 0    
        self.short_yd_frozen = 0    
        self.short_td_frozen = 0

    def update_position(self, position: PositionData):      
        """"""     
        if position.direction == Direction.LONG:  
            self.long_pos = position.volume   
            self.long_pnl = position.pnl      
            self.long_price = position.price    
            self.long_yd = position.yd_volume   
            self.long_td = self.long_pos - self.long_yd     
            self.long_pos_frozen = position.frozen   
        else:           
            self.short_pos = position.volume        
            self.short_pnl = position.pnl           
            self.short_price = position.price         
            self.short_yd = position.yd_volume         
            self.short_td = self.short_pos - self.short_yd                
            self.short_pos_frozen = position.frozen

    def update_order(self, order: OrderData):    
        """"""       
        #active_orders只记录未成交和部分成交委托单   
        if order.status in [Status.NOTTRADED, Status.PARTTRADED]:   
            self.active_orders[order.vt_orderid] = order     
        else:           
            if order.vt_orderid in self.active_orders:        
                self.active_orders.pop(order.vt_orderid)

        self.calculate_frozen()
 ......

 

新增get_position_detail函数

 

进入目录vnpy\app\cta_strategy打开engine.py文件,这一次我们要新增一个函数get_position_detail。该函数功能就是获取我们修改后的PositionHolding对象,从而知道更加详细的持仓信息,如开仓均价,持仓盈亏等:

 

from collections import defaultdict,OrderedDict
......

    def get_position_detail(self, vt_symbol):    
        """   
        查询long_pos,short_pos(持仓),long_pnl,short_pnl(盈亏),active_order(未成交字典)      
        收到PositionHolding类数据     
        """        
        try:        
            return self.offset_converter.get_position_holding(vt_symbol)     
        except:            
            self.write_log(f"当前获取持仓信息为:{self.offset_converter.get_position_holding(vt_symbol)},等待获取持仓信息") 
            position_detail = OrderedDict()            
            position_detail.active_orders = {}            
            position_detail.long_pos = 0           
            position_detail.long_pnl = 0       
            position_detail.long_yd = 0            
            position_detail.long_td = 0          
            position_detail.long_pos_frozen = 0        
            position_detail.long_price = 0            
            position_detail.short_pos = 0            
            position_detail.short_pnl = 0          
            position_detail.short_yd = 0      
            position_detail.short_td = 0         
            position_detail.short_price = 0      
            position_detail.short_pos_frozen = 0         
            return position_detail

 

然后,为了让交易策略能够直接从引擎调用get_position_detail函数,对CTA策略模板也得增加一个调用函数。在同一目录找到template.py文件,开打后在CtaTemplate类中加入以下代码即可:

 

    def get_position_detail(self, vt_symbol: str):        
        """"""        
        return self.cta_engine.get_position_detail(vt_symbol)

 

修改CTA策略代码

 

底层的功能都添加完毕了,那么现在轮到对具体交易策略逻辑进行改动,从而实现基于实时tick行情的追单和撤单。

 

添加策略运行时变量

 

包括委托状态的触发控制器、具体委托量以及拆单间隔:

 

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

        #状态控制初始化     
        self.chase_long_trigger = False   
        self.chase_sell_trigger = False     
        self.chase_short_trigger = False   
        self.chase_cover_trigger = False    
        self.long_trade_volume = 0    
        self.short_trade_volume = 0    
        self.sell_trade_volume = 0       
        self.cover_trade_volume = 0    
        self.chase_interval = 10    #拆单间隔:秒
......

 

修改on_tick函数逻辑

 

需要在on_tick函数增加的对委托挂撤单管理逻辑如下:

 

  1. 调用cta策略模板新增的get_position_detail函数,通过engine获取活动委托字典active_orders。注意的是,该字典只缓存未成交或者部分成交的委托,其中key是字符串格式的vt_orderid,value对应OrderData对象;

     

  2. engine取到的活动委托为空,表示委托已完成,即order_finished=True;否则,表示委托还未完成,即order_finished=False,需要进行精细度管理;

     

  3. 精细管理的第一步是先处理最老的活动委托,先获取委托号vt_orderid和OrderData对象,然后对OrderData对象的开平仓属性(即offst)判断是进行开仓追单还是平仓追单:

     

    a. 开仓追单情况下,先得到未成交量order.untraded,若当前委托超过10秒还未成交(chase_interval = 10),并且没有触发追单(chase_long_trigger = False),先把该委托撤销掉,然后把触发追单器启动;

    b. 平仓追单情况下,同样先得到未成交量,若委托超时还未成交并且平仓触发器没有启动,先撤单,然后启动平仓触发器;

     

  4. 当所有未成交委托处理完毕后,活动委托字典将清空,此时order_finished状态从False变成True,用最新的买卖一档行情,该追单开仓的追单开仓,该追单平仓的赶紧平仓,每次操作后恢复委托触发器的初始状态:

 

    def on_tick(self, tick: TickData):        
        """"""        
        active_orders = self.get_position_detail(tick.vt_symbol).active_orders

        if active_orders:          
            #委托完成状态            
            order_finished = False      
            vt_orderid = list(active_orders.keys())[0]   #委托单vt_orderid                     
            order = list(active_orders.values())[0]      #委托单字典

            #开仓追单,部分交易没有平仓指令(Offset.NONE)       
            if order.offset in (Offset.NONE, Offset.OPEN):               
                if order.direction == Direction.LONG:     
                    self.long_trade_volume = order.untraded     
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and self.long_trade_volume > 0 and (not self.chase_long_trigger) and vt_orderid: 
                        #撤销之前发出的未成交订单    
                        self.cancel_order(vt_orderid)    
                        self.chase_long_trigger = True      
                elif order.direction == Direction.SHORT:     
                    self.short_trade_volume = order.untraded                 
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and self.short_trade_volume > 0 and (not self.chase_short_trigger) and vt_orderid:   
                        self.cancel_order(vt_orderid)     
                        self.chase_short_trigger = True   
            #平仓追单           
            elif order.offset in (Offset.CLOSE, Offset.CLOSETODAY):    
                if order.direction == Direction.SHORT:    
                    self.sell_trade_volume = order.untraded   
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and self.sell_trade_volume > 0 and (not self.chase_sell_trigger) and vt_orderid:      
                        self.cancel_order(vt_orderid) 
                        self.chase_sell_trigger = True 
                if order.direction == Direction.LONG: 
                    self.cover_trade_volume = order.untraded        
                    if (tick.datetime - order.datetime).seconds > self.chase_interval and self.cover_trade_volume > 0 and (not self.chase_cover_trigger) and vt_orderid:        

                        self.cancel_order(vt_orderid)        
                        self.chase_cover_trigger = True  
        else:            
            order_finished = True   
        if self.chase_long_trigger and order_finished:    
            self.buy(tick.ask_price_1, self.long_trade_volume)      
            self.chase_long_trigger = False       
        elif self.chase_short_trigger and order_finished:         
            self.short(tick.bid_price_1, self.short_trade_volume)     
            self.chase_short_trigger = False        
        elif self.chase_sell_trigger and order_finished:     
            self.sell(tick.bid_price_1, self.sell_trade_volume)  
            self.chase_sell_trigger = False      
        elif self.chase_cover_trigger and order_finished:  
            self.cover(tick.ask_price_1, self.cover_trade_volume) 
            self.chase_cover_trigger = False

 

最后需要注意的是,Tick级精细挂撤单的管理逻辑,无法通过K线来进行回测检验,因此通过仿真交易(比如期货基于SimNow)进行充分的测试就是重中之重了。

 

 

 

《vn.py全实战进阶 - 期权零基础入门》课程已经更新过半!内容专门面向从未接触过期权交易的新手,共计30节课程带你一步步掌握期权的基础知识、了解合约特征和品种细节、学习方向交易和套利组合等各种常用期权交易策略,详细内容请戳新课上线:《期权零基础入门》

发布于vn.py社区公众号【vnpy-community】

 

原文作者:KeKe | 发布时间:2020-03-08

 

传统的回撤分析

 

在对CTA策略的历史数据回测研究中,除了直接观察资金曲线的形状外,更多会基于各类围绕资金曲线的统计指标,来对策略的业绩表现进行评估,例如:

 

  • 年化收益率
  • 收益率标准差
  • 夏普比率
  • 历史最大回撤
  • 最长回撤时间
  • 等等

 

其中历史最大回撤和最长回撤时间两个统计指标,描述的是在最坏情况下(即连续亏损时)策略的损失程度。但在任何给定的样本数据上跑完回测,得到的回撤指标都分别只有一个点的估计值(最坏情况),同时其对策略参数变化的敏感性非常高。

 

因此在对策略的整体风险评估上,如果仅仅使用以上两个回撤指标,可能使得我们分关注极端情况。尤其是在参数优化时,导致我们选择一些仅仅只是幸运避开了个别几笔亏损交易,但整体预测效果未必最佳的参数组合。

 

好在,最近一篇新的论文提供了一个可靠的研究改进方向。

 

一篇新的研究论文

 

2019年德国科隆大学金融研究中心的Korn, Moller和Schwehm合作发布了一篇研究量化策略回撤分析的文章:

 

《Drawdown Measures: Are They All the Same?》

 

1) 在文章中,他们首先创建了6个不同的回撤指标:

 

  • Average Drawdown, 平均回撤(ADD)
  • Linearly Weighted Drawdown, 线性加权回撤(lwDD)
  • Average Squared Drawdown, 均方回撤(ADD^2)
  • Trend Weighted Drawdown, 趋势加权回撤(twDD)
  • Maximum Drawdown, 最大回撤(MDD)
  • End-of-period Drawdown, 期末回撤(eopDD)

 

指标的公式和图形如下:

description

 

2)然后做了一个对比分析,用不同回撤指标来识别:

 

  • 随机策略(hit ratio 0.5)
  • 具有正数学期望的策略(hit ratio 0.6)

 

发现平均回撤(ADD)、线性加权回撤(lwDD)以及均方回撤(ADD^2)三者的效果最为接近:

 

description

 

3)除了相似性以外,这3个指标的识别效果也更佳,即相比于最大回撤 MDD,这些回撤指标能够更有效的区分随机和正期望策略:

 

description

 

基于vn.py的代码实现

 

首先,我们需要在Jupyter Notebook中,以命令行CLI交互的模式调用CtaStrategy模块的回测功能(不会的请戳这里),对一个策略执行历史数据回测,得到资金曲线图和百分比回撤图:

 

description

 

此时可以访问BacktestingEngine对象实例的daily_df(DataFrame格式)数据,获取基于逐日盯市规则计算的每日策略统计数据(如当日盈亏、累计盈亏、成交笔数、百分比最大回撤等):

 

description

 

由于百分比回撤是负数,为了后续研究的时候方便观察,先创建一个新的列【ddpercent_justed】,这是调整为正数后的百分比最大回撤,然后画图显示出来:

 

description

 

最大回撤可以通过对百分比回撤序列调用max函数方法,得到结果为1.4%:

 

description

 

有了参考对象,接下来我们开始计算以上论文中提到的3个更为有效回撤指标。

 

平均回撤(ADD)

 

平均回撤就是每日百分比回撤的算数平均,直接调用mean方法得到结果为0.41%,即采用简单的算术平均计算策略整体每日亏损的风险为0.41%:

 

description

 

线性加权回撤(lwDD)

 

线性加权回撤就是使用最小二乘法(OLS)对回撤曲线进行线性回归,从而得到一条回撤的趋势线。最小化误差平方和的方法有利于避免极端情况的影响,让我们把关注点更多集中在策略的整体风险水平上。一个好的策略,其回撤的回归直线的斜率应该尽可能的小。

 

由于这里要求的只是回归线的终值,而不需要过程中的其他信息,所以无需动用那些巨型的数据分析库,如sklearn、statsmodels、scipy等等,这里我们选择更加轻量级的talib库(没错,算技术指标的那个)。

 

在交互式模式下执行命令:help(talib.LINEARREG),我们可以知道线性回归函数的入参有2个:

 

description

 

其中:

 

  • real:需要被回归的数据,即每天的百分比回撤数据;
  • tiemperiod:回望周期,我们需要最整体样本进行回归,故回望周期等于数据的样本数(在逐日盯市统计下,为策略回测的天数)。

 

这样,我们得到线性加权回撤的结果为0.31%,即通过最小二乘法去噪声后,策略整体每日亏损的风险为0.31%:

 

description

 

均方回撤(ADD^2)

 

均方回撤就是每日百分比回撤的平方的期望值,采用这种计量方法的原因在于认为策略样本内回测属于小样本评估,属于有偏估算。

 

均方回撤的计算方法同样简单,只需要将百分比回撤这一列的数据平方后,再求平均数即可,最终得到均方回撤的结果为0.32%:

 

description

 

回测引擎扩展

 

最后我们可以将上述3个新的回撤统计指标,添加到回测引擎的统计指标计算函数中。打开位于C:\vnstudio\Lib\site-packages\vnpy\app\cta_strategy目录中的backtesting.py文件,找到其中的calculate_statistics函数,修改的内容分为三步:

 

  1. 初始化新的回撤指标数据;
  2. 计算3个新的回撤指标数值;
  3. 打印输出新的回撤指标;

 

新增的代码如下:

 

...            
         average_drawdown = 0            
         lw_drawdown = 0            
         average_square_drawdown = 0   
...
         average_drawdown = df["ddpercent"].mean()            
         lw_drawdown = talib.LINEARREG(df["ddpercent"], total_days)[-1]

         df["ddpercent^2"] = df["ddpercent"] * df["ddpercent"]                             
         average_square_drawdown = - df["ddpercent^2"].mean()
...           
         self.output(f"百分比平均回撤: {average_drawdown:,.2f}%")                           
         self.output(f"百分比线性加权回撤: {lw_drawdown:,.2f}%")                           
         self.output(f"百分比均方回撤: {average_square_drawdown:,.2f}%")

 

完成修改后,再次回到Jupyter Notebook中执行回测,已经可以在打印输出的回测结果中找到三个新增的回撤指标:

 

description

 

同样在CLI命令行模式下,我们也可以很方便的这3个新的回撤指标(average_drawdown、lw_drawdown、average_square_drawdown),作为CTA策略参数优化的目标函数,筛选出稳健性更好的参数组合。

 

 

 

《vn.py全实战进阶 - 期权零基础入门》课程已经更新过半!内容专门面向从未接触过期权交易的新手,共计30节课程带你一步步掌握期权的基础知识、了解合约特征和品种细节、学习方向交易和套利组合等各种常用期权交易策略,详细内容请戳新课上线:《期权零基础入门》

发布于vn.py社区公众号【vnpy-community】

 

原文作者:用Python的交易员 | 发布时间:2020-02-11

 

新冠病毒肆虐,大家多多注意身体,有条件远程办公的还是远程吧,从概率角度讲复工后的一周风险也许是最大的。废话不多说,上周末发布了2020年的第一个版本v2.1.0:期权电子眼,主要更新了期权自动化交易(电子眼算法)相关的功能。

 

和之前一样,对于使用VN Studio的用户,启动VN Station后,直接点击界面右下角的【更新】按钮就能完成自动更新升级。对于没有安装的用户,请下载VNStudio-2.1.0,体验一键安装的量化交易Python发行版,下载链接:

 

https://download.vnpy.com/vnstudio-2.1.0.exe

 

OptionMaster电子眼

 

几大股指期权上线后已经平稳运行一个多月,期权自动交易方面的口风也没那么紧了,所以新版本开源了OptionMaster Pro中的全部自动交易功能。

 

期权领域的量化交易主要集中在波动率交易策略上。而期权的波动率无法从其价格上直接反应出来,需要通过定价公式来实时反推隐含波动率。

 

description

 

通过上图中的波动率曲线图表,交易员可以直观的看到波动率曲线(面)的形状,并且作出相应的判断分析,每个月份的波动率曲线采用不同的颜色显示:

 

  • 向上三角形:市场上看涨期权的买卖价隐含波动率中值
  • 向下三角形:市场上看跌期权的买卖价隐含波动率中值
  • 圆形曲线:当前系统内的定价参考波动率

 

用户可以通过顶部的勾选框来选择当前要显示波动率曲线的月份,方便根据自己的需求来详细分析单一月份的形状或者多月份的曲面形态。

 

对当前市场的隐含波动率曲线形态有所了解后,交易员需要根据自己的判断,使用下图中的波动率管理组件,来设置定价参考波动率,作为后续电子眼交易算法的定价依据:

 

description
 

定价参考波动率的设置需要比较多的期权定价原理知识,以及一定的波动率交易经验,对于单一月份波动率曲线拟合的流程如下:

 

  1. 点击【重置】按钮,将每个行权价的期权定价波动率,初始化为该行权价虚值期权(OTM)的中值波动率;
  2. 观察图表中的曲线形态,对于波动率异常的行权价,勾选【执行拟合】列的勾选框;
  3. 全部勾选好后,点击顶部的【拟合】按钮,基于Cubic Spline算法来对勾选了的行权价的波动率执行拟合计算;
  4. 拟合完成后,再次检查图表中的曲线形态,并通过【定价隐波】列的调节器进行细微调整

 

description

 

完成定价参考波动率曲线的拟合后,就可以通过上图中的期权电子眼算法(英文名Electronic Eye),来自动扫描市场上的最优交易机会(max price edge),并在Tick级的时间级别上瞬时完成交易。

 

在波动率交易过程中,无论是期权开平仓本身引入的Delta,或者是由于持仓Gamma随着市场波动带来的Delta,都必须根据交易需求及时处理:

 

  • 做多Gamma时锁定波动利润
  • 做空Gamma时对冲额外风险

 

description

 

使用上图中的Delta对冲算法,交易员可以根据自己的需求选择任意定价标的物作为对冲合约,并灵活设置对冲检查执行的频率、Delta对冲目标、对冲触发阈值,以及下单时采用的委托超价等参数,来实现Delta自动实时对冲的功能。

 

实时K线图表模块

 

在最初的设计上,vnpy.chart这个K线图表主要是为了满足CtaBacktester模块中对于CTA策略回测历史买卖点显示的需求。

 

没想到自从发布以来,社区中就不断出现实时K线图表的需求,加上前段时间的文华事件,开发一套100%完全开源的高性能图表工具,可能也变成了挺有价值的工作。

 

所以v2.1.0版本中,新增了ChartWizard模块,用于实时K线图表的显示,如下图:

 

description

 

目前只是第一个版本,仅仅实现了简单的1分钟实时K线行情显示,直接在本地合约代码的编辑框中输入vt_symbol(比如IF2002.CFFEX),点击【新建图表】的按钮就会打开对应合约的图表。

 

需要注意的是,vn.py本身并不提供任何数据服务,对于绘图要用到的历史K线数据,国内期货目前通过RQData数据服务获取(需要准备和配置RQData账号)。

 

后续会根据社区用户的反馈情况,来决定是否要进一步增加图表功能,例如:

 

  • 多周期K线图表
  • 任意时间范围
  • 技术指标
  • 画线分析
  • 等等(图上能玩的太多了~)

 

Excel RTD数据服务

 

RTD全称是RealTimeData,是微软主要为金融行业的实时数据需求设计的Excel数据对接方案,号称可以达到每秒全表60000次的刷新速率,能不能达到我们其实都不用管(其实1秒6次数字刷新普通人眼估计就要瞎了吧)...

 

对于个人投资者来说可能比较陌生,但对于机构交易员来说(不管量化还是主观),Wind/Choice等金融行情终端的Excel RTD插件,可以说是日常工作中常用的工具之一。毕竟即使是量化交易员,也没必要为了所有要跟踪的市场行情数据都开发一套前端界面(性价比太低),方便易用的Excel电子表格就是好的替代工具。

 

description

 

本次更新中添加的ExcelRtd模块,用于实现在Excel中访问vn.py程序内任意数据信息的功能,上图中的简单例子是在Excel表中获取来自CTP接口的实时行情数据刷新。ExcelRtd依赖于PyXLL模块(www.pyxll.com),该模块属于商业软件,需要购买才能使用(提供30天免费试用)。

 

恒生UFT接口

 

2.1.0版本中新增了恒生UFT接口的对接,可以用于期货(包括期货期权)和ETF期权的量化交易,终于vn.py可以对接国内几乎所有期货公司了(部分还是只有金仕达柜台的就算了)。

 

UFT接口在使用时比起CTP来说稍微麻烦一些:除了满足穿透式认证的需求外(获取AppID和AuthCode),还需要用户向期货公司申请获取接入授权文件(liscense.dat),具体请联系咨询期货公司的IT部门。

 

其他更新

 

vn.py的CI服务从CircleCI切换到了Github Actions,用了一个多月下来发现无论脚本配置方便度还是测试运行速度都有明显的提升,对于在vn.py上做二次扩展开发的用户强烈推荐使用。

 

PS:CI的全称是Continuous Integration,中文名“持续集成”,主要用于在每次更新代码后自动运行各类测试检查程序,来保证持续开发过程中整体代码的质量。许多用户反应v2.0后的代码质量有了明显的提升,其中CI的帮助居功至伟。

 

Python自从3.5版本引入Type-Hint可选类型声明后,代码的可读性以及IDE开发工具(尤其是VSCode)的智能提示检查功能都有了飞跃式的进步。vn.py在后续的开发中会全面加入Type-Hint的类型声明,目前主要完成了核心模块的修改(包括vnpy.trader/rpc/event模块)。

 

 

 

线上活动公告

 

考虑到大家的健康安全的风险,准备把2、3月份社区的线下活动全部改为线上活动的模式,计划在2月底举行的同时也是vn.py社区2020年第1次线上活动了。

 

内容:

  1. ChartWizard模块扩展开发
    a. vnpy.chart模块

    b. 静态绘图脚本

    c. 行情事件流

    d. 绘图图层开发

    e. 技术指标扩展

  2. ExcelRtd模块应用
    a. pyxll安装配置

 

时间: 2月29日下午14:00-16:00

报名费:99元

报名方式:扫描下方二维码

 

description

发布于vn.py社区公众号【vnpy-community】

 

原文作者:用Python的交易员 | 发布时间:2019-08-11

 

因为某些外部原因,年初开始和华尔街见闻合作的《Quant全实战成长计划》,在完成第一阶段的内容后等待了两个月的时间,目前已经确定无法再往下推进了。之前购买了的见闻课程,依旧可以通过见识APP来回看学习(直接用见闻账户登录见识APP即可),这点请放心。

 

考虑到社区里很多用户还是希望有在线课程来帮助学习vn.py在量化方面的进阶使用,接下来准备由我们核心开发团队自己,来独立制作一个完整的《vn.py全实战进阶》系列(避免再出现外部幺蛾子~~~)。

 

上周在QQ群里做了个关于主题的投票调查,结果如下:

description

 

决定接下来推出的第一阶段内容,就是CTA策略主题了。后面按顺序的应该是:价差交易、算法交易、期权波动率、其他内容。其中期权波动率和算法交易的票数比较接近,同时目前国内最主要的50ETF期权限制了新增程序化接入(只有老用户能用API),所以做了调整。

 

课程一共计划50节,内容大纲如下:

 

description

 

计划下周正式上线,直接在社区公众号(vnpy-community)里就能购买和观看,方便大家利用各种碎片时间,同样可以在PC上打开,边学边练更加深入。

发布于vn.py社区公众号【vnpy-community】
 
原文作者:用Python的交易员 | 发布时间:2020-04-17
 

v2.1.2版本的vn.py已经于本周三正式发布,此次主要更新的内容包括多标的组合策略模块PortfolioStrategy的支持。

 

和之前一样,对于使用VN Studio的用户,启动VN Station后,直接点击界面右下角的【更新】按钮就能完成自动更新升级,对于没有安装的用户,请下载VNStudio-2.1.2,体验一键安装的量化交易Python发行版,下载链接:

下载地址

 

PortfolioStrategy模块

 

PortfolioStrategy模块专门针对需要同时交易多合约的量化策略设计,满足其历史数据回测和实盘自动交易的需求,一些策略例子包括:

 

  • ETF基金轮动策略
  • 期货多空组合策略
  • 量化资产配置策略
  • 期权套利类策略
  • 波动率交易策略
  • 股票Alpha类策略

 

目前模块自带的Demo策略只有个最简单的趋势跟踪策略(AtrRsiStrategy移植过来的),后面更新中争取会把上述每个策略类型都提供一个Demo。

 

相同点

 

PortfolioStrategy模块在设计上为了降低大家的学习成本,采用了尽可能接近CTA策略模块的方案。下图中展示的是一套铁矿石期权平价套利策略在Jupyter Notebook中的回测结果,同时涉及到三个合约,包括看涨期权、看跌期权、铁矿石期货:

 

description

 

除了上图中的回测功能外,PortfolioStrategy的实盘自动交易管理界面也和CtaStrategy比较相似,只是少掉了右上角停止单状态的显示区域:

 

description
 

对于已经掌握CTA策略开发的用户来说,基本可以无缝过渡到多标的组合策略开发上:

 

  1. 继承策略模板StrategyTemplate,创建一个新的策略类
  2. 在回调函数on_bars中,接收K线行情推送,并实现核心交易逻辑
  3. 调用buy/sell/short/cover/cancel_order等函数来发送交易请求

 

不同点

 

当然,毕竟是针对不同类型的量化策略,PortfolioStrategy在一些细节方面和CTA策略模块还是有所区别:

 

CtaStrategyCTA策略 PortfolioStrategy组合策略
K线推送 on_bar收到单一合约的K线 on_bars同时收到所有合约的K线
TICK推送 on_tick回测和实盘均支持 on_tick只有实盘中会调用,回测不支持
成交推送 on_trade
委托推送 on_order
查询持仓 通过访问策略的pos获取 调用get_pos获取某一合约的持仓
查询委托 调用get_order获取某一委托的状态
查询活动委托号 调用get_all_active_orderids获取当前全部活动委托号
交易委托 buy/sell/short/cover只需传入价格、数量 buy/sell/short/cover需要传入合约代码、价格、数量

 

其中最主要的区别,在于组合策略在接收K线推送时,是通过on_bars回调函数一次性接收该时间点上所有合约的K线数据,而不是通过on_bar函数一个个接收(无法判断当前时点的K线是否全部走完了 )。组合策略的回测只支持K线模式,但在实盘中用户可以在on_tick回调下自行管理所有K线的合成推送逻辑(比如用非标准的K线切分点)。

 

第二个区别点,在于组合策略中需要对多合约同时下单交易,在回测时无法判断某一段K线内部每个合约委托成交的先后时间顺序,因此无法提供on_order和on_trade获取委托成交推送,而只能在每次on_bars回调时通过get_pos和get_order来进行相关的状态查询。

 

第三点区别在于委托下单函数:组合策略在调用委托函数下单时必须传入具体要交易合约的代码,而不像CTA策略只能交易一个合约所以用不着。同时组合策略模块只支持限价单交易,不提供停止单功能(StopOrder)。

 

其他更新

 

DataManager模块发布后,有一堆用户询问了如何删除不需要的数据的问题,所以新版本中我们加上了一键删除的功能,就在查询出来后的数据统计结果每行的最右侧按钮。
 

接口方面的更新包括:
 

  1. 新增中亿汇达ComStar接口,主要用于国内银行间市场(中国外汇交易中心,简称CFETS)的债券固收产品交易,请注意ComStar只有各类大型金融机构才能用(券商自营交易部、银行金融市场部等),私募或者个人都用不了;
  2. 新增恒生集中柜台期权接口HsoptionGateway,实现提供ETF期权的API接入,主要是部分券商使用,比如中信证券;
  3. 中泰证券的XtpGateway增加期权交易功能的支持,至此XTP已经成为vn.py证券这边支持功能最完整的接口了(现货、两融、期权);
  4. 对易盛外盘接口TapGateway进行了代码重构,提供更好的代码可读性方便修改。

 

 
 

vn.py核心团队的底层接口开发小班课上线

日期:2020年5月16日(周六)和5月17日(周日)

时间:两天下午1点-6点,共计10小时

内容:

  • 两天共计10小时的实战训练课程

  • 市场微观结构知识的详细剖析

  • C++代码的Python封装实操

    • pybind11
    • ctypes
    • cython
  • 底层接口业务功能的对接设计

    • 扩展订单类型:FOK、FAK、Post-Only
    • 特殊交易指令:询价RFQ、做市报价Quoting

价格:9999元

报名请发送邮件到vn.py@foxmail.com,注明想参加的课程、姓名、手机、公司、职位。

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

沪公网安备 31011502017034号

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