VeighNa量化社区
你的开源社区量化交易平台
hxxjava's Avatar
Member
离线
419 帖子
声望: 155

网关接口的状态感知

1 行情和交易服务器的接口状态

网关分很多种,如CTP,XTP等 。其中CTP中又包含行情(MD)接口和交易(TD)接口。它们在连接和断开的时候,都有推送接口。这些接口是:
MD的onFrontConnected()和onFrontDisconnected(),TD的onFrontConnected()和onFrontDisconnected()。行情和交易服务器的接口状态,vnpy已经做了log输出,用户是可以阅读的,可是不方便软件使用,于是对接口和主引擎做了如下修改,以便于上层应用可以使用这些消息,编写出针对行情服务器通断情况的处理方法,也可以编写针对交易服务器连接的处理方法。

2 实现步骤

下面的代码修改时,需要相互引用的部分,如from... import... 之类的,就不再逐一指出了,太基础了。

2.1 在vnpy\trader\event.py添加如下消息类型:

EVENT_CONNECT = "eConnected"             # hxxjava add
EVENT_DISCONNECT = "eDisconnected"   # hxxjava add

2.2 在vnpy\trader\object.py添加如下数据类型:

@dataclass
class GatewayData():     # hxxjava add
    """
    Gateway data
    """
    name:str = ""     # 网关名称,如 'CTP'
    type:str = ""     # 接口类型,如 'TD','MD'
    reason:int = 0      # 状态或者原因

2.3 在vnpy\trader\gateway.py 中为BaseGateway添加如下函数:

    def on_connect(self,gateway:GatewayData) -> None:    # hxxjava add   
        """
        gateway connect enent
        """
        self.on_event(EVENT_CONNECT, gateway)

    def on_disconnect(self,gateway:GatewayData) -> None:    # hxxjava add   
        """
        gateway disconnect enent
        """
        self.on_event(EVENT_DISCONNECT, gateway)

2.4 在vnpy\app\ctp\ctp_gateway.py 中修改如下4个函数:

1 CtpMdApi类的两个函数:

    def onFrontConnected(self):
        """
        Callback when front server is connected.
        """
        self.gateway.on_connect(GatewayData("CTP",'MD'))    # hxxjava add

        self.gateway.write_log("行情服务器连接成功")
        self.login()

    def onFrontDisconnected(self, reason: int):
        """
        Callback when front server is disconnected.
        """
        self.login_status = False
        self.gateway.on_disconnect(GatewayData(name="CTP",type='MD',reason=reason)) # hxxjava add
        self.gateway.write_log(f"行情服务器连接断开,原因{reason}")

2 CtpTdApi类的两个函数:

    def onFrontConnected(self):
        """"""
        self.gateway.on_connect(GatewayData("CTP",'TD')) # hxxjava add
        self.gateway.write_log("交易服务器连接成功")

        if self.auth_code:
            self.authenticate()
        else:
            self.login()

    def onFrontDisconnected(self, reason: int):
        """"""
        self.login_status = False
        self.gateway.on_disconnect(GatewayData(name="CTP",type='TD',reason=reason)) # hxxjava add
        self.gateway.write_log(f"交易服务器连接断开,原因{reason}")

2.5 修改vnpy\trader\engine.py中的主引擎MainEngine

修改register_event()函数:

    def register_event(self):
        ... ...
        # 添加下面两句 
        self.event_engine.register(EVENT_CONNECT, self.process_connect_event)
        self.event_engine.register(EVENT_DISCONNECT, self.process_disconnect_event)

添加下面两个消息处理函数:

    def process_connect_event(self, event: Event) -> None:
        gateway:GatewayData = event.data
        print(f"gateway connect event {gateway}")

    def process_disconnect_event(self, event: Event) -> None:
        gateway:GatewayData = event.data
        print(f"gateway disconnect_event {gateway}")

3 如何测试

在VN Studio Prompt窗口输入python - m vnstation命令,启动vnpy系统后,选择连接CTP接口,输入用户名和密码等信息后,连接CTP网关后可以在VN Studio Prompt窗口见到如下:

gateway connect event GatewayData(name='CTP', type='TD', reason=0)
gateway connect event GatewayData(name='CTP', type='MD', reason=0)

当然你也可以制造一下CTP网关的故障,如故意拔掉你的路由器的电,或者网线,你应该可以看到

gateway disconnect event GatewayData(name='CTP', type='TD', reason=?)   
gateway disconnect event GatewayData(name='CTP', type='MD', reason=?)

上面使用问号,是因为我不知道你软件会提示什么错误原因。

先分享一下策略账户设计的最新成果

用户策略只需要调用策略账户引擎提供的接口,启动之初创建本策略实例的策略账户,策略账户引擎就可以在后台监视策略的交易活动了。它可以监视策略的每笔交易盈亏,分配资金的使用情况,保证金的占用情况,累计手续费,目前的可用资金(按分配资金计算)等信息,这些信息对用户策略是非常有用的。

主界面面中多了个策略账户区域

description

双点策略账户区域,进入策略账户管理界面

description

这是策略的出入金管理界面

可以查询策略自创建以来的所有的出让金记录。当你策略中资金不足,你可以在此往策略账户中入金,当然如果策略很赚钱,你也可以通过出金减小策略的交易规模,做到落袋为安!
description

这是策略账户中保存的属于本策略的历史委托单记录:

description

这是策略账户中保存的属于本策略的历史成交单记录:

description

1. algo应用和CTA应用增加用户策略方法不一样

  • 新建CTA用户策略只要在 [用户命令]\strategies\目录下,创建自己的策略,实现on_start ,on_init, on_tick, on_bar, on_trade, on_order, on_stop_order等推送接口。重新加载cta app,vnpy系统会自动把系统自带策略和用户策略一起编译添加进CTA应用界面,直接选择就OK;
  • algo应用却不是这样的,它的策略是在algo引擎初始化时候,执行load_algo_template()加载的,加载哪些策略是写死的,它没有像CTA app那样考虑用户策略的加载,代码如下:
    def load_algo_template(self):
        """"""
        from .algos.twap_algo import TwapAlgo
        from .algos.iceberg_algo import IcebergAlgo
        from .algos.sniper_algo import SniperAlgo
        from .algos.stop_algo import StopAlgo
        from .algos.best_limit_algo import BestLimitAlgo
        from .algos.grid_algo import GridAlgo
        from .algos.dma_algo import DmaAlgo
        from .algos.arbitrage_algo import ArbitrageAlgo
        from .algos.test_algo import TestAlgo       # hxxjava add

        self.add_algo_template(TwapAlgo)
        self.add_algo_template(IcebergAlgo)
        self.add_algo_template(SniperAlgo)
        self.add_algo_template(StopAlgo)
        self.add_algo_template(BestLimitAlgo)
        self.add_algo_template(GridAlgo)
        self.add_algo_template(DmaAlgo)
        self.add_algo_template(ArbitrageAlgo)
        self.add_algo_template(TestAlgo)    # hxxjava add

        from .genus import (
            GenusVWAP,
            GenusTWAP,
            GenusPercent,
            GenusPxInline,
            GenusSniper,
            GenusDMA
        )

        self.add_algo_template(GenusVWAP)
        self.add_algo_template(GenusTWAP)
        self.add_algo_template(GenusPercent)
        self.add_algo_template(GenusPxInline)
        self.add_algo_template(GenusSniper)
        self.add_algo_template(GenusDMA)

考虑到这一特点,只能像上面带注释的行这么添加algo策略。

2. 复制Iceberg策略代码,修改成TestAlgo策略

from vnpy.trader.constant import Offset, Direction
from vnpy.trader.object import TradeData, OrderData, TickData
from vnpy.trader.engine import BaseEngine

from vnpy.app.algo_trading import AlgoTemplate


class TestAlgo(AlgoTemplate):
    """"""

    display_name = "TestAlgo 测试算法"

    default_setting = {
        "vt_symbol": "",
        "direction": [Direction.LONG.value, Direction.SHORT.value],
        "price": 0.0,
        "volume": 0.0,
        "display_volume": 0.0,
        "interval": 0,
        "offset": [
            Offset.NONE.value,
            Offset.OPEN.value,
            Offset.CLOSE.value,
            Offset.CLOSETODAY.value,
            Offset.CLOSEYESTERDAY.value
        ]
    }

    variables = [
        "traded",
        "timer_count",
        "vt_orderid"
    ]

    def __init__(
        self,
        algo_engine: BaseEngine,
        algo_name: str,
        setting: dict
    ):
        """"""
        super().__init__(algo_engine, algo_name, setting)

        # Parameters
        self.vt_symbol = setting["vt_symbol"]
        self.direction = Direction(setting["direction"])
        self.price = setting["price"]
        self.volume = setting["volume"]
        self.display_volume = setting["display_volume"]
        self.interval = setting["interval"]
        self.offset = Offset(setting["offset"])

        # Variables
        self.timer_count = 0
        self.vt_orderid = ""
        self.traded = 0

        self.last_tick = None

        self.subscribe(self.vt_symbol)
        self.put_parameters_event()
        self.put_variables_event()

    def on_stop(self):
        """"""
        self.write_log("停止算法")

    def on_tick(self, tick: TickData):
        """"""
        self.last_tick = tick

    def on_order(self, order: OrderData):
        """"""
        msg = f"委托号:{order.vt_orderid},委托状态:{order.status.value}"
        self.write_log(msg)

        if not order.is_active():
            self.vt_orderid = ""
            self.put_variables_event()

    def on_trade(self, trade: TradeData):
        """"""
        self.traded += trade.volume

        if self.traded >= self.volume:
            self.write_log(f"已交易数量:{self.traded},总数量:{self.volume}")
            self.stop()
        else:
            self.put_variables_event()

    def on_timer(self):
        """"""
        self.timer_count += 1

        if self.timer_count < self.interval:
            self.put_variables_event()
            return

        self.timer_count = 0

        contract = self.get_contract(self.vt_symbol)
        if not contract:
            return

        print(f"last_tick={self.last_tick}")

        # # If order already finished, just send new order
        # if not self.vt_orderid:
        #     order_volume = self.volume - self.traded
        #     order_volume = min(order_volume, self.display_volume)

        #     if self.direction == Direction.LONG:
        #         self.vt_orderid = self.buy(
        #             self.vt_symbol,
        #             self.price,
        #             order_volume,
        #             offset=self.offset
        #         )
        #     else:
        #         self.vt_orderid = self.sell(
        #             self.vt_symbol,
        #             self.price,
        #             order_volume,
        #             offset=self.offset
        #         )
        # # Otherwise check for cancel
        # else:
        #     if self.direction == Direction.LONG:
        #         if self.last_tick.ask_price_1 <= self.price:
        #             self.cancel_order(self.vt_orderid)
        #             self.vt_orderid = ""
        #             self.write_log(u"最新Tick卖一价,低于买入委托价格,之前委托可能丢失,强制撤单")
        #     else:
        #         if self.last_tick.bid_price_1 >= self.price:
        #             self.cancel_order(self.vt_orderid)
        #             self.vt_orderid = ""
        #             self.write_log(u"最新Tick买一价,高于卖出委托价格,之前委托可能丢失,强制撤单")

        self.put_variables_event()

3. 重新启动algo就可以看见 ”TestAlgo 测试算法“啦

description

用Python的交易员 wrote:

我们为了装这玩意也搞了4、5天,他的pip安装需要C++版本的二进制链接库,还要放在指定目录,非常非常麻烦,最后才找了这个wheel包

安装您的wheel包确实快!!!

老师 :
在AlgoTrading模块里面如果没有使用StopAlgo策略的话,选择其他策略,下单类型为STOP也是会出错的,因为AlgoTrading模块连本地停止单都没有维护,它也会立即出错。
这么看来不管CTA策略模块,还是AlgoTrading模块,也许还有其他app模块,可能都会有下停止单的需要,如果能够把本地停止单的维护上提到main_engine的话,不光是CTA策略模块可以下本地停止单,AlgoTrading模块也可以下本地停止单,对AlgoTrading模块的策略编写是否就更多一种非常有用的下单手段。您觉得是否有道理?

4. 函数测试及结果

4.1 找出ag2012合约的所有节假日

执行测试函数test2()
1)其中dt是选择了每天的一个交易时刻,days是下个节假日的天数,holiday是节假日的起止时间。
2)其中dt不是交易时刻,则days=0,holiday为None

