vn.py量化社区
By Traders, For Traders.
Member
avatar
加入于:
帖子: 5
声望: 1

在使用VNPY时,只有两张表,一个是bar数据,一个是tick,实际使用中,这个数据库文件相当大,检索很费时。并看到有人也遇到了同样的困惑,建议vnpy-2.0.9 用关系型数据库

于是,我尝试基于vnpy 2.0.8,修改数据为 MongoDB,并将数据保存至指定的 Collection,同时保证当未指定 Collection 时,数据仍保存在原有的数据表中。适用于 tick数据 和 bar 数据。分享出来,如有错误,希望指正。本文也同时发于我的 CSDN 博客

更改数据库为 MongoDB

注意在MongoDB中需要创建新数据库,如“vnpytest”,然后在全局配置对话框中,修改相关配置(或直接修改vnpy运行目录 .vntrader 下的 vt_setting.json 文件):

"database.driver": "mongodb",
"database.database": "vnpytest",
"database.host": "localhost",
"database.port": 27017,
"database.user": "",
"database.password": "",
"database.authentication_source": ""

注意输入上述内容到配置对话框中时,请忽略引号。修改完毕保存后,请重新启动VN Trader,检查相关配置是否已经修改成功。

保存数据至指定的 mongodb collection

数据来源可以是 csv 文件,也可以从开源数据如 tushare (https://tushare.pro/register?reg=347489) 获取,等等,这里不再给出。

  1. 新建py文件,写入主函数
from vnpy.trader.database import database_manager
from vnpy.trader.object import BarData
from vnpy.trader.constant import Interval, Exchange


def ts2bar(df, collection_name=None):
    bars = []
    for i in range(df.shape[0]):
        bar = BarData(
            symbol=df.ts_code[i].split('.')[0],
            exchange=exc,
            datetime=df.trade_date[i],
            interval=Interval.DAILY,
            volume=df.vol[i],
            open_price=df.open[i],
            high_price=df.high[i],
            low_price=df.low[i],
            close_price=df.close[i],
            gateway_name='DB',
        )
        bars.append(bar)
    collection_name = collection_name
    print('Saving data to database ...')
    database_manager.save_bar_data(bars, collection_name)
  1. 修改 vnpy/trader/database/database.py 中的 save_bar_datasave_tick_data
@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
  1. 修改 vnpy/trader/database/database_mongo.py 中的 save_bar_datasave_tick_data
def save_bar_data(self, datas: Sequence[BarData], collection_name: str = None):
        for d in datas:
            ...

            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:
            ...

            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)
                    )

对于 MySQL 数据库,修改方法应该是类似的。

Administrator
avatar
加入于:
帖子: 4040
声望: 223

赞,给你加个精华

Member
avatar
加入于:
帖子: 5
声望: 1

谢谢,哈哈。

补充一下,使用 switch_collection 需要先载入指定模块 from mongoengine.context_managers import switch_collection

另外,关于读取指定 数据表/collection 中的数据进行回测和策略优化,我之后也会更新的。

Member
加入于:
帖子: 7
声望: 2

好tie

Member
加入于:
帖子: 7
声望: 2

楼主,请问这样设置后数据库存哪里了?.vtrader文件夹吗找不到?vnpy mongod数据库存储路径如何自己修改呢?谢谢。

Member
avatar
加入于:
帖子: 5
声望: 1

不会保存在vntrader中,是mongodb数据库的保存位置。修改的话就是更改mongodb的配置文件了,具体可以查一下,我也不太清楚

Member
avatar
加入于:
帖子: 1
声望: 0

谢谢大神,有两个问题想请教一下:1.mongodb的数据库db_tick_data的大索引(超过内存),按文中方法拆分成几个collection来减小索引,大索引和几个小索引哪个速度更好?如果拆分,回测时指定读哪个collection要怎么改?
2.文章中说的写入主函数是写入在什么地方?

Member
avatar
加入于:
帖子: 5
声望: 1

