发布于vn.py社区公众号【vnpy-community】
原文作者:庸木 | 发布时间: 2020-02-17
1.0时代的vn.py只支持MongoDB数据库,在数据结构上使用MongoDB的数据库(DATABASE)来区分不同的历史数据时间周期,使用集合(COLLECTION)来区分不同的交易合约数据,许多用户(尤其以职业交易员背景居多)喜欢这种一目了然的数据结构。
2.0版本在数据库的设计上则是选择同时兼容SQL(SQLite、MySQL、PostgreSQL)和NoSQL(MongoDB)两种不同范式,使用软件工程中ORM(对象关系印射)/ODM(对象文件印射)构建了独立的抽象中间层(vnpy.trader.database),在数据存储的结构上发生了比较大的变化。
以2.0版本中MongoDB数据库为例,首先需要创建一个单独的数据库,通常命名为vnpy。在该数据库中只包含两张表,分别用于存放K线和tick数据:
db_bar_data集合
存放所有K线数据,包括不同的交易合约以及时间周期
通过vt_symbol+interval+datetime三个字段来保证唯一性
db_tick_data集合
存放所有Tick数据
通过vt_symbol+datetime两个字段来保证唯一性
通过Robo3T客户端查看的结构如下图:
尽管这种设计保证了和SQL类数据库的兼容性,但缺陷是随着使用时间的增长,集合中数据的长度和宽度都在增加。刚开始数据库可能只存5年内IF88的数据,到后来变成了所有国内期货品种从开始上市至今的所有1分钟历史数据,此时单一集合的数据总量会变得相当大,读取数据时查询检索很费时。
本文中试着给出一种解决方案:
若指定特定的集合名,则保存在该集合中;
若没有指定,则将数据仍保存在原有的集合中。
配置数据库为MongDB
如果数据库已经配置为MongoDB可以直接跳过这一段。找到用户目录下.vntrader
文件夹,用VSCode打开vt_setting.json
文件,对全局配置中的数据库相关字段进行以下修改。这里我们为了演示方便,创建一个新的名为【vnpytest】的数据库:
"database.driver": "mongodb",
"database.database": "vnpytest",
"database.host": "localhost",
"database.port": 27017,
"database.user": "",
"database.password": "",
"database.authentication_source": ""
如果尚未安装MongDB数据库以及可视化工具,可以参考往期文章:vn.py社区精选6 - 做交易,你需要选好数据库。
修改database.py文件
找到vn.py源代码所在的路径,进入目录vnpy/trader/database
打开database.py
文件,需要对save_bar_data
和save_tick_data
这两个函数进行修改,主要增加了一个参数collection_name:
@abstractmethod
def save_bar_data(
self,
datas: Sequence["BarData"],
collection_name: str = None,
):
pass
@abstractmethod
def save_tick_data(
self,
datas: Sequence["TickData"],
collection_name: str = None,
):
pass
修改database_mongo.py文件
然后打开位于同一目录下的database_mongo.py
文件,同样我们需要对 save_bar_data
和 save_tick_data
进行修改。
改动后的数据写入逻辑如下:
若没有指定collection_name,数据存入db_bar_data或db_tick_data;
若指定了collection_name,则调用switch_collection函数,把数据切换到写入到指定的Collection中。
from mongoengine.context_managers import switch_collection
def save_bar_data(self, datas: Sequence[BarData], collection_name: str = None):
for d in datas:
updates = self.to_update_param(d)
updates.pop("set__gateway_name")
updates.pop("set__vt_symbol")
if collection_name is None:
(
DbBarData.objects(
symbol=d.symbol, interval=d.interval.value, datetime=d.datetime
).update_one(upsert=True, **updates)
)
else:
with switch_collection(DbBarData, collection_name):
(
DbBarData.objects(
symbol=d.symbol, interval=d.interval.value, datetime=d.datetime
).update_one(upsert=True, **updates)
)
def save_tick_data(self, datas: Sequence[TickData], collection_name: str = None):
for d in datas:
updates = self.to_update_param(d)
updates.pop("set__gateway_name")
updates.pop("set__vt_symbol")
if collection_name is None:
(
DbTickData.objects(
symbol=d.symbol, exchange=d.exchange.value, datetime=d.datetime
).update_one(upsert=True, **updates)
)
else:
with switch_collection(DbTickData, collection_name):
(
DbTickData.objects(
symbol=d.symbol, exchange=d.exchange.value, datetime=d.datetime
).update_one(upsert=True, **updates)
)
Jupyter Notebook使用示例
最后可以通过Jupyter Notebook来测试下使用效果,这里我们将把CSV格式的1小时周期的XBTUSD数据,载入到指定的集合【XBTUSD】中。
具体步骤如下:
调用csv库读取"XBTUSD.csv"文件到内存中,其中DictReader类似于常规reader,但是将每行中的信息映射到一个字典,该字典的key由表头可选参数给出;
遍历每行的rows对象,生成BarData对象,并且缓存至bars列表中;
调用save_bar_data函数,把bars列表中的数据写入到我们指定的集合【XBTUSD】中。
注意:若该函数第二个参数collection_name为空,则bars列表中的数据会被写入默认的数据集合【db_bar_data】中。
from vnpy.trader.database import database_manager
from vnpy.trader.object import BarData
from vnpy.trader.constant import Interval, Exchange
import csv
file_name='XBTUSD.csv'
with open(file_name,newline='',encoding='UTF-8') as csvfile:
rows=csv.DictReader(csvfile)
bars = []
for i in rows:
bar = BarData(
symbol=i["symbol"],
exchange=Exchange.BITMEX,
datetime=i["datetime"],
interval=Interval.HOUR,
volume=i["volume"],
open_price=i["open"],
high_price=i["high"],
low_price=i["low"],
close_price=i["close"],
gateway_name='DB',
)
bars.append(bar)
print('Saving data to database ...')
database_manager.save_bar_data(bars, "XBTUSD")
载入完成后,打开MongDB客户端Robo3T,我们可以看到在vnpytest数据库下新增了一个集合【XBTUSD】,且所有1小时K线数据均已成功写入其中:
《vn.py全实战进阶 - 期权零基础入门》课程已经更新到第9集,内容专门面向从未接触过期权交易的新手,共计30节课程带你一步步掌握期权的基础知识、了解合约特征和品种细节、学习方向交易和套利组合等各种常用期权交易策略,详细内容请戳新课上线:《期权零基础入门》。