dt=2019-12-17 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 21), datetime.date(2019, 12, 22))
dt=2019-12-18 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 21), datetime.date(2019, 12, 22))
dt=2019-12-19 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 21), datetime.date(2019, 12, 22))
dt=2019-12-20 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 21), datetime.date(2019, 12, 22))
dt=2019-12-21 09:20:15+08:00,days=0,holiday=(None, None)
dt=2019-12-22 09:20:15+08:00,days=0,holiday=(None, None)
dt=2019-12-23 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 28), datetime.date(2019, 12, 29))
dt=2019-12-24 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 28), datetime.date(2019, 12, 29))
dt=2019-12-25 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 28), datetime.date(2019, 12, 29))
dt=2019-12-26 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 28), datetime.date(2019, 12, 29))
dt=2019-12-27 09:20:15+08:00,days=2,holiday=(datetime.date(2019, 12, 28), datetime.date(2019, 12, 29))
dt=2019-12-28 09:20:15+08:00,days=0,holiday=(None, None)
dt=2019-12-29 09:20:15+08:00,days=0,holiday=(None, None)
dt=2019-12-30 09:20:15+08:00,days=1,holiday=(datetime.date(2020, 1, 1), datetime.date(2020, 1, 1))
dt=2019-12-31 09:20:15+08:00,days=1,holiday=(datetime.date(2020, 1, 1), datetime.date(2020, 1, 1))
dt=2020-01-01 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-02 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 4), datetime.date(2020, 1, 5))
dt=2020-01-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 4), datetime.date(2020, 1, 5))
dt=2020-01-04 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-05 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-06 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 11), datetime.date(2020, 1, 12))
dt=2020-01-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 11), datetime.date(2020, 1, 12))
dt=2020-01-08 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 11), datetime.date(2020, 1, 12))
dt=2020-01-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 11), datetime.date(2020, 1, 12))
dt=2020-01-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 11), datetime.date(2020, 1, 12))
dt=2020-01-11 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-12 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 18), datetime.date(2020, 1, 19))
dt=2020-01-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 18), datetime.date(2020, 1, 19))
dt=2020-01-15 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 18), datetime.date(2020, 1, 19))
dt=2020-01-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 18), datetime.date(2020, 1, 19))
dt=2020-01-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 1, 18), datetime.date(2020, 1, 19))
dt=2020-01-18 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-19 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-20 09:20:15+08:00,days=10,holiday=(datetime.date(2020, 1, 24), datetime.date(2020, 2, 2))
dt=2020-01-21 09:20:15+08:00,days=10,holiday=(datetime.date(2020, 1, 24), datetime.date(2020, 2, 2))
dt=2020-01-22 09:20:15+08:00,days=10,holiday=(datetime.date(2020, 1, 24), datetime.date(2020, 2, 2))
dt=2020-01-23 09:20:15+08:00,days=10,holiday=(datetime.date(2020, 1, 24), datetime.date(2020, 2, 2))
dt=2020-01-24 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-25 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-26 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-27 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-28 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-29 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-30 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-01-31 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-01 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-02 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 8), datetime.date(2020, 2, 9))
dt=2020-02-04 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 8), datetime.date(2020, 2, 9))
dt=2020-02-05 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 8), datetime.date(2020, 2, 9))
dt=2020-02-06 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 8), datetime.date(2020, 2, 9))
dt=2020-02-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 8), datetime.date(2020, 2, 9))
dt=2020-02-08 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-09 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 15), datetime.date(2020, 2, 16))
dt=2020-02-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 15), datetime.date(2020, 2, 16))
dt=2020-02-12 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 15), datetime.date(2020, 2, 16))
dt=2020-02-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 15), datetime.date(2020, 2, 16))
dt=2020-02-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 15), datetime.date(2020, 2, 16))
dt=2020-02-15 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-16 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 22), datetime.date(2020, 2, 23))
dt=2020-02-18 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 22), datetime.date(2020, 2, 23))
dt=2020-02-19 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 22), datetime.date(2020, 2, 23))
dt=2020-02-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 22), datetime.date(2020, 2, 23))
dt=2020-02-21 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 22), datetime.date(2020, 2, 23))
dt=2020-02-22 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-23 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-02-24 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 29), datetime.date(2020, 3, 1))
dt=2020-02-25 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 29), datetime.date(2020, 3, 1))
dt=2020-02-26 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 29), datetime.date(2020, 3, 1))
dt=2020-02-27 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 29), datetime.date(2020, 3, 1))
dt=2020-02-28 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 2, 29), datetime.date(2020, 3, 1))
dt=2020-02-29 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-01 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-02 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 7), datetime.date(2020, 3, 8))
dt=2020-03-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 7), datetime.date(2020, 3, 8))
dt=2020-03-04 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 7), datetime.date(2020, 3, 8))
dt=2020-03-05 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 7), datetime.date(2020, 3, 8))
dt=2020-03-06 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 7), datetime.date(2020, 3, 8))
dt=2020-03-07 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-08 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 14), datetime.date(2020, 3, 15))
dt=2020-03-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 14), datetime.date(2020, 3, 15))
dt=2020-03-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 14), datetime.date(2020, 3, 15))
dt=2020-03-12 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 14), datetime.date(2020, 3, 15))
dt=2020-03-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 14), datetime.date(2020, 3, 15))
dt=2020-03-14 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-15 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 21), datetime.date(2020, 3, 22))
dt=2020-03-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 21), datetime.date(2020, 3, 22))
dt=2020-03-18 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 21), datetime.date(2020, 3, 22))
dt=2020-03-19 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 21), datetime.date(2020, 3, 22))
dt=2020-03-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 21), datetime.date(2020, 3, 22))
dt=2020-03-21 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-22 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-23 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 28), datetime.date(2020, 3, 29))
dt=2020-03-24 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 28), datetime.date(2020, 3, 29))
dt=2020-03-25 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 28), datetime.date(2020, 3, 29))
dt=2020-03-26 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 28), datetime.date(2020, 3, 29))
dt=2020-03-27 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 3, 28), datetime.date(2020, 3, 29))
dt=2020-03-28 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-29 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-03-30 09:20:15+08:00,days=3,holiday=(datetime.date(2020, 4, 4), datetime.date(2020, 4, 6))
dt=2020-03-31 09:20:15+08:00,days=3,holiday=(datetime.date(2020, 4, 4), datetime.date(2020, 4, 6))
dt=2020-04-01 09:20:15+08:00,days=3,holiday=(datetime.date(2020, 4, 4), datetime.date(2020, 4, 6))
dt=2020-04-02 09:20:15+08:00,days=3,holiday=(datetime.date(2020, 4, 4), datetime.date(2020, 4, 6))
dt=2020-04-03 09:20:15+08:00,days=3,holiday=(datetime.date(2020, 4, 4), datetime.date(2020, 4, 6))
dt=2020-04-04 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-05 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-06 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 11), datetime.date(2020, 4, 12))
dt=2020-04-08 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 11), datetime.date(2020, 4, 12))
dt=2020-04-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 11), datetime.date(2020, 4, 12))
dt=2020-04-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 11), datetime.date(2020, 4, 12))
dt=2020-04-11 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-12 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 18), datetime.date(2020, 4, 19))
dt=2020-04-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 18), datetime.date(2020, 4, 19))
dt=2020-04-15 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 18), datetime.date(2020, 4, 19))
dt=2020-04-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 18), datetime.date(2020, 4, 19))
dt=2020-04-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 18), datetime.date(2020, 4, 19))
dt=2020-04-18 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-19 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 25), datetime.date(2020, 4, 26))
dt=2020-04-21 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 25), datetime.date(2020, 4, 26))
dt=2020-04-22 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 25), datetime.date(2020, 4, 26))
dt=2020-04-23 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 25), datetime.date(2020, 4, 26))
dt=2020-04-24 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 4, 25), datetime.date(2020, 4, 26))
dt=2020-04-25 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-26 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-04-27 09:20:15+08:00,days=5,holiday=(datetime.date(2020, 5, 1), datetime.date(2020, 5, 5))
dt=2020-04-28 09:20:15+08:00,days=5,holiday=(datetime.date(2020, 5, 1), datetime.date(2020, 5, 5))
dt=2020-04-29 09:20:15+08:00,days=5,holiday=(datetime.date(2020, 5, 1), datetime.date(2020, 5, 5))
dt=2020-04-30 09:20:15+08:00,days=5,holiday=(datetime.date(2020, 5, 1), datetime.date(2020, 5, 5))
dt=2020-05-01 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-02 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-03 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-04 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-05 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-06 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 9), datetime.date(2020, 5, 10))
dt=2020-05-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 9), datetime.date(2020, 5, 10))
dt=2020-05-08 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 9), datetime.date(2020, 5, 10))
dt=2020-05-09 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-10 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 16), datetime.date(2020, 5, 17))
dt=2020-05-12 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 16), datetime.date(2020, 5, 17))
dt=2020-05-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 16), datetime.date(2020, 5, 17))
dt=2020-05-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 16), datetime.date(2020, 5, 17))
dt=2020-05-15 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 16), datetime.date(2020, 5, 17))
dt=2020-05-16 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-17 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-18 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 23), datetime.date(2020, 5, 24))
dt=2020-05-19 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 23), datetime.date(2020, 5, 24))
dt=2020-05-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 23), datetime.date(2020, 5, 24))
dt=2020-05-21 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 23), datetime.date(2020, 5, 24))
dt=2020-05-22 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 23), datetime.date(2020, 5, 24))
dt=2020-05-23 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-24 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-25 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 30), datetime.date(2020, 5, 31))
dt=2020-05-26 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 30), datetime.date(2020, 5, 31))
dt=2020-05-27 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 30), datetime.date(2020, 5, 31))
dt=2020-05-28 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 30), datetime.date(2020, 5, 31))
dt=2020-05-29 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 5, 30), datetime.date(2020, 5, 31))
dt=2020-05-30 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-05-31 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-01 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 6), datetime.date(2020, 6, 7))
dt=2020-06-02 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 6), datetime.date(2020, 6, 7))
dt=2020-06-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 6), datetime.date(2020, 6, 7))
dt=2020-06-04 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 6), datetime.date(2020, 6, 7))
dt=2020-06-05 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 6), datetime.date(2020, 6, 7))
dt=2020-06-06 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-07 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-08 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 13), datetime.date(2020, 6, 14))
dt=2020-06-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 13), datetime.date(2020, 6, 14))
dt=2020-06-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 13), datetime.date(2020, 6, 14))
dt=2020-06-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 13), datetime.date(2020, 6, 14))
dt=2020-06-12 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 13), datetime.date(2020, 6, 14))
dt=2020-06-13 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-14 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-15 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 20), datetime.date(2020, 6, 21))
dt=2020-06-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 20), datetime.date(2020, 6, 21))
dt=2020-06-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 20), datetime.date(2020, 6, 21))
dt=2020-06-18 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 20), datetime.date(2020, 6, 21))
dt=2020-06-19 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 6, 20), datetime.date(2020, 6, 21))
dt=2020-06-20 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-21 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-22 09:20:15+08:00,days=4,holiday=(datetime.date(2020, 6, 25), datetime.date(2020, 6, 28))
dt=2020-06-23 09:20:15+08:00,days=4,holiday=(datetime.date(2020, 6, 25), datetime.date(2020, 6, 28))
dt=2020-06-24 09:20:15+08:00,days=4,holiday=(datetime.date(2020, 6, 25), datetime.date(2020, 6, 28))
dt=2020-06-25 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-26 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-27 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-28 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-06-29 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 4), datetime.date(2020, 7, 5))
dt=2020-06-30 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 4), datetime.date(2020, 7, 5))
dt=2020-07-01 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 4), datetime.date(2020, 7, 5))
dt=2020-07-02 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 4), datetime.date(2020, 7, 5))
dt=2020-07-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 4), datetime.date(2020, 7, 5))
dt=2020-07-04 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-05 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-06 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 11), datetime.date(2020, 7, 12))
dt=2020-07-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 11), datetime.date(2020, 7, 12))
dt=2020-07-08 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 11), datetime.date(2020, 7, 12))
dt=2020-07-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 11), datetime.date(2020, 7, 12))
dt=2020-07-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 11), datetime.date(2020, 7, 12))
dt=2020-07-11 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-12 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 18), datetime.date(2020, 7, 19))
dt=2020-07-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 18), datetime.date(2020, 7, 19))
dt=2020-07-15 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 18), datetime.date(2020, 7, 19))
dt=2020-07-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 18), datetime.date(2020, 7, 19))
dt=2020-07-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 18), datetime.date(2020, 7, 19))
dt=2020-07-18 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-19 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 25), datetime.date(2020, 7, 26))
dt=2020-07-21 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 25), datetime.date(2020, 7, 26))
dt=2020-07-22 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 25), datetime.date(2020, 7, 26))
dt=2020-07-23 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 25), datetime.date(2020, 7, 26))
dt=2020-07-24 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 7, 25), datetime.date(2020, 7, 26))
dt=2020-07-25 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-26 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-07-27 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 1), datetime.date(2020, 8, 2))
dt=2020-07-28 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 1), datetime.date(2020, 8, 2))
dt=2020-07-29 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 1), datetime.date(2020, 8, 2))
dt=2020-07-30 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 1), datetime.date(2020, 8, 2))
dt=2020-07-31 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 1), datetime.date(2020, 8, 2))
dt=2020-08-01 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-02 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 8), datetime.date(2020, 8, 9))
dt=2020-08-04 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 8), datetime.date(2020, 8, 9))
dt=2020-08-05 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 8), datetime.date(2020, 8, 9))
dt=2020-08-06 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 8), datetime.date(2020, 8, 9))
dt=2020-08-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 8), datetime.date(2020, 8, 9))
dt=2020-08-08 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-09 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 15), datetime.date(2020, 8, 16))
dt=2020-08-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 15), datetime.date(2020, 8, 16))
dt=2020-08-12 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 15), datetime.date(2020, 8, 16))
dt=2020-08-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 15), datetime.date(2020, 8, 16))
dt=2020-08-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 15), datetime.date(2020, 8, 16))
dt=2020-08-15 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-16 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 22), datetime.date(2020, 8, 23))
dt=2020-08-18 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 22), datetime.date(2020, 8, 23))
dt=2020-08-19 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 22), datetime.date(2020, 8, 23))
dt=2020-08-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 22), datetime.date(2020, 8, 23))
dt=2020-08-21 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 22), datetime.date(2020, 8, 23))
dt=2020-08-22 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-23 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-24 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 29), datetime.date(2020, 8, 30))
dt=2020-08-25 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 29), datetime.date(2020, 8, 30))
dt=2020-08-26 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 29), datetime.date(2020, 8, 30))
dt=2020-08-27 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 29), datetime.date(2020, 8, 30))
dt=2020-08-28 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 8, 29), datetime.date(2020, 8, 30))
dt=2020-08-29 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-30 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-08-31 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 5), datetime.date(2020, 9, 6))
dt=2020-09-01 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 5), datetime.date(2020, 9, 6))
dt=2020-09-02 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 5), datetime.date(2020, 9, 6))
dt=2020-09-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 5), datetime.date(2020, 9, 6))
dt=2020-09-04 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 5), datetime.date(2020, 9, 6))
dt=2020-09-05 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-06 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 12), datetime.date(2020, 9, 13))
dt=2020-09-08 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 12), datetime.date(2020, 9, 13))
dt=2020-09-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 12), datetime.date(2020, 9, 13))
dt=2020-09-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 12), datetime.date(2020, 9, 13))
dt=2020-09-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 12), datetime.date(2020, 9, 13))
dt=2020-09-12 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-13 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 19), datetime.date(2020, 9, 20))
dt=2020-09-15 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 19), datetime.date(2020, 9, 20))
dt=2020-09-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 19), datetime.date(2020, 9, 20))
dt=2020-09-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 19), datetime.date(2020, 9, 20))
dt=2020-09-18 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 19), datetime.date(2020, 9, 20))
dt=2020-09-19 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-20 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-21 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 26), datetime.date(2020, 9, 27))
dt=2020-09-22 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 26), datetime.date(2020, 9, 27))
dt=2020-09-23 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 26), datetime.date(2020, 9, 27))
dt=2020-09-24 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 26), datetime.date(2020, 9, 27))
dt=2020-09-25 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 9, 26), datetime.date(2020, 9, 27))
dt=2020-09-26 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-27 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-09-28 09:20:15+08:00,days=8,holiday=(datetime.date(2020, 10, 1), datetime.date(2020, 10, 8))
dt=2020-09-29 09:20:15+08:00,days=8,holiday=(datetime.date(2020, 10, 1), datetime.date(2020, 10, 8))
dt=2020-09-30 09:20:15+08:00,days=8,holiday=(datetime.date(2020, 10, 1), datetime.date(2020, 10, 8))
dt=2020-10-01 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-02 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-03 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-04 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-05 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-06 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-07 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-08 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 10), datetime.date(2020, 10, 11))
dt=2020-10-10 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-11 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-12 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 17), datetime.date(2020, 10, 18))
dt=2020-10-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 17), datetime.date(2020, 10, 18))
dt=2020-10-14 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 17), datetime.date(2020, 10, 18))
dt=2020-10-15 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 17), datetime.date(2020, 10, 18))
dt=2020-10-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 17), datetime.date(2020, 10, 18))
dt=2020-10-17 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-18 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-19 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 24), datetime.date(2020, 10, 25))
dt=2020-10-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 24), datetime.date(2020, 10, 25))
dt=2020-10-21 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 24), datetime.date(2020, 10, 25))
dt=2020-10-22 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 24), datetime.date(2020, 10, 25))
dt=2020-10-23 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 24), datetime.date(2020, 10, 25))
dt=2020-10-24 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-25 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-10-26 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 31), datetime.date(2020, 11, 1))
dt=2020-10-27 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 31), datetime.date(2020, 11, 1))
dt=2020-10-28 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 31), datetime.date(2020, 11, 1))
dt=2020-10-29 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 31), datetime.date(2020, 11, 1))
dt=2020-10-30 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 10, 31), datetime.date(2020, 11, 1))
dt=2020-10-31 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-01 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-02 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 7), datetime.date(2020, 11, 8))
dt=2020-11-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 7), datetime.date(2020, 11, 8))
dt=2020-11-04 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 7), datetime.date(2020, 11, 8))
dt=2020-11-05 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 7), datetime.date(2020, 11, 8))
dt=2020-11-06 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 7), datetime.date(2020, 11, 8))
dt=2020-11-07 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-08 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 14), datetime.date(2020, 11, 15))
dt=2020-11-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 14), datetime.date(2020, 11, 15))
dt=2020-11-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 14), datetime.date(2020, 11, 15))
dt=2020-11-12 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 14), datetime.date(2020, 11, 15))
dt=2020-11-13 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 14), datetime.date(2020, 11, 15))
dt=2020-11-14 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-15 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-16 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 21), datetime.date(2020, 11, 22))
dt=2020-11-17 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 21), datetime.date(2020, 11, 22))
dt=2020-11-18 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 21), datetime.date(2020, 11, 22))
dt=2020-11-19 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 21), datetime.date(2020, 11, 22))
dt=2020-11-20 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 21), datetime.date(2020, 11, 22))
dt=2020-11-21 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-22 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-23 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 28), datetime.date(2020, 11, 29))
dt=2020-11-24 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 28), datetime.date(2020, 11, 29))
dt=2020-11-25 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 28), datetime.date(2020, 11, 29))
dt=2020-11-26 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 28), datetime.date(2020, 11, 29))
dt=2020-11-27 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 11, 28), datetime.date(2020, 11, 29))
dt=2020-11-28 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-29 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-11-30 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 5), datetime.date(2020, 12, 6))
dt=2020-12-01 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 5), datetime.date(2020, 12, 6))
dt=2020-12-02 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 5), datetime.date(2020, 12, 6))
dt=2020-12-03 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 5), datetime.date(2020, 12, 6))
dt=2020-12-04 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 5), datetime.date(2020, 12, 6))
dt=2020-12-05 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-12-06 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-12-07 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 12), datetime.date(2020, 12, 13))
dt=2020-12-08 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 12), datetime.date(2020, 12, 13))
dt=2020-12-09 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 12), datetime.date(2020, 12, 13))
dt=2020-12-10 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 12), datetime.date(2020, 12, 13))
dt=2020-12-11 09:20:15+08:00,days=2,holiday=(datetime.date(2020, 12, 12), datetime.date(2020, 12, 13))
dt=2020-12-12 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-12-13 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-12-14 09:20:15+08:00,days=0,holiday=(None, None)
dt=2020-12-15 09:20:15+08:00,days=0,holiday=(None, None)

