【vn.py学习笔记(八)】vn.py utility、BarGenerator、ArrayManager源码阅读

写在前面

  笔者刚接触量化投资,对量化投资挺感兴趣,在闲暇时间进行量化投资的学习,只能进行少量资金进行量化实践。目前在进行基于vnpy的A股市场的量化策略学习,主要尝试攻克的技术难点在:A股市场日线数据的免费获取维护、自动下单交易、全市场选股程序、选股策略的回测程序、基于机器学习的股票趋势预测。现阶段的计划是阅读vn.py的源码,学习vn.py架构机制,在学习的过程中,会以分享的形式记录,以加深对vn.py的理解,有不对的地方欢迎大家批评指正。
  分享的github仓库:https://github.com/PanAndy/quant_share

觉得文章有收获,欢迎关注公众号鼓励一下作者呀~
在学习的过程中,也搜集了一些量化、技术的视频及书籍资源,欢迎大家关注公众号【亚里随笔】获取
百度网盘资源


  这次来学习一下vnpy/trader/utility.py下的内容,utility.py下的内容可以分为三个部分:工具函数、BarGenerator、ArrayManager,其中工具函数部分比较好理解,只是对通用的一些功能进行的封装;BarGenerator是K线合成器,负责根据实时接收的tick数据合成1分钟k线,并借此合成n分钟K线;ArrayManager是指标计算辅助类,负责维护一定量的历史数据,以供常见指标如sma、ema、atr等的计算。BarGenerator和ArrayManager是这次重点关注的内容。

1 工具函数

  utility.py提供的工具函数是主要是对合约代码的转换、路径的读取、json文件读写、数值位数的设置、日志等相关的功能,这些函数主要是对基本功能的封装,没有特别复杂的算法。工具函数的接口如下:

# 合约代码的转换
def extract_vt_symbol(vt_symbol: str) -> Tuple[str, Exchange]:
def generate_vt_symbol(symbol: str, exchange: Exchange) -> str:

# 路径的读取
def _get_trader_dir(temp_name: str) -> Tuple[Path, Path]:
def get_file_path(filename: str) -> Path:
def get_folder_path(folder_name: str) -> Path:
def get_icon_path(filepath: str, ico_name: str) -> str:

# json文件读写
def load_json(filename: str) -> dict:
def save_json(filename: str, data: dict) -> None:

# 数值位数设置
def round_to(value: float, target: float) -> float:
def floor_to(value: float, target: float) -> float:
def ceil_to(value: float, target: float) -> float:
def get_digits(value: float) -> int:

def virtual(func: Callable) -> Callable:

# 日志相关
def _get_file_logger_handler(filename: str) -> logging.FileHandler:
def get_file_logger(filename: str) -> logging.Logger:

2 BarGenerator

  BarGenerator类用于从tick数据中生成1分钟bar数据,也可以用于从1分钟的bar数据中合成x分钟或者x小时的bar。对于合成x分钟的bar,x必须能被60整除,如2、3、5、6、10、15、20、30;对于合成x小时的bar,x可以是任何整数。BarGenerator的主要函数有6个:

  • update_tick,负责向BarGenerator类输入一个最新的tick,使用输入的tick更新bar。
  • update_bar,负责向BarGenerator类输入一个最新的bar,可以使用这个方法生成x分钟/小时的新bar,它会调用update_bar_minute_window或者update_bar_hour_window。
  • update_bar_minute_window,由1分钟bar生成x分钟bar的逻辑。
  • update_bar_hour_window,由1分钟bar生成1小时bar的逻辑。
  • on_hour_bar,由1小时bar生成x小时bar的逻辑。
  • generate,强制把当前缓存着的k线合成完毕并推送出来,它主要用于郑州商品交易所每天在收盘后缺失收盘最终的tick推送导致行情记录的时候,最后的k线推不出来。

接下来,我们逐个来看具体实现算法。

