VNPY的源代码目录众多,分门别类非常多,许多文件的内容都很类似,甚至函数名称都是一样的,如果不做特别设置,查询代码会把一些你不使用的app、api、gateway下的文件都查询出来,很是烦人,经过本人琢磨,发现这么设置就不错:
随便怎么命名都可以,我就为其取名VNPY,如下图所示:
1 把vnpy的源代码目录加入工作区
2 把自己的策略源代码目录加入工作区
查询代码设置步骤
- 1 点击查找图标;
- 2 输入查找字符串;
- 3 设置需要包含的目录或者文件,只输入文件夹名称会包含下面的文件和子文件夹下的文件。需要输入多个文件夹时,用逗号割开;
- 4 设置需要排除的文件,与步骤3类似,可以使用通配符,如图所示:
经过这样设置以后,当你输入任何要查找字符串的时候,就不会显示那些你不感兴趣的app、gateway下的子目录的文件了。本人目前感兴趣的是trader,event,chart,rpc,app\cta_strategy,app\rpc_service,gateway\ctp,api,user_tools这些子目录中的文件,你也可以根据自己的需要特别设定,以提高代码查询的效率。
进阶课程里看过陈晓优老师讲的很多策略例子,都是讲如何买和如何卖,如何止盈和止损的,又是仿真、优化、实盘的,看到人是心潮澎湃!
于是看是着手仿照例子编写自己的策略,策略经过计算买卖信号后,总是要下单的,那我下多大仓位呢? 回过头这时候参考例子才发现,几乎所有的仓位都是self.fixed_size=1,就没有讲如何动态决定仓位的例子!
于是VNPY的QQ群里问前辈、先知,无人回答,再在论坛里问老师,终于回答了:”不建议动态仓位,这么重要的事情必须交给手工完成!“,这个答复让我有点懵——做量化交易你让为手动决定仓位???
假设是按手续费率(而不是按手收费),那么开仓资金为:
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%的资金,可以动态开仓的手数。这样不就可以动态开仓了吗?
当然实际开仓时的仓位计算可能比这复杂多了,比如你可以考虑交易合约的波动水平,需要考虑投资者愿意承担的风险水平等等,但不管怎么变化,策略动态开仓都必须要有如下这几个参数:
经过艰苦和漫长的代码研读和梳理,发现CTA策略交易中只有pos、trading和inited这些策略成员,没有与资金相关的东西。我们来看看这几个动态下单必须具备的参数是否提供了:
目前VNPY的CTA策略因为缺少上述几个关键参数,无法实现动态仓位交易。是不能也,而非不可以!
作为交易赚钱的CTA策略,怎么可以不与这些资金相关的参数打交道?因人而异的保证金和手续费不应该成为不提供这些参数的理由!
当多个策略在同时运行的时候,你的实际账户权益的消长,到底是哪个策略赚的,哪个策略赔的都无法说清楚,运行3天后就已经是一本糊涂账了,这怎么可以!
虽然有上面的困难,但是办法总比困难多!可以参考文华财经8.3或者库安9.0的办法(熟悉文华财经客户端的人应该都知道),它们的方法是用模组账户的方法来为每个用户模组创建一个虚拟的模组账户,很好地解决用户算法对资金、保证金和手续费等参数的设定!
策略账户的已经基本上实现了,目前只在测试中,且看我一步一步慢慢为大家分享......
有兴趣的可以先看看 策略账户界面展示。
按K线周期长度:
按K线参考起始时间划分:
VNPY对常规周期K线和自定义周期K线的处理是一视同仁的,统一使用BarGenerator()就可以了,它只使用1分钟K线数据,根据需要产生N分钟K线,至于产生出来是常规周期K线,还是自定义周期K线,主要看N是多少了。这样做的好处是自由!缺点是处理复杂些、速度慢些(不过反正不用手工处理,让计算机做,无所谓)。
它是按照自然时间来计算一根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分钟!
熟悉文华财经8.3的应该熟悉下图的设置:
以合约的日交易时间的起点为参考,然后以等交易时间宽度产生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线。
分别以合约的日盘交易时间起点和夜盘交易时间起点为参考,然后以等交易时间宽度产生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线。
以合约上市日的交易时间起点为参考,取出节假日,考虑交易时段,然后以等交易时间宽度产生K线。如FixedBarGenerator对N分钟K线的处理就是这种情况。FixedBarGenerator的原理参见和具体实现代码参见在前面的帖子里面已经描述得比较清楚了,这里不再重复。
参考合约上市日起点、等交易时间的好处是:所有的K线的交易时长都是想等的,它们的成交量是可类比的。因为交易时长不等的K线产生的所谓“地量”或者“天量”,有多大意义是可想而知的。这种K线产生机制的缺点是:可能产生跨交易日,跨日盘和夜盘的K线,这样可能造成观察不到交易日开盘跳空,日盘和夜盘开市时的跳空现象,因为可能那根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线参考起始时间划分:
VNPY对常规周期K线和自定义周期K线的处理是一视同仁的,统一使用BarGenerator()就可以了,它只使用1分钟K线数据,根据需要产生N分钟K线,至于产生出来是常规周期K线,还是自定义周期K线,主要看N是多少了。这样做的好处是自由!缺点是处理复杂些、速度慢些(不过反正不用手工处理,让计算机做,无所谓)。
它是按照自然时间来计算一根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分钟!
熟悉文华财经8.3的应该熟悉下图的设置:
以合约的日交易时间的起点为参考,然后以等交易时间宽度产生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线。
分别以合约的日盘交易时间起点和夜盘交易时间起点为参考,然后以等交易时间宽度产生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线。
以合约上市日的交易时间起点为参考,取出节假日,考虑交易时段,然后以等交易时间宽度产生K线。如FixedBarGenerator对N分钟K线的处理就是这种情况。FixedBarGenerator的原理参见和具体实现代码参见在前面的帖子里面已经描述得比较清楚了,这里不再重复。
参考合约上市日起点、等交易时间的好处是:所有的K线的交易时长都是想等的,它们的成交量是可类比的。因为交易时长不等的K线产生的所谓“地量”或者“天量”,有多大意义是可想而知的。这种K线产生机制的缺点是:可能产生跨交易日,跨日盘和夜盘的K线,这样可能造成观察不到交易日开盘跳空,日盘和夜盘开市时的跳空现象,因为可能那根K线还没有结束。
鱼和熊掌不可兼得,每种K线产生机制都有优点和缺点,至于您选择哪一种来产生你的K线,就看你的取舍了。明白与不明白个中的道理却是大相径庭的,所谓胸有成竹,才能够临危不乱。谨以本贴分享本人的一点浅薄之见,希望能够帮助到您!
合约参数中的保证金比例率和手续费是最难搞定的。
本贴介绍一种本人研究出来的一种办法:
1)手工创建合约参数文件(格式为json文件),内容包含:合约代码,市场,合约名称,类别,多头保证金率,空头保证金率,开仓手续费(率),平仓手续费(率),平今仓手续费(率)。
2)创建合约参数类,它的功能是从json文件夹中加载合约参数到合约参数字典,然后为外部提供合约参数查询功能。
3)当合约保证金率或者手续费(率)发生变化时,及时手动修改该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}
}
}
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策略的交易活动可以使用下面这些来描述:
只要你策略里面没有把账户持仓直接使用到策略里面,就不会相互影响。也就是说,如果你的这两个策略的交易如果使用的是理论持仓,那么它们会互相不影响。如果你的策略不是使用self.pos,而是直接从Oms中读取账户的总仓位进行平仓的话,那么A策略会把B策略的持仓一起平掉,等B再去平仓的时候已经无仓可平了。
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_())
[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
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.
我照帖子做了,错误少了些,可是仍然有未实现的、不支持的许可类型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
安装完成OpenSLL的时候,显示捐献窗口,最少10$,没有选择。如何在覆盖vnpy下图的这两个DLL,这会有问题吗?
以下代码保存在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
以下代码为 [用户目录]\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()
具体操作方法以及缺少的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线图的用户策略
以下为ag2012.SHFE的30分钟K线图,它显示的K线是等交易时长,位置也是固定的。
之前发布的交易时间段有错误,经过细致修改终于把各种特殊情况都修改,现在重新上传:
代码保存文件: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
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)
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')
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
不同合约的交易时间段可能是不一样的,它们可以利用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
注意把start和stop的单位换算为日内的分钟数量
已知tick中包含时间datetime,K线的周期为KW,那么从上市日算起的各个时间:
利用tick.datetime求出所在交易日,从而求出STTD
TickTD:tick在交易日内的分钟数
TickLD:tick从上市开始的分钟数=STTD+TickTD
IndexK:tick所在K线的所有=int(TickLD/KW)
STTK:K线开始时间=IndexK*KW
当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分钟
如果BarGenerator采用等交易时长产生K线,策略初始化时通过load_bar(n),读取1分钟历史K线,目前BarGenerator时是从n日之前的第一个1分钟K线区合约其他周期的K线的。
这导某些周期K线随n值不同,K线的起止时间会变化。而如果采用从上市日期开始计算等交易时长的K线位置,则无论何时初始化策略,K线的起止时间都是一样的。
1 等自然时长K线——无需考虑交易时段
2 等交易时长K线——需要考虑交易时段
3 从上市日起算K线——起止位置固定
4 从策略初始化时起算K线——起止位置不固定
策略的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的例子来说明问题。
陈老师在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()
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
)
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线与新合成处理的数据空档。
理由如下:
1 实盘策略不要从数据库读取数据的选择,因为容易造成无法补救的K线数据空档;
2 它发生在rqdatac无法读取的情况下,自然无法再从rqdata补齐数据;
3 而行情接口通常不提供历史分钟K线数据功能;
4 可以没有数据,没有当然不会计算买卖点,也就不会交易。
5 如果简单使用有空档K线数据,发出了错误的买卖信号,进行了错误的交易,这是不应该的!
目前的K线图表只是可以显示行情,下一步计划是:让CTA策略的交易在K线图上可以K得见。