4.2 找出交易时间距离下个节假日的时间长度

合约ag2012,开始时间2020-8-28 14:20:15, 步长1分钟
执行测试函数test3()
结果如下:

dt=2020-08-28 14:20:15+08:00,time_diff,time_diff=12:09:45
dt=2020-08-28 14:21:15+08:00,time_diff,time_diff=12:08:45
dt=2020-08-28 14:22:15+08:00,time_diff,time_diff=12:07:45
dt=2020-08-28 14:23:15+08:00,time_diff,time_diff=12:06:45
dt=2020-08-28 14:24:15+08:00,time_diff,time_diff=12:05:45
dt=2020-08-28 14:25:15+08:00,time_diff,time_diff=12:04:45
dt=2020-08-28 14:26:15+08:00,time_diff,time_diff=12:03:45
dt=2020-08-28 14:27:15+08:00,time_diff,time_diff=12:02:45
dt=2020-08-28 14:28:15+08:00,time_diff,time_diff=12:01:45
dt=2020-08-28 14:29:15+08:00,time_diff,time_diff=12:00:45
dt=2020-08-28 14:30:15+08:00,time_diff,time_diff=11:59:45
dt=2020-08-28 14:31:15+08:00,time_diff,time_diff=11:58:45
dt=2020-08-28 14:32:15+08:00,time_diff,time_diff=11:57:45
dt=2020-08-28 14:33:15+08:00,time_diff,time_diff=11:56:45
dt=2020-08-28 14:34:15+08:00,time_diff,time_diff=11:55:45
dt=2020-08-28 14:35:15+08:00,time_diff,time_diff=11:54:45
dt=2020-08-28 14:36:15+08:00,time_diff,time_diff=11:53:45
dt=2020-08-28 14:37:15+08:00,time_diff,time_diff=11:52:45
dt=2020-08-28 14:38:15+08:00,time_diff,time_diff=11:51:45
dt=2020-08-28 14:39:15+08:00,time_diff,time_diff=11:50:45
dt=2020-08-28 14:40:15+08:00,time_diff,time_diff=11:49:45
dt=2020-08-28 14:41:15+08:00,time_diff,time_diff=11:48:45
dt=2020-08-28 14:42:15+08:00,time_diff,time_diff=11:47:45
dt=2020-08-28 14:43:15+08:00,time_diff,time_diff=11:46:45
dt=2020-08-28 14:44:15+08:00,time_diff,time_diff=11:45:45
dt=2020-08-28 14:45:15+08:00,time_diff,time_diff=11:44:45
dt=2020-08-28 14:46:15+08:00,time_diff,time_diff=11:43:45
dt=2020-08-28 14:47:15+08:00,time_diff,time_diff=11:42:45
dt=2020-08-28 14:48:15+08:00,time_diff,time_diff=11:41:45
dt=2020-08-28 14:49:15+08:00,time_diff,time_diff=11:40:45
dt=2020-08-28 14:50:15+08:00,time_diff,time_diff=11:39:45
dt=2020-08-28 14:51:15+08:00,time_diff,time_diff=11:38:45
dt=2020-08-28 14:52:15+08:00,time_diff,time_diff=11:37:45
dt=2020-08-28 14:53:15+08:00,time_diff,time_diff=11:36:45
dt=2020-08-28 14:54:15+08:00,time_diff,time_diff=11:35:45
dt=2020-08-28 14:55:15+08:00,time_diff,time_diff=11:34:45
dt=2020-08-28 14:56:15+08:00,time_diff,time_diff=11:33:45
dt=2020-08-28 14:57:15+08:00,time_diff,time_diff=11:32:45
dt=2020-08-28 14:58:15+08:00,time_diff,time_diff=11:31:45
dt=2020-08-28 14:59:15+08:00,time_diff,time_diff=11:30:45
dt=2020-08-28 21:00:15+08:00,time_diff,time_diff=7 days, 5:29:45
dt=2020-08-28 21:01:15+08:00,time_diff,time_diff=7 days, 5:28:45
dt=2020-08-28 21:02:15+08:00,time_diff,time_diff=7 days, 5:27:45
dt=2020-08-28 21:03:15+08:00,time_diff,time_diff=7 days, 5:26:45
dt=2020-08-28 21:04:15+08:00,time_diff,time_diff=7 days, 5:25:45
dt=2020-08-28 21:05:15+08:00,time_diff,time_diff=7 days, 5:24:45
dt=2020-08-28 21:06:15+08:00,time_diff,time_diff=7 days, 5:23:45
dt=2020-08-28 21:07:15+08:00,time_diff,time_diff=7 days, 5:22:45
dt=2020-08-28 21:08:15+08:00,time_diff,time_diff=7 days, 5:21:45
dt=2020-08-28 21:09:15+08:00,time_diff,time_diff=7 days, 5:20:45
dt=2020-08-28 21:10:15+08:00,time_diff,time_diff=7 days, 5:19:45
dt=2020-08-28 21:11:15+08:00,time_diff,time_diff=7 days, 5:18:45
dt=2020-08-28 21:12:15+08:00,time_diff,time_diff=7 days, 5:17:45
dt=2020-08-28 21:13:15+08:00,time_diff,time_diff=7 days, 5:16:45
dt=2020-08-28 21:14:15+08:00,time_diff,time_diff=7 days, 5:15:45
dt=2020-08-28 21:15:15+08:00,time_diff,time_diff=7 days, 5:14:45
dt=2020-08-28 21:16:15+08:00,time_diff,time_diff=7 days, 5:13:45
dt=2020-08-28 21:17:15+08:00,time_diff,time_diff=7 days, 5:12:45
dt=2020-08-28 21:18:15+08:00,time_diff,time_diff=7 days, 5:11:45
dt=2020-08-28 21:19:15+08:00,time_diff,time_diff=7 days, 5:10:45
dt=2020-08-28 21:20:15+08:00,time_diff,time_diff=7 days, 5:09:45
dt=2020-08-28 21:21:15+08:00,time_diff,time_diff=7 days, 5:08:45
dt=2020-08-28 21:22:15+08:00,time_diff,time_diff=7 days, 5:07:45
dt=2020-08-28 21:23:15+08:00,time_diff,time_diff=7 days, 5:06:45
dt=2020-08-28 21:24:15+08:00,time_diff,time_diff=7 days, 5:05:45
dt=2020-08-28 21:25:15+08:00,time_diff,time_diff=7 days, 5:04:45
dt=2020-08-28 21:26:15+08:00,time_diff,time_diff=7 days, 5:03:45
dt=2020-08-28 21:27:15+08:00,time_diff,time_diff=7 days, 5:02:45
dt=2020-08-28 21:28:15+08:00,time_diff,time_diff=7 days, 5:01:45
dt=2020-08-28 21:29:15+08:00,time_diff,time_diff=7 days, 5:00:45
dt=2020-08-28 21:30:15+08:00,time_diff,time_diff=7 days, 4:59:45
dt=2020-08-28 21:31:15+08:00,time_diff,time_diff=7 days, 4:58:45
dt=2020-08-28 21:32:15+08:00,time_diff,time_diff=7 days, 4:57:45
dt=2020-08-28 21:33:15+08:00,time_diff,time_diff=7 days, 4:56:45
dt=2020-08-28 21:34:15+08:00,time_diff,time_diff=7 days, 4:55:45
dt=2020-08-28 21:35:15+08:00,time_diff,time_diff=7 days, 4:54:45
dt=2020-08-28 21:36:15+08:00,time_diff,time_diff=7 days, 4:53:45
dt=2020-08-28 21:37:15+08:00,time_diff,time_diff=7 days, 4:52:45
dt=2020-08-28 21:38:15+08:00,time_diff,time_diff=7 days, 4:51:45
dt=2020-08-28 21:39:15+08:00,time_diff,time_diff=7 days, 4:50:45
dt=2020-08-28 21:40:15+08:00,time_diff,time_diff=7 days, 4:49:45
dt=2020-08-28 21:41:15+08:00,time_diff,time_diff=7 days, 4:48:45
dt=2020-08-28 21:42:15+08:00,time_diff,time_diff=7 days, 4:47:45
dt=2020-08-28 21:43:15+08:00,time_diff,time_diff=7 days, 4:46:45
dt=2020-08-28 21:44:15+08:00,time_diff,time_diff=7 days, 4:45:45
dt=2020-08-28 21:45:15+08:00,time_diff,time_diff=7 days, 4:44:45
dt=2020-08-28 21:46:15+08:00,time_diff,time_diff=7 days, 4:43:45
dt=2020-08-28 21:47:15+08:00,time_diff,time_diff=7 days, 4:42:45
dt=2020-08-28 21:48:15+08:00,time_diff,time_diff=7 days, 4:41:45
dt=2020-08-28 21:49:15+08:00,time_diff,time_diff=7 days, 4:40:45
dt=2020-08-28 21:50:15+08:00,time_diff,time_diff=7 days, 4:39:45
dt=2020-08-28 21:51:15+08:00,time_diff,time_diff=7 days, 4:38:45
dt=2020-08-28 21:52:15+08:00,time_diff,time_diff=7 days, 4:37:45
dt=2020-08-28 21:53:15+08:00,time_diff,time_diff=7 days, 4:36:45
dt=2020-08-28 21:54:15+08:00,time_diff,time_diff=7 days, 4:35:45
dt=2020-08-28 21:55:15+08:00,time_diff,time_diff=7 days, 4:34:45
dt=2020-08-28 21:56:15+08:00,time_diff,time_diff=7 days, 4:33:45
dt=2020-08-28 21:57:15+08:00,time_diff,time_diff=7 days, 4:32:45
dt=2020-08-28 21:58:15+08:00,time_diff,time_diff=7 days, 4:31:45
dt=2020-08-28 21:59:15+08:00,time_diff,time_diff=7 days, 4:30:45
dt=2020-08-28 22:00:15+08:00,time_diff,time_diff=7 days, 4:29:45
dt=2020-08-28 22:01:15+08:00,time_diff,time_diff=7 days, 4:28:45
dt=2020-08-28 22:02:15+08:00,time_diff,time_diff=7 days, 4:27:45
dt=2020-08-28 22:03:15+08:00,time_diff,time_diff=7 days, 4:26:45
dt=2020-08-28 22:04:15+08:00,time_diff,time_diff=7 days, 4:25:45
dt=2020-08-28 22:05:15+08:00,time_diff,time_diff=7 days, 4:24:45
dt=2020-08-28 22:06:15+08:00,time_diff,time_diff=7 days, 4:23:45
dt=2020-08-28 22:07:15+08:00,time_diff,time_diff=7 days, 4:22:45
dt=2020-08-28 22:08:15+08:00,time_diff,time_diff=7 days, 4:21:45
dt=2020-08-28 22:09:15+08:00,time_diff,time_diff=7 days, 4:20:45
dt=2020-08-28 22:10:15+08:00,time_diff,time_diff=7 days, 4:19:45
dt=2020-08-28 22:11:15+08:00,time_diff,time_diff=7 days, 4:18:45
dt=2020-08-28 22:12:15+08:00,time_diff,time_diff=7 days, 4:17:45
dt=2020-08-28 22:13:15+08:00,time_diff,time_diff=7 days, 4:16:45
dt=2020-08-28 22:14:15+08:00,time_diff,time_diff=7 days, 4:15:45
dt=2020-08-28 22:15:15+08:00,time_diff,time_diff=7 days, 4:14:45
dt=2020-08-28 22:16:15+08:00,time_diff,time_diff=7 days, 4:13:45
dt=2020-08-28 22:17:15+08:00,time_diff,time_diff=7 days, 4:12:45
dt=2020-08-28 22:18:15+08:00,time_diff,time_diff=7 days, 4:11:45
dt=2020-08-28 22:19:15+08:00,time_diff,time_diff=7 days, 4:10:45
dt=2020-08-28 22:20:15+08:00,time_diff,time_diff=7 days, 4:09:45
dt=2020-08-28 22:21:15+08:00,time_diff,time_diff=7 days, 4:08:45
dt=2020-08-28 22:22:15+08:00,time_diff,time_diff=7 days, 4:07:45
dt=2020-08-28 22:23:15+08:00,time_diff,time_diff=7 days, 4:06:45
dt=2020-08-28 22:24:15+08:00,time_diff,time_diff=7 days, 4:05:45
dt=2020-08-28 22:25:15+08:00,time_diff,time_diff=7 days, 4:04:45
dt=2020-08-28 22:26:15+08:00,time_diff,time_diff=7 days, 4:03:45
dt=2020-08-28 22:27:15+08:00,time_diff,time_diff=7 days, 4:02:45
dt=2020-08-28 22:28:15+08:00,time_diff,time_diff=7 days, 4:01:45
dt=2020-08-28 22:29:15+08:00,time_diff,time_diff=7 days, 4:00:45
dt=2020-08-28 22:30:15+08:00,time_diff,time_diff=7 days, 3:59:45
dt=2020-08-28 22:31:15+08:00,time_diff,time_diff=7 days, 3:58:45
dt=2020-08-28 22:32:15+08:00,time_diff,time_diff=7 days, 3:57:45
dt=2020-08-28 22:33:15+08:00,time_diff,time_diff=7 days, 3:56:45
dt=2020-08-28 22:34:15+08:00,time_diff,time_diff=7 days, 3:55:45
dt=2020-08-28 22:35:15+08:00,time_diff,time_diff=7 days, 3:54:45
dt=2020-08-28 22:36:15+08:00,time_diff,time_diff=7 days, 3:53:45
dt=2020-08-28 22:37:15+08:00,time_diff,time_diff=7 days, 3:52:45
dt=2020-08-28 22:38:15+08:00,time_diff,time_diff=7 days, 3:51:45
dt=2020-08-28 22:39:15+08:00,time_diff,time_diff=7 days, 3:50:45
dt=2020-08-28 22:40:15+08:00,time_diff,time_diff=7 days, 3:49:45
dt=2020-08-28 22:41:15+08:00,time_diff,time_diff=7 days, 3:48:45
dt=2020-08-28 22:42:15+08:00,time_diff,time_diff=7 days, 3:47:45
dt=2020-08-28 22:43:15+08:00,time_diff,time_diff=7 days, 3:46:45
dt=2020-08-28 22:44:15+08:00,time_diff,time_diff=7 days, 3:45:45
dt=2020-08-28 22:45:15+08:00,time_diff,time_diff=7 days, 3:44:45
dt=2020-08-28 22:46:15+08:00,time_diff,time_diff=7 days, 3:43:45
dt=2020-08-28 22:47:15+08:00,time_diff,time_diff=7 days, 3:42:45
dt=2020-08-28 22:48:15+08:00,time_diff,time_diff=7 days, 3:41:45
dt=2020-08-28 22:49:15+08:00,time_diff,time_diff=7 days, 3:40:45
dt=2020-08-28 22:50:15+08:00,time_diff,time_diff=7 days, 3:39:45
dt=2020-08-28 22:51:15+08:00,time_diff,time_diff=7 days, 3:38:45
dt=2020-08-28 22:52:15+08:00,time_diff,time_diff=7 days, 3:37:45
dt=2020-08-28 22:53:15+08:00,time_diff,time_diff=7 days, 3:36:45
dt=2020-08-28 22:54:15+08:00,time_diff,time_diff=7 days, 3:35:45
dt=2020-08-28 22:55:15+08:00,time_diff,time_diff=7 days, 3:34:45
dt=2020-08-28 22:56:15+08:00,time_diff,time_diff=7 days, 3:33:45
dt=2020-08-28 22:57:15+08:00,time_diff,time_diff=7 days, 3:32:45
dt=2020-08-28 22:58:15+08:00,time_diff,time_diff=7 days, 3:31:45
dt=2020-08-28 22:59:15+08:00,time_diff,time_diff=7 days, 3:30:45
dt=2020-08-28 23:00:15+08:00,time_diff,time_diff=7 days, 3:29:45
dt=2020-08-28 23:01:15+08:00,time_diff,time_diff=7 days, 3:28:45
dt=2020-08-28 23:02:15+08:00,time_diff,time_diff=7 days, 3:27:45
dt=2020-08-28 23:03:15+08:00,time_diff,time_diff=7 days, 3:26:45
dt=2020-08-28 23:04:15+08:00,time_diff,time_diff=7 days, 3:25:45
dt=2020-08-28 23:05:15+08:00,time_diff,time_diff=7 days, 3:24:45
dt=2020-08-28 23:06:15+08:00,time_diff,time_diff=7 days, 3:23:45
dt=2020-08-28 23:07:15+08:00,time_diff,time_diff=7 days, 3:22:45
dt=2020-08-28 23:08:15+08:00,time_diff,time_diff=7 days, 3:21:45
dt=2020-08-28 23:09:15+08:00,time_diff,time_diff=7 days, 3:20:45
dt=2020-08-28 23:10:15+08:00,time_diff,time_diff=7 days, 3:19:45
dt=2020-08-28 23:11:15+08:00,time_diff,time_diff=7 days, 3:18:45
dt=2020-08-28 23:12:15+08:00,time_diff,time_diff=7 days, 3:17:45
dt=2020-08-28 23:13:15+08:00,time_diff,time_diff=7 days, 3:16:45
dt=2020-08-28 23:14:15+08:00,time_diff,time_diff=7 days, 3:15:45
dt=2020-08-28 23:15:15+08:00,time_diff,time_diff=7 days, 3:14:45
dt=2020-08-28 23:16:15+08:00,time_diff,time_diff=7 days, 3:13:45
dt=2020-08-28 23:17:15+08:00,time_diff,time_diff=7 days, 3:12:45
dt=2020-08-28 23:18:15+08:00,time_diff,time_diff=7 days, 3:11:45
dt=2020-08-28 23:19:15+08:00,time_diff,time_diff=7 days, 3:10:45
dt=2020-08-28 23:20:15+08:00,time_diff,time_diff=7 days, 3:09:45
dt=2020-08-28 23:21:15+08:00,time_diff,time_diff=7 days, 3:08:45
dt=2020-08-28 23:22:15+08:00,time_diff,time_diff=7 days, 3:07:45
dt=2020-08-28 23:23:15+08:00,time_diff,time_diff=7 days, 3:06:45
dt=2020-08-28 23:24:15+08:00,time_diff,time_diff=7 days, 3:05:45
dt=2020-08-28 23:25:15+08:00,time_diff,time_diff=7 days, 3:04:45
dt=2020-08-28 23:26:15+08:00,time_diff,time_diff=7 days, 3:03:45
dt=2020-08-28 23:27:15+08:00,time_diff,time_diff=7 days, 3:02:45
dt=2020-08-28 23:28:15+08:00,time_diff,time_diff=7 days, 3:01:45
dt=2020-08-28 23:29:15+08:00,time_diff,time_diff=7 days, 3:00:45
dt=2020-08-28 23:30:15+08:00,time_diff,time_diff=7 days, 2:59:45
dt=2020-08-28 23:31:15+08:00,time_diff,time_diff=7 days, 2:58:45
dt=2020-08-28 23:32:15+08:00,time_diff,time_diff=7 days, 2:57:45
dt=2020-08-28 23:33:15+08:00,time_diff,time_diff=7 days, 2:56:45
dt=2020-08-28 23:34:15+08:00,time_diff,time_diff=7 days, 2:55:45
dt=2020-08-28 23:35:15+08:00,time_diff,time_diff=7 days, 2:54:45
dt=2020-08-28 23:36:15+08:00,time_diff,time_diff=7 days, 2:53:45
dt=2020-08-28 23:37:15+08:00,time_diff,time_diff=7 days, 2:52:45
dt=2020-08-28 23:38:15+08:00,time_diff,time_diff=7 days, 2:51:45
dt=2020-08-28 23:39:15+08:00,time_diff,time_diff=7 days, 2:50:45
dt=2020-08-28 23:40:15+08:00,time_diff,time_diff=7 days, 2:49:45
dt=2020-08-28 23:41:15+08:00,time_diff,time_diff=7 days, 2:48:45
dt=2020-08-28 23:42:15+08:00,time_diff,time_diff=7 days, 2:47:45
dt=2020-08-28 23:43:15+08:00,time_diff,time_diff=7 days, 2:46:45
dt=2020-08-28 23:44:15+08:00,time_diff,time_diff=7 days, 2:45:45
dt=2020-08-28 23:45:15+08:00,time_diff,time_diff=7 days, 2:44:45
dt=2020-08-28 23:46:15+08:00,time_diff,time_diff=7 days, 2:43:45
dt=2020-08-28 23:47:15+08:00,time_diff,time_diff=7 days, 2:42:45
dt=2020-08-28 23:48:15+08:00,time_diff,time_diff=7 days, 2:41:45
dt=2020-08-28 23:49:15+08:00,time_diff,time_diff=7 days, 2:40:45
dt=2020-08-28 23:50:15+08:00,time_diff,time_diff=7 days, 2:39:45
dt=2020-08-28 23:51:15+08:00,time_diff,time_diff=7 days, 2:38:45
dt=2020-08-28 23:52:15+08:00,time_diff,time_diff=7 days, 2:37:45
dt=2020-08-28 23:53:15+08:00,time_diff,time_diff=7 days, 2:36:45
dt=2020-08-28 23:54:15+08:00,time_diff,time_diff=7 days, 2:35:45
dt=2020-08-28 23:55:15+08:00,time_diff,time_diff=7 days, 2:34:45
dt=2020-08-28 23:56:15+08:00,time_diff,time_diff=7 days, 2:33:45
dt=2020-08-28 23:57:15+08:00,time_diff,time_diff=7 days, 2:32:45
dt=2020-08-28 23:58:15+08:00,time_diff,time_diff=7 days, 2:31:45
dt=2020-08-28 23:59:15+08:00,time_diff,time_diff=7 days, 2:30:45
dt=2020-08-29 00:00:15+08:00,time_diff,time_diff=7 days, 2:29:45
dt=2020-08-29 00:01:15+08:00,time_diff,time_diff=7 days, 2:28:45
dt=2020-08-29 00:02:15+08:00,time_diff,time_diff=7 days, 2:27:45
dt=2020-08-29 00:03:15+08:00,time_diff,time_diff=7 days, 2:26:45
dt=2020-08-29 00:04:15+08:00,time_diff,time_diff=7 days, 2:25:45
dt=2020-08-29 00:05:15+08:00,time_diff,time_diff=7 days, 2:24:45
dt=2020-08-29 00:06:15+08:00,time_diff,time_diff=7 days, 2:23:45
dt=2020-08-29 00:07:15+08:00,time_diff,time_diff=7 days, 2:22:45
dt=2020-08-29 00:08:15+08:00,time_diff,time_diff=7 days, 2:21:45
dt=2020-08-29 00:09:15+08:00,time_diff,time_diff=7 days, 2:20:45
dt=2020-08-29 00:10:15+08:00,time_diff,time_diff=7 days, 2:19:45
dt=2020-08-29 00:11:15+08:00,time_diff,time_diff=7 days, 2:18:45
dt=2020-08-29 00:12:15+08:00,time_diff,time_diff=7 days, 2:17:45
dt=2020-08-29 00:13:15+08:00,time_diff,time_diff=7 days, 2:16:45
dt=2020-08-29 00:14:15+08:00,time_diff,time_diff=7 days, 2:15:45
dt=2020-08-29 00:15:15+08:00,time_diff,time_diff=7 days, 2:14:45
dt=2020-08-29 00:16:15+08:00,time_diff,time_diff=7 days, 2:13:45
dt=2020-08-29 00:17:15+08:00,time_diff,time_diff=7 days, 2:12:45
dt=2020-08-29 00:18:15+08:00,time_diff,time_diff=7 days, 2:11:45
dt=2020-08-29 00:19:15+08:00,time_diff,time_diff=7 days, 2:10:45
dt=2020-08-29 00:20:15+08:00,time_diff,time_diff=7 days, 2:09:45
dt=2020-08-29 00:21:15+08:00,time_diff,time_diff=7 days, 2:08:45
dt=2020-08-29 00:22:15+08:00,time_diff,time_diff=7 days, 2:07:45
dt=2020-08-29 00:23:15+08:00,time_diff,time_diff=7 days, 2:06:45
dt=2020-08-29 00:24:15+08:00,time_diff,time_diff=7 days, 2:05:45
dt=2020-08-29 00:25:15+08:00,time_diff,time_diff=7 days, 2:04:45
dt=2020-08-29 00:26:15+08:00,time_diff,time_diff=7 days, 2:03:45
dt=2020-08-29 00:27:15+08:00,time_diff,time_diff=7 days, 2:02:45
dt=2020-08-29 00:28:15+08:00,time_diff,time_diff=7 days, 2:01:45
dt=2020-08-29 00:29:15+08:00,time_diff,time_diff=7 days, 2:00:45
dt=2020-08-29 00:30:15+08:00,time_diff,time_diff=7 days, 1:59:45
dt=2020-08-29 00:31:15+08:00,time_diff,time_diff=7 days, 1:58:45
dt=2020-08-29 00:32:15+08:00,time_diff,time_diff=7 days, 1:57:45
dt=2020-08-29 00:33:15+08:00,time_diff,time_diff=7 days, 1:56:45
dt=2020-08-29 00:34:15+08:00,time_diff,time_diff=7 days, 1:55:45
dt=2020-08-29 00:35:15+08:00,time_diff,time_diff=7 days, 1:54:45
dt=2020-08-29 00:36:15+08:00,time_diff,time_diff=7 days, 1:53:45
dt=2020-08-29 00:37:15+08:00,time_diff,time_diff=7 days, 1:52:45
dt=2020-08-29 00:38:15+08:00,time_diff,time_diff=7 days, 1:51:45
dt=2020-08-29 00:39:15+08:00,time_diff,time_diff=7 days, 1:50:45
dt=2020-08-29 00:40:15+08:00,time_diff,time_diff=7 days, 1:49:45
dt=2020-08-29 00:41:15+08:00,time_diff,time_diff=7 days, 1:48:45
dt=2020-08-29 00:42:15+08:00,time_diff,time_diff=7 days, 1:47:45
dt=2020-08-29 00:43:15+08:00,time_diff,time_diff=7 days, 1:46:45
dt=2020-08-29 00:44:15+08:00,time_diff,time_diff=7 days, 1:45:45
dt=2020-08-29 00:45:15+08:00,time_diff,time_diff=7 days, 1:44:45
dt=2020-08-29 00:46:15+08:00,time_diff,time_diff=7 days, 1:43:45
dt=2020-08-29 00:47:15+08:00,time_diff,time_diff=7 days, 1:42:45
dt=2020-08-29 00:48:15+08:00,time_diff,time_diff=7 days, 1:41:45
dt=2020-08-29 00:49:15+08:00,time_diff,time_diff=7 days, 1:40:45
dt=2020-08-29 00:50:15+08:00,time_diff,time_diff=7 days, 1:39:45
dt=2020-08-29 00:51:15+08:00,time_diff,time_diff=7 days, 1:38:45
dt=2020-08-29 00:52:15+08:00,time_diff,time_diff=7 days, 1:37:45
dt=2020-08-29 00:53:15+08:00,time_diff,time_diff=7 days, 1:36:45
dt=2020-08-29 00:54:15+08:00,time_diff,time_diff=7 days, 1:35:45
dt=2020-08-29 00:55:15+08:00,time_diff,time_diff=7 days, 1:34:45
dt=2020-08-29 00:56:15+08:00,time_diff,time_diff=7 days, 1:33:45
dt=2020-08-29 00:57:15+08:00,time_diff,time_diff=7 days, 1:32:45
dt=2020-08-29 00:58:15+08:00,time_diff,time_diff=7 days, 1:31:45
dt=2020-08-29 00:59:15+08:00,time_diff,time_diff=7 days, 1:30:45
dt=2020-08-29 01:00:15+08:00,time_diff,time_diff=7 days, 1:29:45
dt=2020-08-29 01:01:15+08:00,time_diff,time_diff=7 days, 1:28:45
dt=2020-08-29 01:02:15+08:00,time_diff,time_diff=7 days, 1:27:45
dt=2020-08-29 01:03:15+08:00,time_diff,time_diff=7 days, 1:26:45
dt=2020-08-29 01:04:15+08:00,time_diff,time_diff=7 days, 1:25:45
dt=2020-08-29 01:05:15+08:00,time_diff,time_diff=7 days, 1:24:45
dt=2020-08-29 01:06:15+08:00,time_diff,time_diff=7 days, 1:23:45
dt=2020-08-29 01:07:15+08:00,time_diff,time_diff=7 days, 1:22:45
dt=2020-08-29 01:08:15+08:00,time_diff,time_diff=7 days, 1:21:45
dt=2020-08-29 01:09:15+08:00,time_diff,time_diff=7 days, 1:20:45
dt=2020-08-29 01:10:15+08:00,time_diff,time_diff=7 days, 1:19:45
dt=2020-08-29 01:11:15+08:00,time_diff,time_diff=7 days, 1:18:45
dt=2020-08-29 01:12:15+08:00,time_diff,time_diff=7 days, 1:17:45
dt=2020-08-29 01:13:15+08:00,time_diff,time_diff=7 days, 1:16:45
dt=2020-08-29 01:14:15+08:00,time_diff,time_diff=7 days, 1:15:45
dt=2020-08-29 01:15:15+08:00,time_diff,time_diff=7 days, 1:14:45
dt=2020-08-29 01:16:15+08:00,time_diff,time_diff=7 days, 1:13:45
dt=2020-08-29 01:17:15+08:00,time_diff,time_diff=7 days, 1:12:45
dt=2020-08-29 01:18:15+08:00,time_diff,time_diff=7 days, 1:11:45
dt=2020-08-29 01:19:15+08:00,time_diff,time_diff=7 days, 1:10:45
dt=2020-08-29 01:20:15+08:00,time_diff,time_diff=7 days, 1:09:45
dt=2020-08-29 01:21:15+08:00,time_diff,time_diff=7 days, 1:08:45
dt=2020-08-29 01:22:15+08:00,time_diff,time_diff=7 days, 1:07:45
dt=2020-08-29 01:23:15+08:00,time_diff,time_diff=7 days, 1:06:45
dt=2020-08-29 01:24:15+08:00,time_diff,time_diff=7 days, 1:05:45
dt=2020-08-29 01:25:15+08:00,time_diff,time_diff=7 days, 1:04:45
dt=2020-08-29 01:26:15+08:00,time_diff,time_diff=7 days, 1:03:45
dt=2020-08-29 01:27:15+08:00,time_diff,time_diff=7 days, 1:02:45
dt=2020-08-29 01:28:15+08:00,time_diff,time_diff=7 days, 1:01:45
dt=2020-08-29 01:29:15+08:00,time_diff,time_diff=7 days, 1:00:45
dt=2020-08-29 01:30:15+08:00,time_diff,time_diff=7 days, 0:59:45
dt=2020-08-29 01:31:15+08:00,time_diff,time_diff=7 days, 0:58:45
dt=2020-08-29 01:32:15+08:00,time_diff,time_diff=7 days, 0:57:45
dt=2020-08-29 01:33:15+08:00,time_diff,time_diff=7 days, 0:56:45
dt=2020-08-29 01:34:15+08:00,time_diff,time_diff=7 days, 0:55:45
dt=2020-08-29 01:35:15+08:00,time_diff,time_diff=7 days, 0:54:45
dt=2020-08-29 01:36:15+08:00,time_diff,time_diff=7 days, 0:53:45
dt=2020-08-29 01:37:15+08:00,time_diff,time_diff=7 days, 0:52:45
dt=2020-08-29 01:38:15+08:00,time_diff,time_diff=7 days, 0:51:45
dt=2020-08-29 01:39:15+08:00,time_diff,time_diff=7 days, 0:50:45
dt=2020-08-29 01:40:15+08:00,time_diff,time_diff=7 days, 0:49:45
dt=2020-08-29 01:41:15+08:00,time_diff,time_diff=7 days, 0:48:45
dt=2020-08-29 01:42:15+08:00,time_diff,time_diff=7 days, 0:47:45
dt=2020-08-29 01:43:15+08:00,time_diff,time_diff=7 days, 0:46:45
dt=2020-08-29 01:44:15+08:00,time_diff,time_diff=7 days, 0:45:45
dt=2020-08-29 01:45:15+08:00,time_diff,time_diff=7 days, 0:44:45
dt=2020-08-29 01:46:15+08:00,time_diff,time_diff=7 days, 0:43:45
dt=2020-08-29 01:47:15+08:00,time_diff,time_diff=7 days, 0:42:45
dt=2020-08-29 01:48:15+08:00,time_diff,time_diff=7 days, 0:41:45
dt=2020-08-29 01:49:15+08:00,time_diff,time_diff=7 days, 0:40:45
dt=2020-08-29 01:50:15+08:00,time_diff,time_diff=7 days, 0:39:45
dt=2020-08-29 01:51:15+08:00,time_diff,time_diff=7 days, 0:38:45
dt=2020-08-29 01:52:15+08:00,time_diff,time_diff=7 days, 0:37:45
dt=2020-08-29 01:53:15+08:00,time_diff,time_diff=7 days, 0:36:45
dt=2020-08-29 01:54:15+08:00,time_diff,time_diff=7 days, 0:35:45
dt=2020-08-29 01:55:15+08:00,time_diff,time_diff=7 days, 0:34:45
dt=2020-08-29 01:56:15+08:00,time_diff,time_diff=7 days, 0:33:45
dt=2020-08-29 01:57:15+08:00,time_diff,time_diff=7 days, 0:32:45
dt=2020-08-29 01:58:15+08:00,time_diff,time_diff=7 days, 0:31:45
dt=2020-08-29 01:59:15+08:00,time_diff,time_diff=7 days, 0:30:45
dt=2020-08-29 02:00:15+08:00,time_diff,time_diff=7 days, 0:29:45
dt=2020-08-29 02:01:15+08:00,time_diff,time_diff=7 days, 0:28:45
dt=2020-08-29 02:02:15+08:00,time_diff,time_diff=7 days, 0:27:45
dt=2020-08-29 02:03:15+08:00,time_diff,time_diff=7 days, 0:26:45
dt=2020-08-29 02:04:15+08:00,time_diff,time_diff=7 days, 0:25:45
dt=2020-08-29 02:05:15+08:00,time_diff,time_diff=7 days, 0:24:45
dt=2020-08-29 02:06:15+08:00,time_diff,time_diff=7 days, 0:23:45
dt=2020-08-29 02:07:15+08:00,time_diff,time_diff=7 days, 0:22:45
dt=2020-08-29 02:08:15+08:00,time_diff,time_diff=7 days, 0:21:45
dt=2020-08-29 02:09:15+08:00,time_diff,time_diff=7 days, 0:20:45
dt=2020-08-29 02:10:15+08:00,time_diff,time_diff=7 days, 0:19:45
dt=2020-08-29 02:11:15+08:00,time_diff,time_diff=7 days, 0:18:45
dt=2020-08-29 02:12:15+08:00,time_diff,time_diff=7 days, 0:17:45
dt=2020-08-29 02:13:15+08:00,time_diff,time_diff=7 days, 0:16:45
dt=2020-08-29 02:14:15+08:00,time_diff,time_diff=7 days, 0:15:45
dt=2020-08-29 02:15:15+08:00,time_diff,time_diff=7 days, 0:14:45
dt=2020-08-29 02:16:15+08:00,time_diff,time_diff=7 days, 0:13:45
dt=2020-08-29 02:17:15+08:00,time_diff,time_diff=7 days, 0:12:45
dt=2020-08-29 02:18:15+08:00,time_diff,time_diff=7 days, 0:11:45
dt=2020-08-29 02:19:15+08:00,time_diff,time_diff=7 days, 0:10:45
dt=2020-08-29 02:20:15+08:00,time_diff,time_diff=7 days, 0:09:45
dt=2020-08-29 02:21:15+08:00,time_diff,time_diff=7 days, 0:08:45
dt=2020-08-29 02:22:15+08:00,time_diff,time_diff=7 days, 0:07:45
dt=2020-08-29 02:23:15+08:00,time_diff,time_diff=7 days, 0:06:45
dt=2020-08-29 02:24:15+08:00,time_diff,time_diff=7 days, 0:05:45
dt=2020-08-29 02:25:15+08:00,time_diff,time_diff=7 days, 0:04:45
dt=2020-08-29 02:26:15+08:00,time_diff,time_diff=7 days, 0:03:45
dt=2020-08-29 02:27:15+08:00,time_diff,time_diff=7 days, 0:02:45
dt=2020-08-29 02:28:15+08:00,time_diff,time_diff=7 days, 0:01:45
dt=2020-08-29 02:29:15+08:00,time_diff,time_diff=7 days, 0:00:45

