增加交易策略、交易指标、量化库代码等文件夹
This commit is contained in:
171
1.交易策略/3.期权策略/42_dualthrust.ipynb
Normal file
171
1.交易策略/3.期权策略/42_dualthrust.ipynb
Normal file
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 加载功能模块\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"from vnpy.trader.constant import Interval\n",
|
||||
"\n",
|
||||
"from elite_optionstrategy import BacktestingEngine\n",
|
||||
"\n",
|
||||
"from AdvancedSpreadStrategy_dualthrust2 import AdvancedSpreadStrategy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"设置回测的参数。参数及其含义如下 1. vt_symbol ==> 产品名称 2. interval ==> 周期 3. start ==> 开始时间 4. rate ==> 手续费 5. slippage ==> 滑点 6. size ==> 合约乘数 7. pricetick ==> 价格跳动 8. capital ==> 回测资本 9. end ==> 截止时间 10. mode ==> 回测的模式,一共有两种:BacktestingMode.BAR和BacktestingMode.TICK 11. inverse ==> 周期"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 创建回测引擎\n",
|
||||
"engine = BacktestingEngine()\n",
|
||||
"\n",
|
||||
"engine.set_parameters(\n",
|
||||
" interval=Interval.MINUTE,\n",
|
||||
" start=datetime(2024, 7, 1),\n",
|
||||
" end=datetime(2024, 8, 30),\n",
|
||||
" rate=0,\n",
|
||||
" slippage=0.6 + (16 / 100),\n",
|
||||
" capital= 1000000\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"setting = {\n",
|
||||
" # \"aroon_window\": 5,\n",
|
||||
" \"k1\":0.3,\n",
|
||||
" \"k2\": 0.9,\n",
|
||||
" \"percent_add\": 0,\n",
|
||||
" \"otm_level\": 0\n",
|
||||
"}\n",
|
||||
"engine.add_strategy(AdvancedSpreadStrategy, setting)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" 72%|███████▏ | 44/61 [02:21<00:55, 3.28s/it]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 历史数据回放\n",
|
||||
"engine.run_backtesting()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 计算每日盈亏\n",
|
||||
"engine.calculate_result()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 统计绩效结果\n",
|
||||
"result = engine.calculate_statistics()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 显示资金图表\n",
|
||||
"engine.show_chart()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 打印逐笔成交\n",
|
||||
"for trade in engine.all_trades.values():\n",
|
||||
" print(trade.datetime, trade.vt_symbol, trade.direction.value, trade.offset.value, trade.volume, \"@\", trade.price)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.9"
|
||||
},
|
||||
"vscode": {
|
||||
"interpreter": {
|
||||
"hash": "1b43cb0bd93d5abbadd54afed8252f711d4681fe6223ad6b67ffaee289648f85"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
201
1.交易策略/3.期权策略/AdvancedSpreadStrategy_dualthrust.py
Normal file
201
1.交易策略/3.期权策略/AdvancedSpreadStrategy_dualthrust.py
Normal file
@@ -0,0 +1,201 @@
|
||||
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
|
||||
245
1.交易策略/3.期权策略/AdvancedSpreadStrategy_dualthrust2.py
Normal file
245
1.交易策略/3.期权策略/AdvancedSpreadStrategy_dualthrust2.py
Normal file
@@ -0,0 +1,245 @@
|
||||
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,
|
||||
)
|
||||
|
||||
from datetime import time
|
||||
|
||||
class AdvancedSpreadStrategy(StrategyTemplate):
|
||||
"""基于DualThrust信号做空符合价差的策略"""
|
||||
|
||||
author: str = "用Python的交易员"
|
||||
|
||||
option_portfolio: str = Parameter("MO") # 期权产品代码
|
||||
underlying_symbol: str = Parameter("IMJQ00.CFFEX") # 标的合约代码
|
||||
k1: float = Parameter(0.3) # DualThrust参数k1
|
||||
k2: float = Parameter(0.9) # DualThrust参数k2
|
||||
risk_level: int = Parameter(40) # 开仓风险度
|
||||
percent_add: float = Parameter(0.02) # 委托超价比例
|
||||
otm_level: int = Parameter(0) # 做空期权档位
|
||||
leg1_ratio: int = Parameter(4) # 顺势腿的比例
|
||||
leg2_ratio: int = Parameter(1) # 逆势腿的比例
|
||||
|
||||
dt_signal: int = Variable(0) # 当前信号多空
|
||||
trading_size: int = Variable(1) # 当前交易数量
|
||||
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)
|
||||
|
||||
# 加载标的历史数据初始化
|
||||
bars: list[BarData] = self.load_bars(self.underlying_symbol, 40, 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.dt_signal = data.get("dt_signal", 0)
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""策略停止"""
|
||||
self.write_log("策略停止")
|
||||
|
||||
data: dict = {"dt_signal": self.dt_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
|
||||
|
||||
# 获取当前DualThrust多空信号
|
||||
dt_signal: int = self.factor.get_signal()
|
||||
|
||||
# 如果DualThrust多头信号,且尚未做多
|
||||
if dt_signal > 0 and self.dt_signal <= 0:
|
||||
# 做空Put
|
||||
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:
|
||||
# 清空之前的目标
|
||||
self.clear_targets()
|
||||
|
||||
atr_value: float = self.factor.get_atr()
|
||||
if atr_value:
|
||||
# self.trading_size = int(self.risk_level / atr_value)
|
||||
self.trading_size = 1 # max(self.trading_size, 1)
|
||||
else:
|
||||
self.trading_size = 1
|
||||
|
||||
self.set_target(put.vt_symbol, -self.trading_size * self.leg1_ratio)
|
||||
self.set_target(call.vt_symbol, -self.trading_size * self.leg2_ratio)
|
||||
# 如果DualThrust空头信号,且尚未做空
|
||||
elif dt_signal < 0 and self.dt_signal >= 0:
|
||||
# 做空Call
|
||||
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:
|
||||
# 清空之前的目标
|
||||
self.clear_targets()
|
||||
|
||||
atr_value: float = self.factor.get_atr()
|
||||
if atr_value:
|
||||
# self.trading_size = int(self.risk_level / atr_value)
|
||||
self.trading_size = 1 #max(self.trading_size, 1)
|
||||
else:
|
||||
self.trading_size = 1
|
||||
|
||||
self.set_target(call.vt_symbol, -self.trading_size * self.leg1_ratio)
|
||||
self.set_target(put.vt_symbol, -self.trading_size * self.leg2_ratio)
|
||||
|
||||
# 缓存DualThrust多空信号
|
||||
self.dt_signal = dt_signal
|
||||
|
||||
# 执行具体的委托交易
|
||||
self.execute_trading(price_data, self.percent_add)
|
||||
|
||||
# 推送UI事件更新
|
||||
self.put_event()
|
||||
|
||||
|
||||
class DualThrustFactor:
|
||||
"""标的物DualThrust因子(基于DualThrust输出多空信号)"""
|
||||
|
||||
def __init__(self, vt_symbol: str, k1: float, k2: float) -> None:
|
||||
"""构造函数"""
|
||||
self.vt_symbol: str = vt_symbol
|
||||
self.k1: float = k1
|
||||
self.k2: float = k2
|
||||
|
||||
self.bg: BarGenerator = BarGenerator(self.update_bar, 30, self.update_window_bar)
|
||||
self.am: ArrayManager = ArrayManager(10)
|
||||
|
||||
self.day_open = 0
|
||||
self.day_high = 0
|
||||
self.day_low = 0
|
||||
self.day_range = 0
|
||||
self.long_entry = 0
|
||||
self.short_entry = 0
|
||||
self.exit_time = time(hour=14, minute=55)
|
||||
|
||||
self.long_entered = False
|
||||
self.short_entered = False
|
||||
|
||||
self.signal: int = 0
|
||||
self.bars: list[BarData] = [] # 手动维护最近两根K线
|
||||
|
||||
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.am.update_bar(bar)
|
||||
if not self.am.inited:
|
||||
return
|
||||
|
||||
# 维护最近两根K线
|
||||
self.bars.append(bar)
|
||||
if len(self.bars) > 2:
|
||||
self.bars.pop(0)
|
||||
|
||||
# 确保至少有两根K线用于日期比较
|
||||
if len(self.bars) < 2:
|
||||
return
|
||||
|
||||
last_bar = self.bars[-2]
|
||||
# bar = self.bars[-1]
|
||||
|
||||
# 检查是否新的一天
|
||||
if last_bar.datetime.date() != bar.datetime.date():
|
||||
if self.day_high:
|
||||
self.day_range = self.day_high - self.day_low
|
||||
self.long_entry = bar.open_price + self.k1 * self.day_range
|
||||
self.short_entry = bar.open_price - self.k2 * self.day_range
|
||||
|
||||
self.day_open = bar.open_price
|
||||
self.day_high = bar.high_price
|
||||
self.day_low = bar.low_price
|
||||
|
||||
self.long_entered = False
|
||||
self.short_entered = False
|
||||
else:
|
||||
self.day_high = max(self.day_high, bar.high_price)
|
||||
self.day_low = min(self.day_low, bar.low_price)
|
||||
|
||||
if not self.day_range:
|
||||
return
|
||||
|
||||
# 生成信号逻辑
|
||||
if bar.datetime.time() < self.exit_time:
|
||||
if bar.close_price > self.day_open:
|
||||
self.signal = 1
|
||||
else:
|
||||
self.signal = -1
|
||||
else:
|
||||
self.signal = 0
|
||||
|
||||
def get_signal(self) -> int:
|
||||
"""获取当前多空信号"""
|
||||
return self.signal
|
||||
|
||||
def get_atr(self) -> float:
|
||||
"""获取ATR风险度"""
|
||||
if self.am.inited:
|
||||
return self.am.atr(14)
|
||||
else:
|
||||
return 0
|
||||
215
1.交易策略/3.期权策略/advanced_spread_strategy_SuperComboStrategy.py
Normal file
215
1.交易策略/3.期权策略/advanced_spread_strategy_SuperComboStrategy.py
Normal file
@@ -0,0 +1,215 @@
|
||||
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):
|
||||
"""基于均线信号做空符合价差的策略"""
|
||||
|
||||
author: str = "用Python的交易员"
|
||||
|
||||
option_portfolio: str = Parameter("IO") # 期权产品代码
|
||||
underlying_symbol: str = Parameter("IFJQ00.CFFEX") # 标的合约代码
|
||||
fast_window: int = Parameter(5) # 快速均线周期
|
||||
slow_window: int = Parameter(60) # 慢速均线周期
|
||||
risk_level: int = Parameter(40) # 开仓风险度
|
||||
percent_add: float = Parameter(0.02) # 委托超价比例
|
||||
otm_level: int = Parameter(0) # 做空期权档位
|
||||
leg1_ratio: int = Parameter(4) # 顺势腿的比例
|
||||
leg2_ratio: int = Parameter(1) # 逆势腿的比例
|
||||
|
||||
ma_signal: int = Variable(0) # 当前信号多空
|
||||
trading_size: int = Variable(1) # 当前交易数量
|
||||
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: MaFactor = MaFactor(self.underlying_symbol, self.fast_window, self.slow_window)
|
||||
|
||||
# 加载标的历史数据初始化
|
||||
bars: list[BarData] = self.load_bars(self.underlying_symbol, 40, 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.ma_signal = data.get("ma_signal", 0)
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""策略停止"""
|
||||
self.write_log("策略停止")
|
||||
|
||||
data: dict = {"ma_signal": self.ma_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
|
||||
|
||||
# 获取当前均线多空信号
|
||||
ma_signal: int = self.factor.get_signal()
|
||||
|
||||
# 如果均线多头排列,且尚未做多
|
||||
if ma_signal > 0 and self.ma_signal <= 0:
|
||||
# 做空Put
|
||||
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:
|
||||
# 清空之前的目标
|
||||
self.clear_targets()
|
||||
|
||||
atr_value: float = self.factor.get_atr()
|
||||
if atr_value:
|
||||
self.trading_size = int(self.risk_level / atr_value)
|
||||
self.trading_size = max(self.trading_size, 1)
|
||||
else:
|
||||
self.trading_size = 1
|
||||
|
||||
self.set_target(put.vt_symbol, -self.trading_size * self.leg1_ratio)
|
||||
self.set_target(call.vt_symbol, -self.trading_size * self.leg2_ratio)
|
||||
# 如果均线空头排列,且尚未做空
|
||||
elif ma_signal < 0 and self.ma_signal >= 0:
|
||||
# 做空Call
|
||||
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:
|
||||
# 清空之前的目标
|
||||
self.clear_targets()
|
||||
|
||||
atr_value: float = self.factor.get_atr()
|
||||
if atr_value:
|
||||
self.trading_size = int(self.risk_level / atr_value)
|
||||
self.trading_size = max(self.trading_size, 1)
|
||||
else:
|
||||
self.trading_size = 1
|
||||
|
||||
self.set_target(call.vt_symbol, -self.trading_size * self.leg1_ratio)
|
||||
self.set_target(put.vt_symbol, -self.trading_size * self.leg2_ratio)
|
||||
|
||||
# 缓存均线多空信号
|
||||
self.ma_signal = ma_signal
|
||||
|
||||
# 执行具体的委托交易
|
||||
self.execute_trading(price_data, self.percent_add)
|
||||
|
||||
# 推送UI事件更新
|
||||
self.put_event()
|
||||
|
||||
|
||||
class MaFactor:
|
||||
"""标的物均线因子(基于均线输出多空信号)"""
|
||||
|
||||
def __init__(self, vt_symbol: str, fast_window: int, slow_window: int) -> None:
|
||||
"""构造函数"""
|
||||
self.vt_symbol: str = vt_symbol
|
||||
self.fast_window: int = fast_window
|
||||
self.slow_window: int = slow_window
|
||||
|
||||
self.bg: BarGenerator = BarGenerator(self.update_bar, 30, self.update_window_bar)
|
||||
self.am: ArrayManager = ArrayManager(slow_window + 10)
|
||||
|
||||
self.signal: int = 0
|
||||
|
||||
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.am.update_bar(bar)
|
||||
if not self.am.inited:
|
||||
return
|
||||
|
||||
# 计算均线
|
||||
self.fast_ma = self.am.sma(self.fast_window)
|
||||
self.slow_ma = self.am.sma(self.slow_window)
|
||||
|
||||
# 判断信号
|
||||
if self.fast_ma > self.slow_ma:
|
||||
self.signal = 1
|
||||
elif self.fast_ma < self.slow_ma:
|
||||
self.signal = -1
|
||||
else:
|
||||
self.signal = 0
|
||||
|
||||
class TurtleSignalStrategy:
|
||||
def __init__(self, vt_symbol: str, entry_window: int, exit_window: int, atr_window: int) -> None:
|
||||
"""构造函数"""
|
||||
self.vt_symbol: str = vt_symbol
|
||||
self.entry_window: int = entry_window
|
||||
self.exit_window: int = exit_window
|
||||
|
||||
self.atr_window: int = atr_window
|
||||
|
||||
self.bg: BarGenerator = BarGenerator(self.update_bar, 30, self.update_window_bar)
|
||||
self.am: ArrayManager = ArrayManager(slow_window + 10)
|
||||
|
||||
def get_signal(self) -> int:
|
||||
"""获取当前多空信号"""
|
||||
return self.signal
|
||||
|
||||
def get_atr(self) -> float:
|
||||
"""获取ATR风险度"""
|
||||
if self.am.inited:
|
||||
return self.am.atr(self.slow_window)
|
||||
else:
|
||||
return 0
|
||||
Reference in New Issue
Block a user