yaohui0610 wrote:

谢谢大神,有两个问题想请教一下:1.mongodb的数据库db_tick_data的大索引(超过内存),按文中方法拆分成几个collection来减小索引,大索引和几个小索引哪个速度更好?如果拆分,回测时指定读哪个collection要怎么改?
2.文章中说的写入主函数是写入在什么地方?

你好,不是什么大神,过奖啦。

  1. 速度我并没有测试,不确定。想着分为多个collection的起因是觉得所有数据都存入一个collection,个人觉得很不习惯,就改了改,速度不一定好。
  2. 回测时指定collection,主要需要修改 load_bar_data 函数。我更新一下帖子,附在后面吧
  3. 主函数的话,自己新建一个py 文件就好了,主要是用来测试修改是否成功了。
Member
avatar
加入于:
帖子: 5
声望: 1

更新:回测及策略优化

从指定 Mongo collection 读取数据进行回测

  1. 修改 vnpy/trader/database/database.py 中的 load_bar_dataload_tick_data

    def load_bar_data(
            self,
            ... # (省略不需要修改之处,下同)
            collection_name: str = None
        ) -> Sequence["BarData"]:
            pass
    
        @abstractmethod
        def load_tick_data(
            self,
            ...
            collection_name: str = None
        ) -> Sequence["TickData"]:
            pass
    
  2. 修改 vnpy/trader/database/database_mongo.py 中的 load_bar_dataload_tick_data

    def load_bar_data(
            self,
            ...
            collection_name: str = None,
        ) -> Sequence[BarData]:
            if collection_name is None:
                s = DbBarData.objects(
                    symbol=symbol,
                    exchange=exchange.value,
                    interval=interval.value,
                    datetime__gte=start,
                    datetime__lte=end,
                )
            else:
                with switch_collection(DbBarData, collection_name):
                    s = DbBarData.objects(
                        symbol=symbol,
                        exchange=exchange.value,
                        interval=interval.value,
                        datetime__gte=start,
                        datetime__lte=end,
                    )
            data = [db_bar.to_bar() for db_bar in s]
            return data
    
        def load_tick_data(
            self, ..., collection_name: str = None,
        ) -> Sequence[TickData]:
            if collection_name is None:
                s = DbTickData.objects(
                    symbol=symbol,
                    exchange=exchange.value,
                    datetime__gte=start,
                    datetime__lte=end,
                )
            else:
                with switch_collection(DbTickData, collection_name):
                    s = DbTickData.objects(
                        symbol=symbol,
                        exchange=exchange.value,
                        datetime__gte=start,
                        datetime__lte=end,
                    )
            data = [db_tick.to_tick() for db_tick in s]
            return data
    
  3. 修改 vnpy/app/cta_strategy/backtesting.py 中的 load_bar_dataload_tick_data

   @lru_cache(maxsize=999)
   def load_bar_data(
       ...,
       collection_name: str = None
   ):
       """"""
       return database_manager.load_bar_data(
           symbol, exchange, interval, start, end, collection_name,
       )


   @lru_cache(maxsize=999)
   def load_tick_data(
       ...,
       collection_name: str = None
   ):
       """"""
       return database_manager.load_tick_data(
           symbol, exchange, start, end, collection_name
       )
  1. 修改 vnpy/app/cta_strategy/backtesting.py 中的 BacktestingEngine.set_parameters

    def set_parameters(
            ...
            collection_name: str = None
        ):
            self.collection_name = collection_name
            ...
    
  2. 修改 vnpy/app/cta_strategy/backtesting.py 中的 BacktestingEngine.load_data

    ...
                if self.mode == BacktestingMode.BAR:
                    data = load_bar_data(
                        self.symbol,
                        self.exchange,
                        self.interval,
                        start,
                        end,
                        self.collection_name,
                    )
                else:
                    data = load_tick_data(
                        self.symbol,
                        self.exchange,
                        start,
                        end,
                        self.collection_name
                    )
    ...
    