4.2.1 什么时候是要放假了?

当 time_diff=7 days, 0:00:00的时候就是了

1. 计算下一个节假日的起止日期

    def get_next_holiday(self,dt:datetime):
        """ 
        计算下一个节假日的起止日期
        注意:dt必须为交易时间,否则返回为(0,(None,None))
        """
        days,holday_start,holday_stop = 0,None,None
        index,trade_times = self.get_trade_datetimes(dt)
        if trade_times:
            # dt在交易时间段内
            day_count = len(self.trade_index_date)
            for idx in range(index,day_count-1):
                # 寻找间隔超过一天的前后两个交易日
                date1 = self.trade_index_date[idx]
                date2 = self.trade_index_date[idx+1]
                date_diff = date2 - date1
                if date_diff > datetime.timedelta(days=1):
                    # 找到了间隔超过一天的前后两个交易日
                    days = date_diff.days - 1
                    # 前1日加一天为节假日开始
                    holday_start = date1 + datetime.timedelta(days=1)
                    # 后1日减一天为节假日结束
                    holday_stop = date2 + datetime.timedelta(days=-1)
                    break

        return days,(holday_start,holday_stop)

2. 计算交易时间距离节假日的时间

    def to_holiday_time(self,dt:datetime):
        """
        计算交易时间距离节假日的时间。
        无夜盘合约 : 到节假日前一交易日的日盘收盘时间
        有夜盘合约 : 到节假日后一交易日的夜盘收盘时间
        注意:dt必须为交易时间,否则返回为None
              返回值:
        """
        index,trade_times = self.get_trade_datetimes(dt,True)
        if trade_times:
            # dt在交易时间段内
            day_count = len(self.trade_index_date)
            for idx in range(index,day_count-1):
                # 寻找间隔超过一天的前后两个交易日
                date1 = self.trade_index_date[idx]
                date2 = self.trade_index_date[idx+1]
                date_diff = date2 - date1
                if date_diff > datetime.timedelta(days=1):
                    if not self.has_night_trading:
                        days,trade_times = self.get_date_tradetimes(date1)
                        stop_dt = trade_times[-1][1]
                        # print(f"{stop_dt} ")
                    else:
                        days,trade_times = self.get_date_tradetimes(date2)
                        dt1 = trade_times[0][0] 
                        has,(start_dt,stop_dt) = self.get_night_start_stop(dt1)
                        # print(f"dt1={dt1},{(start_dt,stop_dt)} ")

                    # print(f"has_night_trading ={self.has_night_trading}, stop_dt={stop_dt}")  
                    time_diff = stop_dt - dt
                    return time_diff
        return None

