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

VNPY的源代码目录众多,分门别类非常多,许多文件的内容都很类似,甚至函数名称都是一样的,如果不做特别设置,查询代码会把一些你不使用的app、api、gateway下的文件都查询出来,很是烦人,经过本人琢磨,发现这么设置就不错:

创建VNPY工作区

随便怎么命名都可以,我就为其取名VNPY,如下图所示:

1 把vnpy的源代码目录加入工作区
2 把自己的策略源代码目录加入工作区
description

查询代码设置步骤

  • 1 点击查找图标;
  • 2 输入查找字符串;
  • 3 设置需要包含的目录或者文件,只输入文件夹名称会包含下面的文件和子文件夹下的文件。需要输入多个文件夹时,用逗号割开;
  • 4 设置需要排除的文件,与步骤3类似,可以使用通配符,如图所示:

经过这样设置以后,当你输入任何要查找字符串的时候,就不会显示那些你不感兴趣的app、gateway下的子目录的文件了。本人目前感兴趣的是trader,event,chart,rpc,app\cta_strategy,app\rpc_service,gateway\ctp,api,user_tools这些子目录中的文件,你也可以根据自己的需要特别设定,以提高代码查询的效率。
description

1 几乎所有例子的仓位都是self.fixed_size=1

进阶课程里看过陈晓优老师讲的很多策略例子,都是讲如何买和如何卖,如何止盈和止损的,又是仿真、优化、实盘的,看到人是心潮澎湃!
于是看是着手仿照例子编写自己的策略,策略经过计算买卖信号后,总是要下单的,那我下多大仓位呢? 回过头这时候参考例子才发现,几乎所有的仓位都是self.fixed_size=1,就没有讲如何动态决定仓位的例子!
于是VNPY的QQ群里问前辈、先知,无人回答,再在论坛里问老师,终于回答了:”不建议动态仓位,这么重要的事情必须交给手工完成!“,这个答复让我有点懵——做量化交易你让为手动决定仓位???

2 是不建议动态仓位,还是无法动态仓位?

动态仓位需要什么条件:

  • 开仓价格(Price)
  • 可用资金(Money)
  • 合约乘数(Size)
  • 保证金(率)(Margin)
  • 手续费(率)(FeeRate)
  • 开仓的量 (N)
  • 开仓资金(KM)

假设是按手续费率(而不是按手收费),那么开仓资金为:

KM = Price*N*Size*Margin (1+ FeeRate)

那么必须符合添加KM < Money,进而推出

 N < Money/(Price*Size*Margin (1+ FeeRate))。

当然你不可能满仓干,也许还要一个最大开仓资金比例R,例如:

N = int(Money*R/(Price*Size*Margin (1+ FeeRate)))。

当R=40%表示用你账户里40%的资金,可以动态开仓的手数。这样不就可以动态开仓了吗?
当然实际开仓时的仓位计算可能比这复杂多了,比如你可以考虑交易合约的波动水平,需要考虑投资者愿意承担的风险水平等等,但不管怎么变化,策略动态开仓都必须要有如下这几个参数:

  • 开仓价格(Price)
  • 合约乘数(Size)
  • 可用资金(Money)
  • 保证金(率)(Margin)
  • 手续费(率)(FeeRate)

3 用户策略怎么可以与”钱“无关?

经过艰苦和漫长的代码研读和梳理,发现CTA策略交易中只有pos、trading和inited这些策略成员,没有与资金相关的东西。我们来看看这几个动态下单必须具备的参数是否提供了:

  • 开仓价格(Price):策略从合约的行情数据中提取
  • 合约乘数(Size):main_engine中可以使用vt_symbol作为参数提取,函数是main_engine.get_contract()
  • 可用资金(Money):目前有,可以通过 main_engine.get_account()函数提供 但是是当你运行多个策略的时候,这些策略是共用同一个账户的,无法直接使用。
  • 保证金(率)(Margin):目前有,但是这是各个交易所公布的统一的保证金(率),不是你开户的期货各种给你的保证金(率),如rb2010.SHFE的保证金是率8%,可是也许你的开户的期货公司给你的保证金率为却为12%或者15%,怎么用?
  • 手续费(率)(FeeRate):目前没有,而且这个费率回是因人而异的,这样是看你怎么和你的期货经纪人怎么谈判的,所以也没有办法用!

结论:

目前VNPY的CTA策略因为缺少上述几个关键参数,无法实现动态仓位交易。是不能也,而非不可以!

4 为CTA账户引入策略账户!

作为交易赚钱的CTA策略,怎么可以不与这些资金相关的参数打交道?因人而异的保证金和手续费不应该成为不提供这些参数的理由!
当多个策略在同时运行的时候,你的实际账户权益的消长,到底是哪个策略赚的,哪个策略赔的都无法说清楚,运行3天后就已经是一本糊涂账了,这怎么可以!

虽然有上面的困难,但是办法总比困难多!可以参考文华财经8.3或者库安9.0的办法(熟悉文华财经客户端的人应该都知道),它们的方法是用模组账户的方法来为每个用户模组创建一个虚拟的模组账户,很好地解决用户算法对资金、保证金和手续费等参数的设定!

策略账户的功能:

  1. 分配交易的初始资金,还可以做出金入金等虚拟操作,目的就是控制策略的交易规模。
  2. 设置各个合约实际使用的保证金(率)
  3. 设置各个合约可以使用的手续费(率),包括开仓、平仓和平今仓手续费(率)
  4. 可以记录策略产出的委托单
  5. 可以记录策略初始的成交单
  6. 可以为策略提供合约的当前可用资金、保证金(率)和手续费(率)
  7. 算策略的历史交易盈亏和当前交易的浮动盈亏
  8. 提供当前策略账户的权益和可用资金
  9. 提供策略自创建以来的所有历史委托单和成交单查询,解决目前CTA策略之知道最新活动委托单,当日成交单和未平仓的仓位,而不知道历史交易的情况的问题。

如何实现策略账户 ?

策略账户的已经基本上实现了,目前只在测试中,且看我一步一步慢慢为大家分享......

5. 最新进展

有兴趣的可以先看看 策略账户界面展示

K线的种类可以这样划分:

按K线周期长度:

  • 1 常规周期K线
  • 2 自定义K周期线

按K线参考起始时间划分:

  • 1 等自然时长K线
  • 2 等交易时长K线
    • 1)参考交易日起点的等交易时长K线
    • 2)参考日盘起点和夜盘起点的等交易时长K线
    • 3)参考合约上市日起点的等交易时长K线

一、按照K线周期划分

  • 1 常规周期K线:如1,5,10,15,20,30分钟K线,1,2,4小时K线,日K线,周K线、月K线,年K线等。它们通常在常见的金融终端的GUI界面都会出现。因为是常规周期K线,为了加快软件的加载、显示和速度,常常把它们直接在服务器的数据库中做存储。这样做的好处是K线数据的处理和加载简单,显示速度快,缺点是需要为每个用户可能用到的周都准备一个表。
  • 2 自定义周期K线:在常规周期之外,用户自定义时间周期的K线,如7,19分钟周期K线。

VNPY对常规周期K线和自定义周期K线的处理是一视同仁的,统一使用BarGenerator()就可以了,它只使用1分钟K线数据,根据需要产生N分钟K线,至于产生出来是常规周期K线,还是自定义周期K线,主要看N是多少了。这样做的好处是自由!缺点是处理复杂些、速度慢些(不过反正不用手工处理,让计算机做,无所谓)。

二、按K线起始时间的参考起点划分

1 等自然时长K线

它是按照自然时间来计算一根K线是否结束的。如对于30分钟K线,rb2010合约在每个交易日都会产生这样的一组K线:
起止时间 交易时长
21:00-21:30——30分钟
21:30-22:00——30分钟
22:00-22:30——30分钟
22:30-23:00——30分钟
23:00-23:30——30分钟
09:00-09:30——30分钟
09:30-10:00——30分钟
10:00-10:30——15分钟(?)
10:30-11:00——30分钟
11:00-11:30——30分钟
13:30-14:00——30分钟
14:00-14:30——30分钟
14:30-15:00——30分钟
目前vnpy的BarGenerator()就是这种K线参考机制。优点点是实现简单,无需考虑交易时段;缺点是明天都会有特别30分钟K是个另类:10:00-10:30的K线,其实只交易了15分钟!

2 等交易时长K线

熟悉文华财经8.3的应该熟悉下图的设置:
description

1)参考交易日起点

以合约的日交易时间的起点为参考,然后以等交易时间宽度产生K线。如文华财经的常规周期K线划分中的 “交易时间机制”就是这种情况。
如RB2010交易时间段:'21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00'。以交易日起点方式的30分钟K线分别是
起止时间 交易时长
21:00-21:30——30分钟
21:30-22:00——30分钟
22:00-22:30——30分钟
22:30-23:00——30分钟
23:00-23:30——30分钟
09:00-09:30——30分钟
09:30-10:00——30分钟
10:00-10:45——30分钟
10:45-11:15——30分钟
11:15-11:45——30分钟
13:45-14:15——30分钟
14:15-14:45——30分钟
14:45-15:00——15分钟(?)

这种K线时间参考机制的优点是不会模糊每日开市时的跳空行情,缺点是每日都可能产生一根交易时间不足30分钟的K线。

2)参考日盘起点和夜盘起点

分别以合约的日盘交易时间起点和夜盘交易时间起点为参考,然后以等交易时间宽度产生K线。如文华财经的常规周期K线划分中的 “交易时间机制改进型”就是这种情况。
如RB2010交易时间段:夜盘:'21:00-02:30, 日盘:09:00-10:15,10:30-11:30,13:30-15:00'。以交易日起点方式的60分钟K线分别是
夜盘K线:
起止时间 交易时长
21:00-22:00——60分钟
22:00-23:00——60分钟
23:00-00:00——60分钟
00:00-01:00——60分钟
01:00-02:00——60分钟
02:00-02:30——30分钟(?)
日盘K线:
起止时间 交易时长
09:00-10:00——60分钟
10:00-11:15——60分钟
11:15-11:15——60分钟
11:15-14:15——60分钟
14:15-15:00——45分钟(?)
这种K线时间参考机制的优点是不会模糊各种日盘和夜盘开市时的跳空行情,缺点是每日都可能产生交易时长不足60分钟的K线。

