网关接口的状态感知
网关分很多种,如CTP,XTP等 。其中CTP中又包含行情(MD)接口和交易(TD)接口。它们在连接和断开的时候,都有推送接口。这些接口是:
MD的onFrontConnected()和onFrontDisconnected(),TD的onFrontConnected()和onFrontDisconnected()。行情和交易服务器的接口状态,vnpy已经做了log输出,用户是可以阅读的,可是不方便软件使用,于是对接口和主引擎做了如下修改,以便于上层应用可以使用这些消息,编写出针对行情服务器通断情况的处理方法,也可以编写针对交易服务器连接的处理方法。
下面的代码修改时,需要相互引用的部分,如from... import... 之类的,就不再逐一指出了,太基础了。
EVENT_CONNECT = "eConnected" # hxxjava add
EVENT_DISCONNECT = "eDisconnected" # hxxjava add
@dataclass
class GatewayData(): # hxxjava add
"""
Gateway data
"""
name:str = "" # 网关名称,如 'CTP'
type:str = "" # 接口类型,如 'TD','MD'
reason:int = 0 # 状态或者原因
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)
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}")
修改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}")
在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=?)
上面使用问号,是因为我不知道你软件会提示什么错误原因。
用户策略只需要调用策略账户引擎提供的接口,启动之初创建本策略实例的策略账户,策略账户引擎就可以在后台监视策略的交易活动了。它可以监视策略的每笔交易盈亏,分配资金的使用情况,保证金的占用情况,累计手续费,目前的可用资金(按分配资金计算)等信息,这些信息对用户策略是非常有用的。
可以查询策略自创建以来的所有的出让金记录。当你策略中资金不足,你可以在此往策略账户中入金,当然如果策略很赚钱,你也可以通过出金减小策略的交易规模,做到落袋为安!
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策略。
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()
用Python的交易员 wrote:
我们为了装这玩意也搞了4、5天,他的pip安装需要C++版本的二进制链接库,还要放在指定目录,非常非常麻烦,最后才找了这个wheel包
安装您的wheel包确实快!!!
老师 :
在AlgoTrading模块里面如果没有使用StopAlgo策略的话,选择其他策略,下单类型为STOP也是会出错的,因为AlgoTrading模块连本地停止单都没有维护,它也会立即出错。
这么看来不管CTA策略模块,还是AlgoTrading模块,也许还有其他app模块,可能都会有下停止单的需要,如果能够把本地停止单的维护上提到main_engine的话,不光是CTA策略模块可以下本地停止单,AlgoTrading模块也可以下本地停止单,对AlgoTrading模块的策略编写是否就更多一种非常有用的下单手段。您觉得是否有道理?
执行测试函数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)
合约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
当 time_diff=7 days, 0:00:00的时候就是了
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
保存文件: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()
xiaohe wrote:
可参考https://www.vnpy.com/forum/topic/4250-geng-xin-dao-2-1-5wu-fa-da-kai-vn-trader-pro?page=1#pid14798
谢谢您 !
还是管理员厉害 ! 这条命令好使:python -m pip install https://pip.vnpy.com/colletion/quickfix-1.15.1-cp37-cp37m-win_amd64.whl
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
那就先安装Ms Visual C++ V14.0 build tools,
搜索到Ms Visual C++ V14.0 build tools,
也装上了,
提示重新启动,
那就重新启动
命令是:
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.
老师:
就算CTP柜台没有把停止单功能给关了,OrderPriceType字段也是必填字典,现在这种ORDERTYPE_VT2CTP字典也是映射不到有效值的,也是出错。很多人都碰到主界面中无法下停止单的问题,困惑不已。1)要不在主界面中下单类型一栏不提供'STOP'选项;2)要不就在MainEngine中维护本地停止单,使得手动也可以下单。你看看这两个建议是否可以可行?
在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': '',
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()}
用户的CTA策略发送委托停止单的过程:
目前在主界面中是不可以发送任何停止单的!!!
目前在主界面中是不可以发送任何停止单的!!!
目前在主界面中是不可以发送任何停止单的!!!
重要的话说三遍
已经分析过了,那么是否可以改进呢?
创建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} 非夜盘时间")
交易日内等自然时长K线生成器,可以实现周以下的周期单位的时间就间隔:
x=window,interval的取:
Interval.MINUTE :x分钟宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.HOUR : x小时宽度的K线,对齐交易日开始时间,不可以超过日交易时长
Interval.DAILY : x日宽度的K线,对齐上市交易日开始时间开始的整x日,x可以任意值
Interval.WEEKLY : x周宽度的K线,对齐上市交易日开始时间整x周,x可以任意值
再策略中的应用举例:
保存文件: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)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
你是如何确定你生成K线open,high,low,close和实际值不一致?和谁比较?我猜你是用第三方软件来比较的吧!
那你知道你使用的第三方软件它生成的日K线规则是什么?大智慧、通达信、博弈、文华财经?它们本身的日K线的产生规则都不完全相同,有的是区分日盘和夜盘的,有的却不区分,有的是等自然时长,有的等交易时长。
不同的产生机制,产生不同K线,不可以说谁是对的,谁是错误的。
1)大智慧、通达信、博弈、文华财经和米筐都是从交易所采集数据(tick),然后自己合成各自K线。
2)它们可能使用了交易所的不同的数据采集通道(这些通道都是真实有效的)。
3)这些不同的数据采集通道,交易所保证真实性,不保证一致性,也就是说可能是不一样的
4)你可以去比较大智慧、通达信、文华财经,选择同一个合约的同一个周期,比较一下看看,都可能是不一样的
因为测不准原理,世界本来就是没有绝对的准确,只有相对准确。你永远无法知道1米的绝对长度是多少!你不可以说哪个数据采集是正确的,哪个是错误的。
准备好这些,如果你还是发现K线不一样,你可以怀疑的元素:
策略账户有点类似文华赢智和库安软件中的模组账户的概念,但它根植vnpy的系统架构,完全用python写成。
本人已经提供长时间的构思和设计,经过一段时间的调试和运行,目前策略账户已经可以正常运行。现在展示如下:
原因:https://www.vnpy.com/forum/topic/4461-shuo-shi-r-breakerce-lue-de-wen-ti
修改方法: 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:
老师,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