3. 完整的交易时间段类实现

保存文件:vnpy\usertools\trade_hour.py

"""
本文件主要实现合约的交易时间段
作者:hxxjava
日期:2020-8-1
"""
from typing import Callable,List,Dict, Tuple, Union
from enum import Enum

import datetime
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")

from vnpy.trader.utility import extract_vt_symbol
from vnpy.trader.constant import Interval

from rqdatac.utils import to_date
import rqdatac as rq


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.upper()
        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.has_night_trading = False
        self.time_dn_pairs = self._get_trading_times_dn(trading_hours)

        trading_hours0 = [(CHINA_TZ.localize(start),CHINA_TZ.localize(stop)) 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]
                start_dt = CHINA_TZ.localize(datetime.datetime.combine(d,start))
                stop_dt = CHINA_TZ.localize(datetime.datetime.combine(d,stop))
                trade_datetimes.append((start_dt,stop_dt+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:
                self.has_night_trading = True
                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 = CHINA_TZ.localize(datetime.datetime.combine(today,start)) + datetime.timedelta(days=dn1)
            stop_dt = CHINA_TZ.localize(datetime.datetime.combine(today,stop)) + 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 get_day_tradetimes(self,dt:datetime):
        """ 
        得到合约日盘的交易时间段 
        """
        index,trade_times = self.get_trade_datetimes(dt,allday=True)
        trade_times1 = []
        if trade_times:
            for start_dt,stop_dt in trade_times:
                if start_dt.time() < datetime.time(18,0,0):
                    trade_times1.append((start_dt,stop_dt))
            return index,trade_times1
        return (index,trade_times1)

    def get_night_tradetimes(self,dt:datetime):
        """ 
        得到合约夜盘的交易时间段 
        """
        index,trade_times = self.get_trade_datetimes(dt,allday=True)
        trade_times1 = []
        if trade_times:
            for start_dt,stop_dt in trade_times:
                if start_dt.time() > datetime.time(18,0,0):
                    trade_times1.append((start_dt,stop_dt))
            return index,trade_times1
        return (index,trade_times1)

    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

    def get_date_start_stop(self,dt:datetime):
        """
        获得dt所在交易日的开始和停止时间
        """
        index,trade_times = self.get_trade_datetimes(dt,allday=True)
        if trade_times:
            valid_dt = False
            for t1,t2 in trade_times:
                if t1 < dt and dt < t2:
                    valid_dt = True
                    break
            if valid_dt:
                start_dt = trade_times[0][0]
                stop_dt = trade_times[-1][1]
                return True,(start_dt,stop_dt)
        return False,(None,None)

    def get_day_start_stop(self,dt:datetime):
        """
        获得dt所在交易日日盘的开始和停止时间
        """
        index,trade_times = self.get_day_tradetimes(dt)
        if trade_times:
            valid_dt = False
            for t1,t2 in trade_times:
                if t1 <= dt and dt < t2:
                    valid_dt = True
                    break
            if valid_dt:
                start_dt = trade_times[0][0]
                stop_dt = trade_times[-1][1]
                return True,(start_dt,stop_dt)
        return False,(None,None)

    def get_night_start_stop(self,dt:datetime):
        """
        获得dt所在交易日夜盘的开始和停止时间
        """
        index,trade_times = self.get_night_tradetimes(dt)
        if trade_times:
            valid_dt = False
            for t1,t2 in trade_times:
                if t1 <= dt and dt < t2:
                    valid_dt = True
                    break
            if valid_dt:
                start_dt = trade_times[0][0]
                stop_dt = trade_times[-1][1]
                return True,(start_dt,stop_dt)
        return False,(None,None)

    def get_next_holiday(self,dt:datetime):
        """ 
        计算下一个节假日的起止日期
        注意:dt必须为交易时间,否则返回为(0,(None,None))
        """
        days,holday_start,holday_stop = 0,None,None
        index,trade_times = self.get_trade_datetimes(dt)
        if trade_times:
            # dt在交易时间段内
            day_count = len(self.trade_index_date)
            for idx in range(index,day_count-1):
                # 寻找间隔超过一天的前后两个交易日
                date1 = self.trade_index_date[idx]
                date2 = self.trade_index_date[idx+1]
                date_diff = date2 - date1
                if date_diff > datetime.timedelta(days=1):
                    # 找到了间隔超过一天的前后两个交易日
                    days = date_diff.days - 1
                    # 前1日加一天为节假日开始
                    holday_start = date1 + datetime.timedelta(days=1)
                    # 后1日减一天为节假日结束
                    holday_stop = date2 + datetime.timedelta(days=-1)
                    break

        return days,(holday_start,holday_stop)

    def to_holiday_time(self,dt:datetime):
        """
        计算交易时间距离节假日的时间。
        无夜盘合约 : 到节假日前一交易日的日盘收盘时间
        有夜盘合约 : 到节假日后一交易日的夜盘收盘时间
        注意:dt必须为交易时间,否则返回为None
              返回值:
        """
        index,trade_times = self.get_trade_datetimes(dt,True)
        if trade_times:
            # dt在交易时间段内
            day_count = len(self.trade_index_date)
            for idx in range(index,day_count-1):
                # 寻找间隔超过一天的前后两个交易日
                date1 = self.trade_index_date[idx]
                date2 = self.trade_index_date[idx+1]
                date_diff = date2 - date1
                if date_diff > datetime.timedelta(days=1):
                    if not self.has_night_trading:
                        days,trade_times = self.get_date_tradetimes(date1)
                        stop_dt = trade_times[-1][1]
                        # print(f"{stop_dt} ")
                    else:
                        days,trade_times = self.get_date_tradetimes(date2)
                        dt1 = trade_times[0][0] 
                        has,(start_dt,stop_dt) = self.get_night_start_stop(dt1)
                        # print(f"dt1={dt1},{(start_dt,stop_dt)} ")

                    # print(f"has_night_trading ={self.has_night_trading}, stop_dt={stop_dt}")  
                    time_diff = stop_dt - dt
                    return time_diff
        return None


if __name__ == "__main__":

    def test1():
        # vt_symbols = ["rb2010.SHFE","ag2012.SHFE","i2010.DCE"]
        vt_symbols = ["ag2012.SHFE"]
        date0 = datetime.date(2020,8,31)
        dt0 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,15))
        for vt_symbol in vt_symbols:
            symbol,exchange = extract_vt_symbol(vt_symbol)
            th = TradeHours(symbol)
            # trade_hours = th.get_date_tradetimes(date0)
            # print(f"\n{vt_symbol} {date0} trade_hours={trade_hours}")

            days,trade_hours = th.get_trade_datetimes(dt0,allday=True)

            print(f"\n{vt_symbol} {dt0} days:{days} trade_hours={trade_hours}")

            if trade_hours:
                day_start = trade_hours[0][0]
                day_end = trade_hours[-1][1]
                print(f"day_start={day_start} day_end={day_end}")
                exit_time = day_end + datetime.timedelta(minutes=-5)
                print(f"exit_time={exit_time}")

            dt1 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,15))
            dt2 = CHINA_TZ.localize(datetime.datetime(2020,9,1,1,1,15))

            for dt in [dt1,dt2]:
                in_trade,(start,stop) = th.get_date_start_stop(dt)
                if (in_trade):
                    print(f"\n{vt_symbol} 时间 {dt} 交易日起止:{start,stop}")
                else:
                    print(f"\n{vt_symbol} 时间 {dt} 非交易时间")

                in_day,(start,stop) = th.get_day_start_stop(dt)
                if (in_day):
                    print(f"\n{vt_symbol} 时间 {dt} 日盘起止:{start,stop}")
                else:
                    print(f"\n{vt_symbol} 时间 {dt} 非日盘时间")

                in_night,(start,stop) = th.get_night_start_stop(dt)
                if in_night:
                    print(f"\n{vt_symbol} 时间 {dt} 夜盘起止:{start,stop}")
                else:
                    print(f"\n{vt_symbol} 时间 {dt} 非夜盘时间")

    def test2():
        vt_symbols = ["ag2012.SHFE"]
        date0 = datetime.date(2020,8,31)
        dt0 = CHINA_TZ.localize(datetime.datetime(2019,12,10,9,20,15))

        for vt_symbol in vt_symbols:      
            symbol,exchange = extract_vt_symbol(vt_symbol)
            th = TradeHours(symbol)

            for i in range(365):
                td = th.listed_date + datetime.timedelta(days=i)
                dt = CHINA_TZ.localize(
                        datetime.datetime.combine(td,datetime.time(9,20,15)))
                days,(date1,date2) = th.get_next_holiday(dt)
                print(f"dt={dt},days={days},holiday={date1,date2}")            



    def test3():
        """
        TradeHour.to_holiday_time()验证
        """
        vt_symbol = "ag2012.SHFE"
        date0 = datetime.date(2020,8,29)
        dt0 = CHINA_TZ.localize(datetime.datetime(2020,8,28,9,20,15))

        symbol,exchange = extract_vt_symbol(vt_symbol)
        th = TradeHours(symbol)

        for i in range(1200):
            dt = dt0 + datetime.timedelta(minutes=i)
            time_diff = th.to_holiday_time(dt)
            if time_diff:
                print(f"dt={dt},time_diff,time_diff={time_diff}")            


    # 开始测试
    rq.init('xxxxxx','******',("rqdatad-pro.ricequant.com",16011))

    # test1()
    # test2()
    test3()

