CtpTdApi 未提供数据,需要修改onRspQryInvestorPosition接口实现
实现方案:
具体代码:
新增CtpTdApiVNPY类
class CtpTdApiVNPY(CtpTdApi):
def __init__(self, gateway: CtpGateway) -> None:
"""构造函数"""
super().__init__(gateway)
def onRspQryInvestorPosition(self, data: dict, error: dict, reqid: int, last: bool) -> None:
"""持仓查询回报"""
if not data:
return
# 必须已经收到了合约信息后才能处理
symbol: str = data["InstrumentID"]
contract = symbol_contract_map.get(symbol, None)
if contract:
# 获取之前缓存的持仓数据缓存
key: str = f"{data['InstrumentID'], data['PosiDirection']}"
position = self.positions.get(key, None)
if not position:
position = PositionData(
symbol=data["InstrumentID"],
exchange=contract.exchange,
direction=DIRECTION_CTP2VT[data["PosiDirection"]],
gateway_name=self.gateway_name
)
self.positions[key] = position
# 对于上期所昨仓需要特殊处理
if position.exchange in {Exchange.SHFE, Exchange.INE}:
if data["YdPosition"] and not data["TodayPosition"]:
position.yd_volume = data["Position"]
# 对于其他交易所昨仓的计算
else:
position.yd_volume = data["Position"] - data["TodayPosition"]
# 获取合约的乘数信息
size: int = contract.size
# 计算之前已有仓位的持仓总成本
cost: float = position.price * position.volume * size
position.opencost = data["OpenCost"]
position.openprice = 0
# 累加更新持仓数量和盈亏
position.volume += data["Position"]
position.pnl += data["PositionProfit"]
# 计算更新后的持仓总成本和均价
if position.volume and size:
cost += data["PositionCost"]
position.price = cost / (position.volume * size)
# 计算开仓均价
position.openprice = position.opencost / (position.volume * size)
# 更新仓位冻结数量
if position.direction == Direction.LONG:
position.frozen += data["ShortFrozen"]
else:
position.frozen += data["LongFrozen"]
# 计算浮盈
position.settlementPrice = data["SettlementPrice"]
floatprofit = position.settlementPrice * position.volume * size - position.opencost
if position.direction == Direction.LONG:
position.floatProfit = floatprofit
else:
position.floatProfit = -floatprofit
if last:
for position in self.positions.values():
self.gateway.on_position(position)
self.positions.clear()
新增CtpVNPY类
class CtpVNPY(CtpGateway):
def __init__(self, event_engine: EventEngine, gateway_name: str) -> None:
super().__init__(event_engine, gateway_name)
self.td_api: "CtpTdApi" = CtpTdApiVNPY(self)
init_cli_trading([CtpVNPY])
如何编写一个python脚本将本地.csv
文件导入数据库是vn.py论坛中新用户提问较多的问题之一。本文的主要目的是帮助新用户解决数据入库问题,以便用户可以快速进入量化策略的开发。
本文主要分为三大部分,第一部分介绍了在vn.py中使用MongoDB数据库所需要进行的配置 (只打算使用vn.py默认SQLite数据库的用户,可以简单了解一下 )。第二部分介绍了数据入库的基本流程 (适用于vn.py支持的所有数据库)。最后一部分则是具体的实现:分别将数据导入MongoDB和SQLite数据库(适用于vn.py支持的所有数据库)。
另外,在正文开始之前,还需要提醒大家:在将数据导入数据库之前,务必要确保这些数据已经是被清洗过的干净 的数据。如果将没有被清洗过,质量差的数据直接入库进行回测,可能会导致各种问题,影响策略开发的效率。因此,建议大家使用 高质量 的数据源。
vn.py 中默认使用 SQLite 数据库。 因此,如果需要使用 MongoDB 数据库,则需要修改 vn.py 的全局配置。具体流程如下:
在C:\Users\你的用户名\.vntrader
目录下找到 vt_setting.json
文件
对vt_setting.json
文件中的database.driver,database.database,database.host,database.port
进行修改。
下图是我自己设置的数据库配置信息:
上图中的两个 "mongodb"
可能让人会有些困扰。实际上第一个"mongodb"
是告诉 vn.py 我们使用的数据库类型是 MongoDB 数据库而不是默认的 SQLite 数据库。 第二个 "mongodb"
是告诉 vn.py 回测所需要的数据储存在 MongoDB 数据库中一个叫做 "mongodb"
的 database 中。这样说可能有些绕口,请看下图:
上图是 MongoDB 数据库的官方图形界面。我们可以清楚的看到在该 MongoDB 数据库中一共有四个 database, 分别是 admin,config,local, mongodb
。 另外,mongodb
database 中分别储存了不同类型的期货数据。比如,AP888,IF888, RB888
等。在该 database 中,不同类型的期货数据,分别 储存在不同的 collection(表)中。vn.py默认是将所有的期货数据储存在同一个collection中并进行读取。 如果,你想让 vn.py 将不同类型的期货数据分别储存在不同的collection中。请阅读这两篇文章vn.py社区精选18 - 老用户福音,MongDB分表重构 和 vn.py社区精选19 - 福音收尾,MongoDB分表读取数据。
在配置 MongoDB 数据库和设置好 MongoDB 分表读取之后(不配置数据库或设置分表读取,不影响后面的内容),我们可以正式开始讨论数据入库的基本流程。vn.py 提供了很多工具使数据入库这个过程变得简单,快捷。下面是数据入库的基本流程:
先确定要入库的数据是 Tick
还是 Bar
(K线) 类型数据
将需要入库的数据转化成 vn.py 定义的 TickData
或 BarData
数据类型
使用 vn.py 提供的数据入库工具函数 database_manager.save_tick_data
或 database_manager.save_bar_data
将相应的 TickData
或 BarData
入库
从上面的数据入库流程中可以看出,在 vn.py 中数据入库的流程还是比较简单的。难点集中在第二步(将本地数据转换成vn.py 定义的 TickData
或 BarData
)。一般来说,就是对数据的时间戳处理上。另外,如果数据本身的质量不高,比如数据的整个时间戳格式前后不一致,那需要先对数据的时间戳格式进行统一。时间戳相关知识请查看 Python时间日期格式化之time与datetime模块 这篇文章。
在了解了数据入库的基本流程之后,我们来实现一次数据入库的过程。首先,来看一看我们要入库的数据:
从上图可以看出,C 列储存的是表示时间的数据且 C2 和 C3 的时间间隔是1分钟。所以,要入库的数据是1分钟的 Bar
(K线)数据类型。下面我们进行第二步:将需要入库的数据转化成 vn.py 定义的 BarData
。
首先,我们先来认识一下 vn.py 中的BarData
:
从上图中可以看出,BarData
一共有11个属性。其中,BarData.vt_symbol
会在 BarData
实例化的时候自动生成。另外,需要指出的是BarData.exchange
和 BarData.inteval
的数据类型分别是 vn.py 中定义好的枚举常量 Exchange
和 Inteval
而 BarData.datetime
则是 python 标准库 datetime 中的 datetime
数据类型。
Exchange
数据结构的代码:
Interval
数据结构的代码:
在认识了vn.py 中的 BarData
之后,我们开始着手将需要入库的数据转化成 BarData
类型数据。再来重温一下,需要入库数据的格式:
通过和上文 BarData
的数据结构对比,我们有以下几个发现:
csv文件中的 合约代码 时间 开 高 低 收 成交量 持仓量
和 BarData
中的 symbol datetime open_price high_price low_price close_price volume open_interest
一一对应(从名称就就可以看出)。
csv文件中 市场代码
没办法和 BarData
中的 exchange
对应。因为csv文件中 市场代码
都是 SC
,而在上图Exchange
数据结构代码截图中找不到和 SC 对应的枚举常量的绑定值。从合约代码 ag1608(沪银1608) 可以推断出这里的 SC 指的就是上海期货交易所,对应的枚举常量是 Exchang.SHFE
。
csv文件中缺少了和 BarData
中的 interval
相对应的数据。上文我们已经发现了 csv文件中储存的是1分钟的BarData
,对应的枚举常量是 Interval.MINUTE
。
基于上面的发现,很自然的,我们需要进行如下的操作:
将csv文件中 市场代码
的 SC
替换成 Exchang.SHFE
增加一列数据,且该列数据的所有值都是 Interval.MINUTE
一般情况下,使用 python 的 pandas
库可以方便的完成上面的操作。如果数据的质量较差,比如数据的分隔符设置存在问题,会使得pd.read_csv
函数没办法正确的读取.csv
文件。这时则需要使用python的 csv
库。本文的数据入库过程统一使用 pandas
来完成。 具体操作,如下:
from vnpy.trader.constant import (Exchange, Interval)
import pandas as pd
# 读取需要入库的csv文件,该文件是用gbk编码
imported_data = pd.read_csv('需要入库的数据的绝对路径',encoding='gbk')
# 将csv文件中 `市场代码`的 SC 替换成 Exchange.SHFE SHFE
imported_data['市场代码'] = Exchange.SHFE
# 增加一列数据 `inteval`,且该列数据的所有值都是 Interval.MINUTE
imported_data['interval'] = Interval.MINUTE
接下来,我们还需要对每列数据的数据类型进行修改,确保和 BarData
中各个属性的数据类型一致。BarData
中属性的数据类型可以分为三大类:float
类, datetime
类 和 自定义枚举类 (Interval
和 Exchange
)。因为,上面已经修改过了Interval
和 Exchange
,下面只需要修改 float
和 datetime
类。
修改 float
类代码:
# 明确需要是float数据类型的列
float_columns = ['开', '高', '低', '收', '成交量', '持仓量']
for col in float_columns:
imported_data[col] = imported_data[col].astype('float')
修改 datatime
类代码:
# 明确时间戳的格式
# %Y/%m/%d %H:%M:%S 代表着你的csv数据中的时间戳必须是 2020/05/01 08:32:30 格式
datetime_format = '%Y%m%d %H:%M:%S'
imported_data['时间'] = pd.to_datetime(imported_data['时间'],format=datetime_format)
下一步,我们还需要对列名进行修改:
# 因为没有用到 成交额 这一列的数据,所以该列列名不变
imported_data.columns = ['exchange','symbol','datetime','open','high','low','close','volume','成交额','open_interest','interval']
另外,因为该csv文件储存的是ag的主力连续数据,即多张ag合约的拼接。因此,symbol
列中有多个不同到期日的ag合约代码,这里需要将合约代码统一为ag88
:
imported_data['symbol'] ='ag88'
最后,我们使用 vn.py 封装好的 database_manager.save_bar_data
将数据入库:
# 导入 database_manager 模块
from vnpy.trader.database import database_manager
from vnpy.trader.object import (BarData,TickData)
# 封装函数
def move_df_to_mongodb(imported_data:pd.DataFrame,collection_name:str):
bars = []
start = None
count = 0
for row in imported_data.itertuples():
bar = BarData(
symbol=row.symbol,
exchange=row.exchange,
datetime=row.datetime,
interval=row.interval,
volume=row.volume,
open_price=row.open,
high_price=row.high,
low_price=row.low,
close_price=row.close,
open_interest=row.open_interest,
gateway_name="DB",
)
bars.append(bar)
# do some statistics
count += 1
if not start:
start = bar.datetime
end = bar.datetime
# insert into database
database_manager.save_bar_data(bars, collection_name)
print(f"Insert Bar: {count} from {start} - {end}")
如果,默认的数据库是其它vn.py支持的数据库,上面的代码需要做略微修改后便可以使用(详情请看结尾Debug部分)。
如果,没有设置分表储存不同类型的数据。则需要先将move_df_to_mongodb
函数中的collection_name
参数删除,同时将上面代码的倒数第二行修改为:
database_manager.save_bar_data(bars)
如果,想要将数据储存储存在 SQLite 数据库中也很简单(默认数据库不是SQLite)。只需要两步就可以完成。
from vnpy.trader.database.initialize import init_sql
from vnpy.trader.database.database import Driver
settings={
"database": "database.db",
"host": "localhost",
"port": 3306,
"user": "root",
"password": "",
"authentication_source": "admin"
}
sqlite_manager = init_sql(driver=Driver.SQLITE, settings=settings)
2.使用sqlite数据库连接对象将数据入库
# 替换函数 move_df_to_mongodb 的倒数第二行
sqlite_manager.save_bar_data(bars)
本文尝试从数据库配置,数据入库基本流程,数据入库具体实现,三部分来帮助vn.py新用户解决编写python脚本实现数据入库这个难点。借助vn.py的database_manager
模块,用户基本上可以无缝切换SQLite,MongoDB等vn.py支持的数据库来读取和存入数据。希望这篇文章能帮助大家快速进入量化策略的研究和开发。
使用上述代码进行Sqlite
数据入库的时候,会出现peewee.InterfaceError: Error binding parameter 2 - probably unsupported type
错误,解决方法:
imported_data['时间'] = pd.to_datetime(imported_data['时间'],format=datetime_format)
代码所在行imported_data['时间'] = imported_data['时间'].dt.strftime('%Y%m%d %H:%M:%S')
详细的Debug过程记录在sqlite数据入库Debug. 将该文件夹内的内容下载到本地同一个位置,运行Jupyter Notebook就可以复现整个过程.
from vnpy.trader.constant import (Exchange, Interval)
import pandas as pd
from vnpy.trader.database import database_manager
from vnpy.trader.object import (BarData,TickData)
# 封装函数
def move_df_to_mongodb(imported_data:pd.DataFrame,collection_name:str):
bars = []
start = None
count = 0
for row in imported_data.itertuples():
bar = BarData(
symbol=row.symbol,
exchange=row.exchange,
datetime=row.datetime,
interval=row.interval,
volume=row.volume,
open_price=row.open,
high_price=row.high,
low_price=row.low,
close_price=row.close,
open_interest=row.open_interest,
gateway_name="DB",
)
bars.append(bar)
# do some statistics
count += 1
if not start:
start = bar.datetime
end = bar.datetime
# insert into database
database_manager.save_bar_data(bars, collection_name)
print(f'Insert Bar: {count} from {start} - {end}')
if __name__ == "__main__":
# 读取需要入库的csv文件,该文件是用gbk编码
imported_data = pd.read_csv('D:/1分钟数据压缩包/FutAC_Min1_Std_2016/ag主力连续.csv',encoding='gbk')
# 将csv文件中 `市场代码`的 SC 替换成 Exchange.SHFE SHFE
imported_data['市场代码'] = Exchange.SHFE
# 增加一列数据 `inteval`,且该列数据的所有值都是 Interval.MINUTE
imported_data['interval'] = Interval.MINUTE
# 明确需要是float数据类型的列
float_columns = ['开', '高', '低', '收', '成交量', '持仓量']
for col in float_columns:
imported_data[col] = imported_data[col].astype('float')
# 明确时间戳的格式
# %Y/%m/%d %H:%M:%S 代表着你的csv数据中的时间戳必须是 2020/05/01 08:32:30 格式
datetime_format = '%Y%m%d %H:%M:%S'
imported_data['时间'] = pd.to_datetime(imported_data['时间'],format=datetime_format)
# 因为没有用到 成交额 这一列的数据,所以该列列名不变
imported_data.columns = ['exchange','symbol','datetime','open','high','low','close','volume','成交额','open_interest','interval']
imported_data['symbol'] ='ag88'
move_df_to_mongodb(imported_data,'ag88')
这个文章主要是记录IB模拟交易使用的一些坑,IB TWS和VNPY链接和一般的接口不一样,不是VNPY通过API直接联网IB交易服务器,而且现在本地安装一个类似gateway的tws客户端,vnpy链接本地tws客户端,在由客户端中转到交易服务器。IB也提供非客户端的纯本地gatway软件,没有试过,这里只是说说通过TWS客户端。
在写之前没有看到陈总在知乎的文章,走了不少坑。https://zhuanlan.zhihu.com/p/75787960
1 - 安装IB TWS桌面版,然后登录模拟账户。假定你已经申请好了用户,并且有了模拟账户。如果没有可以直接官网申请。
2 - 等罗后一定要记得勾选“Enable ActiveX and Socket Clients”这个选项,此外在下方的“Trusted IPs”,要注意是否允许本地连接已经打开了,这个勾选后,默认127.0.0.1可以直接访问TWS,而其他地址,需要通过“Create”命令,加入到TWS的白名单里。
另外Socket port,和VNPY里面要填入一致,一般7497是模拟交易,7496是正式交易。
3 - 链接后,会返回message code,可以去下面网址查询message code含义,-1是warning
ERROR -1 2104 Market data farm connection is OK:usfarm.nj
ERROR -1 2104 Market data farm connection is OK:cashfarm
ERROR -1 2104 Market data farm connection is OK:usfarm
ERROR -1 2106 HMDS data farm connection is OK:hkhmds
ERROR -1 2106 HMDS data farm connection is OK:ushmds
https://interactivebrokers.github.io/tws-api/message_codes.html
4 - 研究下IB Gateway的三个类,简单说明下, 后面有空在研究
IbGateway: BaseGateway的继承,提供BaseGateway的一些abstractmethod的实现,被其他引擎按照标准调用,其实那些实现都是打包IbAPI的方法。
IbAPI:IBAPI 中Ewrapper 类的继承,基本所有订阅查询下单都是在这里二次打包了。
IbClient: IBAPI中EClient的继承,主要保持和TWS连通
5 - 订阅行情,这里是查询是盈透证券对于每个合约在某一交易所的唯一标识符ConId码,而非Symbol或者LocalName。要去下面官网查询,比如BABA就是166090175
https://contract.ibkr.info/v3.10/index.php
关于交易所,可以在TWS中点击右键 - Finacial Instrument Info 看到,SMART并不是交易所,而且IB会自动选择Primary Exhcange,避免选取太多。
如果提示错误信息 354, 那就是你没有订阅相关行情数据,那么去安装下面也去订阅,还有模拟账户共享真实账户订阅数据,所以即使模拟账户,还是要现在真实账户订阅。
https://interactivebrokers.github.io/tws-api/market_data.html
7 - 交易,直接在vnpy交易界面下单,没有什么问题,对于股票来说就是多是买入,空是卖出,和方向开平没有什么关系。
返回成交信息的时候,vnpy系统报错,研究发现是IB的模拟交易用了一个模拟交易所exhange,所以在vnpy/gateway/ib/ib_gateway.py和 trader/constant.py文件中加入这个exhange
Exchange.IBKRATS:'IBKRATS'
8 - 延时行情订阅,当你去订阅时候,会发现,嗯,收费的,不如我大A股,测试环境只能用延时数据,暂时还不准备打钱。这时候虽然在tws上是有数据,但是在vnpy尝试订阅延时数据时候,会提示说
ERROR 6 10168 Requested market data is not subscribed. Delayed market data is not enabled
研究了下,在vnpy/gateway/ib/ib_gateway.py中reqMktData前的延时快照数据读取激活,如果真的买了,最好改回去,虽然系统会自动推送最新数据如果发现你购买了。
这里默认是1真实行情, 3 是延时行情。live(1)frozen(2)delayed(3)delayedfrozen(4).
在reqMktData之前加一行
self.client.reqMarketDataType(3)
但是发现还是没有vnpy数据,再一看,是因为delyed的ticktype不一样,是88,而vnpy只让45这个实际的过来。而且还有tick数据也不一样编码,要从新加入到ENUM TICKFIELD_IB2VT, 好像折腾,直接再TWS就不可以看到了。改到这里就改下去吧,最好可以显示。
def tickString 修改成
if tickType!="45"ortickType!="88":
TICKFIELD_IB2VT 加入下面
66:"bid_volume_1",
69:"bid_price_1",
67:"ask_price_1",
70:"ask_volume_1",
68:"last_price",
71:"last_volume",
73:"high_price",
73:"low_price",
74:"volume",
75:"pre_close",
76:"open_price",
最后显示效果如下
后面我会尝试用Jupyter Notebook试试交易
发布于veighna社区公众号【vnpy-community】
原文作者: VeighNa小助手 | 发布时间:2023-12-20
由丛子龙老师主讲的《连续信号强度策略实战》课程已经正式开售,子龙老师之前在公众号上分享过的大类资产ETF策略和RSI多头择时策略收到了不少同学的好评。
连续信号强度策略基于VeighNa平台的PortfolioStrategy投组策略模块实现。不同于海龟策略基于通道的趋势突破逻辑,连续信号强度策略核心使用的是基于均线的趋势跟踪逻辑,同时加入了降噪指标、带通过滤器、以及连续信号强度计算器等优化技术。该策略不但适用于期货品种,也可用于其他标的(做空限制的因素考虑在内)。
众所周知,简单的双均线量化策略(金叉做多,死叉做空)在实盘中的效果往往都比较差强人意。即便是在样本内优化出了回测效果相对优秀的参数,但在样本外运行时也很难表现出足够的稳健性。其中很大的原因是因为信号忽闪(均线缠绕),也就是在震荡市中容易出现连续的错误信号。
连续信号强度策略的开发正是针对这一痛点,将信号(金叉死叉)出现的连续强度进行量化,配以业界标准的风控模型,与符合直觉的降噪指标相结合,力图打造一个更加有效的交易策略。
其次,很多同学对于策略参数优化结果的选择,还停留在简单选取排名第一的参数组。其实,在优化结果中还包含了众多可以利用并加以研究的细节信息,本课程中也会加以讲解。
这里放上课程结尾经过多种优化改进后的策略回测绩效图:
课程一共计划7节,内容大纲如下:
这门课程适合的人群:
课程当前已经上线,价格99元。直接在【VeighNa开源量化】公众号(vnpy-community)里就能购买和观看(点击底部菜单栏的【进阶资料】进入)。推荐使用PC微信打开,视频分辨率更加清晰。
本线上课程包含在【Elite会员】免费学习权益内。
发布于veighna社区公众号【vnpy-community】
原文作者:用Python的交易员 | 发布时间:2024-03-07
今年计划的社区活动场次较多,收到部分同学的建议决定上线【社区活动尊享卡】:包含12场任选活动报名(价值1188元,不设到期时间),同时提供报名活动的线上直播观看和会后视频回看(包含仅提供线下参会的场次)。尊享卡原价999元,早鸟价899元(3月底结束),参加活动较多的同学强烈推荐!购买请戳传送门或者扫描下方二维码:
Qlib系列活动的前3场已经结束,真是每场都座无虚席,这里放几张之前的活动照片:
再列举下整个系列活动计划的五场内容,分别是:
本次第4场活动同样仅提供线下参会(40人位置)。报名优先对参加了之前场次的同学开放,目前只剩大概1/4名额(提前报名的比例越来越高了),感兴趣的同学请抓紧。
内容:
面向AI模型的投资组合构建策略
对于策略的不同定位:Qlib vs VeighNa
标准化策略开发模板:BaseStrategy
Qlib中的内置策略研究:
信号类策略
规则类策略
委托生成器模块OrderGenerator
Qlib的历史回测和绩效评估
历史回测流程解析
绩效评估和图表梳理
和Alphalens的异同之处
QA问答和交流环节
时间:3月23日 14:00-17:00
地点:上海(具体地址后续在微信群中通知)
报名费:99元(Elite会员免费参加)
报名方式:扫描下方二维码填写表单报名(报名成功小助手会添加微信联系确认)
先厘清大思路,后面逐步完成。
vnpy系统自带了一个BarGenerator,它可以帮助我们生成1分钟,n分钟,n小时,日周期的K线,也叫bar。可是除了1分钟比较完美之外,有很多问题。它在读取历史数据、回测的时候多K线的处理和实盘却有不一样的效果。具体的问题我已经在解决vnpy 2.9.0版本的BarGenerator产生30分钟Bar的错误!这个帖子中做过尝试,但也不是很成功。因为系统的BarGenerator靠时间窗口与1分钟bar的时间分钟关系来决定是否该新建和结束一个bar,这个有问题。于是我改用对1分钟bar进行计数来决定是否该新建和结束一个bar,这也是有不可靠的问题,遇到行情比较清淡的时候,可能有的分钟就没有1分钟bar产生,这是完全有可能的!
K线几乎是绝大部分交易策略分析的基础,除非你从事的是极高频交易,否则你就得用它。可是如果你连生成一个稳健可靠的K线都不能够保证,那么运行在K线基础上的指标及由此产生的交易信号就无从谈起,K线错了,它们就是错误的,以此为依据做出点交易指令有可能是南辕北辙,所以必须解决它!
K线不是交易所发布的,它有很多种产生机制。其对齐方式、表现形式多种多样。关于K线的分类本人在以往的帖子中做出过比较详细的说明,有兴趣的读者可以去我以往的帖子中查看,这里就不再赘述。
市面上的绝大部分软件如通达信、大智慧、文华财经等软件,除非用户特别设定,他们最常提供给用户的K线多是日内对齐等交易时长K线。常用是一定是有道理的,因为它们已经为广大用户和投资者所接受。
1)什么是日内对齐等交易时长K线?
它具有这些特点:以每日开盘为起点,每根K线都包含相同交易时间的数据,跳过中间的休市时间,直至当前交易日的收盘,收盘不足n分钟也就是K线。实盘中,每日的第一个n分钟K线含集合竞价的那个tick数据。
2)为什么这种K线能够被普遍接受?
为它尽可能地保证一个交易日内的所有K线所表达的意义内容上是一致的,它们包含相等的交易时长。这非常重要,因为你把一个5分钟时长的K线与一个30分钟时长的K线放在一起谈论是没有意义的。但是如果为了保证K线在交易时长上的一致性,让n分钟K线跨日的话也是不太合理,因为这跨日,跨周末时间太长,这中间会发生什么意外事情,可能会产生出非常巨大的幅度大K线,掩盖了隔日跳空的行情变化,这对解读行情是不利的。当然n日的K线日跨日的,但是它是n个交易日的K线融合而成的,不过其融合的每个日K线也是对齐各自的日开盘的。
另外日内对齐等交易时长K线还有一个好处,那就是你以任何之前的时间为起点,在读取历史数据重新生成该日的n分钟K线的时候,得到的改日的K线是一致的。举个例子,如果我们的CTA策略在init()中常常是这么一句:
self.load_bar(20) # 先加载20日历史1分钟数据
这么简单的一句,包含着很多你意识不到的变化——你今天运行策略和明天运行你的策略,其中的历史数据的范围发生了变化,也就是说加载数据的起点变了。如果我们合成的K线的对齐方式不采用日内对齐的话,而采用对齐加载时间起点的话,你今天、明天加载出来之前的某日的K线就可能完全是不同的。而采用日内对齐等交易时长的K线则不存在这个问题。
3)需要知道合约的交易时间段
既然要对齐每日开盘,还有跳过各个休市时间,还要知道收市时间,那么我们就知道生成这种K线必须知道其所表达合约或对象的交易时间段,交易时间段中包含了这些信息,不知道这些信息,BarGenerator就不知道如何生成这种bar。这是必须的!
目前vnpy系统中的是没有合约的交易时间段的。到哪里获取合约的交易时间段的呢?
1) 它与合约相关,应该到保存合约的数据类ContractData中去找,没有找到。
2) 是否可以提供接口,从交易所获得,这个也是比较基础的数据。于是到CTP接口中(我使用的是CTP接口,您也许不一样) ,在最新版本的CTP接口文档中也没有找到任何与交易时间段相关的信息,绝望!
解决方法:
打开vnpy.trader.datafeed.py文件为Datafeed的基类BaseDatafeed扩展下面的接口
class BaseDatafeed(ABC):
"""
Abstract datafeed class for connecting to different datafeed.
"""
def init(self) -> bool:
"""
Initialize datafeed service connection.
"""
pass
def update_all_trading_hours(self) -> bool: # hxxjava add
""" 更新所有合约的交易时间段到trading_hours.json文件中 """
pass
def load_all_trading_hours(self) -> dict: # hxxjava add
""" 从trading_hours.json文件中读取所有合约的交易时间段 """
pass
def query_bar_history(self, req: HistoryRequest) -> Optional[List[BarData]]:
"""
Query history bar data.
"""
pass
def query_tick_history(self, req: HistoryRequest) -> Optional[List[TickData]]:
"""
Query history tick data.
"""
pass
其中的trading_hours.json文件我会在后面的文章中做详细的介绍。有了它我们才能展开其他的设计。
在vnpy_rqdata\rqdata_datafeed.py中增加下面的代码
from datetime import timedelta,date # hxxjava add
def update_all_trading_hours(self) -> bool: # hxxjava add
""" 更新所有合约的交易时间段到trading_hours.json文件中 """
if not self.inited:
self.init()
if not self.inited:
return False
ths_dict = load_json(self.trading_hours_file)
# instruments = all_instruments(type=['Future','Stock','Index','Spot'])
trade_hours = {}
for stype in ['Future','Stock','Index','Fund','Spot']:
instruments = all_instruments(type=[stype])
# print(f"{stype} instruments count={len(instruments)}")
for idx,inst in instruments.iterrows():
# 获取每个最新发布的合约的建议时间段
if ('trading_hours' not in inst) or not(isinstance(inst.trading_hours,str)):
# 跳过没有交易时间段或者交易时间段无效的合约
continue
inst_name = inst.trading_code if stype == 'Future' else inst.order_book_id
inst_name = inst_name.upper()
if inst_name.find('.') < 0:
inst_name += '.' + inst.exchange
if inst_name not in ths_dict:
str_trading_hours = inst.trading_hours
# 把'01-'或'31-'者替换成'00-'或'30-'
suffix_pair = [('1-','0-'),('6-','5-')]
for s1,s2 in suffix_pair:
str_trading_hours = str_trading_hours.replace(s1,s2)
# 如果原来没有,提取出来
trade_hours[inst_name] = {"name": inst.symbol,"trading_hours": str_trading_hours}
# print(f"trade_hours old count {len(ths_dict)},append count={len(trade_hours)}")
if trade_hours:
ths_dict.update(trade_hours)
save_json(self.trading_hours_file,ths_dict)
return True
def load_all_trading_hours(self) -> dict: # hxxjava add
""" 从trading_hours.json文件中读取所有合约的交易时间段 """
json_file = get_file_path(self.trading_hours_file)
if not json_file.exists():
return {}
else:
return load_json(self.trading_hours_file)
在vnpy\trader\engine.py中:
from .datafeed import get_datafeed # hxxjava add
def get_trading_hours(self,vt_symbol:str) -> str: # hxxjava add
""" get vt_symbol's trading hours """
ths = self.all_trading_hours.get(vt_symbol.upper(),"")
return ths["trading_hours"] if ths else ""
因为无论你运行vnpy中的哪个app,你都会启动main_engine,无需绕弯子就可以得到这些信息,而我们的用户策略中都包含各自策略的引擎,这样就方便获取交易时间段信息。
如CTA策略中包含cta_engine,而cta_engine它的成员就包含main_engine。那么在策略中执行类似下面的语句就可以获取您交易品种的交易时间段信息:
trading_hours = self.cta_engine.main_engine.get_trading_hours(selt.vt_symbol)
如PortFolioStrategy策略中包含strategy_engine,而strategy_engine它的成员就包含main_engine。那么在策略中执行类似下面的语句就可以获取多个交易品种的交易时间段信息:
trading_hours_list = [self.cta_engine.main_engine.get_trading_hours(vt_symbol) for vt_symbol in self.vt_symbols]
是不是很方便呢?
vnpy 3.0的启动界面中已经集成了一个叫“投研”的功能,其实它是jupyter lab,启动之后输入下面的代码:
# 测试update_all_trading_hours()函数和load_all_trading_hours()
from vnpy.trader.datafeed import get_datafeed
df = get_datafeed()
df.init()
df.update_all_trading_hours() # 更新所有合约的交易时间段到本地文件中
ths = df.load_all_trading_hours() # 从本地文件中读取所有合约的交易时间段
当然您可以在vnpy的trader中主界面的菜单中增加一项,方便您在需要的时候执行下面语句。不过这个更新交易时间段的功能并不需要频繁执行,手动也就够了,记得就好。
经过上面步骤3.4.4,您就在本地得到了一个trading_hours.json文件,该文件在您的用户目录下的.vntrader\中,其内容如下:
{
"A0303.DCE": {
"name": "豆一0303",
"trading_hours": "21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00"
},
"A0305.DCE": {
"name": "豆一0305",
"trading_hours": "21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00"
},
"A0307.DCE": {
"name": "豆一0307",
"trading_hours": "21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00"
},
"A0309.DCE": {
"name": "豆一0309",
"trading_hours": "21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00"
},
"A0311.DCE": {
"name": "豆一0311",
"trading_hours": "21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00"
},
"A0401.DCE": {
"name": "豆一0401",
"trading_hours": "21:00-23:00,09:00-10:15,10:30-11:30,13:30-15:00"
},
... ...
}
观察其格式,在你没有米筐数据接口或者这里没有的合约,您也可以手动输入合约交易时间段信息。
按照程序中算法,这个文件文件中一共包含约16500多个合约的交易时间段信息。可以覆盖国内金融市场几乎全部都产品,但是不包括金融二次衍生品期权。
为什么没有期权交易时间段信息,因为不需要。期权合约有其对应的标的物,从其名称和编号就可以解析出来。期权合约的交易时间段其和标的物的交易时间段是完全相同的,因此不需要保存到该文件中。
2019年写了《vn.py 2.0.7源代码深入分析》,感谢各位老师的认可。
vn.py的维护团队是一个非常有朝气的团队,版本更新很快,3月底发布了最新的2.2.0版。
使用vn.py做交易,因为需要做一些自己的定制,所以并不是每个版本都随着更新,前面使用的是2.1.4。
但vn.py的每个新版本我都会关注,并不断更新文档。vn.py从2.1.9开始对基本功能(主要是数据管理部分)做了比较大的优化,我认为2.2.0应该是比较稳定好用的版本,准备将日常使用的系统升级到2.2.0。3月26日终于等到了2.2.0版,立刻开始分析代码并完善文档,这几天夜以继日,终于形成了这个文档,发出来请各位老师批评指正。
《10-vn.py 2.2.0源代码深入分析》
百度网盘链接:https://pan.baidu.com/s/1X3WNoE27RKJgxx6leC5X0g
提取码:6vl3
我学Python的目的很明确,就是量化交易。从一开始就有关注vn.py,但我学的是Python3,那时vn.py还处于版本1.x时期,所以只能望vn.py兴叹。
vn.py 2.0出来之后我并没有及时注意,等反应过来已经是2.0.7版。很兴奋,认真研究,并将心得写成《vn.py 2.0.7源代码深入分析》,分享在vn.py社区的经验分享板块。
出于对量化交易的爱好,出于对Python在量化交易中作用的认同,一定程度受vn.py强大功能的鼓舞,我与同事合写了《Python量化交易从入门到实战》一书,对vn.py的讨论是其中很重要的一部分内容。
后续又写了《vn.py 2.1.4源代码深入分析》和《vn.py 2.2.0源代码深入分析》两个文档,感谢各位老师的认可。
vn.py 3.0.0版发布于2022-03-23,这是我一直期待的一个版本,所以它刚一推出,我就立刻开始试用,并着手整理《vn.py 3.0.0源代码深入分析》。夜以继日,终于在前天完成。先发到了书籍的资源群中,接受了两天批评,现分享到此处。
写作本文档的一个主要目的是对vn.py的开源精神做出一点支持,希望本文档能够对大家学习使用vn.py有所帮助。
百度网盘链接:https://pan.baidu.com/s/1cl2MA9hNFhHlxfHM0gGe2A
提取码:s7u6
发布于veighna社区公众号【vnpy-community】
原文作者:用Python的交易员 | 发布时间:2024-02-28
《精研期权价差策略》课程已经更新到38集,预计将在两周内全部更新结束。在完成对跨式价差和牛熊价差的策略回测绩效评估后,将要进入课程的收尾环节,结合CTA趋势信号和比例价差组合开发更具实盘价值的复合策略,感兴趣的同学请戳【传送门】。
之前的《连续信号强度策略》课程得到了不少主攻期货量化同学的好评支持,我们基于课程内容制作了这张【知识要点图】:
本课程同样包含在【VeighNa Elite版】的会员权益范围内,其他会员权益包括:
感兴趣的同学请扫描下方二维码,或者点击底部的【阅读原文】跳转:
耗时2个多月和迅投团队配合测试对接,VeighNa框架的迅投研数据服务的接口vnpy_xt正式上线,支持股票、期货、期权、基金等历史量价数据的获取。
迅投为VeighNa社区提供了专属的试用申请链接:https://xuntou.net/#/signup?utm_source=vnpy
注册申请后即可获取14天的免费试用,目前数据流量上限较高,推荐有需要的同学不要错过!!!(在有效期内多下载一些数据)
整体使用流程如下:
使用过程中遇到任何问题可以通过社区论坛寻投研专区提问交流:https://www.vnpy.com/forum/forum/35-xun-tou-yan