1、新增dp策略
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
# src/strategies/ReversalVolatilityStrategy.py
|
||||
|
||||
import numpy as np
|
||||
import talib
|
||||
from typing import Optional, Dict, Any, List, Tuple
|
||||
|
||||
from src.core_data import Bar, Order
|
||||
from src.indicators.indicators import StochasticOscillator, RSI
|
||||
from src.strategies.base_strategy import Strategy
|
||||
|
||||
|
||||
# def calculate_atr(bars: List[Bar], period: int) -> float:
|
||||
# """
|
||||
# 从Bar对象列表中计算平均真实波幅(ATR)。
|
||||
# """
|
||||
# if len(bars) < period + 1:
|
||||
# return 0.0
|
||||
|
||||
# true_ranges = []
|
||||
# for i in range(1, len(bars)):
|
||||
# high_low = bars[i].high - bars[i].low
|
||||
# high_prev_close = abs(bars[i].high - bars[i - 1].close)
|
||||
# low_prev_close = abs(bars[i].low - bars[i - 1].close)
|
||||
# tr = max(high_low, high_prev_close, low_prev_close)
|
||||
# true_ranges.append(tr)
|
||||
|
||||
# return np.mean(true_ranges[-(period):])
|
||||
|
||||
|
||||
def find_fvg(bars: List[Bar]) -> Optional[Tuple[str, float, float]]:
|
||||
"""
|
||||
使用最近的三根K线识别公允价值缺口(FVG)。
|
||||
|
||||
返回:
|
||||
一个元组 (方向, 上沿价格, 下沿价格) 或 None (如果没有找到FVG)。
|
||||
'bullish': K线1的最高价和K线3的最低价之间的缺口。
|
||||
'bearish': K线1的最低价和K线3的最高价之间的缺口。
|
||||
"""
|
||||
if len(bars) < 3:
|
||||
return None
|
||||
|
||||
bar1, bar2, bar3 = bars[-3], bars[-2], bars[-1]
|
||||
|
||||
# 检查看涨FVG (向上的失衡)
|
||||
if bar1.high < bar3.low:
|
||||
# 缺口本身就意味着中间的K线是强劲的
|
||||
return ('bullish', bar3.low, bar1.high)
|
||||
|
||||
# 检查看跌FVG (向下的失衡)
|
||||
if bar1.low > bar3.high:
|
||||
return ('bearish', bar1.low, bar3.high)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class ReversalVolatilityStrategy(Strategy):
|
||||
"""
|
||||
一个反转波动率策略,该策略在价格回调至公允价值缺口(FVG)时入场,
|
||||
并使用基于ATR的两阶段止盈系统,其中第二阶段为ATR回撤止盈。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
enable_log: bool,
|
||||
trade_volume: int,
|
||||
atr_period: int = 14,
|
||||
stop_loss_atr_multiplier: float = 1.0,
|
||||
partial_profit_atr_multiplier: float | List[float] = 2.0,
|
||||
# full_profit_atr_multiplier: float = 4.0, # 移除固定止盈乘数
|
||||
trailing_stop_atr_multiplier: float = 1.0, # 新增:回撤止盈ATR乘数
|
||||
open_atr_multiplier: float | List[float] = 1.0,
|
||||
order_direction=None,
|
||||
indicators=[None, None],
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
context: 回测上下文。
|
||||
symbol (str): 主要交易的合约代码。
|
||||
trade_volume (int): 每笔交易的总手数。必须是偶数。
|
||||
atr_period (int): 计算ATR的回看周期。
|
||||
stop_loss_atr_multiplier (float): ATR的乘数,用于设置止损。
|
||||
partial_profit_atr_multiplier (float): ATR的乘数,用于设置第一止盈目标。
|
||||
trailing_stop_atr_multiplier (float): ATR的乘数,用于设置最大回撤止盈。
|
||||
"""
|
||||
super().__init__(context, main_symbol, enable_log)
|
||||
if order_direction is None:
|
||||
order_direction = ['BUY', 'SELL', ]
|
||||
# if trade_volume % 2 != 0:
|
||||
# raise ValueError("为了实现部分止盈, trade_volume必须是偶数。")
|
||||
|
||||
self.trade_volume = trade_volume
|
||||
self.atr_period = atr_period
|
||||
self.stop_loss_atr_multiplier = stop_loss_atr_multiplier
|
||||
if isinstance(partial_profit_atr_multiplier, float) or isinstance(partial_profit_atr_multiplier, int):
|
||||
self.partial_profit_atr_multiplier = [partial_profit_atr_multiplier, partial_profit_atr_multiplier]
|
||||
elif isinstance(partial_profit_atr_multiplier, list):
|
||||
self.partial_profit_atr_multiplier = partial_profit_atr_multiplier
|
||||
|
||||
self.trailing_stop_atr_multiplier = trailing_stop_atr_multiplier
|
||||
if isinstance(open_atr_multiplier, float) or isinstance(open_atr_multiplier, int):
|
||||
self.open_atr_multiplier = [open_atr_multiplier, open_atr_multiplier]
|
||||
elif isinstance(open_atr_multiplier, list):
|
||||
self.open_atr_multiplier = open_atr_multiplier # 初始化新增参数
|
||||
self.order_direction = order_direction
|
||||
|
||||
self.indicator_long = indicators[0]
|
||||
self.indicator_short = indicators[1]
|
||||
|
||||
self.main_symbol = main_symbol
|
||||
self.order_id_counter = 0
|
||||
self._last_order_id: Optional[str] = None
|
||||
|
||||
# 字典,用于存储交易的元数据,如入场价、入场时的ATR和最高/最低价
|
||||
self.position_meta: Dict[str, Any] = {}
|
||||
|
||||
self.log(
|
||||
"ReversalVolatilityStrategy 初始化参数: \n"
|
||||
f"交易量={self.trade_volume}, ATR周期={self.atr_period}, \n"
|
||||
f"止损ATR乘数={self.stop_loss_atr_multiplier}, 部分止盈ATR乘数={self.partial_profit_atr_multiplier}, \n"
|
||||
f"回撤止盈ATR乘数={self.trailing_stop_atr_multiplier}" # 更新日志信息
|
||||
)
|
||||
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.cancel_all_pending_orders(self.main_symbol)
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
current_time = self.get_current_time()
|
||||
|
||||
# --- 1. 取消上一根K线未成交的限价单 ---
|
||||
if self._last_order_id and self._last_order_id in self.get_pending_orders():
|
||||
self.cancel_order(self._last_order_id)
|
||||
self.log(f"已取消上一根K线的挂单: {self._last_order_id}")
|
||||
self._last_order_id = None
|
||||
|
||||
bar_history = self.get_bar_history()
|
||||
if len(bar_history) < self.atr_period + 3:
|
||||
return # 数据不足,无法计算指标
|
||||
|
||||
# --- 2. 计算指标 ---
|
||||
# current_atr = calculate_atr(bar_history, self.atr_period)
|
||||
current_atr = talib.ATR(np.array([bar.high for bar in bar_history]),
|
||||
np.array([bar.low for bar in bar_history]),
|
||||
np.array([bar.close for bar in bar_history]),
|
||||
timeperiod=self.atr_period)[-1]
|
||||
if current_atr == 0:
|
||||
return # 避免除以零或在不活跃的市场中交易
|
||||
|
||||
fvg_signal = find_fvg(bar_history)
|
||||
|
||||
# --- 3. 管理现有持仓 (平仓逻辑) ---
|
||||
current_positions = self.get_current_positions()
|
||||
position_volume = current_positions.get(self.symbol, 0)
|
||||
avg_entry_price = self.get_average_position_price(self.symbol)
|
||||
|
||||
if position_volume != 0:
|
||||
# 传递当前Bar的最高价和最低价用于更新最优价格
|
||||
current_bar_high = bar_history[-1].high
|
||||
current_bar_low = bar_history[-1].low
|
||||
self.manage_open_position(position_volume, open_price, current_bar_high, current_bar_low, avg_entry_price, current_atr)
|
||||
return # 如果正在管理平仓,则不评估进场信号
|
||||
|
||||
# --- 4. 评估新机会 (开仓逻辑) ---
|
||||
if position_volume == 0 and fvg_signal:
|
||||
self.evaluate_entry_signal(fvg_signal, open_price, current_atr)
|
||||
|
||||
def manage_open_position(self, volume: int, current_price: float, current_bar_high: float, current_bar_low: float,
|
||||
entry_price: float, current_atr: float):
|
||||
|
||||
# current_atr = self.position_meta[self.symbol]['entry_atr']
|
||||
|
||||
# 定义基于入场时ATR的止损目标价格
|
||||
# stop_loss_price = entry_price - entry_atr * self.stop_loss_atr_multiplier if volume > 0 else entry_price + entry_atr * self.stop_loss_atr_multiplier
|
||||
stop_loss_price = entry_price - 5 if volume > 0 else entry_price + 5
|
||||
partial_profit_atr_multiplier = self.partial_profit_atr_multiplier[0] if volume > 0 else \
|
||||
self.partial_profit_atr_multiplier[1]
|
||||
partial_tp_price = entry_price + current_atr * partial_profit_atr_multiplier if volume > 0 else entry_price - current_atr * partial_profit_atr_multiplier
|
||||
|
||||
self.log(f'持仓:{volume}, pos:{current_price - entry_price}, current_price:{current_price}, stop_loss_price:{stop_loss_price}, partial_profit_atr_multiplier:{partial_profit_atr_multiplier}, partial_tp_price:{partial_tp_price}')
|
||||
|
||||
# 检查多头持仓
|
||||
|
||||
if volume > 0:
|
||||
|
||||
# # 最大回撤止盈 (只在部分止盈后激活)
|
||||
# if partially_exited:
|
||||
# trailing_stop_level = optimal_price - self.trailing_stop_atr_multiplier * current_atr
|
||||
# if current_price <= trailing_stop_level:
|
||||
# self.log(f"多头回撤止盈到达 {current_price:.2f}. 关闭剩余仓位。")
|
||||
# self.close_position("CLOSE_LONG", abs(volume))
|
||||
# 部分止盈
|
||||
if current_price >= partial_tp_price:
|
||||
self.log(f"多头部分止盈到达 {current_price:.2f}. 关闭仓位。")
|
||||
# self.send_market_order("CLOSE_LONG", self.trade_volume)
|
||||
self.close_position("CLOSE_LONG", abs(volume))
|
||||
# 首次触发部分止盈时,将当前价格设置为最优价格,作为回撤止盈的起点
|
||||
# 止损
|
||||
elif current_price <= stop_loss_price:
|
||||
self.log(f"多头止损到达 {current_price:.2f}. 关闭全部仓位。")
|
||||
self.close_position("CLOSE_LONG", abs(volume))
|
||||
|
||||
# 检查空头持仓
|
||||
elif volume < 0:
|
||||
|
||||
# # 最大回撤止盈 (只在部分止盈后激活)
|
||||
# if partially_exited:
|
||||
# trailing_stop_level = optimal_price + self.trailing_stop_atr_multiplier * current_atr
|
||||
# if current_price >= trailing_stop_level:
|
||||
# self.log(f"空头回撤止盈到达 {current_price:.2f}. 关闭剩余仓位。")
|
||||
# self.close_position("CLOSE_SHORT", abs(volume))
|
||||
# 部分止盈
|
||||
if current_price <= partial_tp_price:
|
||||
self.log(f"空头部分止盈到达 {current_price:.2f}. 关闭一半仓位。")
|
||||
# self.send_market_order("CLOSE_SHORT", self.trade_volume)
|
||||
self.close_position("CLOSE_SHORT", abs(volume))
|
||||
# 止损
|
||||
elif current_price >= stop_loss_price:
|
||||
self.log(f"空头止损到达 {current_price:.2f}. 关闭全部仓位。")
|
||||
self.close_position("CLOSE_SHORT", abs(volume))
|
||||
|
||||
def evaluate_entry_signal(self, fvg_signal, open_price, current_atr):
|
||||
direction, fvg_high, fvg_low = fvg_signal
|
||||
open_atr_multiplier = self.open_atr_multiplier[0] if 'bullish' == direction else self.open_atr_multiplier[1]
|
||||
|
||||
# 多头入场信号
|
||||
if 'bullish' == direction:
|
||||
# 在FVG上沿挂限价单,等待价格回调
|
||||
if self.indicator_long is None or self.indicator_long.is_condition_met(*self.get_indicator_tuple()):
|
||||
limit_price = fvg_low + open_atr_multiplier * current_atr
|
||||
# 避免追高
|
||||
if open_price > limit_price:
|
||||
self.log(f"检测到看涨FVG。在 {limit_price:.2f} 挂限价买单。fvg_high:{fvg_low},current_atr:{current_atr}")
|
||||
new_order = self.send_limit_order("BUY", limit_price, self.trade_volume, {'entry_atr': current_atr})
|
||||
if new_order:
|
||||
self._last_order_id = new_order.id
|
||||
|
||||
# 空头入场信号
|
||||
elif 'bearish' == direction:
|
||||
# 在FVG下沿挂限价单
|
||||
if self.indicator_short is None or self.indicator_short.is_condition_met(*self.get_indicator_tuple()):
|
||||
limit_price = fvg_high - open_atr_multiplier * current_atr
|
||||
# 避免追低
|
||||
if open_price < limit_price:
|
||||
self.log(f"检测到看跌FVG。在 {limit_price:.2f} 挂限价卖单。fvg_high:{fvg_high},current_atr:{current_atr}")
|
||||
new_order = self.send_limit_order("SELL", limit_price, self.trade_volume, {'entry_atr': current_atr})
|
||||
if new_order:
|
||||
self._last_order_id = new_order.id
|
||||
|
||||
def close_position(self, direction: str, volume: int):
|
||||
self.send_market_order(direction, volume)
|
||||
if self.symbol in self.position_meta:
|
||||
del self.position_meta[self.symbol] # 完全平仓后清理元数据
|
||||
|
||||
def send_limit_order(self, direction: str, limit_price: float, volume: int, meta: Dict) -> Optional[Order]:
|
||||
if direction not in self.order_direction:
|
||||
return None
|
||||
|
||||
order_id = f"{self.symbol}_{direction}_{self.get_current_time().strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
|
||||
self.order_id_counter += 1
|
||||
order = Order(
|
||||
id=order_id,
|
||||
symbol=self.symbol,
|
||||
direction=direction,
|
||||
volume=volume,
|
||||
price_type="LIMIT",
|
||||
limit_price=limit_price,
|
||||
submitted_time=self.get_current_time(),
|
||||
offset="OPEN",
|
||||
)
|
||||
|
||||
# 存储开仓时的元数据,包括入场价和入场时的ATR
|
||||
self.position_meta[self.symbol] = {
|
||||
"entry_price": limit_price,
|
||||
"entry_atr": meta.get('entry_atr', 0),
|
||||
"partially_exited": False,
|
||||
"optimal_price": limit_price # 初始最优价格设置为入场价格
|
||||
}
|
||||
|
||||
entry_price = limit_price
|
||||
entry_atr = meta.get('entry_atr', 0)
|
||||
stop_loss_price = entry_price - 5 if direction == "BUY" else entry_price + 5
|
||||
partial_profit_atr_multiplier = self.partial_profit_atr_multiplier[0] if volume > 0 else \
|
||||
self.partial_profit_atr_multiplier[1]
|
||||
partial_tp_price = entry_price + entry_atr * partial_profit_atr_multiplier if volume > 0 else entry_price - entry_atr * partial_profit_atr_multiplier
|
||||
|
||||
self.log(
|
||||
f'atr: {meta.get("entry_atr", 0)}, stop_loss_price: {stop_loss_price}, partial_tp_price: {partial_tp_price}')
|
||||
|
||||
return self.send_order(order)
|
||||
|
||||
def send_market_order(self, direction: str, volume: int):
|
||||
order_id = f"{self.symbol}_{direction}_{self.get_current_time().strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
|
||||
self.order_id_counter += 1
|
||||
order = Order(
|
||||
id=order_id,
|
||||
symbol=self.symbol,
|
||||
direction=direction,
|
||||
volume=volume,
|
||||
price_type="MARKET",
|
||||
submitted_time=self.get_current_time(),
|
||||
offset="CLOSE" # 在本策略中,所有市价单都是为了平仓
|
||||
)
|
||||
self.send_order(order)
|
||||
|
||||
def on_rollover(self, old_symbol: str, new_symbol: str):
|
||||
super().on_rollover(old_symbol, new_symbol)
|
||||
self._last_order_id = None
|
||||
self.position_meta = {} # 换月时清空所有元数据
|
||||
self.log("检测到换月。已清空挂单和持仓元数据。")
|
||||
|
||||
def on_close_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
|
||||
self.cancel_all_pending_orders(self.main_symbol)
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user