启动时选择algo应用,遇到下面的错误(首次尝试运行algo应用)

Traceback (most recent call last):
  File "D:\ProgramFiles\VnStudio\lib\site-packages\vnstation\cli.py", line 90, in run_trader
    module = importlib.import_module(d["module"])
  File "D:\ProgramFiles\VnStudio\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "D:\ProgramFiles\VnStudio\lib\site-packages\vnpy\app\algo_trading\__init__.py", line 5, in <module>
    from .engine import AlgoEngine, APP_NAME
  File "D:\ProgramFiles\VnStudio\lib\site-packages\vnpy\app\algo_trading\engine.py", line 17, in <module>
    from .genus import GenusClient
  File "D:\ProgramFiles\VnStudio\lib\site-packages\vnpy\app\algo_trading\genus.py", line 6, in <module>
    import quickfix as fix
ModuleNotFoundError: No module named 'quickfix'

从错误提示看是缺少了quickfix模块,那就安装
于是进入VN Staudio Prompt窗口下输入:

pip install quickfix

安装quickfix模块,提示趋势Ms Visual C++ V14.0 build tools

那就先安装Ms Visual C++ V14.0 build tools,
搜索到Ms Visual C++ V14.0 build tools,
也装上了,
提示重新启动,
那就重新启动

再次安装quickfix

命令是:

pip install quickfix

出错啦:

D:\ProgramFiles\VnStudio>pip install quickfix
Collecting quickfix
  Using cached quickfix-1.15.1.tar.gz (1.5 MB)
Using legacy 'setup.py install' for quickfix, since package 'wheel' is not installed.
Installing collected packages: quickfix
    Running setup.py install for quickfix ... error
    ERROR: Command errored out with exit status 1:
     command: 'd:\programfiles\vnstudio\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"'; __file__='"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\hxxja\AppData\Local\Temp\pip-record-qavjungc\install-record.txt' --single-version-externally-managed --compile --install-headers 'd:\programfiles\vnstudio\Include\quickfix'
         cwd: C:\Users\hxxja\AppData\Local\Temp\pip-install-1sa6_klk\quickfix\
    Complete output (38 lines):
    running install
    running build
    running build_py
    creating build
    creating build\lib.win-amd64-3.7
    copying quickfix.py -> build\lib.win-amd64-3.7
    copying quickfixt11.py -> build\lib.win-amd64-3.7
    copying quickfix40.py -> build\lib.win-amd64-3.7
    copying quickfix41.py -> build\lib.win-amd64-3.7
    copying quickfix42.py -> build\lib.win-amd64-3.7
    copying quickfix43.py -> build\lib.win-amd64-3.7
    copying quickfix44.py -> build\lib.win-amd64-3.7
    copying quickfix50.py -> build\lib.win-amd64-3.7
    copying quickfix50sp1.py -> build\lib.win-amd64-3.7
    copying quickfix50sp2.py -> build\lib.win-amd64-3.7
    running build_ext
    Testing for std::tr1::shared_ptr...
    C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /Tptest_std_tr1_shared_ptr.cpp /Fotest_std_tr1_shared_ptr.obj
    test_std_tr1_shared_ptr.cpp
    test_std_tr1_shared_ptr.cpp(1): fatal error C1083: Cannot open include file: 'tr1/memory': No such file or directory
     ...not found
    Testing for std::shared_ptr...
    C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe -std=c++0x /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /Tptest_std_shared_ptr.cpp /Fotest_std_shared_ptr.obj
    cl : Command line warning D9002 : ignoring unknown option '-std=c++0x'
    test_std_shared_ptr.cpp
    ...found
    Testing for std::unique_ptr...
    C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe -std=c++0x /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -DHAVE_STD_SHARED_PTR -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /Tptest_std_unique_ptr.cpp /Fotest_std_unique_ptr.obj
    cl : Command line warning D9002 : ignoring unknown option '-std=c++0x'
    test_std_unique_ptr.cpp
    ...found
    building '_quickfix' extension
    creating build\temp.win-amd64-3.7
    creating build\temp.win-amd64-3.7\Release
    creating build\temp.win-amd64-3.7\Release\C++
    C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\x86_amd64\cl.exe /c /nologo /Ox /W3 /GL /DNDEBUG /MD -DPYTHON_MAJOR_VERSION=3 -DHAVE_STD_SHARED_PTR -DHAVE_STD_UNIQUE_PTR -IC++ -Id:\programfiles\vnstudio\include -Id:\programfiles\vnstudio\include "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" "-IC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE" "-IC:\Program Files (x86)\Windows Kits\10\include\10.0.10240.0\ucrt" "-IC:\Program Files (x86)\Windows Kits\8.1\include\shared" "-IC:\Program Files (x86)\Windows Kits\8.1\include\um" "-IC:\Program Files (x86)\Windows Kits\8.1\include\winrt" /EHsc /TpC++\Acceptor.cpp /Fobuild\temp.win-amd64-3.7\Release\C++\Acceptor.obj -std=c++0x -Wno-deprecated -Wno-unused-variable -Wno-deprecated-declarations -Wno-maybe-uninitialized
    cl : Command line error D8021 : invalid numeric argument '/Wno-deprecated'
    error: command 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\BIN\\x86_amd64\\cl.exe' failed with exit status 2
    ----------------------------------------
ERROR: Command errored out with exit status 1: 'd:\programfiles\vnstudio\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"'; __file__='"'"'C:\\Users\\hxxja\\AppData\\Local\\Temp\\pip-install-1sa6_klk\\quickfix\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\hxxja\AppData\Local\Temp\pip-record-qavjungc\install-record.txt' --single-version-externally-managed --compile --install-headers 'd:\programfiles\vnstudio\Include\quickfix' Check the logs for full command output.

被吓到了,不就是安装个algo需要的模块吗,出这么多错误!!!

缺啥给啥,要啥补啥,不待这样折磨人的!

那位大神遇到了这种问题,分享下解决问题的经验吧,先叩谢!!!

老师:
就算CTP柜台没有把停止单功能给关了,OrderPriceType字段也是必填字典,现在这种ORDERTYPE_VT2CTP字典也是映射不到有效值的,也是出错。很多人都碰到主界面中无法下停止单的问题,困惑不已。1)要不在主界面中下单类型一栏不提供'STOP'选项;2)要不就在MainEngine中维护本地停止单,使得手动也可以下单。你看看这两个建议是否可以可行?

1. 现象见图中描述

description

1.1 这是接口的错误?

在cta_gateway.py中send_order()函数中,找到出错语句前增加下面的调试打印:

        self.reqid += 1
        print(f"ctp_req={ctp_req}")    # 调试打印
        self.reqOrderInsert(ctp_req, self.reqid)

得到ctp_req的内容如下:

ctp_req={
    'InstrumentID': 'ag2012', 
    'ExchangeID': 'SHFE', 
    'LimitPrice': 6093.0, 
    'VolumeTotalOriginal': 8, 
    'OrderPriceType': '', 
    'Direction': '1', 
    'CombOffsetFlag': '3', 
    'OrderRef': '1', 
    'InvestorID': '147102', 
    'UserID': '147102', 
    'BrokerID': '9999', 
    'CombHedgeFlag': '1', 
    'ContingentCondition': '1', 
    'ForceCloseReason': '0', 
    'IsAutoSuspend': 0, 
    'TimeCondition': '3', 
    'VolumeCondition': '1', 
    'MinVolume': 1
    }

这里面的内容只有最为可疑:

'OrderPriceType': '',

2. 为什么会出错?

2.1 停止单分为两种:

  • 服务器停止单:需要交易所支持,直接可以发送服务器处理
  • 本地停止单:交易所不支持,由本地应用来维护

2.2 CTA的网关目前只处理两类委托请求:

ORDERTYPE_VT2CTP = {
    OrderType.LIMIT: THOST_FTDC_OPT_LimitPrice,   # 限价类型
    OrderType.MARKET: THOST_FTDC_OPT_AnyPrice # 市价类型
}
ORDERTYPE_CTP2VT = {v: k for k, v in ORDERTYPE_VT2CTP.items()}

2.3 用户的CTA策略的停止单是有CtaEngine维护的

用户的CTA策略发送委托停止单的过程:

    1. 调用CtaTemplate的send_order()
    1. 再调用CtaEngine引擎的send_order()
    1. CtaEngine引擎区分委托请求时 限价单还是停止单,
    1. CtaEngine引擎通过过请求中合约的contract,得知是否支持服务器停止单,如果支持直接调用main_engine中ctp_gateway发送到服务器,否则有引擎保存到本地进行维护
    1. CtaEngine引擎中的本地停止单被触发时,CtaEngine引擎把当触发停止单转化未涨停板或跌停板限价单,调用main_engine中ctp_gateway发送到服务器
    1. ctp_gateway对vnpy委托请求OrderRequest中的OrderType进行映射,得到接口识别的请求类型OrderPriceType,OrderPriceType目前只接受两个类型:THOST_FTDC_OPT_LimitPrice和THOST_FTDC_OPT_AnyPrice。
    1. 转化后委托请求ctp_req其实是个字典,然后通过交易接口TdApi发送给交易服务器

3. 假如你从Main_Window(主界面)直接发送停止单,问题就来 !

3.1 Main_Window直接发送停止单过程:

  • 1 系统是直接调用ctp_gateway,它不识别服务器是否支持
  • 2 系统它不会把该停止单发送到CtaEngine引擎,因为你可以压根就没有启动CtaEngine引擎模块,怎么调用?
  • 3 委托请求OrderRequest中的OrderType='STOP',在ORDERTYPE_VT2CTP字典中就没有键值,产生出来的接口请求类型OrderPriceType=''
  • 4 于是就出错啦!!!

3.2 结论:

目前在主界面中是不可以发送任何停止单的!!!
目前在主界面中是不可以发送任何停止单的!!!
目前在主界面中是不可以发送任何停止单的!!!

重要的话说三遍

4. 发送委托停止单出错的修改建议:

  • 1 类型映射错误,就算是服务器停止单支持,我敢断言也会出错,因为OrderPriceType=‘’ 肯定是不可以被接口接受的,这个需要改!
  • 2 主界面中发送的停止单,没有做服务器是支持的判断,如果不支持也的话也无人维护。那么是否可以把下级应用cta_engine的对本地停止单的维护,上提到main_engine中来执行?
  • 3 CTA策略都可以发委托单,为什么手工却不让做呢 ?这么好的功能,应该人工也享受享受!

已经分析过了,那么是否可以改进呢?

3. 必须的合约交易时间段类

创建vnpy\usertools\trade_hour.py
内容如下:

"""
本文件主要实现合约的交易时间段
"""
from typing import Callable,List,Dict, Tuple, Union
from enum import Enum

import datetime
import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")

from vnpy.trader.utility import extract_vt_symbol
from vnpy.trader.constant import Interval

from rqdatac.utils import to_date
import rqdatac as rq


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.upper()
        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 = [(CHINA_TZ.localize(start),CHINA_TZ.localize(stop)) 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]
                start_dt = CHINA_TZ.localize(datetime.datetime.combine(d,start))
                stop_dt = CHINA_TZ.localize(datetime.datetime.combine(d,stop))
                trade_datetimes.append((start_dt,stop_dt+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 = CHINA_TZ.localize(datetime.datetime.combine(today,start)) + datetime.timedelta(days=dn1)
            stop_dt = CHINA_TZ.localize(datetime.datetime.combine(today,stop)) + 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 get_day_tradetimes(self,dt:datetime):
        """ 
        得到合约日盘的交易时间段 
        """
        index,trade_times = self.get_trade_datetimes(dt,allday=True)
        trade_times1 = []
        if trade_times:
            for start_dt,stop_dt in trade_times:
                if start_dt.time() < datetime.time(18,0,0):
                    trade_times1.append((start_dt,stop_dt))
            return index,trade_times1
        return (index,trade_times1)

    def get_night_tradetimes(self,dt:datetime):
        """ 
        得到合约夜盘的交易时间段 
        """
        index,trade_times = self.get_trade_datetimes(dt,allday=True)
        trade_times1 = []
        if trade_times:
            for start_dt,stop_dt in trade_times:
                if start_dt.time() > datetime.time(18,0,0):
                    trade_times1.append((start_dt,stop_dt))
            return index,trade_times1
        return (index,trade_times1)

    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

    def get_date_start_stop(self,dt:datetime):
        """
        获得dt所在交易日的开始和停止时间
        """
        index,trade_times = self.get_trade_datetimes(dt,allday=True)
        if trade_times:
            valid_dt = False
            for t1,t2 in trade_times:
                if t1 < dt and dt < t2:
                    valid_dt = True
                    break
            if valid_dt:
                start_dt = trade_times[0][0]
                stop_dt = trade_times[-1][1]
                return True,(start_dt,stop_dt)
        return False,(None,None)

    def get_day_start_stop(self,dt:datetime):
        """
        获得dt所在交易日日盘的开始和停止时间
        """
        index,trade_times = self.get_day_tradetimes(dt)
        if trade_times:
            valid_dt = False
            for t1,t2 in trade_times:
                if t1 < dt and dt < t2:
                    valid_dt = True
                    break
            if valid_dt:
                start_dt = trade_times[0][0]
                stop_dt = trade_times[-1][1]
                return True,(start_dt,stop_dt)
        return False,(None,None)

    def get_night_start_stop(self,dt:datetime):
        """
        获得dt所在交易日夜盘的开始和停止时间
        """
        index,trade_times = self.get_night_tradetimes(dt)
        if trade_times:
            valid_dt = False
            for t1,t2 in trade_times:
                if t1 < dt and dt < t2:
                    valid_dt = True
                    break
            if valid_dt:
                start_dt = trade_times[0][0]
                stop_dt = trade_times[-1][1]
                return True,(start_dt,stop_dt)
        return False,(None,None)

 # 以下为TradeHours类的测试代码
if __name__ == "__main__":
    rq.init('xxxxx','******',("rqdatad-pro.ricequant.com",16011))

    # vt_symbols = ["rb2010.SHFE","ag2012.SHFE","i2010.DCE"]
    vt_symbols = ["ag2012.SHFE"]
    date0 = datetime.date(2020,8,31)
    dt0 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,15))
    for vt_symbol in vt_symbols:
        symbol,exchange = extract_vt_symbol(vt_symbol)
        th = TradeHours(symbol)
        # trade_hours = th.get_date_tradetimes(date0)
        # print(f"\n{vt_symbol} {date0} trade_hours={trade_hours}")

        days,trade_hours = th.get_trade_datetimes(dt0,allday=True)

        print(f"\n{vt_symbol} {dt0} days:{days} trade_hours={trade_hours}")

        if trade_hours:
            day_start = trade_hours[0][0]
            day_end = trade_hours[-1][1]
            print(f"day_start={day_start} day_end={day_end}")
            exit_time = day_end + datetime.timedelta(minutes=-5)
            print(f"exit_time={exit_time}")

        dt1 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,15))
        dt2 = CHINA_TZ.localize(datetime.datetime(2020,9,1,1,1,15))

        for dt in [dt1,dt2]:
            in_trade,(start,stop) = th.get_date_start_stop(dt)
            if (in_trade):
                print(f"\n{vt_symbol} 时间 {dt} 交易日起止:{start,stop}")
            else:
                print(f"\n{vt_symbol} 时间 {dt} 非交易时间")

            in_day,(start,stop) = th.get_day_start_stop(dt)
            if (in_day):
                print(f"\n{vt_symbol} 时间 {dt} 日盘起止:{start,stop}")
            else:
                print(f"\n{vt_symbol} 时间 {dt} 非日盘时间")

            in_night,(start,stop) = th.get_night_start_stop(dt)
            if in_night:
                print(f"\n{vt_symbol} 时间 {dt} 夜盘起止:{start,stop}")
            else:
                print(f"\n{vt_symbol} 时间 {dt} 非夜盘时间")