策略优化

在策略优化中,也需要对数据的读取位置进行修改,因为默认优化函数读取的数据表/集合仍然是默认的 da_bar_datadb_tick_data。主要设计的函数是 BacktestingEngine.run_ga_optimization

  1. 修改 vnpy/app/cta_strategy/backtesting.py 中的 BacktestingEngine.run_ga_optimization

    # 添加一个全局变量,并赋值
    global ga_collection_name
    
    ga_collection_name = self.collection_name
    
  2. 修改vnpy/app/cta_strategy/backtesting.py 中的 _ga_optimize函数,添加函数参数

    result = optimize(
            ...,
            ga_collection_name,
        )
        return (result[1],)
    
  3. 修改vnpy/app/cta_strategy/backtesting.py 中的 optimize函数,添加函数参数

    def optimize(
        ...,
        collection_name: str = None
    ):
        ...
    
        engine.set_parameters(
            ...,
            collection_name=collection_name
        )
    

至此,应该就搞定了。

Administrator
avatar
加入于:
帖子: 4040
声望: 223

赞,这样读写就齐全了~

Administrator
avatar
加入于:
帖子: 4040
声望: 223

赞,这样读写就齐全了~

Member
avatar
加入于:
帖子: 4
声望: 0

您好,pycharm中运行 找不到这两个模块怎么办?
from vnpy.trader.vtObject import *
ModuleNotFoundError: No module named 'vnpy.trader.vtObject'

Member
avatar
加入于:
帖子: 1
声望: 0

能不能请大神对CTA回测的图形界面相应地做一个更新,在图形界面里面增加一个与collection_name对应的输入框,这样,在CTA回测的图形界面才可以进行回测。谢谢!

Member
avatar
加入于:
帖子: 17
声望: 2
vnpy/app/cta_strategy/backtesting.py    

函数 run_optimization 也需要修改吧

for setting in settings:
        result = (pool.apply_async(optimize, (
            target_name,
            self.strategy_class,
            setting,
            self.vt_symbol,
            self.interval,
            self.start,
            self.rate,
            self.slippage,
            self.size,
            self.pricetick,
            self.capital,
            self.end,
            self.mode,
            self.inverse,

self.collection_name
)))
results.append(result)

Member
avatar
加入于:
帖子: 17
声望: 0

反正就是所有調用这两个函数的地方都需要吧。

Member
avatar
加入于:
帖子: 6
声望: 0

新建的.py文件放到哪里,怎样用

Member
avatar
加入于:
帖子: 2
声望: 0

请问楼主,当前回测模块是没法加载 mysql数据库中 dbtickdata 表里面的tick数据,至少得是1m的K线数据才能驱动策略;

description

问题:现在写了个tick级别的交易策略,我如何加载数据库中的tick数据进来呢? 不胜感激

Member
avatar
加入于:
帖子: 17
声望: 1

按照楼主的描述对
vnpy/trader/database/database.py 中的 save_bar_data 和 save_tick_data
vnpy/trader/database/database_mongo.py 中的 save_bar_data 和 save_tick_data
的四个文件进行 修改,但数据还是会被存储在 db_bar_data 和 db_tick_data 中,并未存储在对应的子表里............

Member
avatar
加入于:
帖子: 23
声望: 2

是不是也可以不手工指定collection name, 就按照调用的exchange , interval 分表, 只在数据库接口根据参数选择相应的表?

Member
avatar
加入于:
帖子: 2
声望: 0

yuslience wrote:

请问楼主,当前回测模块是没法加载 mysql数据库中 dbtickdata 表里面的tick数据,至少得是1m的K线数据才能驱动策略;

description

问题:现在写了个tick级别的交易策略,我如何加载数据库中的tick数据进来呢? 不胜感激

在图形界面我也是没找到tick回测模式 我是在jupyter里直接调用backingtesting.py来解决的

© 2015-2019 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号-3