1. PortfolioStrategy自带的策略PairTradingStrategy
这个组合策略实质上是价差交易。
我理解其本意是选择两个相关的合约进行价差交易,它有两个腿:leg1和leg2,将其leg1和leg2按照leg1_ratio和leg2_ratio设定的比例进行配比做出价差K线。
该组合的开仓信号为:
价差K线突破BOLL上轨或者上轨时,卖出leg1,买入leg2
价差K线突破BOLL上轨或者下轨时,买入leg1,卖出leg2
该组合的平仓信号为:
leg1有持多仓并且价差K线在BOLL中轨之上时,分别平仓leg1和leg2
leg1有持空仓并且价差K线在BOLL中轨之下时,分别平仓leg1和leg2
2. 当leg1_ratio和leg2_ratio不相等的时候就错误了:
当leg1_ratio和leg2_ratio相等的时候,一起都没有毛病。
当leg1_ratio和leg2_ratio不相等的时候,就有问题了。你会发现它们开仓的数量总是一样多,这是不对的。
比如leg1为i2109.DCE, leg2为rb2110.DCE。
我们知道i2109的合约乘数是100,atr=2.7,而rb2109的合约乘数是10,atr=7.7,
leg2_ratio/leg1_ratio =(2.7100)/(7.710) ≈ 7/2
那么我们应该配比leg1_ratio=2和leg2_ratio=7,这意味着i2109.DCE开仓2手,就需要反向开仓rb2110.DCE 开仓7手。
因为价差的计算是这样的:
self.current_spread = (
leg1_bar.close_price * self.leg1_ratio - leg2_bar.close_price * self.leg2_ratio
)
而实际开仓却是(1手,-1手)或(-1手,1手)的组合,与价差计算不符合。
3. 做如下修改:
代码如下,修改部分见注释:
from typing import List, Dict
from datetime import datetime
import numpy as np
from vnpy.app.portfolio_strategy import StrategyTemplate, StrategyEngine
from vnpy.trader.utility import BarGenerator
from vnpy.trader.object import TickData, BarData
class PairTradingStrategy(StrategyTemplate):
""""""
author = "用Python的交易员"
price_add = 5
boll_window = 20
boll_dev = 2
# fixed_size = 1 # 没有使用,去掉
leg1_ratio = 1
leg2_ratio = 1
leg1_symbol = ""
leg2_symbol = ""
current_spread = 0.0
boll_mid = 0.0
boll_down = 0.0
boll_up = 0.0
parameters = [
"price_add",
"boll_window",
"boll_dev",
# "fixed_size", # 没有使用,去掉
"leg1_ratio",
"leg2_ratio",
]
variables = [
"leg1_symbol",
"leg2_symbol",
"current_spread",
"boll_mid",
"boll_down",
"boll_up",
]
def __init__(
self,
strategy_engine: StrategyEngine,
strategy_name: str,
vt_symbols: List[str],
setting: dict
):
""""""
super().__init__(strategy_engine, strategy_name, vt_symbols, setting)
self.bgs: Dict[str, BarGenerator] = {}
self.targets: Dict[str, int] = {}
self.last_tick_time: datetime = None
self.spread_count: int = 0
self.spread_data: np.array = np.zeros(100)
# Obtain contract info
self.leg1_symbol, self.leg2_symbol = vt_symbols
def on_bar(bar: BarData):
""""""
pass
for vt_symbol in self.vt_symbols:
self.targets[vt_symbol] = 0
self.bgs[vt_symbol] = BarGenerator(on_bar)
def on_init(self):
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.load_bars(1)
def on_start(self):
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self):
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_tick(self, tick: TickData):
"""
Callback of new tick data update.
"""
if (
self.last_tick_time
and self.last_tick_time.minute != tick.datetime.minute
):
bars = {}
for vt_symbol, bg in self.bgs.items():
bars[vt_symbol] = bg.generate()
self.on_bars(bars)
bg: BarGenerator = self.bgs[tick.vt_symbol]
bg.update_tick(tick)
self.last_tick_time = tick.datetime
def on_bars(self, bars: Dict[str, BarData]):
""""""
self.cancel_all()
# Return if one leg data is missing
if self.leg1_symbol not in bars or self.leg2_symbol not in bars:
return
# Calculate current spread
leg1_bar = bars[self.leg1_symbol]
leg2_bar = bars[self.leg2_symbol]
# Filter time only run every 5 minutes
if (leg1_bar.datetime.minute + 1) % 5:
return
self.current_spread = (
leg1_bar.close_price * self.leg1_ratio - leg2_bar.close_price * self.leg2_ratio
)
# Update to spread array
self.spread_data[:-1] = self.spread_data[1:]
self.spread_data[-1] = self.current_spread
self.spread_count += 1
if self.spread_count <= self.boll_window:
return
# Calculate boll value
buf: np.array = self.spread_data[-self.boll_window:]
std = buf.std()
self.boll_mid = buf.mean()
self.boll_up = self.boll_mid + self.boll_dev * std
self.boll_down = self.boll_mid - self.boll_dev * std
# Calculate new target position
leg1_pos = self.get_pos(self.leg1_symbol)
if not leg1_pos:
if self.current_spread >= self.boll_up:
self.targets[self.leg1_symbol] = -1*self.leg1_ratio # hxxjava add *self.leg1_ratio
self.targets[self.leg2_symbol] = 1*self.leg2_ratio # hxxjava add *self.leg2_ratio
elif self.current_spread <= self.boll_down:
self.targets[self.leg1_symbol] = 1*self.leg1_ratio # hxxjava add *self.leg1_ratio
self.targets[self.leg2_symbol] = -1*self.leg2_ratio # hxxjava add *self.leg2_ratio
elif leg1_pos > 0:
if self.current_spread >= self.boll_mid:
self.targets[self.leg1_symbol] = 0
self.targets[self.leg2_symbol] = 0
else:
if self.current_spread <= self.boll_mid:
self.targets[self.leg1_symbol] = 0
self.targets[self.leg2_symbol] = 0
# Execute orders
for vt_symbol in self.vt_symbols:
target_pos = self.targets[vt_symbol]
current_pos = self.get_pos(vt_symbol)
pos_diff = target_pos - current_pos
volume = abs(pos_diff)
bar = bars[vt_symbol]
if pos_diff > 0:
price = bar.close_price + self.price_add
if current_pos < 0:
self.cover(vt_symbol, price, volume)
else:
self.buy(vt_symbol, price, volume)
elif pos_diff < 0:
price = bar.close_price - self.price_add
if current_pos > 0:
self.sell(vt_symbol, price, volume)
else:
self.short(vt_symbol, price, volume)
self.put_event()
4. 另外:如何设置正相关和负相关的配对?
当leg1与leg2正相关时,leg1_ratio和leg2_ratio同为正整数;
当leg1与leg2负相关时,leg1_ratio为正整数和leg2_ratio同为负整数。