1. 等自然时长机制的K线生成器说明

交易日内等自然时长K线生成器,可以实现周以下的周期单位的时间就间隔:
x=window,interval的取:
Interval.MINUTE :x分钟宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.HOUR   : x小时宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.DAILY  : x日宽度的K线,对齐上市交易日开始时间开始的整x日,x可以任意值
Interval.WEEKLY  : x周宽度的K线,对齐上市交易日开始时间整x周,x可以任意值

再策略中的应用举例:

  • 等自然时长机制的30分钟K线创建:
    self.bg = ENTBarGenerator(self.on_bar,30,self.on_30m_bar,interval=Interval.MINUTE,self.vt_symbol)
  • 等自然时长机制的2小时K线创建:
    self.bg = ENTBarGenerator(self.on_bar,120,self.on_2h_bar,interval=Interval.MINUTE,self.vt_symbol)
    或者:
    self.bg = ENTBarGenerator(self.on_bar,2,self.on_2h_bar,interval=Interval.HOUR,self.vt_symbol)
  • 等自然时长机制的1日K线创建:
    self.bg = ENTBarGenerator(self.on_bar,1,self.on_1d_bar,interval=Interval.DAILY,self.vt_symbol)
  • 等自然时长机制的1周K线创建:
    self.bg = ENTBarGenerator(self.on_bar,5,self.on_1w_bar,interval=Interval.DAILY,self.vt_symbol)
    或者:
    self.bg = ENTBarGenerator(self.on_bar,1,self.on_1w_bar,interval=Interval.WEEKLY,self.vt_symbol)

2. 先实现等自然时长机制的K线生成器

保存文件:vnpy\usertools\equal_nature_time.py
内容如下:

"""
实现一个等自然时长机制的K线生成器

作者:hxxjava 
日期:2020-9-2
"""

from typing import Callable,List,Dict, Tuple, Union
from vnpy.app.cta_strategy import BarGenerator
from vnpy.trader.constant import Interval
from vnpy.trader.utility import extract_vt_symbol
from vnpy.trader.object import BarData

import pytz
CHINA_TZ = pytz.timezone("Asia/Shanghai")

from vnpy.usertools.trade_hour import (
    TradeHours,
    Timeunit,
    get_listed_date,
    get_de_listed_date,
)

import datetime 
import rqdatac as rq


class MyError(Exception):
    def __init__(self,ErrorInfo):
        super().__init__(self) #初始化父类
        self.errorinfo=ErrorInfo
    def __str__(self):
        return self.errorinfo       

class ENTBarGenerator(BarGenerator):
    """ 
    交易日内等自然时长K线生成器,可以实现周以下的周期单位的时间就间隔:
    x=window,interval的取:
    Interval.MINUTE :x分钟宽度的K线,对齐交易日开始时间,不可以超过日交易时长
    Interval.HOUR   : x小时宽度的K线,对齐交易日开始时间,不可以超过日交易时长
    Interval.DAILY  : x日宽度的K线,对齐上市交易日开始时间开始的整x日,x可以任意值
    Interval.WEEKLY  : x周宽度的K线,对齐上市交易日开始时间整x周,x可以任意值

    ENT:equal nature time ———— 等自然时长的缩写
    """ 
    def __init__(
        self,
        on_bar: Callable,
        window: int = 0,
        on_window_bar: Callable = None,
        interval: Interval = Interval.MINUTE,
        vt_symbol:str=''
    ):
        super().__init__(on_bar,window,on_window_bar,interval)

        symbol,exchange = extract_vt_symbol(vt_symbol)
        self.trade_hours = TradeHours(symbol.upper())

        self.trade_start,self.trade_stop = None,None

        self.bar_width = self.get_bar_witdh()
        self.bar_index = None

        if self.interval in [Interval.DAILY,Interval.WEEKLY]:
            self.kx_start,self.kx_stop = (None,None)

    def get_bar_witdh(self):
        """
        求window_bar的宽度
        """
        # 求每日的交易时长
        TTPD = self.trade_hours.get_trade_time_perday()

        try:
            if self.interval == Interval.MINUTE:
                # 以分钟作为单位
                bar_width = self.window
                if bar_width>TTPD:
                    raise MyError(f'window_bar宽度不可以大于1日的交易总时长')

            elif self.interval == Interval.HOUR:
                # 以小时作为单位
                bar_width = self.window*60
                if bar_width>TTPD:
                    raise MyError(f'window_bar宽度不可以大于1日的交易总时长')

            elif self.interval == Interval.DAILY:
                # 以日作为单位
                bar_width = self.window * TTPD

            elif self.interval == Interval.WEEKLY:
                # 以周作为单位
                bar_width = self.window * TTPD * 5

            else:
                raise MyError(f'不可以使用{self.interval}作为interval参数')

            return bar_width

        except MyError as e:
            print(e)


    def get_bar_index(self,bar:BarData):
        """
        计算当前分钟bar所属window_bar的日内索引
        """
        time_diff = bar.datetime - self.trade_start
        days = time_diff.days 
        sec = time_diff.seconds
        us = time_diff.microseconds
        minutes = (days*24*3600 +sec+us*0.000001)/60.0 
        index = int(minutes/self.bar_width)
        return index 

    def update_bar(self,bar:BarData) -> None:
        if self.interval in [Interval.MINUTE,Interval.HOUR]:
            self.update_bar1(bar)
        elif self.interval in [Interval.DAILY,Interval.WEEKLY]:
            self.update_bar2(bar)

    def update_bar1(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

        in_trade,(trade_start,trade_stop) = self.trade_hours.get_date_start_stop(bar.datetime)
        if not in_trade:
            # 无效K线,不可以处理
            # print(f"bar.datetime= {bar.datetime} 无效K线,不可以处理")
            return

        if (self.trade_start,self.trade_stop) == (None,None):
            # 计算当日的开始和停止交易时间
            self.trade_start,self.trade_stop = trade_start,trade_stop
        elif trade_start != self.trade_start:
            # 判断是否跨日,更新当日的开始和停止交易时间
            self.trade_start,self.trade_stop = trade_start,trade_stop

        # print(f"!1 {self.trade_start,self.trade_stop}")

        # 计算当前window_bar的日内索引
        bar_index = self.get_bar_index(bar)

        if self.bar_index is None or bar_index != self.bar_index:
            # 产生新window_barK线
            self.bar_index = bar_index

            if self.window_bar:
                # 推送已经走完的window_bar
                self.on_window_bar(self.window_bar)
                self.window_bar = None

            # 计算K线的交易起止时间
            bar_datetime = self.trade_start + datetime.timedelta(minutes=self.bar_index*self.bar_width)

            # 生成新的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,
            )         

        # 更新window_bar的high和low
        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)

        # 更新 close price/volume到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 

    def update_bar2(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 



# 下面是ENTBarGenerator类的测试代码
def test():
    from vnpy.trader.constant import Exchange
    import pytz
    CHINA_TZ = pytz.timezone("Asia/Shanghai")

    def on_bar(bar: BarData):
        pass

    def on_xbar_bar(bar: BarData):
        print(bar)

    bar_gen = ENTBarGenerator(on_bar,30,on_xbar_bar,Interval.MINUTE,vt_symbol='rb2010.SHFE')
    dt0 = CHINA_TZ.localize(datetime.datetime(2020,8,31,9,20,1))

    for i in range(60*24):
        # print(f"{[dt0 + datetime.timedelta(minutes=i)]}")
        bar = BarData(
            gateway_name = 'CTP', 
            symbol = 'rb2010', 
            exchange = Exchange.SHFE, 
            datetime = dt0 + datetime.timedelta(minutes=i),
            interval=Interval.MINUTE,
            volume=2,
            open_price=3560.0,
            high_price= 3565.0,
            close_price=3558.0,
            low_price=3555.0,
            open_interest=10,
        )

        bar_gen.update_bar(bar)

if __name__ == "__main__":
    # RQDatac初始化
    rq.init('xxxxxx','******',("rqdatad-pro.ricequant.com",16011))

    test()

1. 不是所有期货都是一日7根小时线

1)self.bg = BarGenerator(slef.on_bar,7,self.on_day_bar,interval=Interval.Hour)生成日线对部分期货合约适用,如RB,AP,I之类的,对另外的却是错误的,因为:
ag(白银)11根:
21:00~22:00 ,22:00~23:00 ,23:00~00:00,00:00~01:00,01:00~02:00, 02:00~02:30,9:00~10:00 , 10:00~11:00, 11:00~11:30, 13:30~14:00 , 14:00~15:00
IF(股指)5根:
9:30~10:00 , 10:00~11:00, 11:00~11:30, 13:30~14:00 , 14:00~15:00

2. 以谁为标准判断对错?

你是如何确定你生成K线open,high,low,close和实际值不一致?和谁比较?我猜你是用第三方软件来比较的吧!
那你知道你使用的第三方软件它生成的日K线规则是什么?大智慧、通达信、博弈、文华财经?它们本身的日K线的产生规则都不完全相同,有的是区分日盘和夜盘的,有的却不区分,有的是等自然时长,有的等交易时长。
不同的产生机制,产生不同K线,不可以说谁是对的,谁是错误的。

3. 数据采集不同会造成open,high,low,close不一样

1)大智慧、通达信、博弈、文华财经和米筐都是从交易所采集数据(tick),然后自己合成各自K线。
2)它们可能使用了交易所的不同的数据采集通道(这些通道都是真实有效的)。
3)这些不同的数据采集通道,交易所保证真实性,不保证一致性,也就是说可能是不一样的
4)你可以去比较大智慧、通达信、文华财经,选择同一个合约的同一个周期,比较一下看看,都可能是不一样的

因为测不准原理,世界本来就是没有绝对的准确,只有相对准确。你永远无法知道1米的绝对长度是多少!你不可以说哪个数据采集是正确的,哪个是错误的。

4 这样的K线比较才有意义:

  • 相同数据采集通道:这个你通常是不知道的,问问你数据提供商(也许他们也不确定知道,除非你问对了人——他特别清楚接口底层)。但是这个因素造成的数据不同概率比较低,但确实存在。
  • 相同K线生成机制:如:都是自然时间机制,这通常可以通过第三方软件的设置中自己选择(如果可以选择的话,如文华赢顺客户端就是等自然时长机制,文华赢智客户端就可以选择等自然时长机制、等交易时长机制,还有等交易时长机制改进型[分别从夜盘、日盘的开始时间开始划分K线]),然后你在vnpy中确定你的K线生成机制,能保证二者相同。更多的知识参见 K线种类总结
  • 相同的合约:这个最容易保证了(哈哈)
  • 相同的时间段:这个也容易保证了(哈哈)

准备好这些,如果你还是发现K线不一样,你可以怀疑的元素:

  • 1 数据接口丢了
  • 2 数据供应商数据转发出错了,如果是付费的你可以去理论理论,如果是免费的就无人可问啦

1. 什么是策略账户?

策略账户有点类似文华赢智和库安软件中的模组账户的概念,但它根植vnpy的系统架构,完全用python写成。

  1. 策略账户是一个虚拟的账户。它可以为每个策略运行实例分配虚拟资金,如果有需要你还可以进行入金和出金操作,当然这也是虚拟的资金操作。
  2. 用户策略可以从创建增加策略账户,可以从策略账户中获取当前可用资金,以控制策略的交易规模。
  3. 策略账户在后台跟踪记录用户策略交易活动,从而用户策略可以查询历史交易和当前交易的盈亏状况。
  4. 策略账户还可以设定风控水平报警,为用户策略交易提供更加稳健的风控指导。
  5. 策略账户可以记录自策略实例运行以来,所有的委托单和成交单历史记录,为策略的可视化提供后台支持
  6. 策略账户面对的策略不只是CTA策略,其它应用也可以使用。
  7. 策略账户可以计算策略的每笔历史交易盈亏,当前交易的保证金和手续费,策略账户可以让你实时低知道这些结果。

2. 策略账户长的啥样子?

本人已经提供长时间的构思和设计,经过一段时间的调试和运行,目前策略账户已经可以正常运行。现在展示如下:

description

3. 一点希望:希望vnpy官方能够合并这个功能到vn.py中,以便让更多粉丝都能够享受到策略账户的好处。

1. 这是一个不可以完整交易夜盘的R-Breaker策略

原因:https://www.vnpy.com/forum/topic/4461-shuo-shi-r-breakerce-lue-de-wen-ti

2. 这么修改就可以完整交易夜盘了

修改方法: https://www.vnpy.com/forum/topic/4463-yi-ge-ke-yi-jiao-yi-ye-pan-de-rbreakerstrategy

这一句有问题:
self.bg_daily = BarGenerator(self.on_bar, 1, self.on_daily_bar, Interval.DAILY)
因为:BarGenerator没有实现interval = Interval.DAILY时的情况,需要另外实现一个 可以处理日线间隔的bar生成器。

晴空 wrote:

楼主,有新的进展吗?

有了,正在测试中......

湘水量化 wrote:

老师,usertools文件没有呀,能开源不,学习下

在vnpy下创建usertools文件夹,然后创建kx_chart.py,内容如下:

from datetime import datetime
from typing import List, Tuple, Dict

import numpy as np
import pyqtgraph as pg
import talib
import copy

from vnpy.trader.ui import create_qapp, QtCore, QtGui, QtWidgets
from vnpy.trader.database import database_manager
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.object import BarData

from vnpy.chart import ChartWidget, VolumeItem, CandleItem
from vnpy.chart.item import ChartItem
from vnpy.chart.manager import BarManager
from vnpy.chart.base import NORMAL_FONT

from vnpy.trader.engine import MainEngine
from vnpy.event import Event, EventEngine

from vnpy.trader.event import (
    EVENT_TICK,
    EVENT_TRADE,
    EVENT_ORDER,
    EVENT_POSITION,
    EVENT_ACCOUNT,
    EVENT_LOG
)

from vnpy.app.cta_strategy.base import (    # hxxjava add
    EVENT_CTA_TICK,     
    EVENT_CTA_BAR,      
    EVENT_CTA_ORDER,    
    EVENT_CTA_TRADE,     
    EVENT_CTA_HISTORY_BAR
)

from vnpy.trader.object import TickData,BarData  # hxxjava add


class LineItem(CandleItem):
    """"""

    def __init__(self, manager: BarManager):
        """"""
        super().__init__(manager)

        self.white_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 255), width=1)

    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """"""
        last_bar = self._manager.get_bar(ix - 1)

        # Create objects
        picture = QtGui.QPicture()
        painter = QtGui.QPainter(picture)

        # Set painter color
        painter.setPen(self.white_pen)

        # Draw Line
        end_point = QtCore.QPointF(ix, bar.close_price)

        if last_bar:
            start_point = QtCore.QPointF(ix - 1, last_bar.close_price)
        else:
            start_point = end_point

        painter.drawLine(start_point, end_point)

        # Finish
        painter.end()
        return picture


class SmaItem(CandleItem):
    """"""

    def __init__(self, manager: BarManager):
        """"""
        super().__init__(manager)

        self.blue_pen: QtGui.QPen = pg.mkPen(color=(100, 100, 255), width=2)

        self.sma_window = 10
        self.sma_data: Dict[int, float] = {}

    def get_sma_value(self, ix: int) -> float:
        """"""
        if ix < 0:
            return 0

        # When initialize, calculate all rsi value
        if not self.sma_data:
            bars = self._manager.get_all_bars()
            close_data = [bar.close_price for bar in bars]
            sma_array = talib.SMA(np.array(close_data), self.sma_window)

            for n, value in enumerate(sma_array):
                self.sma_data[n] = value

        # Return if already calcualted
        if ix in self.sma_data:
            return self.sma_data[ix]

        # Else calculate new value
        close_data = []
        for n in range(ix - self.sma_window, ix + 1):
            bar = self._manager.get_bar(n)
            close_data.append(bar.close_price)

        sma_array = talib.SMA(np.array(close_data), self.sma_window)
        sma_value = sma_array[-1]
        self.sma_data[ix] = sma_value

        return sma_value

    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """"""
        sma_value = self.get_sma_value(ix)
        last_sma_value = self.get_sma_value(ix - 1)

        # Create objects
        picture = QtGui.QPicture()
        painter = QtGui.QPainter(picture)

        # Set painter color
        painter.setPen(self.blue_pen)

        # Draw Line
        start_point = QtCore.QPointF(ix-1, last_sma_value)
        end_point = QtCore.QPointF(ix, sma_value)
        painter.drawLine(start_point, end_point)

        # Finish
        painter.end()
        return picture

    def get_info_text(self, ix: int) -> str:
        """"""
        if ix in self.sma_data:
            sma_value = self.sma_data[ix]
            text = f"SMA {sma_value:.1f}"
        else:
            text = "SMA -"

        return text