3)参考合约上市日起点

以合约上市日的交易时间起点为参考,取出节假日,考虑交易时段,然后以等交易时间宽度产生K线。如FixedBarGenerator对N分钟K线的处理就是这种情况。FixedBarGenerator的原理参见具体实现代码参见在前面的帖子里面已经描述得比较清楚了,这里不再重复。

参考合约上市日起点、等交易时间的好处是:所有的K线的交易时长都是想等的,它们的成交量是可类比的。因为交易时长不等的K线产生的所谓“地量”或者“天量”,有多大意义是可想而知的。这种K线产生机制的缺点是:可能产生跨交易日,跨日盘和夜盘的K线,这样可能造成观察不到交易日开盘跳空,日盘和夜盘开市时的跳空现象,因为可能那根K线还没有结束。

对声称“三行代码搞定国内期货10:15-10:30的K线“的看法:

总有人声称“三行代码搞定国内期货10:15-10:30的K线“,并且上了精华版。三行代码可以搞定?那只是按下葫芦起了瓢的解决方法。
同样是国内期货,IF就没有这样的休市时间段,难倒你都要有这么做?自定义周期7分钟,19分钟K线你有怎么办?还有 再说了,就是改也最好是对BarGenerator扩展定制一个新的K线生成器,二不能直接修改BarGenerator的代码,修改它会导致在不同的合约、某些周期长度上出错,而你却浑然不知!
举例下面这些合约,都是没有10:15-10:30休市时间段的

合约代码:IC2008 名称:中证500指数2008 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC888 名称:中证主力连续价差平滑 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC99 名称:中证指数连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC2009 名称:中证500指数2009 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC2103 名称:中证500指数2103 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC88A3 名称:中证次次主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC88 名称:中证主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC889 名称:中证主力连续价差平滑(后复权) 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC2012 名称:中证500指数2012 交易时间段:09:31-11:30,13:01-15:00
合约代码:IC88A2 名称:中证次主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF888 名称:沪深主力连续价差平滑 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF2009 名称:IF2009 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF2103 名称:IF2103 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF2012 名称:IF2012 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF889 名称:沪深主力连续价差平滑(后复权) 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF2008 名称:IF2008 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF88A2 名称:沪深次主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF88 名称:沪深主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF88A3 名称:沪深次次主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IF99 名称:沪深指数连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH2103 名称:上证50指数2103 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH88 名称:上证主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH888 名称:上证主力连续价差平滑 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH2012 名称:上证50指数2012 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH2009 名称:上证50指数2009 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH2008 名称:上证50指数2008 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH88A3 名称:上证次次主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH88A2 名称:上证次主力连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH889 名称:上证主力连续价差平滑(后复权) 交易时间段:09:31-11:30,13:01-15:00
合约代码:IH99 名称:上证指数连续 交易时间段:09:31-11:30,13:01-15:00
合约代码:T2009 名称:10年期国债2009 交易时间段:09:31-11:30,13:01-15:15
合约代码:T888 名称:年期国债主力连续价差平滑 交易时间段:09:31-11:30,13:01-15:15
合约代码:T889 名称:年期国债主力连续价差平滑(后复权) 交易时间段:09:31-11:30,13:01-15:15
合约代码:T99 名称:年期国债指数连续 交易时间段:09:31-11:30,13:01-15:15
合约代码:T88 名称:年期国债主力连续 交易时间段:09:31-11:30,13:01-15:15
合约代码:T2012 名称:10年期国债2012 交易时间段:09:31-11:30,13:01-15:15
合约代码:T2103 名称:10年期国债2103 交易时间段:09:31-11:30,13:01-15:15
合约代码:TF2009 名称:5年期国债2009 交易时间段:09:31-11:30,13:01-15:15
合约代码:TF2012 名称:5年期国债2012 交易时间段:09:31-11:30,13:01-15:15
合约代码:TF88 名称:年期国债主力连续 交易时间段:09:31-11:30,13:01-15:15
合约代码:TS888 名称:年期国债主力连续价差平滑 交易时间段:09:31-11:30,13:01-15:15
合约代码:TF889 名称:年期国债主力连续价差平滑(后复权) 交易时间段:09:31-11:30,13:01-15:15
合约代码:TF99 名称:年期国债指数连续 交易时间段:09:31-11:30,13:01-15:15
合约代码:TF888 名称:年期国债主力连续价差平滑 交易时间段:09:31-11:30,13:01-15:15
合约代码:TF2103 名称:5年期国债2103 交易时间段:09:31-11:30,13:01-15:15
合约代码:TS2012 名称:2年期国债2012 交易时间段:09:31-11:30,13:01-15:15
合约代码:TS889 名称:年期国债主力连续价差平滑(后复权) 交易时间段:09:31-11:30,13:01-15:15
合约代码:TS99 名称:年期国债指数连续 交易时间段:09:31-11:30,13:01-15:15
合约代码:TS88 名称:年期国债主力连续 交易时间段:09:31-11:30,13:01-15:15
合约代码:TS2009 名称:2年期国债2009 交易时间段:09:31-11:30,13:01-15:15
合约代码:TS2103 名称:2年期国债2103 交易时间段:09:31-11:30,13:01-15:15

总结:

鱼和熊掌不可兼得,每种K线产生机制都有优点和缺点,至于您选择哪一种来产生你的K线,就看你的取舍了。明白与不明白个中的道理却是大相径庭的,所谓胸有成竹,才能够临危不乱。谨以本贴分享本人的一点浅薄之见,希望能够帮助到您!

K线种类总结:

K线的种类可以这样划分:
按K线周期长度:

  • 1 常规周期K线
  • 2 自定义K周期线

按K线参考起始时间划分:

  • 1 等自然时长K线
  • 2 等交易时长K线
    • 1)参考交易日起点的等交易时长K线
    • 2)参考日盘起点和夜盘起点的等交易时长K线
    • 3)参考合约上市日起点的等交易时长K线

一、按照K线周期划分

  • 1 常规周期K线:如1,5,10,15,20,30分钟K线,1,2,4小时K线,日K线,周K线、月K线,年K线等。它们通常在常见的金融终端的GUI界面都会出现。因为是常规周期K线,为了加快软件的加载、显示和速度,常常把它们直接在服务器的数据库中做存储。这样做的好处是K线数据的处理和加载简单,显示速度快,缺点是需要为每个用户可能用到的周都准备一个表。
  • 2 自定义周期K线:在常规周期之外,用户自定义时间周期的K线,如7,19分钟周期K线。

VNPY对常规周期K线和自定义周期K线的处理是一视同仁的,统一使用BarGenerator()就可以了,它只使用1分钟K线数据,根据需要产生N分钟K线,至于产生出来是常规周期K线,还是自定义周期K线,主要看N是多少了。这样做的好处是自由!缺点是处理复杂些、速度慢些(不过反正不用手工处理,让计算机做,无所谓)。

二、按K线起始时间的参考起点划分

1 等自然时长K线

它是按照自然时间来计算一根K线是否结束的。如对于30分钟K线,rb2010合约在每个交易日都会产生这样的一组K线:
起止时间 交易时长
21:00-21:30——30分钟
21:30-22:00——30分钟
22:00-22:30——30分钟
22:30-23:00——30分钟
23:00-23:30——30分钟
09:00-09:30——30分钟
09:30-10:00——30分钟
10:00-10:30——15分钟(?)
10:30-11:00——30分钟
11:00-11:30——30分钟
13:30-14:00——30分钟
14:00-14:30——30分钟
14:30-15:00——30分钟
目前vnpy的BarGenerator()就是这种K线参考机制。优点点是实现简单,无需考虑交易时段;缺点是明天都会有特别30分钟K是个另类:10:00-10:30的K线,其实只交易了15分钟!

2 等交易时长K线

熟悉文华财经8.3的应该熟悉下图的设置:
description

1)参考交易日起点

以合约的日交易时间的起点为参考,然后以等交易时间宽度产生K线。如文华财经的常规周期K线划分中的 “交易时间机制”就是这种情况。
如RB2010交易时间段:'21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00'。以交易日起点方式的30分钟K线分别是
起止时间 交易时长
21:00-21:30——30分钟
21:30-22:00——30分钟
22:00-22:30——30分钟
22:30-23:00——30分钟
23:00-23:30——30分钟
09:00-09:30——30分钟
09:30-10:00——30分钟
10:00-10:45——30分钟
10:45-11:15——30分钟
11:15-11:45——30分钟
13:45-14:15——30分钟
14:15-14:45——30分钟
14:45-15:00——15分钟(?)

这种K线时间参考机制的优点是不会模糊每日开市时的跳空行情,缺点是每日都可能产生一根交易时间不足30分钟的K线。

2)参考日盘起点和夜盘起点

分别以合约的日盘交易时间起点和夜盘交易时间起点为参考,然后以等交易时间宽度产生K线。如文华财经的常规周期K线划分中的 “交易时间机制改进型”就是这种情况。
如RB2010交易时间段:夜盘:'21:00-02:30, 日盘:09:00-10:15,10:30-11:30,13:30-15:00'。以交易日起点方式的60分钟K线分别是
夜盘K线:
起止时间 交易时长
21:00-22:00——60分钟
22:00-23:00——60分钟
23:00-00:00——60分钟
00:00-01:00——60分钟
01:00-02:00——60分钟
02:00-02:30——30分钟(?)
日盘K线:
起止时间 交易时长
09:00-10:00——60分钟
10:00-11:15——60分钟
11:15-11:15——60分钟
11:15-14:15——60分钟
14:15-15:00——45分钟(?)
这种K线时间参考机制的优点是不会模糊各种日盘和夜盘开市时的跳空行情,缺点是每日都可能产生交易时长不足60分钟的K线。

3)参考合约上市日起点

以合约上市日的交易时间起点为参考,取出节假日,考虑交易时段,然后以等交易时间宽度产生K线。如FixedBarGenerator对N分钟K线的处理就是这种情况。FixedBarGenerator的原理参见具体实现代码参见在前面的帖子里面已经描述得比较清楚了,这里不再重复。