2.1 update_tick

  害,本来想画个流程图直观一些的,发现判断太多了,还不如python代码看着清晰,我就只在代码里添加了一些注释说明了。update_tick的基本逻辑就是先对tick数据进行过滤,然后判断self.bar是否为空,如果为空,说明是新的一分钟;如果self.bar不为空,判断接收到的tick与self.bar是否处于同一分钟, 如果不处于同一分钟,则是新一分钟,把当前的self.bar推送出去。接下来,如果是新的一分钟,则新创建一个BarData对象,用于后续累积更新,如果不是新的一分钟,由使用当前接收到的tick对self.bar进行累积更新。随后,如果self.last_tick非空,则可以计算self.bar的在这一分钟内的成交量。最后,更新self.last_tick进行缓存。

    def update_tick(self, tick: TickData) -> None:
        """
        Update new tick data into generator.
        """
        # 判断是否走完了一分钟
        new_minute = False

        # Filter tick data with 0 last price
        # 最新成交价为0
        if not tick.last_price:
            return

        # Filter tick data with older timestamp
        # 过滤掉收到的过去的tick
        if self.last_tick and tick.datetime < self.last_tick.datetime:
            return

        if not self.bar:
            # self.bar为None,那收到的tick就是新的一分钟的tick
            new_minute = True
        elif (
            (self.bar.datetime.minute != tick.datetime.minute)
            or (self.bar.datetime.hour != tick.datetime.hour)
        ):
            # self.bar不为None,判断是否到了下一分钟,如果到了下一分钟,就给self.bar推出去。
            self.bar.datetime = self.bar.datetime.replace(
                second=0, microsecond=0
            )
            # 已经过了当着这一分钟了,把已经合成的bar推出去
            self.on_bar(self.bar)

            new_minute = True

        if new_minute:
            # 新的一分钟,新生成一个bar对象
            # 初始化bar
            self.bar = BarData(
                symbol=tick.symbol,
                exchange=tick.exchange,
                interval=Interval.MINUTE,
                datetime=tick.datetime,
                gateway_name=tick.gateway_name,
                open_price=tick.last_price,
                high_price=tick.last_price,
                low_price=tick.last_price,
                close_price=tick.last_price,
                open_interest=tick.open_interest
            )
        else:
            # 将当前tick的信息更新到bar里
            self.bar.high_price = max(self.bar.high_price, tick.last_price)
            if tick.high_price > self.last_tick.high_price:
                self.bar.high_price = max(self.bar.high_price, tick.high_price)

            self.bar.low_price = min(self.bar.low_price, tick.last_price)
            if tick.low_price < self.last_tick.low_price:
                self.bar.low_price = min(self.bar.low_price, tick.low_price)

            self.bar.close_price = tick.last_price
            self.bar.open_interest = tick.open_interest
            self.bar.datetime = tick.datetime

        if self.last_tick:
            # 当前品种、全天交易到当前tick时的成交量,而不是最新的一笔tick的成交量
            volume_change = tick.volume - self.last_tick.volume
            self.bar.volume += max(volume_change, 0)

        self.last_tick = tick

2.2 update_bar

  update_bar函数的角色相当于一个调度员,根据self.interval的粒度来选择是调用self.update_bar_minute_window还是调用self.update_bar_hour_window,最终相应粒度的bar数据合成完成之后,都会回调self.on_window_bar函数。

    def update_bar(self, bar: BarData) -> None:
        """
        Update 1 minute bar into generator
        """
        if self.interval == Interval.MINUTE:
            self.update_bar_minute_window(bar)
        else:
            self.update_bar_hour_window(bar)

2.3 update_bar_minute_window

  update_bar_hour_window的实现逻辑和update_tick的逻辑差不多,它在一个函数实现了缓存x分钟的逻辑,主要是缓存1分钟bar的逻辑在update_tick中已经实现了。update_bar_minute_window的逻辑就是将一分钟bar积累起来,当累积的数目达到目标self.window时,进行推送。

    def update_bar_minute_window(self, bar: BarData) -> None:
        """"""
        # If not inited, create window bar object
        if not self.window_bar:
            dt = bar.datetime.replace(second=0, microsecond=0)
            self.window_bar = BarData(
                symbol=bar.symbol,
                exchange=bar.exchange,
                datetime=dt,
                gateway_name=bar.gateway_name,
                open_price=bar.open_price,
                high_price=bar.high_price,
                low_price=bar.low_price
            )
        # Otherwise, update high/low price into window bar
        else:
            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
            )

        # 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

        # Check if window bar completed
        if not (bar.datetime.minute + 1) % self.window:
            self.on_window_bar(self.window_bar)
            self.window_bar = None

        # Cache last bar object
        self.last_bar = bar