class RsiItem(ChartItem):
    """"""

    def __init__(self, manager: BarManager):
        """"""
        super().__init__(manager)

        self.white_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 255), width=1)
        self.yellow_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 0), width=2)

        self.rsi_window = 14
        self.rsi_data: Dict[int, float] = {}

    def get_rsi_value(self, ix: int) -> float:
        """"""
        if ix < 0:
            return 50

        # When initialize, calculate all rsi value
        if not self.rsi_data:
            bars = self._manager.get_all_bars()
            close_data = [bar.close_price for bar in bars]
            rsi_array = talib.RSI(np.array(close_data), self.rsi_window)

            for n, value in enumerate(rsi_array):
                self.rsi_data[n] = value

        # Return if already calcualted
        if ix in self.rsi_data:
            return self.rsi_data[ix]

        # Else calculate new value
        close_data = []
        for n in range(ix - self.rsi_window, ix + 1):
            bar = self._manager.get_bar(n)
            close_data.append(bar.close_price)

        rsi_array = talib.RSI(np.array(close_data), self.rsi_window)
        rsi_value = rsi_array[-1]
        self.rsi_data[ix] = rsi_value

        return rsi_value

    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """"""
        rsi_value = self.get_rsi_value(ix)
        last_rsi_value = self.get_rsi_value(ix - 1)

        # Create objects
        picture = QtGui.QPicture()
        painter = QtGui.QPainter(picture)

        # Draw RSI line
        painter.setPen(self.yellow_pen)

        if np.isnan(last_rsi_value) or np.isnan(rsi_value):
            # print(ix - 1, last_rsi_value,ix, rsi_value,)
            pass
        else:
            end_point = QtCore.QPointF(ix, rsi_value)
            start_point = QtCore.QPointF(ix - 1, last_rsi_value)
            painter.drawLine(start_point, end_point)

        # Draw oversold/overbought line
        painter.setPen(self.white_pen)

        painter.drawLine(
            QtCore.QPointF(ix, 70),
            QtCore.QPointF(ix - 1, 70),
        )

        painter.drawLine(
            QtCore.QPointF(ix, 30),
            QtCore.QPointF(ix - 1, 30),
        )

        # Finish
        painter.end()
        return picture

    def boundingRect(self) -> QtCore.QRectF:
        """"""
        # min_price, max_price = self._manager.get_price_range()
        rect = QtCore.QRectF(
            0,
            0,
            len(self._bar_picutures),
            100
        )
        return rect

    def get_y_range( self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
        """  """
        return 0, 100

    def get_info_text(self, ix: int) -> str:
        """"""
        if ix in self.rsi_data:
            rsi_value = self.rsi_data[ix]
            text = f"RSI {rsi_value:.1f}"
            # print(text)
        else:
            text = "RSI -"

        return text


def to_int(value: float) -> int:
    """"""
    return int(round(value, 0))

""" 将y方向的显示范围扩大到1.1 """
def adjust_range(in_range:Tuple[float, float])->Tuple[float, float]:
    ret_range:Tuple[float, float]
    diff = abs(in_range[0] - in_range[1])
    ret_range = (in_range[0]-diff*0.05,in_range[1]+diff*0.05)
    return ret_range

class MacdItem(ChartItem):
    """"""
    _values_ranges: Dict[Tuple[int, int], Tuple[float, float]] = {}

    last_range:Tuple[int, int] = (-1,-1)    # 最新显示K线索引范围

    def __init__(self, manager: BarManager):
        """"""
        super().__init__(manager)

        self.white_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 255), width=1)
        self.yellow_pen: QtGui.QPen = pg.mkPen(color=(255, 255, 0), width=1)
        self.red_pen: QtGui.QPen = pg.mkPen(color=(255, 0, 0), width=1)
        self.green_pen: QtGui.QPen = pg.mkPen(color=(0, 255, 0), width=1)

        self.short_window = 12
        self.long_window = 26
        self.M = 9

        self.macd_data: Dict[int, Tuple[float,float,float]] = {}

    def get_macd_value(self, ix: int) -> Tuple[float,float,float]:
        """"""
        if ix < 0:
            return (0.0,0.0,0.0)

        # When initialize, calculate all macd value
        if not self.macd_data:
            bars = self._manager.get_all_bars()
            close_data = [bar.close_price for bar in bars]

            diffs,deas,macds = talib.MACD(np.array(close_data), 
                                    fastperiod=self.short_window, 
                                    slowperiod=self.long_window, 
                                    signalperiod=self.M)

            for n in range(0,len(diffs)):
                self.macd_data[n] = (diffs[n],deas[n],macds[n])

        # Return if already calcualted
        if ix in self.macd_data:
            return self.macd_data[ix]

        # Else calculate new value
        close_data = []
        for n in range(ix-self.long_window-self.M+1, ix + 1):
            bar = self._manager.get_bar(n)
            close_data.append(bar.close_price)

        diffs,deas,macds = talib.MACD(np.array(close_data), 
                                            fastperiod=self.short_window, 
                                            slowperiod=self.long_window, 
                                            signalperiod=self.M) 
        diff,dea,macd = diffs[-1],deas[-1],macds[-1]
        self.macd_data[ix] = (diff,dea,macd)

        return (diff,dea,macd)

    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """"""
        macd_value = self.get_macd_value(ix)
        last_macd_value = self.get_macd_value(ix - 1)

        # # Create objects
        picture = QtGui.QPicture()
        painter = QtGui.QPainter(picture)

        # # Draw macd lines
        if np.isnan(macd_value[0]) or np.isnan(last_macd_value[0]):
            # print("略过macd lines0")
            pass
        else:
            end_point0 = QtCore.QPointF(ix, macd_value[0])
            start_point0 = QtCore.QPointF(ix - 1, last_macd_value[0])
            painter.setPen(self.white_pen)
            painter.drawLine(start_point0, end_point0)

        if np.isnan(macd_value[1]) or np.isnan(last_macd_value[1]):
            # print("略过macd lines1")
            pass
        else:
            end_point1 = QtCore.QPointF(ix, macd_value[1])
            start_point1 = QtCore.QPointF(ix - 1, last_macd_value[1])
            painter.setPen(self.yellow_pen)
            painter.drawLine(start_point1, end_point1)

        if not np.isnan(macd_value[2]):
            if (macd_value[2]>0):
                painter.setPen(self.red_pen)
                painter.setBrush(pg.mkBrush(255,0,0))
            else:
                painter.setPen(self.green_pen)
                painter.setBrush(pg.mkBrush(0,255,0))
            painter.drawRect(QtCore.QRectF(ix-0.3,0,0.6,macd_value[2]))
        else:
            # print("略过macd lines2")
            pass

        painter.end()
        return picture

    def boundingRect(self) -> QtCore.QRectF:
        """"""
        min_y, max_y = self.get_y_range()
        rect = QtCore.QRectF(
            0,
            min_y,
            len(self._bar_picutures),
            max_y
        )
        return rect

    def get_y_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
        #   获得3个指标在y轴方向的范围   
        #   hxxjava 修改,2020-6-29
        #   当显示范围改变时,min_ix,max_ix的值不为None,当显示范围不变时,min_ix,max_ix的值不为None,

        offset = max(self.short_window,self.long_window) + self.M - 1

        if not self.macd_data or len(self.macd_data) < offset:
            return 0.0, 1.0

        # print("len of range dict:",len(self._values_ranges),",macd_data:",len(self.macd_data),(min_ix,max_ix))

        if min_ix != None:          # 调整最小K线索引
            min_ix = max(min_ix,offset)

        if max_ix != None:          # 调整最大K线索引
            max_ix = min(max_ix, len(self.macd_data)-1)

        last_range = (min_ix,max_ix)    # 请求的最新范围   

        if last_range == (None,None):   # 当显示范围不变时
            if self.last_range in self._values_ranges:  
                # 如果y方向范围已经保存
                # 读取y方向范围
                result = self._values_ranges[self.last_range]
                # print("1:",self.last_range,result)
                return adjust_range(result)
            else:
                # 如果y方向范围没有保存
                # 从macd_data重新计算y方向范围
                min_ix,max_ix = 0,len(self.macd_data)-1

                macd_list = list(self.macd_data.values())[min_ix:max_ix + 1]
                ndarray = np.array(macd_list)           
                max_price = np.nanmax(ndarray)
                min_price = np.nanmin(ndarray)

                # 保存y方向范围,同时返回结果
                result = (min_price, max_price)
                self.last_range = (min_ix,max_ix)
                self._values_ranges[self.last_range] = result
                # print("2:",self.last_range,result)
                return adjust_range(result)

        """ 以下为显示范围变化时 """

        if last_range in self._values_ranges:
            # 该范围已经保存过y方向范围
            # 取得y方向范围,返回结果
            result = self._values_ranges[last_range]
            # print("3:",last_range,result)
            return adjust_range(result)

        # 该范围没有保存过y方向范围
        # 从macd_data重新计算y方向范围
        macd_list = list(self.macd_data.values())[min_ix:max_ix + 1]
        ndarray = np.array(macd_list) 
        max_price = np.nanmax(ndarray)
        min_price = np.nanmin(ndarray)

        # 取得y方向范围,返回结果
        result = (min_price, max_price)
        self.last_range = last_range
        self._values_ranges[self.last_range] = result
        # print("4:",self.last_range,result)
        return adjust_range(result)


    def get_info_text(self, ix: int) -> str:
        # """"""
        if ix in self.macd_data:
            diff,dea,macd = self.macd_data[ix]
            words = [
                f"diff {diff:.3f}"," ",
                f"dea {dea:.3f}"," ",
                f"macd {macd:.3f}"
                ]
            text = "\n".join(words)
        else:
            text = "diff - \ndea - \nmacd -"

        return text



class NewChartWidget(ChartWidget):
    """"""
    MIN_BAR_COUNT = 100

    strategy_name:str = ""
    signal_cta_history_bar:QtCore.pyqtSignal = QtCore.pyqtSignal(Event)
    signal_cta_tick: QtCore.pyqtSignal = QtCore.pyqtSignal(Event)
    signal_cta_bar:QtCore.pyqtSignal = QtCore.pyqtSignal(Event)

    def __init__(self, parent: QtWidgets.QWidget = None,event_engine: EventEngine = None,strategy_name:str=""):
        """"""
        super().__init__(parent)

        self.strategy_name = strategy_name

        # print(f"NewChartWidget.strategy_name = {self.strategy_name}")

        self.event_engine = event_engine

        self.last_price_line: pg.InfiniteLine = None

        # self.register_event()

        # self.event_engine.start()

    def register_event(self) -> None:
        """"""
        self.signal_cta_history_bar.connect(self.process_cta_history_bar)
        self.event_engine.register(EVENT_CTA_HISTORY_BAR, self.signal_cta_history_bar.emit)

        self.signal_cta_tick.connect(self.process_tick_event)
        self.event_engine.register(EVENT_CTA_TICK, self.signal_cta_tick.emit)

        self.signal_cta_bar.connect(self.process_cta_bar)
        self.event_engine.register(EVENT_CTA_BAR, self.signal_cta_bar.emit)

    def process_cta_history_bar(self, event:Event) -> None:
        """ 处理历史K线推送 """
        strategy_name,history_bars = event.data
        if strategy_name == self.strategy_name:
            self.update_history(history_bars)

            # print(f" {strategy_name} got an EVENT_CTA_HISTORY_BAR")

    def process_tick_event(self, event: Event) -> None:
        """ 处理tick数据推送 """
        strategy_name,tick = event.data
        if strategy_name == self.strategy_name:
            if self.last_price_line:
                self.last_price_line.setValue(tick.last_price)
            #print(f" {strategy_name} got an EVENT_CTA_TICK")

    def process_cta_bar(self, event:Event)-> None:
        """ 处理K线数据推送 """

        strategy_name,bar = event.data
        if strategy_name == self.strategy_name:
            self.update_bar(bar)
            # print(f"{strategy_name} got an EVENT_CTA_BAR")

    def add_last_price_line(self):
        """"""
        plot = list(self._plots.values())[0]
        color = (255, 255, 255)

        self.last_price_line = pg.InfiniteLine(
            angle=0,
            movable=False,
            label="{value:.1f}",
            pen=pg.mkPen(color, width=1),
            labelOpts={
                "color": color,
                "position": 1,
                "anchors": [(1, 1), (1, 1)]
            }
        )
        self.last_price_line.label.setFont(NORMAL_FONT)
        plot.addItem(self.last_price_line)

    def update_history(self, history: List[BarData]) -> None:
        """
        Update a list of bar data.
        """
        self._manager.update_history(history)

        for item in self._items.values():
            item.update_history(history)

        self._update_plot_limits()

        self.move_to_right()

        self.update_last_price_line(history[-1])

    def update_bar(self, bar: BarData) -> None:
        """
        Update single bar data.
        """
        self._manager.update_bar(bar)

        for item in self._items.values():
            item.update_bar(bar)

        self._update_plot_limits()

        if self._right_ix >= (self._manager.get_count() - self._bar_count / 2):
            self.move_to_right()

        self.update_last_price_line(bar)

    def update_last_price_line(self, bar: BarData) -> None:
        """"""
        if self.last_price_line:
            self.last_price_line.setValue(bar.close_price)


if __name__ == "__main__":
    app = create_qapp()

    # bars = database_manager.load_bar_data(
    #     "IF888",
    #     Exchange.CFFEX,
    #     interval=Interval.MINUTE,
    #     start=datetime(2019, 7, 1),
    #     end=datetime(2019, 7, 17)
    # )

    symbol = "rb2010"
    exchange = Exchange.SHFE
    interval=Interval.MINUTE
    start=datetime(2020, 6, 1)
    end=datetime(2020, 6, 30)    

    dynamic = False  # 是否动态演示
    n = 200          # 缓冲K线根数


    bars = database_manager.load_bar_data(
        symbol=symbol,
        exchange=exchange,
        interval=interval,
        start=start,
        end=end
    )

    event_engine = EventEngine()

    widget = NewChartWidget(event_engine = event_engine)
    widget.setWindowTitle(f"K线图表——{symbol}.{exchange.value},{interval},{start}-{end}")
    widget.add_plot("candle", hide_x_axis=True)
    widget.add_plot("volume", maximum_height=150)
    widget.add_plot("rsi", maximum_height=150)
    widget.add_plot("macd", maximum_height=150)
    widget.add_item(CandleItem, "candle", "candle")
    widget.add_item(VolumeItem, "volume", "volume")

    widget.add_item(LineItem, "line", "candle")
    widget.add_item(SmaItem, "sma", "candle")
    widget.add_item(RsiItem, "rsi", "rsi")
    widget.add_item(MacdItem, "macd", "macd")
    widget.add_last_price_line()
    widget.add_cursor()

    if dynamic:
        history = bars[:n]      # 先取得最早的n根bar作为历史
        new_data = bars[n:]     # 其它留着演示
    else:
        history = bars[-n:]     # 先取得最新的n根bar作为历史
        new_data = []           # 演示的为空

    widget.update_history(history)

    def update_bar():
        if new_data:
            bar = new_data.pop(0)
            widget.update_bar(bar)

        # event = Event(EVENT_TICK,None)
        # event_engine.put(event)

    timer = QtCore.QTimer()
    timer.timeout.connect(update_bar)
    if dynamic:
        timer.start(100)

    widget.show()

    # event_engine.start()
    app.exec_()

用Python的交易员 wrote:

R-BREAKDER策略刚诞生的时候,是没有夜盘的。。。。

其实根据目前规则,应该用夜盘开盘来作为开始计算

老师,我现在的修改就是从夜盘开始计算,下午14:55退出平仓的。
修改方法见:https://www.vnpy.com/forum/topic/4463-yi-ge-ke-yi-jiao-yi-ye-pan-de-rbreakerstrategy

© 2015-2022 微信 18391752892
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】