Files
Quant_Code/1.交易策略/3.期权策略/AdvancedSpreadStrategy_dualthrust.py

201 lines
7.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from datetime import time
from vnpy.trader.constant import Interval
from vnpy.trader.utility import ArrayManager, BarGenerator, load_json, save_json
from vnpy.trader.object import TickData, BarData
from elite_optionstrategy import (
StrategyTemplate,
Variable,
Parameter,
PortfolioData,
ChainData,
OptionData,
OptionBarGenerator,
)
class AdvancedSpreadStrategy(StrategyTemplate):
"""基于Dual Thrust信号做空符合价差的策略"""
author: str = "用Python的交易员"
option_portfolio: str = Parameter("MO") # 期权产品代码
underlying_symbol: str = Parameter("IMJQ00.CFFEX") # 标的合约代码
k1: float = Parameter(0.3) # 多头系数
k2: float = Parameter(0.9) # 空头系数
exit_time: time = Parameter(time(14, 55)) # 平仓时间
percent_add: float = Parameter(0.002) # 委托超价比例
otm_level: int = Parameter(0) # 做空期权档位
leg1_ratio: int = Parameter(4) # 顺势腿的比例
leg2_ratio: int = Parameter(1) # 逆势腿的比例
dual_thrust_signal: int = Variable(0) # 当前信号多空1多头-1空头0无信号
atm_strike: float = Variable(0) # 当前平值行权价
def on_init(self) -> None:
"""策略初始化"""
self.write_log("策略初始化")
# K线截面合成器
self.obg: OptionBarGenerator = OptionBarGenerator(self.on_bars)
# 订阅行情
self.subscribe_options(self.option_portfolio)
self.subscribe_data(self.underlying_symbol)
# 标的信号对象
self.factor: DualThrustFactor = DualThrustFactor(
self.underlying_symbol, self.k1, self.k2, self.exit_time
)
# 加载标的历史数据初始化
bars: list[BarData] = self.load_bars(self.underlying_symbol, 100, Interval.MINUTE)
for bar in bars:
self.factor.update_bar(bar)
# 缓存文件名称
self.data_filename: str = f"{self.name}_data.json"
def on_start(self) -> None:
"""策略启动"""
self.write_log("策略启动")
data: dict = load_json(self.data_filename)
self.dual_thrust_signal = data.get("dual_thrust_signal", 0)
def on_stop(self) -> None:
"""策略停止"""
self.write_log("策略停止")
data: dict = {"dual_thrust_signal": self.dual_thrust_signal}
save_json(self.data_filename, data)
def on_tick(self, tick: TickData) -> None:
"""Tick推送"""
self.obg.update_tick(tick)
def on_bars(self, bars: dict[str, BarData]) -> None:
"""K线推送"""
# 更新标的信号
underlying_bar: BarData = bars.pop(self.underlying_symbol, None)
if underlying_bar:
self.factor.update_bar(underlying_bar)
# 获取期权组合对象
portfolio: PortfolioData = self.get_portfolio(self.option_portfolio)
price_data: dict[str, float] = {}
for bar in bars.values():
price_data[bar.vt_symbol] = bar.close_price
portfolio.update_price(price_data)
# 获取当月期权链
front_chain: ChainData = portfolio.get_chain_by_level(0)
if not front_chain:
self.write_log("无法获取当月期权链,请检查是否正确添加了期权合约")
return
# 计算平值期权
front_chain.calculate_atm()
self.atm_strike = front_chain.atm_strike
# 获取Dual Thrust信号
signal: int = self.factor.get_signal()
# 固定交易手数为1
trading_size: int = 1
# 根据信号开仓
if signal != self.dual_thrust_signal:
self.clear_targets()
call: OptionData = front_chain.get_option_by_level(cp=1, level=self.otm_level)
put: OptionData = front_chain.get_option_by_level(cp=-1, level=self.otm_level)
if call and put:
if signal == 1: # 多头信号做空Put顺势腿和Call逆势腿
self.set_target(put.vt_symbol, -trading_size * self.leg1_ratio)
self.set_target(call.vt_symbol, -trading_size * self.leg2_ratio)
elif signal == -1: # 空头信号做空Call顺势腿和Put逆势腿
self.set_target(call.vt_symbol, -trading_size * self.leg1_ratio)
self.set_target(put.vt_symbol, -trading_size * self.leg2_ratio)
# 平仓逻辑在exit_time前平仓
if underlying_bar.datetime.time() >= self.exit_time:
self.clear_targets()
self.dual_thrust_signal = signal
self.execute_trading(price_data, self.percent_add)
self.put_event()
class DualThrustFactor:
"""标的物Dual Thrust因子基于价格区间输出多空信号"""
def __init__(self, vt_symbol: str, k1: float, k2: float, exit_time: time) -> None:
self.vt_symbol: str = vt_symbol
self.k1: float = k1
self.k2: float = k2
self.exit_time: time = exit_time
self.bars = []
self.day_open: float = 0
self.day_high: float = 0
self.day_low: float = 0
self.day_range: float = 0
self.long_entry: float = 0
self.short_entry: float = 0
self.signal: int = 0 # 1多头-1空头0无信号
self.bg: BarGenerator = BarGenerator(self.update_bar, 1, self.update_window_bar)
self.am: ArrayManager = ArrayManager(40) # 缓存足够的历史数据
def update_tick(self, tick: TickData) -> None:
"""Tick更新"""
self.bg.update_tick(tick)
def update_bar(self, bar: BarData) -> None:
"""K线更新"""
self.bg.update_bar(bar)
def update_window_bar(self, bar: BarData) -> None:
"""日级别K线更新"""
# 检查是否为新交易日
self.bars.append(bar)
if len(self.bars) <= 2:
return
else:
self.bars.pop(0)
last_bar = self.bars[-2]
if len(self.bars) == 0 or last_bar.datetime.date() != bar.datetime.date():
if len(self.bars) >= 1:
# 计算前一交易日的波动范围
prev_high = self.am.high[-1]
prev_low = self.am.low[-1]
prev_close = self.am.close[-1]
self.day_range = max(prev_high - prev_low, prev_high - prev_close, prev_close - prev_low)
# 重置当日参数
self.day_open = bar.open_price
self.day_high = bar.high_price
self.day_low = bar.low_price
else:
# 更新当日最高最低价
self.day_high = max(self.day_high, bar.high_price)
self.day_low = min(self.day_low, bar.low_price)
self.am.update_bar(bar)
# 计算入场价
if self.day_range > 0:
self.long_entry = self.day_open + self.k1 * self.day_range
self.short_entry = self.day_open - self.k2 * self.day_range
# 生成信号
if bar.close_price > self.long_entry:
self.signal = 1
elif bar.close_price < self.short_entry:
self.signal = -1
else:
self.signal = 0
def get_signal(self) -> int:
"""获取当前多空信号"""
return self.signal