2.4 update_bar_hour_window

  update_bar_hour_window的实现逻辑和update_tick的逻辑也差不多,只是将分钟级别的合成升级到小时级别的合成。如果self.hour_bar为None,由新建一个BarData对象;如果self.hour_bar不空None,则判断当前接收bar的分钟是不是第59分钟,如果是,则说明1小时bar合成完成,缓存进finished_bar中,后面给推送出去,并把self.hour_bar清空;如果当前接收的bar是位于新的一个小时,则把缓存的一小时bar推送出去,开启新的一小时bar缓存;其他情况,则说明在当前一小时内,继续累积更新缓存的bar;接着,如果finished_bar不为None,则说明有可以推送出去的bar,调用self.on_hour_bar,继续进行累积x小时bar的逻辑;最后,把当前接收到的bar数据缓存到self.last_bar中。

    def update_bar_hour_window(self, bar: BarData) -> None:
        """"""
        # If not inited, create window bar object
        if not self.hour_bar:
            dt = bar.datetime.replace(minute=0, second=0, microsecond=0)
            self.hour_bar = BarData(
                symbol=bar.symbol,
                exchange=bar.exchange,
                datetime=dt,
                gateway_name=bar.gateway_name,
                open_price=bar.open_price,
                high_price=bar.high_price,
                low_price=bar.low_price
            )
            return

        finished_bar = None

        # If minute is 59, update minute bar into window bar and push
        if bar.datetime.minute == 59:
            self.hour_bar.high_price = max(
                self.hour_bar.high_price,
                bar.high_price
            )
            self.hour_bar.low_price = min(
                self.hour_bar.low_price,
                bar.low_price
            )

            self.hour_bar.close_price = bar.close_price
            self.hour_bar.volume += int(bar.volume)
            self.hour_bar.open_interest = bar.open_interest

            finished_bar = self.hour_bar
            self.hour_bar = None

        # If minute bar of new hour, then push existing window bar
        elif bar.datetime.hour != self.hour_bar.datetime.hour:
            finished_bar = self.hour_bar

            dt = bar.datetime.replace(minute=0, second=0, microsecond=0)
            self.hour_bar = BarData(
                symbol=bar.symbol,
                exchange=bar.exchange,
                datetime=dt,
                gateway_name=bar.gateway_name,
                open_price=bar.open_price,
                high_price=bar.high_price,
                low_price=bar.low_price
            )
        # Otherwise only update minute bar
        else:
            self.hour_bar.high_price = max(
                self.hour_bar.high_price,
                bar.high_price
            )
            self.hour_bar.low_price = min(
                self.hour_bar.low_price,
                bar.low_price
            )

            self.hour_bar.close_price = bar.close_price
            self.hour_bar.volume += int(bar.volume)
            self.hour_bar.open_interest = bar.open_interest

        # Push finished window bar
        if finished_bar:
            self.on_hour_bar(finished_bar)

        # Cache last bar object
        self.last_bar = bar

2.5 on_hour_bar

  on_hour_bar就和update_bar_minute_window的逻辑一样了,将由update_bar_hour_window产生的一小时bar积累起来,当累积数目达到目标self.window时,进行推送。

    def on_hour_bar(self, bar: BarData) -> None:
        """"""
        if self.window == 1:
            self.on_window_bar(bar)
        else:
            if not self.window_bar:
                self.window_bar = BarData(
                    symbol=bar.symbol,
                    exchange=bar.exchange,
                    datetime=bar.datetime,
                    gateway_name=bar.gateway_name,
                    open_price=bar.open_price,
                    high_price=bar.high_price,
                    low_price=bar.low_price
                )
            else:
                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
                )

                self.window_bar.close_price = bar.close_price
                self.window_bar.volume += int(bar.volume)
                self.window_bar.open_interest = bar.open_interest

            self.interval_count += 1
            if not self.interval_count % self.window:
                self.interval_count = 0
                self.on_window_bar(self.window_bar)
                self.window_bar = None

2.6 generate

  generate函数的作用就是强制把当前缓存着的bar推送出去。

    def generate(self) -> Optional[BarData]:
        """
        Generate the bar data and call callback immediately.
        """
        bar = self.bar

        if self.bar:
            bar.datetime = bar.datetime.replace(second=0, microsecond=0)
            self.on_bar(bar)

        self.bar = None
        return bar

3 ArrayManager

  ArrayManager是时间序列容器,用于按时间序列缓存bar数据,提供技术指标的计算。ArrayManager的整体结构图(来源于《全实战进阶系统 - CTA策略》)如下图所示,它提供的函数分为四类:init函数、update_bar、@property函数、技术指标函数。

  • init函数中定义了缓存K线的计数count、需要缓存的最小K线数量size、是否缓存足够K数据标识inited。
  • update_bar函数采用切片平移的方式更新最新数据,当K线数量达到size大小时,将inited设置为true。
  • @property函数提供了访问缓存K线数据的方式。
  • 技术指标函数提供了利用缓存K线数据计算指标的方法,它们既可以返回最新一个周期的指标值,也可以返回计算出的所有指标序列,可以通过参数array控制。
    在这里插入图片描述

学习资料

  1. vn.py community 《全实战进阶系统 - CTA策略》课程8-K线自定义合成
  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
