1. 每个价差策略都会在on_init()时调用load_bar()
1.1 价差策略的on_init()
例如下面的DemoStrategy价差策略的代码:
class DemoStrategy(SpreadStrategyTemplate):
"""
利用BOLL通道进行套利的一种价差交易
"""
author = "hxxjava"
bar_window = 5 # K线周期
boll_window = 26 # BOLL参数1
boll_dev = 2 # BOLL参数2
target_pos = 1 # 开仓数量
payup = 10
interval = 5
spread_pos = 0.0
boll_mid = None
boll_up = None
boll_down = None
sk_algoid:str = ""
bp_algoid:str = ""
bk_algoid:str = ""
sp_algoid:str = ""
parameters = [
"bar_window",
"boll_window",
"boll_dev",
"target_pos",
"payup",
"interval"
]
variables = [
"spread_pos",
"boll_mid",
"boll_up",
"boll_down",
"sk_algoid",
"bp_algoid",
"bk_algoid",
"sp_algoid"
]
def __init__(
self,
strategy_engine,
strategy_name: str,
spread: SpreadData,
setting: dict
):
""""""
super().__init__(
strategy_engine, strategy_name, spread, setting
)
self.bg = BarGenerator(self.on_spread_bar,self.bar_window,self.on_xmin_spread_bar)
self.am = ArrayManager()
def on_init(self):
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.load_bar(days=10,callback=self.on_spread_bar)
def on_spread_bar(self,bar:BarData):
"""
Callback when 1 min spread bar data is generated.
"""
print(f"on_spread_bar bar={bar}") # 看看价差策略的bar长的是什么样子
self.bg.update_bar(bar)
。。。其他省略
2. 假如没有下载过数据到本地数据库,load_bar(10)将加载不了1分钟价差K线数据!
2.1 发现问题的过程
- 编写好了价差交易策略之后;
- 启动vnpy,连接到CTP接口;
- 进入价差交易模块;
- 创建一个跨期价差:rb2109&rb2201,主动腿为rb2109.SHFE,被动腿为rb2201.SHFE。价格乘数分别为1,-1,交易乘数分别为1,-1,没有问题;
- 用DemoStrategy创建一个价差策略实例:DS_rb2109&rb2201,也是成功的。
- 初始化DS_rb2109&rb2201,除了策略的inited=True之外,没有任何反应,看不到有任何1分钟价差K线数据被打印出来!
2.2 为什么加载不到任何1分钟价差K线数据?
进去查找,原来问题出在这里:
DemoStrategy调用的load_bar()是从SpreadStrateyTemplate继承的,而SpreadStrateyTemplate是load_bar()又调用了strategy_engine的load_bar()。
strategy_engine的load_bar()的代码如下:
def load_bar(
self, spread: SpreadData, days: int, interval: Interval, callback: Callable
):
""""""
end = datetime.now()
start = end - timedelta(days)
bars = load_bar_data(spread, interval, start, end)
for bar in bars:
callback(bar)
load_bar_data()的代码:
@lru_cache(maxsize=999)
def load_bar_data(
spread: SpreadData,
interval: Interval,
start: datetime,
end: datetime,
pricetick: float = 0
):
""""""
# Load bar data of each spread leg
leg_bars: Dict[str, Dict] = {}
for vt_symbol in spread.legs.keys():
symbol, exchange = extract_vt_symbol(vt_symbol)
bar_data: List[BarData] = database_manager.load_bar_data(
symbol, exchange, interval, start, end
)
bars: Dict[datetime, BarData] = {bar.datetime: bar for bar in bar_data}
leg_bars[vt_symbol] = bars
# Calculate spread bar data
spread_bars: List[BarData] = []
for dt in bars.keys():
spread_price = 0
spread_value = 0
spread_available = True
for leg in spread.legs.values():
leg_bar = leg_bars[leg.vt_symbol].get(dt, None)
if leg_bar:
price_multiplier = spread.price_multipliers[leg.vt_symbol]
spread_price += price_multiplier * leg_bar.close_price
spread_value += abs(price_multiplier) * leg_bar.close_price
else:
spread_available = False
if spread_available:
if pricetick:
spread_price = round_to(spread_price, pricetick)
spread_bar = BarData(
symbol=spread.name,
exchange=exchange.LOCAL,
datetime=dt,
interval=interval,
open_price=spread_price,
high_price=spread_price,
low_price=spread_price,
close_price=spread_price,
gateway_name="SPREAD",
)
spread_bar.value = spread_value
spread_bars.append(spread_bar)
return spread_bars
原来load_bar_data()中只考虑了从本地数据库加载1分钟价差K线数据(当然是用价差组合中的多个合约的1分钟K线数据合成的)。
而我因为没有事先下载过rb2109.SHFE和rb2201.SHFE的合约的1分钟K线数据,所以加载不到这10天中的任何1分钟价差K线数据!
3. 为什么不优先从米筐接口rqdatac加载1分钟价差K线?
就算加载不到1分钟价差K线的原因已经找到,可是这样的设计仍然是有问题的:
- 1 要求不停地下载价差策略相关的合约数据不合理,因为这很容易忘记
- 2 就算你昨天已经下载了价差策略相关的合约数据,今天你没有下载最新的数据,重新启动了价差策略,策略加载的数据就会缺少最新的数据
4. 修改成优先从米筐接口rqdatac加载1分钟价差K线!
鉴于以上的分析,把app\spread_trading\base.py做如下修改:
4.1 加入从rqdatac读取历史数据的query_bar_from_rq()函数,
# hxxjava debug spread_trading
def query_bar_from_rq(
symbol: str, exchange: Exchange, interval: Interval, start: datetime, end: datetime
):
"""
Query bar data from RQData.
"""
from vnpy.trader.rqdata import rqdata_client
from vnpy.trader.object import HistoryRequest
if not rqdata_client.inited:
rqdata_client.init()
req = HistoryRequest(
symbol=symbol,
exchange=exchange,
interval=interval,
start=start,
end=end
)
data = rqdata_client.query_history(req)
return data
4.2 修改价差K线的读取函数load_bar_data()的读取优先顺序,优先从米筐接口rqdatac加载1分钟价差K线,修改如下:
@lru_cache(maxsize=999)
def load_bar_data(
spread: SpreadData,
interval: Interval,
start: datetime,
end: datetime,
pricetick: float = 0
):
""""""
# Load bar data of each spread leg
leg_bars: Dict[str, Dict] = {}
for vt_symbol in spread.legs.keys():
symbol, exchange = extract_vt_symbol(vt_symbol)
# hxxjava debug spread_trading
bar_data = query_bar_from_rq(
symbol=symbol, exchange=exchange,
interval=interval,start=start,end=end
)
# if bar_data:
# print(f"load {symbol}.{exchange} {interval} bar_data, len of = {len(bar_data)}")
if not bar_data:
bar_data: List[BarData] = database_manager.load_bar_data(
symbol, exchange, interval, start, end
)
bars: Dict[datetime, BarData] = {bar.datetime: bar for bar in bar_data}
leg_bars[vt_symbol] = bars
# Calculate spread bar data
spread_bars: List[BarData] = []
for dt in bars.keys():
spread_price = 0
spread_value = 0
spread_available = True
for leg in spread.legs.values():
leg_bar = leg_bars[leg.vt_symbol].get(dt, None)
if leg_bar:
price_multiplier = spread.price_multipliers[leg.vt_symbol]
spread_price += price_multiplier * leg_bar.close_price
spread_value += abs(price_multiplier) * leg_bar.close_price
else:
spread_available = False
if spread_available:
if pricetick:
spread_price = round_to(spread_price, pricetick)
spread_bar = BarData(
symbol=spread.name,
exchange=exchange.LOCAL,
datetime=dt,
interval=interval,
open_price=spread_price,
high_price=spread_price,
low_price=spread_price,
close_price=spread_price,
gateway_name="SPREAD",
)
spread_bar.value = spread_value
spread_bars.append(spread_bar)
return spread_bars
5. load_bar()从本地数据库加载的1分钟价差K线数据只有K线
看看 load_bar()从本地数据库加载的1分钟价差K线数据,如下所示:
你会发现其的成交量,volume=0,在使用过程从必须加以注意!
也就是说,需要成交量参与计算的指标是不可利用价差K线序列来计算的!
bar=BarData(gateway_name='SPREAD', symbol='rb2110&rb2201', exchange=<Exchange.LOCAL: 'LOCAL'>, datetime=datetime.datetime(2021, 6, 17, 11, 15, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>), interval=<Interval.MINUTE: '1m'>, volume=0, open_interest=0, open_price=109.0, high_price=109.0, low_price=109.0, close_price=109.0)
bar=BarData(gateway_name='SPREAD', symbol='rb2110&rb2201', exchange=<Exchange.LOCAL: 'LOCAL'>, datetime=datetime.datetime(2021, 6, 17, 11, 16, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>), interval=<Interval.MINUTE: '1m'>, volume=0, open_interest=0, open_price=108.0, high_price=108.0, low_price=108.0, close_price=108.0)
bar=BarData(gateway_name='SPREAD', symbol='rb2110&rb2201', exchange=<Exchange.LOCAL: 'LOCAL'>, datetime=datetime.datetime(2021, 6, 17, 11, 17, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>), interval=<Interval.MINUTE: '1m'>, volume=0, open_interest=0, open_price=106.0, high_price=106.0, low_price=106.0, close_price=106.0)