参考合约上市日起点、等交易时间的好处是:所有的K线的交易时长都是想等的,它们的成交量是可类比的。因为交易时长不等的K线产生的所谓“地量”或者“天量”,有多大意义是可想而知的。这种K线产生机制的缺点是:可能产生跨交易日,跨日盘和夜盘的K线,这样可能造成观察不到交易日开盘跳空,日盘和夜盘开市时的跳空现象,因为可能那根K线还没有结束。

总结:

鱼和熊掌不可兼得,每种K线产生机制都有优点和缺点,至于您选择哪一种来产生你的K线,就看你的取舍了。明白与不明白个中的道理却是大相径庭的,所谓胸有成竹,才能够临危不乱。谨以本贴分享本人的一点浅薄之见,希望能够帮助到您!

无法统一的合约参数

合约参数中的保证金比例率和手续费是最难搞定的。

  • 保证金率:交易所有统一的保证金率,期货公司可以在交易所的基础上加收保证金,这些东西也是可以谈判的,导致因人而异的保证金率,但这就是现实。
  • 手续费分两种:1)按手数计算的,开仓手续费,平仓手续费和平今仓手续费;2)按交易金额计算的,开仓手续费率,平仓手续费率和平今仓手续费率。
    ## 合约参数中最无法统一的是保证金比例和手续费率。
    虽然交易所在某个时段内会统一发布上线合约的保证金率和手续费(率),可是这些信息对普通投资者没有多大意义,因为不同的期货公司出于安全的考虑,会在交易所发布合约保证金率的基础上加收额外的比例的保证金,另外由于投资者找到的不同经纪人、不同的期货公司,会得到不同的交易手续费,另外即使你找到同一家期货公司的同一个经纪人,也会因为统治者资金实力不同,谈判技巧的不同而造成交易手续费(率)的不同。这导致你不要通话网络从服务器获得交易合约的保证金比例和手续费率,因为他们通常是提供你开户的网址上公布的,或者文件,手机短信的方式通知的。
    这对我们的策略在计算账户剩余可用资金方面带来了巨大的不便!怎么办?方法总是比困难多!

合约参数维护

本贴介绍一种本人研究出来的一种办法:
1)手工创建合约参数文件(格式为json文件),内容包含:合约代码,市场,合约名称,类别,多头保证金率,空头保证金率,开仓手续费(率),平仓手续费(率),平今仓手续费(率)。
2)创建合约参数类,它的功能是从json文件夹中加载合约参数到合约参数字典,然后为外部提供合约参数查询功能。
3)当合约保证金率或者手续费(率)发生变化时,及时手动修改该json的内容就可以。

实现代码

[.vntrader]\contract_param.json文件内容

{
    "rb2010.SHFE": {
        "inverse":false,
        "margin_rate": 0.15,
        "open_fee":{"费率":0.001},
        "close_fee":{"费率":0.001},
        "close_today_fee":{"费率":0.001}  
    },
    "ag2012.SHFE": {
        "inverse":false,
        "margin_rate": 0.15,
        "open_fee":{"费率":0.005},
        "close_fee":{"费率":0.005},
        "close_today_fee":{"费率":0.005}  
    },
    "ap2012.SHFE": {
        "inverse":false,
        "margin_rate": 0.15,
        "open_fee":{"每手":5},
        "close_fee":{"每手":5},
        "close_today_fee":{"每手":5}  
    },
    "al2010.SHFE": {
        "inverse":false,
        "margin_rate": 0.15,
        "open_fee":{"每手":3},
        "close_fee":{"每手":3},
        "close_today_fee":{"每手":0}      
    }
}

vnpy\usertools\contract_param.py文件内容

from dataclasses import dataclass
from enum import Enum
from typing import Tuple,List,Dict,Union,Set,Sequence,Optional
from vnpy.trader.utility import load_json, save_json, extract_vt_symbol

from vnpy.trader.constant import Direction, Exchange, Interval, Offset, Status,Product, OptionType, OrderType

import datetime
import rqdatac as rq
from rqdatac.utils import to_date


''' 获得上市日期 '''
def get_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.listed_date)

''' 获得交割日期 '''
def get_de_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.de_listed_date)


def str2date(date_str:str):
    ''' 
    日期字符串转化到datetme.date() 
    '''
    return datetime.datetime.strptime(date_str,"%Y-%m-%d").date()

class FeeType(Enum):
    """
    Commission fee type.
    """
    LOT = "每手"    # 手续费 :LOT per lot
    RATE = "费率"   # 手续费率 :RATE%


@dataclass
class ContractParameter():
    """
    合约参数,包含:
        合约代码,市场,合约名称,类别,
        多头保证金率,空头保证金率,
        开仓手续费(率),平仓手续费(率),平今仓手续费(率),
    """

    symbol:str                      # 合约代码
    exchange:Exchange               # 交易所
    name:str                        # 合约名称
    product:Product                 # 类别
    listed_date:datetime.date       # 上市日期
    maturity_date:datetime.date     # 到期日期
    de_listed_date:datetime.date    # 交割日期
    size : int                      # 合约乘数

    # 以下来自手工json文件
    inverse:bool                    # 反向合约
    margin_rate:float               # 保证金率(%)    
    # FeeType.LOT: 手续费;FeeType.RATE:手续费率(%)
    open_fee:Tuple[FeeType,float]           # 开仓手续费(率)
    close_fee:Tuple[FeeType,float]          # 平仓手续费(率)
    close_today_fee:Tuple[FeeType,float]    # 平今仓手续费(率)

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


class ContractParameters():

    """ 
    合约参数类,从json文件读取合约参数 
    注意:对包含保证金率和手续费(率)的json文件的处理只有加载操作,没有修改和删除操作。
         修改和删除合约参数操作通过手工操作就可以了。
    """

    contract_parameters_file = "contract_param.json"
    contract_parameters:Dict[str,ContractParameter] = {}

    def __init__(self):
        self.load_contract_paramters()

    def load_contract_paramters(self):
        """ 读取contract_param.json中的合约参数列表 """
        parameters = load_json(self.contract_parameters_file)

        for vt_symbol,param in parameters.items():
            symbol,exchange = extract_vt_symbol(vt_symbol)
            contract = rq.instruments(symbol.upper())

            # print(f"{contract}")

            cparam = ContractParameter(
                        symbol = symbol,
                        exchange = exchange,
                        product = contract.product,
                        name = contract.symbol,
                        listed_date = str2date(contract.listed_date),
                        maturity_date = str2date(contract.maturity_date),
                        de_listed_date = str2date(contract.de_listed_date),
                        size = contract.contract_multiplier,
                        inverse = param["inverse"],
                        margin_rate = param['margin_rate'],
                        open_fee = param['open_fee'],
                        close_fee = param['close_fee'],
                        close_today_fee = param.get("close_today_fee",{})
                    )

            self.contract_parameters[cparam.vt_symbol] = cparam

    def get_paramter(self,vt_symbol:str):
        return self.contract_parameters.get(vt_symbol,{})



if __name__ == "__main__":
    rq.init('18096678138','Rq131466',("rqdatad-pro.ricequant.com",16011))

    contract_parameters = ContractParameters()
    # print(f"{contract_parameters.contract_parameters}")

    vt_symbols = ['rb2010.SHFE','ag2012.SHFE','ap2012.SHFE','al2010.SHFE','cu2010.SHFE']
    for vt_symbol in vt_symbols:
        param = contract_parameters.get_paramter(vt_symbol) 
        print(f"\n{vt_symbol}'s contract parameters: \n{param}")

    # last_trade_date = rq.get_latest_trading_date() 
    # instruments = rq.all_instruments(type='Future',date=last_trade_date,market='cn')

    # print(f"{last_trade_date} {type(instruments)} ")
    # for idx,instrument in instruments.iterrows():
    #     # print(f"{type(instrument)}   {instrument}")
    #     order_book_id = instrument['order_book_id']
    #     symbol = instrument['symbol']
    #     trading_hours = instrument['trading_hours']
    #     # print(f"{order_book_id,symbol,trading_hours}")

    #     if trading_hours.find("09:01-10:15") < 0:
    #         print(f"合约代码:{order_book_id} 名称:{symbol} 交易时间段:{trading_hours}")

    # print(f"finished")

测试结果

contract_param.py文件中自带测试代码,直接运行可以得到如下结果:

rb2010.SHFE's contract parameters: 
ContractParameter(symbol='rb2010', exchange=<Exchange.SHFE: 'SHFE'>, name='螺纹钢2010', product='Commodity', listed_date=datetime.date(2019, 10, 16), maturity_date=datetime.date(2020, 10, 15), de_listed_date=datetime.date(2020, 10, 15), size=10.0, inverse=False, margin_rate=0.15, open_fee={'费率': 0.001}, close_fee={'费率': 0.001}, close_today_fee={'费率': 0.001})

ag2012.SHFE's contract parameters: 
ContractParameter(symbol='ag2012', exchange=<Exchange.SHFE: 'SHFE'>, name='白银2012', product='Commodity', listed_date=datetime.date(2019, 12, 17), maturity_date=datetime.date(2020, 12, 15), de_listed_date=datetime.date(2020, 12, 15), size=15.0, inverse=False, margin_rate=0.15, open_fee={'费率': 0.005}, close_fee={'费率': 0.005}, close_today_fee={'费率': 0.005})

ap2012.SHFE's contract parameters: 
ContractParameter(symbol='ap2012', exchange=<Exchange.SHFE: 'SHFE'>, name='鲜苹果2012', product='Commodity', listed_date=datetime.date(2019, 12, 16), maturity_date=datetime.date(2020, 12, 14), de_listed_date=datetime.date(2020, 12, 14), size=10.0, inverse=False, margin_rate=0.15, open_fee={'每手': 5}, close_fee={'每手': 5}, close_today_fee={'每手': 5})

al2010.SHFE's contract parameters:
ContractParameter(symbol='al2010', exchange=<Exchange.SHFE: 'SHFE'>, name='铝2010', product='Commodity', listed_date=datetime.date(2019, 10, 16), maturity_date=datetime.date(2020, 10, 15), de_listed_date=datetime.date(2020, 10, 15), size=5.0, inverse=False, margin_rate=0.15, open_fee={'每手': 3}, close_fee={'每手': 3}, close_today_fee={'每手': 0})