vn.py】CTP首次登陆修改密码 之 接口调用法 c++量化 阅读约 7 分钟 编辑 背景 最近一直在玩vn.py,上一篇文章vn.py开发环境搭建(windows)介绍了如何搭建二次开发环境,解决了一些搭建环境过程中遇到的坑。那么接下来这篇文章将解决运行期间的第一个问题。 开始vn.py 1.环境部署完成之后,启动examples/vn_trader/run.py,弹出启动页面。具体搭建过程参考vn.py开发环境搭建(windows)。 image.png 2。连接CTP,点击系统->连接CTP,弹出连接界面。 image.png 3.介绍一下个字段 用户名:在simnow注册的用户名6位数字 密码:登录simnow使用的密码 经纪商代码:9999 交易服务器地址:180.168.146.187:10101 行情服务器地址:180.168.146.187:10111 产品名称:simnow_client_test 授权码:0000000000000000 16个0 4.获取你的用户名和密码 登录http://www.simnow.com.cn/,从右上角的注册账号开始操作,这里就不讲了,大家自己鼓捣吧。 问题来了 所有准备工作做好后,点击连接,这时左下角会显示日志。 image.png 如果你是第一次登陆,那么会提示CTP首次登陆需要修改密码,这也就是我们今天要解决的问题 试图解决 刚看到这个问题觉得没什么,修改一下密码就可以了,但是。。。。。 首先没有再vn.py上找到可以修改密码的地方,后来想了想,也正常人家只是策略平台,也不仅仅是给CTP用, 所以去注册的地方simnow官网找找吧,但是。。。。 很遗憾仍然没有找到,这里不得不吐槽一下simnow了。是不是应该给个改密码的地方? 据说可以使用市场上的交易应用修改密码,大家可以去试试。或者大家有更好的方法也可以回复留言。 开始撸它 好吧,那么我们用程序员的方式来解决这个问题吧。 1.在vnpy/api/ctp目录下可以看到完整ctp开发的api,包括库和头文件。哈哈,是不是会让你产生非分之想? 2.我们在ThostFtdcTraderApi.h中发现了我们想要的东西 ///用户口令更新请求 virtual int ReqUserPasswordUpdate(CThostFtdcUserPasswordUpdateField *pUserPasswordUpdate, int nRequestID) = 0; 3.找到ctp api文档在simnow官网上可以找到,我这里用的事《综合交易平台TraderAPI接口说明.pdf》 4.开始撸代码吧,我显示在linux开始撸,后来发现我的操作系统编译器版本太低,当然升级版本应该可以解决,但升级gcc,glibc还是相当耗时的。所以后来移植到了windows,但工程还是linux风格,使用cmake构建,好在现在vs对cmake支持的不错。 5.继续撸,先贴一段吧 #include "ctp_trade_handler.h" #include "INIReader.h" #include int main(int argc, char* argv[]) { string a; INIReader reader("../conf/ctp.ini"); if (reader.ParseError() != 0) { std::cout << "Can't load 'test.ini'\n"; return 1; } std::cout << reader.GetInteger("user","BrokerID",9999) << endl; ctp_trade_handle ctp; ctp.CreateFtdcTraderApi(); ctp.RegisterFront("tcp://180.168.146.187:10100"); ctp.init(); //CThostFtdcReqAuthenticateField reqAuthenticate = { 0 }; //strcpy(reqAuthenticate.AppID, "simnow_client_test"); //strcpy(reqAuthenticate.UserID, "158477"); //strcpy(reqAuthenticate.AuthCode, "0000000000000000"); //strcpy(reqAuthenticate.BrokerID, "9999"); //ctp.ReqAuthenticate(&reqAuthenticate, 1); CThostFtdcReqUserLoginField reqUserLogin = { 0 }; strcpy(reqUserLogin.BrokerID,"9999"); strcpy(reqUserLogin.UserID,"158477"); strcpy(reqUserLogin.Password,"qwe123"); ctp.ReqUserLogin(&reqUserLogin, 1); std::cout <> a; CThostFtdcUserPasswordUpdateField reqUserPasswordUpdate = { 0 }; strcpy(reqUserPasswordUpdate.BrokerID, "9999"); strcpy(reqUserPasswordUpdate.UserID, "158477"); strcpy(reqUserPasswordUpdate.OldPassword, "qwe123"); strcpy(reqUserPasswordUpdate.NewPassword, a.c_str()); ctp.ReqUserPasswordUpdate(&reqUserPasswordUpdate, 3); std::cin >> a; ctp.exit(); return 0; } 代码很简单,这里就不多说了。目前只是为了解决我修改密码的需求,后续还会不断完善。 如有需要可以自己取来撸,代码托管地址:https://github.com/FrankXMX/c... 欢迎watch和star 5.使用上就是填写好你的相关信息,编译运行,输入新密码。得到屏幕输出。 6.回到vn.py,使用新密码重新连接ctp。 大功告成!!!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值