cu2010.SHFE's contract parameters:
{}

CTA策略的交易活动包含哪些?

CTA策略的交易活动可以使用下面这些来描述:

  • 策略被接受的停止单(如果使用了停止单);
  • 策略被接受的委托单;
  • 策略收到的成交单;
  • 策略的合约持仓;
  • 策略的权益统计。

CTA策略的交易活动的呈现形式

  • 委托单以粉色的实心上箭头(买开)和空心下箭头(卖平)标识,蓝色的实心下箭头(卖开)和空心上箭头(买平)来标识,标注在发生K线的接受时刻。因为停止单实际上是转化为合约当时的涨停价或跌停价的委托单,所以停止单也是以限价委托单的形式来标注的;
  • 成交单以红色的实心上箭头(买开)和空心下箭头(卖平)标识,绿色的实心下箭头(卖开)和空心上箭头(买平)来标识,标注在发生K线的接受时刻;
  • 策略持仓以标题变量的方式显示在主题的标题栏中,随光标所在K线的变化而变化。包含:多头累积持仓,空头持仓,以及在当根K线时浮动盈亏,冻结仓位等信息;
  • 策略的权益统计也是以标题变量的方式显示在主题的标题栏中,随光标所在K线的变化而变化。包含策略自创建以来的权益变化信息。
  • 所有在主图标题栏中的显示信息,可以在策略中配置的,以便只显示你感兴趣的标题项。

几个概念:

  • 一次完整交易:策略的持仓从零开始到非零,再从非零仓位变成零仓位的过程。
  • 策略权益:初始分配资金+各次已完成交易的盈亏+未完成交易的浮动盈亏。
  • 策略账户:每个策略实例对应一个策略账户。策略账户中包含为策略分配的初始资金、历史委托单、历史成交单、历史持仓。利用这些信息,策略账户可以提供策略账户的当前权益,策略账户可用资金。

需要完成的工作

  • 实现一个策略交易监视器。它是一个rpc_server,具有按策略实例名称的方式对策略的委托单、成交单、持仓进行记录、查询、持仓统计的功能。可以在本地运行,也可以单独运行在远程。
  • 实现一个包含rpc_client的cta_template的扩展模版。它可以为其派生的用户策略提供下面的功能:
    1)策略初始化时创建K线图表窗口
    2)可以为用户策略提供K线主图指标、附图指标
    3)策略运行时,把on_order()、on_trade()收到的OrderData,TradeData等数据保存到策略交易监视器;
    4)把on_order()、on_trade()收到的OrderData,TradeData等数据推送到K线图表
  • 实现一个包含从ChartWidget派生类的K线图表
    1)它的主图和附图是用户策略配置的
    2)配置的主图包含一个K线主图,一个可以显示历史交易的交易主图

只要你策略里面没有把账户持仓直接使用到策略里面,就不会相互影响。也就是说,如果你的这两个策略的交易如果使用的是理论持仓,那么它们会互相不影响。如果你的策略不是使用self.pos,而是直接从Oms中读取账户的总仓位进行平仓的话,那么A策略会把B策略的持仓一起平掉,等B再去平仓的时候已经无仓可平了。

1 找到了原因了,这是因为PyQt5中使用到Qt WebEngine造成的

https://stackoverflow.com/questions/57733039/errorpermission-manager-qt-cpp82-unsupported-permission-type-13

2 看看下面这个例子

import sys
from PyQt5.QtCore    import *
from PyQt5.QtGui     import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.webview = QWebEngineView()
        webpage = QWebEnginePage(self.webview)

        self.useragent = QWebEngineProfile(self.webview)

        agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246"
        self.useragent.defaultProfile().setHttpUserAgent(agent)

        self.webview.setPage(webpage)
        self.webview.setUrl(QUrl("http://www.vnpy.com/"))

        self.setCentralWidget(self.webview)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

3 运行后结果

3.1 显示vnpy程序界面

description

3.2 cmd窗口显示结果

[16132:2840:0801/150727.844:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13
[16132:2840:0801/150727.845:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13
[16132:2840:0801/150730.048:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13
[16132:2840:0801/150730.049:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13

4 看看比较靠谱的回答

The problem has nothing to do with program execution permissions.

Qt WebEngine is written on the basis of chromium so the Qt developers do not currently implement all the functionalities but will add more functionalities little by little. In this case, the permissions available by chromium are:

enum class PermissionType {
  MIDI_SYSEX = 1,
  // PUSH_MESSAGING = 2,
  NOTIFICATIONS = 3,
  GEOLOCATION = 4,
  PROTECTED_MEDIA_IDENTIFIER = 5,
  MIDI = 6,
  DURABLE_STORAGE = 7,
  AUDIO_CAPTURE = 8,
  VIDEO_CAPTURE = 9,
  BACKGROUND_SYNC = 10,
  FLASH = 11,
  SENSORS = 12,
  ACCESSIBILITY_EVENTS = 13,
  CLIPBOARD_READ = 14,
  CLIPBOARD_WRITE = 15,
  PAYMENT_HANDLER = 16,
  BACKGROUND_FETCH = 17,
  IDLE_DETECTION = 18,
  PERIODIC_BACKGROUND_SYNC = 19,
  WAKE_LOCK_SCREEN = 20,
  WAKE_LOCK_SYSTEM = 21,

  // Always keep this at the end.
  NUM,
};
But in the case of Qt WebEngine does not handle all cases:

ProfileAdapter::PermissionType toQt(content::PermissionType type)
{
    switch (type) {
    case content::PermissionType::GEOLOCATION:
        return ProfileAdapter::GeolocationPermission;
    case content::PermissionType::AUDIO_CAPTURE:
        return ProfileAdapter::AudioCapturePermission;
    case content::PermissionType::VIDEO_CAPTURE:
        return ProfileAdapter::VideoCapturePermission;
    case content::PermissionType::FLASH:
    case content::PermissionType::NOTIFICATIONS:
    case content::PermissionType::MIDI_SYSEX:
    case content::PermissionType::PROTECTED_MEDIA_IDENTIFIER:
    case content::PermissionType::MIDI:
    case content::PermissionType::DURABLE_STORAGE:
    case content::PermissionType::BACKGROUND_SYNC:
    case content::PermissionType::SENSORS:
    case content::PermissionType::ACCESSIBILITY_EVENTS:
        break;
    case content::PermissionType::CLIPBOARD_READ:
        return ProfileAdapter::ClipboardRead;
    case content::PermissionType::CLIPBOARD_WRITE:
        return ProfileAdapter::ClipboardWrite;
    case content::PermissionType::PAYMENT_HANDLER:
    case content::PermissionType::NUM:
        break;
    }
    return ProfileAdapter::UnsupportedPermission;
}
For example in your case the warning message:

... NOT IMPLEMENTEDUnsupported permission type: 13
It follows that the PermissionType::ACCESSIBILITY_EVENTS permission is required, but according to the QtWebEngine logic return a ProfileAdapter::UnsupportedPermission which is what the warning message indicates.

Conclusion:

There is no way to solve from your side since it is a Qt/chromium warning, besides it is not an error it is only indicating that you do not have that permission.

谢谢xiaohe!

我照帖子做了,错误少了些,可是仍然有未实现的、不支持的许可类型13的问题,这是什么问题呢:

(VN Studio) D:\ProgramFiles\VnStudio>python -m vnstation
[15944:4792:0731/160033.348:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13
[15944:4792:0731/160033.348:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13
[15944:4792:0731/160036.222:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13
[15944:4792:0731/160036.222:ERROR:permission_manager_qt.cpp(82)] NOT IMPLEMENTEDUnsupported permission type: 13

description

是安装OpenSLL时需要捐献造成的吗?

安装完成OpenSLL的时候,显示捐献窗口,最少10$,没有选择。如何在覆盖vnpy下图的这两个DLL,这会有问题吗?

登录时就报,来自qt.network.ssl,QSslSocket,如何解决?

description

一个等交易长度、固定位置的K线产生器FixedBarGenerator

以下代码保存在vnpy\user_tools\my_strategy_tool.py文件中

from typing import Callable,List,Dict, Tuple, Union

import copy
import numpy as np
import talib
import vnpy.usertools.mylib as mylib

from vnpy.app.cta_strategy import (
    BarGenerator,
    ArrayManager
)

from vnpy.trader.object import (
    BarData,
    TickData,
    TradeData
)

from vnpy.trader.constant import Interval
from enum import Enum

import datetime 
import rqdatac as rq
from rqdatac.utils import to_date

import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")


''' 获得上市日期 '''
def get_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.listed_date)

''' 获得交割日期 '''
def get_de_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.de_listed_date)


class Timeunit(Enum):
    """ 时间单位 """
    SECOND = '1s'
    MINUTE = '1m'
    HOUR = '1h'


class TradeHours(object):
    """ 合约交易时间段 """
    def __init__(self,symbol:str):
        self.symbol = symbol
        self.init()

    def init(self):
        """ 初始化交易日字典及交易时间段数据列表 """
        self.listed_date = get_listed_date(self.symbol)
        self.de_listed_date = get_de_listed_date(self.symbol)

        self.trade_date_index = {}   # 合约的交易日索引字典
        self.trade_index_date = {}   # 交易天数与交易日字典

        trade_dates = rq.get_trading_dates(self.listed_date,self.de_listed_date) # 合约的所有的交易日
        days = 0
        for td in trade_dates:
            self.trade_date_index[td] = days
            self.trade_index_date[days] = td
            days += 1

        trading_hours = rq.get_trading_hours(self.symbol,date=self.listed_date,frequency='tick',expected_fmt='datetime')

        self.time_dn_pairs = self._get_trading_times_dn(trading_hours)

        trading_hours0 = [(start.replace(tzinfo=CHINA_TZ),stop.replace(tzinfo=CHINA_TZ)) for start,stop in trading_hours]
        self.trade_date_index[self.listed_date] = (0,trading_hours0)
        for day in range(1,days):
            td = self.trade_index_date[day]
            trade_datetimes = []
            for (start,dn1),(stop,dn2) in self.time_dn_pairs:
                #start:开始时间,dn1:相对交易日前推天数,
                #stop :开始时间,dn2:相对开始时间后推天数     
                d = self.trade_index_date[day+dn1]
                trade_datetimes.append((
                    datetime.datetime.combine(d,start,tzinfo=CHINA_TZ),
                    datetime.datetime.combine(d,stop,tzinfo=CHINA_TZ)+datetime.timedelta(days=dn2)))
            self.trade_date_index[td] = (day,trade_datetimes)

    def _get_trading_times_dn(self,trading_hours:List[Tuple[datetime.datetime,datetime.datetime]]): 
        """ 
        交易时间跨天处理,不推荐外部使用 。
        产生的结果:[((start1,dn11),(stop1,dn21)),((start2,dn12),(stop2,dn22)),...,((startN,dn1N),(stopN,dn2N))]
        其中:
            startN:开始时间,dn1N:相对交易日前推天数,
            stopN:开始时间,dn2N:相对开始时间后推天数      
        """
        ilen = len(trading_hours)
        if ilen == 0:
            return []
        start_stops = []
        for start,stop in trading_hours:
            start_stops.insert(0,(start.time(),stop.time()))

        pre_start,pre_stop = start_stops[0]
        dn1 = 0
        dn2 = 1 if pre_start > pre_stop else 0
        time_dn_pairs = [((pre_start,dn1),(pre_stop,dn2))]
        for start,stop in start_stops[1:]:
            if start > pre_start:
                dn1 -= 1
            dn2 = 1 if start > stop else 0
            time_dn_pairs.insert(0,((start,dn1),(stop,dn2)))
            pre_start,pre_stop = start,stop

        return time_dn_pairs

    def get_date_tradetimes(self,date:datetime.date):
        """ 得到合约date日期的交易时间段 """
        idx,trade_times = self.trade_date_index.get(date,(None,[]))
        return idx,trade_times

    def get_trade_datetimes(self,dt:datetime,allday:bool=False):
        """ 得到合约date日期的交易时间段 """

        # 得到最早的交易时间
        idx0,trade_times0 = self.get_date_tradetimes(self.listed_date)
        start0,stop0 = trade_times0[0]
        if dt < start0:
            return None,[]

        # 首先找到dt日期自上市以来的交易天数
        date,dn = dt.date(),0
        days = None
        while date < self.de_listed_date:
            days,ths = self.trade_date_index.get(date,(None,[]))
            if not days:
                dn += 1
                date = (dt+datetime.timedelta(days=dn)).date()
            else:
                break
        # 如果超出交割日也没有找到,那这就不是一个有效的交易时间
        if days is None:
            return (None,[])

        index_3 = [days,days+1,days-1]  # 前后三天的

        date_3d = []
        for day in index_3: 
            date = self.trade_index_date.get(day,None)
            date_3d.append(date)

        # print(date_3d)

        for date in date_3d:
            if not date:
                # print(f"{date} is not trade date")
                continue

            idx,trade_dts = self.get_date_tradetimes(date)
            # print(f"{date} tradetimes {trade_dts}")
            ilen = len(trade_dts)
            if ilen > 0:
                start0,stop = trade_dts[0]      # start0 是date交易日的开始时间
                start,stop0 = trade_dts[-1]
            if dt<start0 or dt>stop0:
                continue

            for start,stop in trade_dts:
                if dt>=start and dt < stop:
                    if allday:
                        return idx,trade_dts
                    else:
                        return idx,[(start,stop)]

        return None,[]

    def get_trade_time_perday(self):
        """ 计算每日的交易总时长(单位:分钟) """
        TTPD = datetime.timedelta(0,0,0)

        datetimes = []
        today = datetime.datetime.now().date()

        for (start,dn1),(stop,dn2) in self.time_dn_pairs:
            start_dt = datetime.datetime.combine(today,start,tzinfo=CHINA_TZ) + datetime.timedelta(days=dn1)
            stop_dt = datetime.datetime.combine(today,stop,tzinfo=CHINA_TZ) + datetime.timedelta(days=dn2)
            time_delta = stop_dt - start_dt
            TTPD = TTPD + time_delta
        return int(TTPD.seconds/60)

    def get_trade_time_inday(self,dt:datetime,unit:Timeunit=Timeunit.MINUTE):
        """ 
        计算dt在交易日内的分钟数 
        unit: '1s':second;'1m':minute;'1h';1h
        """
        TTID = datetime.timedelta(0,0,0)

        day,trade_times = self.get_trade_datetimes(dt,allday=True)
        if not trade_times:
            return None

        for start,stop in trade_times:
            if dt > stop:
                time_delta = stop - start
                TTID += time_delta
            elif dt > start:
                time_delta = dt - start
                TTID += time_delta     
                break
            else:
                break          

        if unit == Timeunit.SECOND:
            return TTID.seconds
        elif unit == Timeunit.MINUTE:
            return int(TTID.seconds/60) 
        elif unit == Timeunit.HOUR:
            return int(TTID.seconds/3600) 
        else:
            return TTID

    def convet_to_datetime(self,day:int,minutes:int):
        """ 计算minutes在第day交易日内的datetime形式的时间 """
        date = self.trade_index_date.get(day,None)
        if date is None:
            return None
        idx,trade_times = self.trade_date_index.get(date,(None,[]))
        if not trade_times:     # 不一定必要
            return None
        for (start,stop) in trade_times:
            timedelta = stop - start 
            if minutes < int(timedelta.seconds/60):
                return start + datetime.timedelta(minutes=minutes)
            else:
                minutes -= int(timedelta.seconds/60)
        return None

    def get_bar_window(self,dt:datetime,window:int,interval:Interval=Interval.MINUTE):
        """ 计算dt所在K线的起止时间 """
        bar_windows = (None,None)

        day,trade_times = self.get_trade_datetimes(dt,allday=True)
        if not trade_times:
            # print(f"day={day} trade_times={trade_times}")
            return bar_windows


        # 求每个交易日的交易时间分钟数
        TTPD = self.get_trade_time_perday()

        # 求dt在交易日内的分钟数
        TTID = self.get_trade_time_inday(dt,unit=Timeunit.MINUTE)

        # 得到dt时刻K线的起止时间 
        total_minites = day*TTPD + TTID

        # 计算K线宽度(分钟数)
        if interval == Interval.MINUTE:
            bar_width = window
        elif interval == Interval.HOUR:
            bar_width = 60*window
        elif interval == Interval.DAILY:
            bar_width = TTPD*window
        elif interval == Interval.WEEKLY:
            bar_width = TTPD*window*5
        else:
            return bar_windows

        # 求K线的开始时间的和结束的分钟形式
        start_m = int(total_minites/bar_width)*bar_width
        stop_m = start_m + bar_width

        # 计算K开始时间的datetime形式
        start_d = int(start_m / TTPD)
        minites = start_m % TTPD
        start_dt = self.convet_to_datetime(start_d,minites)
        # print(f"start_d={start_d} minites={minites}---->{start_dt}")

        # 计算K结束时间的datetime形式
        stop_d = int(stop_m / TTPD)
        minites = stop_m % TTPD
        stop_dt = self.convet_to_datetime(stop_d,minites)
        # print(f"stop_d={stop_d} minites={minites}---->{stop_dt}")

        return start_dt,stop_dt

class FixedBarGenerator(BarGenerator):
    """ 固定位置K线生成器 """ 
    def __init__(
        self,
        on_bar: Callable,
        window: int = 0,
        on_window_bar: Callable = None,
        interval: Interval = Interval.MINUTE,
        symbol:str=''
    ):
        super().__init__(on_bar,window,on_window_bar,interval)
        self.trade_hours = TradeHours(symbol)

        self.kx_start,self.kx_stop = None,None

    def update_bar(self,bar:BarData) -> None:
        """
        Update 1 minute bar into generator
        """

        # 如果bar的时间戳笔windows的时间戳还早,丢弃bar
        if self.window_bar and bar.datetime < self.window_bar.datetime:
            return

        if (self.kx_start,self.kx_stop) == (None,None):
            self.kx_start,self.kx_stop = self.trade_hours.get_bar_window(bar.datetime,self.window,self.interval)
            if (self.kx_start,self.kx_stop) == (None,None):
                return

        # If not inited, creaate window bar object
        if (not self.window_bar):
            # 获得K线的交易起止时间
            self.window_bar = BarData(
                symbol=bar.symbol,
                exchange=bar.exchange,
                datetime=self.kx_start,
                gateway_name=bar.gateway_name,
                open_price=bar.open_price,
                high_price=bar.high_price,
                low_price=bar.low_price,
            )         

        elif self.kx_start <= bar.datetime and bar.datetime < self.kx_stop:
            # 1分钟K线属于当前K线
            self.window_bar.high_price = max(
                self.window_bar.high_price, bar.high_price)
            self.window_bar.low_price = min(
                self.window_bar.low_price, bar.low_price)

        elif bar.datetime >= self.kx_stop:       # Check if window bar completed
            self.on_window_bar(self.window_bar)
            self.window_bar = None

            self.kx_start,self.kx_stop = self.trade_hours.get_bar_window(bar.datetime,self.window,self.interval)
            if (self.kx_start,self.kx_stop) == (None,None): 
                # 不在交易时段
                return

            self.window_bar = BarData(
                symbol=bar.symbol,
                exchange=bar.exchange,
                datetime=self.kx_start,
                gateway_name=bar.gateway_name,
                open_price=bar.open_price,
                high_price=bar.high_price,
                low_price=bar.low_price,
            )       

        # Update close price/volume into window bar
        self.window_bar.close_price = bar.close_price
        self.window_bar.volume += int(bar.volume)
        self.window_bar.open_interest = bar.open_interest

创建一个可显示K线的策略FixedKxStrategy

以下代码为 [用户目录]\strategies\FixedKxStrategy.py的内容

from typing import Any,List,Dict,Tuple
import copy

from vnpy.app.cta_strategy import (
    CtaTemplate,
    BarGenerator,
    ArrayManager,
    StopOrder,
    Direction
)

from vnpy.trader.engine import MainEngine,EventEngine
from vnpy.app.cta_strategy.engine import CtaEngine
from vnpy.event.engine import Event

from vnpy.trader.object import (
    LogData,
    TickData,
    BarData,
    TradeData,
    OrderData,
)

from vnpy.app.cta_strategy import StopOrder
from vnpy.app.cta_strategy.base import EngineType
from vnpy.trader.constant import Interval

from vnpy.app.cta_strategy.base import (
    APP_NAME,
    EVENT_CTA_LOG,
    EVENT_CTA_TICK,
    EVENT_CTA_HISTORY_BAR,
    EVENT_CTA_BAR,
    EVENT_CTA_ORDER,
    EVENT_CTA_TRADE,    
    EVENT_CTA_STOPORDER,
    EVENT_CTA_STRATEGY,
)

from vnpy.usertools.kx_chart import (   # hxxjava add
    NewChartWidget,
    CandleItem,
    VolumeItem, 
    LineItem,
    SmaItem,
    RsiItem,
    MacdItem,
)

from vnpy.usertools.my_strategy_tool import FixedBarGenerator

class FixedKxStrategy(CtaTemplate):
    """"""

    author = "hxxjava"
    kx_interval = 1
    show_chart = False  # 显示K线图表 

    parameters = [
        "kx_interval",
        "show_chart"
    ]

    kx_count:int = 0
    cta_manager = None

    variables = ["kx_count"]

    def __init__(
        self,
        cta_engine: Any,
        strategy_name: str,
        vt_symbol: str,
        setting: dict,
    ):
        super().__init__(cta_engine,strategy_name,vt_symbol,setting)
        symbol,exchange = self.vt_symbol.split('.')

        self.bg = FixedBarGenerator(self.on_bar,self.kx_interval,self.on_Nmin_bar,symbol=symbol.upper())

        self.am = ArrayManager()

        cta_engine:CtaEngine = self.cta_engine
        self.engine_type = cta_engine.engine_type
        self.even_engine = cta_engine.main_engine.event_engine

        # 必须在这里声明,因为它们是实例变量
        self.all_bars:List[BarData] = [] 
        self.cur_window_bar:[BarData] = None
        self.bar_updated = False

    def on_init(self):
        """
        Callback when strategy is inited.
        """
        self.load_bar(20)

        if len(self.all_bars)>0:
            self.send_event(EVENT_CTA_HISTORY_BAR,self.all_bars)

    def on_start(self):
        """ """
        self.write_log("已开始")

    def on_stop(self):
        """"""
        self.write_log("_kx_strategy 已停止")

    def on_tick(self, tick: TickData):
        """
        Callback of new tick data update.
        """
        self.bar_updated = False
        self.current_tick = tick    # 记录最新tick 

        # 再更新tick,产生1分钟K线乃至N 分钟线
        self.bg.update_tick(tick)

        if self.inited:     
            # 先产生当前临时K线
            self.cur_window_bar = self.get_cur_window_bar()  
            if self.cur_window_bar:
                # 发送当前临时K线更新消息
                self.send_event(EVENT_CTA_BAR,self.cur_window_bar)           

            self.send_event(EVENT_CTA_TICK,tick)  

    def on_bar(self, bar: BarData):
        """
        Callback of new bar data update.
        """
        if self.inited:   
            self.write_log(f"I got a 1min BarData at {bar.datetime}")

        self.bg.update_bar(bar)
        self.bar_updated = True

    def on_Nmin_bar(self, bar: BarData):
        """
        Callback of new bar data update.
        """
        self.all_bars.append(bar)
        self.kx_count = len(self.all_bars)

        if self.inited:
            self.write_log(f"I got a {self.kx_interval}min BarData at {bar.datetime}")
            self.send_event(EVENT_CTA_BAR,bar)

        self.put_event()

    def on_trade(self, trade: TradeData):
        """
        Callback of new trade data update.
        """
        self.send_event(EVENT_CTA_TRADE,trade)

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

    def on_stop_order(self, stop_order: StopOrder):
        """
        Callback of stop order update.
        """
        self.send_event(EVENT_CTA_STOPORDER,stop_order)

    def get_cur_window_bar(self):
        window_bar = copy.deepcopy(self.bg.window_bar)
        bar = self.bg.bar

        if not(window_bar): # 刚产生过window_bar
            return None

        if self.bar_updated: # 刚更新过window_bar
            return window_bar

        # 上一分钟window_bar和当前bar合成出临时window bar
        window_bar.high_price = max(window_bar.high_price, bar.high_price)
        window_bar.low_price = min(window_bar.low_price, bar.low_price)

        # Update close price/volume into window bar
        window_bar.close_price = bar.close_price
        window_bar.volume += int(bar.volume)
        window_bar.open_interest = bar.open_interest
        return window_bar

    def send_event(self,event_type:str,data:Any):   
        """
        只在实盘引擎并且配置为显示K线图表的情况下发送小线
        """     
        if self.engine_type==EngineType.LIVE and self.show_chart:     # "如果显示K线图表"
            self.even_engine.put(Event(event_type,(self.strategy_name,data)))

    def init_kx_chart(self,kx_chart:NewChartWidget=None):    # hxxjava add ----- 提供给外部调用
        # self.write_log("init_kx_chart executed !!!")
        if kx_chart:
            kx_chart.add_plot("candle", hide_x_axis=True)
            kx_chart.add_plot("volume", maximum_height=150)
            kx_chart.add_plot("rsi", maximum_height=150)
            kx_chart.add_plot("macd", maximum_height=150)
            kx_chart.add_item(CandleItem, "candle", "candle")
            kx_chart.add_item(VolumeItem, "volume", "volume")

            kx_chart.add_item(LineItem, "line", "candle")
            kx_chart.add_item(SmaItem, "sma", "candle")
            kx_chart.add_item(RsiItem, "rsi", "rsi")
            kx_chart.add_item(MacdItem, "macd", "macd")
            kx_chart.add_last_price_line()
            kx_chart.add_cursor()

运行策略FixedKxStrategy

具体操作方法以及缺少的NewChartWidget等组件参见:
https://www.vnpy.com/forum/topic/3920-wei-kxian-tu-biao-tian-zhuan-jia-wa-rang-ctace-lue-de-yun-xing-kan-de-jian-1

设置方法

按下图的步骤创建一个可以显示K线图的用户策略

description

运行效果

以下为ag2012.SHFE的30分钟K线图,它显示的K线是等交易时长,位置也是固定的。

description

合约的交易时间段

之前发布的交易时间段有错误,经过细致修改终于把各种特殊情况都修改,现在重新上传:
代码保存文件:vnpy\user_tools\my_strategy_tool.py中

from typing import Callable,List,Dict, Tuple, Union

import copy
import numpy as np
import talib
import vnpy.usertools.mylib as mylib

from vnpy.app.cta_strategy import (
    BarGenerator,
    ArrayManager
)

from vnpy.trader.object import (
    BarData,
    TickData,
    TradeData
)

from vnpy.trader.constant import Interval
from enum import Enum

import datetime 
import rqdatac as rq
from rqdatac.utils import to_date

import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")


''' 获得上市日期 '''
def get_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.listed_date)

''' 获得交割日期 '''
def get_de_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.de_listed_date)


class Timeunit(Enum):
    """ 时间单位 """
    SECOND = '1s'
    MINUTE = '1m'
    HOUR = '1h'


class TradeHours(object):
    """ 合约交易时间段 """
    def __init__(self,symbol:str):
        self.symbol = symbol
        self.init()

    def init(self):
        """ 初始化交易日字典及交易时间段数据列表 """
        self.listed_date = get_listed_date(self.symbol)
        self.de_listed_date = get_de_listed_date(self.symbol)

        self.trade_date_index = {}   # 合约的交易日索引字典
        self.trade_index_date = {}   # 交易天数与交易日字典

        trade_dates = rq.get_trading_dates(self.listed_date,self.de_listed_date) # 合约的所有的交易日
        days = 0
        for td in trade_dates:
            self.trade_date_index[td] = days
            self.trade_index_date[days] = td
            days += 1

        trading_hours = rq.get_trading_hours(self.symbol,date=self.listed_date,frequency='tick',expected_fmt='datetime')

        self.time_dn_pairs = self._get_trading_times_dn(trading_hours)

        trading_hours0 = [(start.replace(tzinfo=CHINA_TZ),stop.replace(tzinfo=CHINA_TZ)) for start,stop in trading_hours]
        self.trade_date_index[self.listed_date] = (0,trading_hours0)
        for day in range(1,days):
            td = self.trade_index_date[day]
            trade_datetimes = []
            for (start,dn1),(stop,dn2) in self.time_dn_pairs:
                #start:开始时间,dn1:相对交易日前推天数,
                #stop :开始时间,dn2:相对开始时间后推天数     
                d = self.trade_index_date[day+dn1]
                trade_datetimes.append((
                    datetime.datetime.combine(d,start,tzinfo=CHINA_TZ),
                    datetime.datetime.combine(d,stop,tzinfo=CHINA_TZ)+datetime.timedelta(days=dn2)))
            self.trade_date_index[td] = (day,trade_datetimes)

    def _get_trading_times_dn(self,trading_hours:List[Tuple[datetime.datetime,datetime.datetime]]): 
        """ 
        交易时间跨天处理,不推荐外部使用 。
        产生的结果:[((start1,dn11),(stop1,dn21)),((start2,dn12),(stop2,dn22)),...,((startN,dn1N),(stopN,dn2N))]
        其中:
            startN:开始时间,dn1N:相对交易日前推天数,
            stopN:开始时间,dn2N:相对开始时间后推天数      
        """
        ilen = len(trading_hours)
        if ilen == 0:
            return []
        start_stops = []
        for start,stop in trading_hours:
            start_stops.insert(0,(start.time(),stop.time()))

        pre_start,pre_stop = start_stops[0]
        dn1 = 0
        dn2 = 1 if pre_start > pre_stop else 0
        time_dn_pairs = [((pre_start,dn1),(pre_stop,dn2))]
        for start,stop in start_stops[1:]:
            if start > pre_start:
                dn1 -= 1
            dn2 = 1 if start > stop else 0
            time_dn_pairs.insert(0,((start,dn1),(stop,dn2)))
            pre_start,pre_stop = start,stop

        return time_dn_pairs

    def get_date_tradetimes(self,date:datetime.date):
        """ 得到合约date日期的交易时间段 """
        idx,trade_times = self.trade_date_index.get(date,(None,[]))
        return idx,trade_times

    def get_trade_datetimes(self,dt:datetime,allday:bool=False):
        """ 得到合约date日期的交易时间段 """

        # 得到最早的交易时间
        idx0,trade_times0 = self.get_date_tradetimes(self.listed_date)
        start0,stop0 = trade_times0[0]
        if dt < start0:
            return None,[]

        # 首先找到dt日期自上市以来的交易天数
        date,dn = dt.date(),0
        days = None
        while date < self.de_listed_date:
            days,ths = self.trade_date_index.get(date,(None,[]))
            if not days:
                dn += 1
                date = (dt+datetime.timedelta(days=dn)).date()
            else:
                break
        # 如果超出交割日也没有找到,那这就不是一个有效的交易时间
        if days is None:
            return (None,[])

        index_3 = [days,days+1,days-1]  # 前后三天的

        date_3d = []
        for day in index_3: 
            date = self.trade_index_date.get(day,None)
            date_3d.append(date)

        # print(date_3d)

        for date in date_3d:
            if not date:
                # print(f"{date} is not trade date")
                continue

            idx,trade_dts = self.get_date_tradetimes(date)
            # print(f"{date} tradetimes {trade_dts}")
            ilen = len(trade_dts)
            if ilen > 0:
                start0,stop = trade_dts[0]      # start0 是date交易日的开始时间
                start,stop0 = trade_dts[-1]
            if dt<start0 or dt>stop0:
                continue

            for start,stop in trade_dts:
                if dt>=start and dt < stop:
                    if allday:
                        return idx,trade_dts
                    else:
                        return idx,[(start,stop)]

        return None,[]

    def get_trade_time_perday(self):
        """ 计算每日的交易总时长(单位:分钟) """
        TTPD = datetime.timedelta(0,0,0)

        datetimes = []
        today = datetime.datetime.now().date()

        for (start,dn1),(stop,dn2) in self.time_dn_pairs:
            start_dt = datetime.datetime.combine(today,start,tzinfo=CHINA_TZ) + datetime.timedelta(days=dn1)
            stop_dt = datetime.datetime.combine(today,stop,tzinfo=CHINA_TZ) + datetime.timedelta(days=dn2)
            time_delta = stop_dt - start_dt
            TTPD = TTPD + time_delta
        return int(TTPD.seconds/60)

    def get_trade_time_inday(self,dt:datetime,unit:Timeunit=Timeunit.MINUTE):
        """ 
        计算dt在交易日内的分钟数 
        unit: '1s':second;'1m':minute;'1h';1h
        """
        TTID = datetime.timedelta(0,0,0)

        day,trade_times = self.get_trade_datetimes(dt,allday=True)
        if not trade_times:
            return None

        for start,stop in trade_times:
            if dt > stop:
                time_delta = stop - start
                TTID += time_delta
            elif dt > start:
                time_delta = dt - start
                TTID += time_delta     
                break
            else:
                break          

        if unit == Timeunit.SECOND:
            return TTID.seconds
        elif unit == Timeunit.MINUTE:
            return int(TTID.seconds/60) 
        elif unit == Timeunit.HOUR:
            return int(TTID.seconds/3600) 
        else:
            return TTID

    def convet_to_datetime(self,day:int,minutes:int):
        """ 计算minutes在第day交易日内的datetime形式的时间 """
        date = self.trade_index_date.get(day,None)
        if date is None:
            return None
        idx,trade_times = self.trade_date_index.get(date,(None,[]))
        if not trade_times:     # 不一定必要
            return None
        for (start,stop) in trade_times:
            timedelta = stop - start 
            if minutes < int(timedelta.seconds/60):
                return start + datetime.timedelta(minutes=minutes)
            else:
                minutes -= int(timedelta.seconds/60)
        return None

    def get_bar_window(self,dt:datetime,window:int,interval:Interval=Interval.MINUTE):
        """ 计算dt所在K线的起止时间 """
        bar_windows = (None,None)

        day,trade_times = self.get_trade_datetimes(dt,allday=True)
        if not trade_times:
            # print(f"day={day} trade_times={trade_times}")
            return bar_windows


        # 求每个交易日的交易时间分钟数
        TTPD = self.get_trade_time_perday()

        # 求dt在交易日内的分钟数
        TTID = self.get_trade_time_inday(dt,unit=Timeunit.MINUTE)

        # 得到dt时刻K线的起止时间 
        total_minites = day*TTPD + TTID

        # 计算K线宽度(分钟数)
        if interval == Interval.MINUTE:
            bar_width = window
        elif interval == Interval.HOUR:
            bar_width = 60*window
        elif interval == Interval.DAILY:
            bar_width = TTPD*window
        elif interval == Interval.WEEKLY:
            bar_width = TTPD*window*5
        else:
            return bar_windows

        # 求K线的开始时间的和结束的分钟形式
        start_m = int(total_minites/bar_width)*bar_width
        stop_m = start_m + bar_width

        # 计算K开始时间的datetime形式
        start_d = int(start_m / TTPD)
        minites = start_m % TTPD
        start_dt = self.convet_to_datetime(start_d,minites)
        # print(f"start_d={start_d} minites={minites}---->{start_dt}")

        # 计算K结束时间的datetime形式
        stop_d = int(stop_m / TTPD)
        minites = stop_m % TTPD
        stop_dt = self.convet_to_datetime(stop_d,minites)
        # print(f"stop_d={stop_d} minites={minites}---->{stop_dt}")

        return start_dt,stop_dt

实现方法:

import datetime

import rqdatac as rq
from rqdatac.utils import to_date

# RQDatac初始化
rq.init('xxxxxx','*****',("rqdatad-pro.ricequant.com",16011))

''' 获得上市日期 '''
def get_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.listed_date)

''' 获得交割日期 '''
def get_de_listed_date(symbol:str):
    info = rq.instruments(symbol)
    return to_date(info.de_listed_date)

symbol = 'RB2010'

listed_date = get_listed_date(symbol)   # 上市日期
de_listed_date = get_de_listed_date(symbol) # 交割日期
trade_dates = rq.get_trading_dates(listed_date,de_listed_date) # 合约的所有的交易日

trade_date_index = {}   # 合约的交易日索引字典
trade_index_date = {}   # 交易天数与交易日字典
days = 0
for td in trade_dates:
    trade_date_index[td] = days
    trade_index_date[days] = td
    days += 1

last_date = rq.get_latest_trading_date()
print(f"{symbol} {last_date}'d index: {trade_date_index[last_date]}")   

trading_hours = rq.get_trading_hours(symbol,date=last_date,frequency='1m',expected_fmt='datetime')

TTPD = datetime.timedelta(0,0,0)
for start,stop in trading_hours:
    timedelta = stop - start + datetime.timedelta(minutes=1)
    TTPD = TTPD + timedelta
    print(f"{symbol}'s start:{start}-stop:{stop},timedelta={timedelta}") 

print(f"{symbol}'s TTPD={TTPD}") 

STTD = TTPD * trade_date_index[last_date]
print(f"{symbol}'s STTD={STTD}") 

# 已知tick时间为最新交易日的14:37,K线周期为30分钟,求K线的开始时间STTK
TickTime = datetime.datetime.combine(last_date,datetime.time(14,37))
#求tick的日内时间
tt_in_day = datetime.timedelta(0,0,0)   
for start,stop in trading_hours:
    if start <= TickTime and TickTime < stop:
        tt_in_day += (TickTime-start)+datetime.timedelta(minutes=1)
    else:
        tt_in_day += (stop-start)+datetime.timedelta(minutes=1)
# 求tick从上市开始的总时间
tt_total = TTPD * trade_date_index[TickTime.date()] + tt_in_day

# K线周期
KW = datetime.timedelta(minutes=30) 
# K线的开始时间
STTK = int(tt_total/KW)*KW

print(f"TickTime={TickTime},tt_total={tt_total},STTK={STTK}")

执行结果

RB2010 2020-07-28'd index: 191
RB2010's start:2020-07-27 21:01:00-stop:2020-07-27 23:00:00,timedelta=2:00:00
RB2010's start:2020-07-28 09:01:00-stop:2020-07-28 10:15:00,timedelta=1:15:00
RB2010's start:2020-07-28 10:31:00-stop:2020-07-28 11:30:00,timedelta=1:00:00
RB2010's start:2020-07-28 13:31:00-stop:2020-07-28 15:00:00,timedelta=1:30:00
RB2010's TTPD=5:45:00
RB2010's STTD=45 days, 18:15:00
TickTime=2020-07-28 14:37:00,tt_total=45 days, 23:37:00,STTK=45 days, 23:30:00

rqdatac可以提供的功能

获取合约信息

rq.instruments(symbol)

返回值:

Instrument(order_book_id='RB2010', symbol='螺纹钢2010', round_lot=1.0, contract_multiplier=10.0, underlying_order_book_id='null', underlying_symbol='RB', maturity_date='2020-10-15', type='Future', exchange='SHFE', listed_date='2019-10-16', de_listed_date='2020-10-15', margin_rate=0.09, trading_hours='21:01-23:00,09:01-10:15,10:31-11:30,13:31-15:00', market_tplus=0, industry_name='焦煤钢矿', product='Commodity')

其中:

listed_date——上市日期
de_listed_date——交割日期
trading_hours——交易时间段

求最新交易日

 rq.get_latest_trading_date()

求两个日期之间的交易日

rq.get_trading_dates(listed_date,last_trade_date)

求下n个交易日

rq.get_next_trading_date(listed_date,n=days)

获取交易时间段

rq.get_trading_hours(symbol,frequency='tick',expected_fmt='str')
rq.get_trading_hours(symbol,frequency='1m',expected_fmt='datetime')
rq.get_trading_hours(symbol,frequency='1m',expected_fmt='time')

如何确定一个Tick属于哪一根K线

合约的交易时段与K线周期的关系

description

LD:合约上市日期(Listed date)
LTD:最新交易日(Lastest trade date)
ULD:合约最后交易日(Unlisted date)
TTPD:每日交易时间(Trade time per day)
KW:K线周期(Kindle witdth)
STTD:交易日开始时间(从LD起算)
STTK:K线日开始时间(从LD起算)

Days = LTD到LD之间到交易日数(去掉节假日)
STTD = TTPD×Days

每日交易时间TTPD的计算

不同合约的交易时间段可能是不一样的,它们可以利用rqdatac的instruments(symbol)函数读取,返回结果中包含 trading_hours字段。如:
RB2010交易时间段:
trading_hours='21:01-23:00,09:01-10:15,10:31-11:30,13:31-15:00'
AG2012交易时间段:
trading_hours='21:01-02:30,09:01-10:15,10:31-11:30,13:31-15:00'
MA2010交易时间段:
trading_hours='21:01-23:00,09:01-10:15,10:31-11:30,13:31-15:00'
trading_hours的形式:start1-stop1,start2-stop2,...,startN-stopN

description

注意把start和stop的单位换算为日内的分钟数量

计算Tick所在K线的索引

已知tick中包含时间datetime,K线的周期为KW,那么从上市日算起的各个时间:
利用tick.datetime求出所在交易日,从而求出STTD
TickTD:tick在交易日内的分钟数
TickLD:tick从上市开始的分钟数=STTD+TickTD
IndexK:tick所在K线的所有=int(TickLD/KW)
STTK:K线开始时间=IndexK*KW

VNPY系统CTA策略中的BarGenerater的问题:

当K线周期为1,5,15分钟或者1日,1周时,它是没有问题的
当K线周期为10,20,30分钟或者一些自定义分钟周期如7分钟,还有1、2,4小时,由于合约的交易时段的不规则,导致某些K线的周期于其时间发生的交易时间不想等。
你开启CTA策略的时机是随机的,这导致self.laod_bar(20)的执行也是随机的,它从米筐或者数据库加载的1分钟数据也是随机,最终导致你所产生的上述K线也是随机的。那你已经这样的K线数据计算出来的指标在某种程度上可能也随机的。

问题出在合约的交易时间上

不赘述原因了,举例吧:
RB2010交易时间段:'21:01-23:00,09:01-10:15,10:31-11:30,13:31-15:00'
假如你的K线每天从21:01开始计算K线,
如果K线的周期为10分钟,那么在10:10-10:20的那个K线其实只交易了5分钟
如果K线的周期为20分钟,那么在10:00-10:20的那个K线其实只交易了15分钟,在10:20-10:40的那个K线其实只交易了10分钟
如果K线的周期为30分钟,那么在10:00-10:30的那个K线其实只交易了15分钟
如果K线的周期为60分钟,那么在10:00-11:00的那个K线其实只交易了45分钟
如果K线的周期为120分钟,那么在10:00-14:00的那个K线其实只交易了105分钟

K线应该是从初始化策略之时往前计算,还是从上市日期开始计算?

如果BarGenerator采用等交易时长产生K线,策略初始化时通过load_bar(n),读取1分钟历史K线,目前BarGenerator时是从n日之前的第一个1分钟K线区合约其他周期的K线的。
这导某些周期K线随n值不同,K线的起止时间会变化。而如果采用从上市日期开始计算等交易时长的K线位置,则无论何时初始化策略,K线的起止时间都是一样的。

你希望那种K线:

1 等自然时长K线——无需考虑交易时段
2 等交易时长K线——需要考虑交易时段
3 从上市日起算K线——起止位置固定
4 从策略初始化时起算K线——起止位置不固定

第6帖子已经实现目标——固定交易时长位置固定的K线图

description

策略的on_tick 、on_bar、on_xmin_bar是行情的推送函数,和成交没有关系。
1)只要接口上收到成交数据,策略就会通过on_trade推送函数得到交易结果,CTA引擎就会自动执行sync_strategy_data(),把交易策略的veriables中变量存入磁盘文件。
2)另外一个执行策略数据同步的时机是策略被停止的时候,CTA引擎就会自动执行sync_strategy_data()对当前合约的veriables中变量存入磁盘文件。
因此无需再调用sync_data()。当然如果你一定要调用,那就在策略里你认为的需要的时刻self.sync_data()执行一下就可以了,其实也是执行了CTA引擎的sync_strategy_data()。

无思路,无代码,无过程,三无,谁能够回答你?呵呵

首先说明:这不只是DemoStrategy的问题,其他策略也一样存在

我只是拿DemoStrategy的例子来说明问题。

通常一个策略需要在on_init()函数中调用self.load_bar()

例如:

陈老师在CTA策略实战进阶中的课程里讲的DemoStrategy策略:

class DemoStrategy(CtaTemplate):
    """ 一个演示策略 """
    author = "hxx"

    fast_window = 10
    slow_window = 20

    fast_ma0 = 0
    fast_ma1 = 0
    slow_ma0 = 0
    slow_ma1 = 0

    parameters = [
        "fast_window",
        "slow_window"
    ]

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

    def __init__(
        self,
        cta_engine: Any,
        strategy_name: str,
        vt_symbol: str,
        setting: dict 
    ):
        """构造函数"""
        super().__init__(cta_engine,strategy_name,vt_symbol,setting)

        self.bg = NewBarGenerator(
            on_bar=self.on_bar,
            window=7,
            on_window_bar=on_7min_bar,
            interval=Interval.Minute)

        self.am = NewArrayManager()


    def on_init(self):
        """"""
        self.write_log("策略初始化")

        self.load_bar(10)       # 加载10日的7分钟K线

    def on_start(self):
        """策略启动"""
        self.write_log("策略启动")

    def on_stop(self):
        """ 策略停止 """
        self.write_log(" 策略停止 ")

    def on_tick(self,tick:TickData):
        """ Tick更新 """
        self.bg.update_tick(tick) 

    def on_bar(self, bar: BarData):
        """K线更新"""
        self.bg.update_bar(bar)

    def on_7min_bar(self, bar: BarData):
        """K线更新"""
        am = self.am
        am.update_bar(bar)
        if not am.inited:
            return

        """ 计算均线 """
        fast_ma = am.sma(self.fast_window,True)
        self.fast_ma0 = fast_ma[-1]
        self.fast_ma1 = fast_ma[-2]

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

        """ 定义金叉和死叉 """

        cross_over = (self.fast_ma0>= self.fast_ma1 and
                      self.slow_ma0<self.slow_ma1)  

        cross_below = (self.slow_ma0>self.slow_ma1 and 
                      self.slow_ma0<=self.slow_ma1)

        if cross_over:
            price = bar.close_price + 5

            if not self.pos:
                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 not self.pos:
                self.short(price,1)
            elif self.pos>0:
                self.sell(price,1)
                self.short(price,1)

        # 更新图形界面 
        self.put_event()

self.load_bar()是CtaTemplate策略模版的方法:

    def load_bar(
        self,
        days: int,
        interval: Interval = Interval.MINUTE,
        callback: Callable = None,
        use_database: bool = False
    ):
        """
        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,
            use_database
        )

CtaTemplate.load_bar()又调用vnpy.app.cta_strategy.engine里的CtaEngine的load_bar()

CtaEngine的load_bar()的代码是这样的:

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

        # Pass gateway and RQData if use_database set to True
        if not use_database:
            # Query bars from gateway if available
            contract = self.main_engine.get_contract(vt_symbol)

            if contract and contract.history_data:
                req = HistoryRequest(
                    symbol=symbol,
                    exchange=exchange,
                    interval=interval,
                    start=start,
                    end=end
                )
                bars = self.main_engine.query_history(req, contract.gateway_name)

            # Try to query bars from RQData, if not found, load from database.
            else:
                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)

可能的问题:

CtaEngine.load_bar()对历史数据的处理逻辑是:
1 首先从rqdatac获取历史分钟数据,利用DemoStrategyd策略的on_bar()合成出7分钟K线,然后调用on_7min_bar()。这样self.am是一个默认缓冲为100个FIFO数组队列,利用它执行策略的买卖信号计算和下单。它的end是当前时间,start是10天前的时间。策略初始化后,从行情接口可以不断地收到tick数据,然后执行DemoStrategyd策略on_tick()-->on_bar()-->on_7min_bar()
2 如果rqdatac没有读取到历史分钟数据,那么就从本地数据库中读取。问题来了:

1) 如果本地数据库中有start-end之间的数据,可是最后的数据时间与当前时间有空档,例如最后保存的有30分钟空档,而初始化后行情接口虽然也是按照on_tick()-->on_bar()-->on_7min_bar()的过程不断地更新self.am,可是我们知道它所管理的缓冲K线可能是不连续的,这样会导致计算的错误!
2) 如果本地数据库中有start-end之间的数据,可是这些数据本身内部可能好几段历史返分钟数据,本身中间就有空档,导致self.am所管理的缓冲K线可能是不连续的,这样会导致计算的错误!
3) 本地数据可能是从rqdatac读取保存的,有谁的动作那么快,这边保存了历史数据,那边立刻启动策略,立刻初始化?所以当rqdatac没有历史数据,转而从本地数据库读取,这本身就会造成历史K线与新合成处理的数据空档。

建议:在实盘环境下,CtaEngine.load_bar()不要保留从本地数据库读取历史数据

理由如下:

1 实盘策略不要从数据库读取数据的选择,因为容易造成无法补救的K线数据空档;
2 它发生在rqdatac无法读取的情况下,自然无法再从rqdata补齐数据;
3 而行情接口通常不提供历史分钟K线数据功能;
4 可以没有数据,没有当然不会计算买卖点,也就不会交易。
5 如果简单使用有空档K线数据,发出了错误的买卖信号,进行了错误的交易,这是不应该的!

比如:本地数据库最后一条数据是2天前的1分钟K线数据,而当前的rqdatac是不通的,可是行情接口是没有问题的,那么合成出来的新K线与历史K线之间会有多大的跳空,这个是无法想象的,依据这样的有空档的新、旧K数据来计算、交易,谁知道会出什么问题?

下一步的计划

目前的K线图表只是可以显示行情,下一步计划是:让CTA策略的交易在K线图上可以K得见。

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

沪公网安备 31011502017034号

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