1、trend + hawks 策略
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -187,6 +187,7 @@ class AsymmetricOriginalDPStrategy(Strategy):
|
||||
self._send_market_order('SELL', self.volume, latest_bar)
|
||||
|
||||
def _exit_logic(self, pos: int, latest_bar: Bar):
|
||||
# 1. ATR追踪止损 (优先,保持不变)
|
||||
if pos > 0:
|
||||
new_stop_price = latest_bar.close - self.atr * self.atr_stop_multiplier
|
||||
if self.trailing_stop_price is None: self.trailing_stop_price = new_stop_price
|
||||
@@ -201,11 +202,32 @@ class AsymmetricOriginalDPStrategy(Strategy):
|
||||
if latest_bar.high >= self.trailing_stop_price:
|
||||
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
||||
return
|
||||
dominance = self.dp_long - self.dp_short
|
||||
if pos > 0 and dominance < 0:
|
||||
self._send_market_order('CLOSE_LONG', abs(pos), latest_bar)
|
||||
elif pos < 0 and dominance > 0:
|
||||
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
||||
|
||||
# 2. 策略性离场 (非对称逻辑)
|
||||
if pos > 0:
|
||||
# --- 多头出场逻辑 (保持不变): 寻找多头优势的丧失 ---
|
||||
dominance = self.dp_long - self.dp_short
|
||||
if dominance < 0: # 可以简化为一个固定的阈值,或者使用之前的动态阈值
|
||||
self.log(f"多头策略性离场: Dominance {dominance:.2f} < 0")
|
||||
self._send_market_order('CLOSE_LONG', abs(pos), latest_bar)
|
||||
|
||||
elif pos < 0:
|
||||
# --- 【全新】空头出场逻辑: 寻找多头力量的重新集结 ---
|
||||
if len(self.dp_long_history) < self.atr_period: return
|
||||
|
||||
# 找到近期多头力量的最低点
|
||||
recent_min_dp_long = min(self.dp_long_history)
|
||||
|
||||
# 计算反弹阈值
|
||||
rebound_threshold = recent_min_dp_long * (1 + self.short_params['long_power_falloff_pct'])
|
||||
# 考虑到dp_long可能为0或很小,增加一个绝对量的阈值,防止过早离场
|
||||
absolute_rebound_threshold = np.std(list(self.dp_long_history)) * 0.5
|
||||
final_threshold = max(rebound_threshold, absolute_rebound_threshold)
|
||||
|
||||
if self.dp_long > final_threshold:
|
||||
self.log(
|
||||
f"空头策略性离场: 多头力量 {self.dp_long:.2f} 从低点 {recent_min_dp_long:.2f} 反弹超过阈值 {final_threshold:.2f}")
|
||||
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
||||
|
||||
def _send_market_order(self, direction: str, vol: int, latest_bar: Bar):
|
||||
offset = 'OPEN' if direction in ('BUY', 'SELL') else 'CLOSE'
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,208 @@
|
||||
from collections import deque
|
||||
from typing import List, Union, Optional
|
||||
import numpy as np
|
||||
from src.core_data import Bar, Order
|
||||
from src.indicators.base_indicators import Indicator
|
||||
from src.strategies.base_strategy import Strategy
|
||||
from src.indicators.indicators import Empty
|
||||
|
||||
|
||||
class SymmetricalDPStrategy(Strategy):
|
||||
"""
|
||||
机构级思路 · 最终对称版 v14.1 (双哲学参数)
|
||||
通过两个核心哲学参数(进攻性、不对称性)对称地推导四种力量权重。
|
||||
"""
|
||||
|
||||
def __init__(self, context, main_symbol: str,
|
||||
trade_volume: int = 1,
|
||||
lookback_period: int = 120,
|
||||
decay_factor: float = 0.95,
|
||||
dominance_multiplier: float = 1.5,
|
||||
activity_multiplier: float = 1.0,
|
||||
exit_multiplier: float = 0.5,
|
||||
# --- 【核心】两个新的“哲学”参数 ---
|
||||
aggression_factor: float = 1.5, # 进攻性因子
|
||||
asymmetry_factor: float = 1.0, # 多空不对称因子
|
||||
atr_period: int = 20,
|
||||
atr_stop_multiplier: float = 2.0,
|
||||
order_direction: Optional[list] = None,
|
||||
enable_log: bool = False,
|
||||
# ... 其他参数 ...
|
||||
):
|
||||
super().__init__(context, main_symbol, enable_log=enable_log)
|
||||
|
||||
# --- 对称的参数 ---
|
||||
self.decay_factor = decay_factor
|
||||
self.dominance_multiplier = dominance_multiplier
|
||||
self.activity_multiplier = activity_multiplier
|
||||
self.exit_multiplier = exit_multiplier
|
||||
|
||||
# --- 【核心】根据两个哲学参数,对称地推导出四个权重 ---
|
||||
base_long_weight = 1.0
|
||||
base_short_weight = 1.0 * asymmetry_factor
|
||||
|
||||
self.weights = {
|
||||
'offensive_long': base_long_weight * aggression_factor,
|
||||
'collapse_short': base_long_weight,
|
||||
'offensive_short': base_short_weight * aggression_factor,
|
||||
'collapse_long': base_short_weight,
|
||||
}
|
||||
|
||||
# ... 其他 ...
|
||||
self.lookback_period = lookback_period
|
||||
self.atr_period = atr_period
|
||||
self.atr_stop_multiplier = atr_stop_multiplier
|
||||
self.symbol = main_symbol
|
||||
self.volume = trade_volume
|
||||
self.order_direction = order_direction or ['BUY', 'SELL']
|
||||
self.min_bars_required = max(self.lookback_period, self.atr_period) + 2
|
||||
|
||||
# --- DP状态变量 ---
|
||||
self.dp_long = 0.0
|
||||
self.dp_short = 0.0
|
||||
self.dominance_history = deque(maxlen=self.atr_period)
|
||||
self.activity_history = deque(maxlen=self.atr_period)
|
||||
self.previous_bar: Optional[Bar] = None
|
||||
self.volume_history = deque(maxlen=self.lookback_period)
|
||||
self.tr_history = deque(maxlen=self.atr_period)
|
||||
self.atr = 0.0
|
||||
self.trailing_stop_price: Optional[float] = None
|
||||
|
||||
if indicators is None: indicators = [Empty(), Empty()]
|
||||
self.indicators = indicators
|
||||
|
||||
# on_open_bar 逻辑不变
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
bars = self.get_bar_history()
|
||||
if len(bars) < self.min_bars_required: return
|
||||
latest_bar = bars[-1]
|
||||
self.cancel_all_pending_orders(symbol)
|
||||
self._update_dp_state(latest_bar)
|
||||
pos = self.get_current_positions().get(symbol, 0)
|
||||
if pos != 0:
|
||||
self._exit_logic(pos, latest_bar)
|
||||
return
|
||||
for side_idx, direction in enumerate([1, -1]):
|
||||
if (direction > 0 and 'BUY' not in self.order_direction) or \
|
||||
(direction < 0 and 'SELL' not in self.order_direction):
|
||||
continue
|
||||
if self._entry_logic(side_idx, latest_bar):
|
||||
break
|
||||
|
||||
# _update_dp_state 使用推导出的四个权重,其他逻辑与v14.0相同
|
||||
def _update_dp_state(self, latest_bar: Bar):
|
||||
# 1. 更新历史数据和基础指标 (不变)
|
||||
if self.previous_bar is None:
|
||||
self.previous_bar = latest_bar
|
||||
self.volume_history.append(latest_bar.volume)
|
||||
return
|
||||
self.volume_history.append(latest_bar.volume)
|
||||
recent_avg_volume = np.mean(list(self.volume_history))
|
||||
price_change = latest_bar.close - self.previous_bar.close
|
||||
oi_change = getattr(latest_bar, 'close_oi', 0) - getattr(self.previous_bar, 'close_oi', 0)
|
||||
epsilon = 1e-9
|
||||
volume_factor = latest_bar.volume / (recent_avg_volume + epsilon)
|
||||
volume_factor = np.clip(volume_factor, 0, 3)
|
||||
|
||||
o, h, l, c = latest_bar.open, latest_bar.high, latest_bar.low, latest_bar.close
|
||||
range_size = h - l if h > l else epsilon
|
||||
candlestick_factor = ((c - l) - (h - c)) / range_size
|
||||
|
||||
# 2. 计算多空基础分数
|
||||
bullish_base_score = 0.0
|
||||
bearish_base_score = 0.0
|
||||
|
||||
bullish_intensity = max(0, candlestick_factor)
|
||||
bearish_intensity = max(0, -candlestick_factor)
|
||||
|
||||
if price_change > 0:
|
||||
if oi_change > 0:
|
||||
bullish_base_score = self.weights['offensive_long'] * bullish_intensity
|
||||
elif oi_change < 0:
|
||||
bullish_base_score = self.weights['collapse_short'] * bullish_intensity
|
||||
|
||||
elif price_change < 0:
|
||||
if oi_change > 0:
|
||||
bearish_base_score = self.weights['offensive_short'] * bearish_intensity
|
||||
elif oi_change < 0:
|
||||
bearish_base_score = self.weights['collapse_long'] * bearish_intensity
|
||||
|
||||
# 3. 应用状态转移方程
|
||||
final_bullish_impulse = bullish_base_score * volume_factor
|
||||
final_bearish_impulse = bearish_base_score * volume_factor
|
||||
|
||||
self.dp_long = self.dp_long * self.decay_factor + final_bullish_impulse
|
||||
self.dp_short = self.dp_short * self.decay_factor + final_bearish_impulse
|
||||
|
||||
# 4. 更新历史和日志
|
||||
dominance = self.dp_long - self.dp_short
|
||||
activity = self.dp_long + self.dp_short
|
||||
self.dominance_history.append(dominance)
|
||||
self.activity_history.append(activity)
|
||||
true_range = max(h - l, abs(h - self.previous_bar.close), abs(l - self.previous_bar.close))
|
||||
self.tr_history.append(true_range)
|
||||
self.atr = np.mean(list(self.tr_history))
|
||||
self.previous_bar = latest_bar
|
||||
self.log(f"DP更新: Dominance={dominance:.2f}, Activity={activity:.2f}")
|
||||
|
||||
# --- 决策和出场逻辑完全对称,无需修改 ---
|
||||
def _entry_logic(self, side_idx: int, latest_bar: Bar):
|
||||
# ... (与v12.0版本相同)
|
||||
dominance = self.dp_long - self.dp_short
|
||||
activity = self.dp_long + self.dp_short
|
||||
if len(self.dominance_history) < self.atr_period: return False
|
||||
dominance_std = np.std(list(self.dominance_history))
|
||||
activity_std = np.std(list(self.activity_history))
|
||||
if dominance_std == 0 or activity_std == 0: return False
|
||||
dynamic_dom_thresh = dominance_std * self.dominance_multiplier
|
||||
dynamic_act_thresh = activity_std * self.activity_multiplier
|
||||
if activity < dynamic_act_thresh: return False
|
||||
signal, side_str = False, ''
|
||||
if side_idx == 0:
|
||||
if dominance > dynamic_dom_thresh: signal, side_str = True, 'BUY'
|
||||
else:
|
||||
if dominance < -dynamic_dom_thresh: signal, side_str = True, 'SELL'
|
||||
if not signal: return False
|
||||
if not self.indicators[side_idx].is_condition_met(*self.get_indicator_tuple()): return
|
||||
self.log(f"触发{side_str}信号: Dominance={dominance:.2f}, Activity={activity:.2f}")
|
||||
self._send_market_order(side_str, self.volume, latest_bar)
|
||||
return True
|
||||
|
||||
def _exit_logic(self, pos: int, latest_bar: Bar):
|
||||
# ... (与v12.0版本相同)
|
||||
if pos > 0:
|
||||
new_stop_price = latest_bar.close - self.atr * self.atr_stop_multiplier
|
||||
if self.trailing_stop_price is None: self.trailing_stop_price = new_stop_price
|
||||
self.trailing_stop_price = max(self.trailing_stop_price, new_stop_price)
|
||||
if latest_bar.low <= self.trailing_stop_price:
|
||||
self._send_market_order('CLOSE_LONG', abs(pos), latest_bar)
|
||||
return
|
||||
elif pos < 0:
|
||||
new_stop_price = latest_bar.close + self.atr * self.atr_stop_multiplier
|
||||
if self.trailing_stop_price is None: self.trailing_stop_price = new_stop_price
|
||||
self.trailing_stop_price = min(self.trailing_stop_price, new_stop_price)
|
||||
if latest_bar.high >= self.trailing_stop_price:
|
||||
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
||||
return
|
||||
dominance = self.dp_long - self.dp_short
|
||||
dominance_std = np.std(list(self.dominance_history))
|
||||
if dominance_std == 0: return
|
||||
dynamic_exit_thresh = dominance_std * self.exit_multiplier
|
||||
if pos > 0 and dominance < dynamic_exit_thresh:
|
||||
self._send_market_order('CLOSE_LONG', abs(pos), latest_bar)
|
||||
elif pos < 0 and dominance > -dynamic_exit_thresh:
|
||||
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
||||
|
||||
def _send_market_order(self, direction: str, vol: int, latest_bar: Bar):
|
||||
# ... (与v12.0版本相同)
|
||||
offset = 'OPEN' if direction in ('BUY', 'SELL') else 'CLOSE'
|
||||
if offset == 'OPEN':
|
||||
if direction == 'BUY':
|
||||
self.trailing_stop_price = latest_bar.close - self.atr * self.atr_stop_multiplier
|
||||
else:
|
||||
self.trailing_stop_price = latest_bar.close + self.atr * self.atr_stop_multiplier
|
||||
else:
|
||||
self.trailing_stop_price = None
|
||||
oid = f"{self.symbol}_{direction}_{self.get_current_time():%Y%m%d%H%M%S}"
|
||||
self.send_order(
|
||||
Order(id=oid, symbol=self.symbol, direction=direction, volume=vol, price_type='MARKET', offset=offset))
|
||||
File diff suppressed because one or more lines are too long
573
src/strategies/RsiStrategy/RsiStrategy.ipynb
Normal file
573
src/strategies/RsiStrategy/RsiStrategy.ipynb
Normal file
File diff suppressed because one or more lines are too long
181
src/strategies/RsiStrategy/RsiStrategy.py
Normal file
181
src/strategies/RsiStrategy/RsiStrategy.py
Normal file
@@ -0,0 +1,181 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import talib
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
from src.core_data import Bar, Order
|
||||
from src.strategies.base_strategy import Strategy
|
||||
|
||||
|
||||
class RsiStrategy(Strategy):
|
||||
"""
|
||||
反转策略:RSI 2-24 → PCA → 模型预测 → 极端值反向开仓
|
||||
开仓:下一根 Open 价挂限价单
|
||||
平仓:满足以下任一条件后市价平仓
|
||||
1. 价格触及固定价差止损线
|
||||
2. 持有满 holding_bars 根 K 线
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
model: Any,
|
||||
pca: Any,
|
||||
scaler: Any,
|
||||
lower_bound: float,
|
||||
upper_bound: float,
|
||||
trade_volume: int = 1,
|
||||
order_direction: Optional[List[str]] = None,
|
||||
holding_bars: int = 5,
|
||||
# --- MODIFICATION START ---
|
||||
stop_loss_points: Optional[float] = 5, # 止损点数, e.g., 50.0 for 50个价格点
|
||||
# --- MODIFICATION END ---
|
||||
enable_log: bool = False,
|
||||
use_talib: bool = True,
|
||||
):
|
||||
super().__init__(context, main_symbol, enable_log)
|
||||
self.main_symbol = main_symbol
|
||||
self.trade_volume = trade_volume
|
||||
self.model = model
|
||||
self.pca = pca
|
||||
self.scaler = scaler
|
||||
self.lower_bound = lower_bound
|
||||
self.upper_bound = upper_bound
|
||||
self.order_direction = order_direction or ["BUY", "SELL"]
|
||||
self.holding_bars = holding_bars
|
||||
# --- MODIFICATION START ---
|
||||
self.stop_loss_points = stop_loss_points
|
||||
# --- MODIFICATION END ---
|
||||
self.use_talib = use_talib
|
||||
|
||||
self.close_cache: List[float] = []
|
||||
self.cache_size = 500
|
||||
self.pos_meta: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# --- MODIFICATION START ---
|
||||
log_message = (
|
||||
f"RsiPcaReversalStrategy 初始化:\n"
|
||||
f"交易量={self.trade_volume}, lower={self.lower_bound}, upper={self.upper_bound}\n"
|
||||
f"时间出场={self.holding_bars} bars\n"
|
||||
f"固定价差止损={self.stop_loss_points if self.stop_loss_points else 'N/A'} points"
|
||||
)
|
||||
self.log(log_message)
|
||||
# --- MODIFICATION END ---
|
||||
|
||||
# ... (工具函数保持不变) ...
|
||||
def update_close_cache(self, bar_history: List[Bar]) -> None:
|
||||
self.close_cache = [b.close for b in bar_history[-self.cache_size:]]
|
||||
|
||||
def calc_rsi_vector(self) -> np.ndarray:
|
||||
close = np.array(self.close_cache, dtype=float)
|
||||
rsi_vec = []
|
||||
for i in range(2, 25):
|
||||
if self.use_talib:
|
||||
# talib 版本(最快)
|
||||
rsi = talib.RSI(close, timeperiod=i)[-1]
|
||||
else:
|
||||
# 原滚动均值版本(与旧代码逻辑完全一致)
|
||||
gain = np.where(np.diff(close) > 0, np.diff(close), 0)
|
||||
loss = np.where(np.diff(close) < 0, -np.diff(close), 0)
|
||||
avg_gain = pd.Series(gain).rolling(window=i, min_periods=i).mean().iloc[-1]
|
||||
avg_loss = pd.Series(loss).rolling(window=i, min_periods=i).mean().iloc[-1]
|
||||
rs = avg_gain / avg_loss if avg_loss != 0 else 100
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
rsi_vec.append(rsi)
|
||||
return np.array(rsi_vec)
|
||||
|
||||
def predict_ret5(self, rsi_vec: np.ndarray) -> float:
|
||||
vec_std = self.scaler.transform(rsi_vec.reshape(1, -1))
|
||||
vec_pca = self.pca.transform(vec_std)
|
||||
return self.model.predict(vec_pca)[0]
|
||||
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.pos_meta.clear()
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
bar_history = self.get_bar_history()
|
||||
if len(bar_history) < 30:
|
||||
return
|
||||
|
||||
self.cancel_all_pending_orders(symbol)
|
||||
pos = self.get_current_positions().get(symbol, 0)
|
||||
|
||||
# 1. 更新缓存 & 计算特征
|
||||
self.update_close_cache(bar_history)
|
||||
rsi_vec = self.calc_rsi_vector()
|
||||
if np.isnan(rsi_vec).any():
|
||||
return
|
||||
pred = self.predict_ret5(rsi_vec)
|
||||
|
||||
# 2. 平仓逻辑
|
||||
meta = self.pos_meta.get(symbol)
|
||||
if meta and pos:
|
||||
exit_reason = None
|
||||
|
||||
# --- MODIFICATION START ---
|
||||
# 2a. 检查固定价差止损
|
||||
if self.stop_loss_points is not None:
|
||||
current_price = open_price # 使用当前bar的开盘价作为检查价格
|
||||
entry_price = meta['entry_price']
|
||||
direction = meta['direction']
|
||||
|
||||
if direction == "BUY" and current_price <= entry_price - self.stop_loss_points:
|
||||
exit_reason = f"Stop Loss Hit ({entry_price - self.stop_loss_points})"
|
||||
elif direction == "SELL" and current_price >= entry_price + self.stop_loss_points:
|
||||
exit_reason = f"Stop Loss Hit ({entry_price + self.stop_loss_points})"
|
||||
# --- MODIFICATION END ---
|
||||
|
||||
# 2b. 检查时间出场
|
||||
if not exit_reason and len(bar_history) >= meta['expiry_idx']:
|
||||
exit_reason = "Time Expiry"
|
||||
|
||||
if exit_reason:
|
||||
self.log(f"平仓信号触发: {exit_reason}")
|
||||
self.send_market_order(
|
||||
"CLOSE_LONG" if meta['direction'] == "BUY" else "CLOSE_SHORT",
|
||||
meta['volume'],
|
||||
)
|
||||
del self.pos_meta[symbol]
|
||||
return
|
||||
|
||||
# 3. 开仓逻辑 (保持不变)
|
||||
if pos == 0:
|
||||
entry_price = open_price
|
||||
if pred < self.lower_bound and "SELL" in self.order_direction:
|
||||
self.send_limit_order("SELL", entry_price, self.trade_volume, bar_history[-1].datetime)
|
||||
elif pred > self.upper_bound and "BUY" in self.order_direction:
|
||||
self.send_limit_order("BUY", entry_price, self.trade_volume, bar_history[-1].datetime)
|
||||
|
||||
def send_open_order(self, direction: str, limit_price: float, volume: int, entry_dt: Any):
|
||||
# (此函数逻辑已在上个版本中更新,记录entry_price,保持不变)
|
||||
order_id = f"{self.symbol}_{direction}_{entry_dt.strftime('%Y%m%d%H%M%S')}"
|
||||
order = Order(
|
||||
id=order_id, symbol=self.symbol, direction=direction, volume=volume,
|
||||
price_type="MARKET", submitted_time=entry_dt, offset="OPEN",
|
||||
)
|
||||
self.send_order(order)
|
||||
|
||||
self.pos_meta[self.symbol] = {
|
||||
"direction": direction,
|
||||
"volume": volume,
|
||||
"expiry_idx": len(self.get_bar_history()) + self.holding_bars,
|
||||
"entry_price": limit_price
|
||||
}
|
||||
self.log(f"开仓信号: {direction} at {limit_price}")
|
||||
|
||||
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')}"
|
||||
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.cancel_all_pending_orders(new_symbol)
|
||||
self.pos_meta.clear()
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,169 @@
|
||||
import numpy as np
|
||||
import talib
|
||||
from typing import Optional, Dict, Any, List, Tuple, Union
|
||||
|
||||
from src.algo.TrendLine import calculate_latest_trendline_values
|
||||
# 假设这些是你项目中的基础模块
|
||||
from src.core_data import Bar, Order
|
||||
from src.indicators.base_indicators import Indicator
|
||||
from src.indicators.indicators import Empty
|
||||
from src.strategies.base_strategy import Strategy
|
||||
|
||||
|
||||
class TrendlineBreakoutStrategy(Strategy):
|
||||
"""
|
||||
趋势线突破策略 V3 (优化版):
|
||||
1. 策略逻辑与 V2 相同,但趋势线计算被重构为一个独立的、
|
||||
高性能的辅助方法。
|
||||
2. 该方法只计算最新的趋势线值,避免不必要的数组生成。
|
||||
|
||||
开仓信号:
|
||||
- 做多: 上一根收盘价上穿下趋势线
|
||||
- 做空: 上一根收盘价下穿上趋势线
|
||||
|
||||
平仓逻辑:
|
||||
- 采用 ATR 滑动止损 (Trailing Stop)。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
trendline_n: int = 50,
|
||||
trade_volume: int = 1,
|
||||
order_direction: Optional[List[str]] = None,
|
||||
atr_period: int = 14,
|
||||
atr_multiplier: float = 1.0,
|
||||
enable_log: bool = True,
|
||||
indicators: Union[Indicator, List[Indicator]] = None,
|
||||
):
|
||||
super().__init__(context, main_symbol, enable_log)
|
||||
self.main_symbol = main_symbol
|
||||
self.trendline_n = trendline_n
|
||||
self.trade_volume = trade_volume
|
||||
self.order_direction = order_direction or ["BUY", "SELL"]
|
||||
self.atr_period = atr_period
|
||||
self.atr_multiplier = atr_multiplier
|
||||
self.pos_meta: Dict[str, Dict[str, Any]] = {}
|
||||
if indicators is None:
|
||||
indicators = [Empty(), Empty()]
|
||||
self.indicators = indicators
|
||||
|
||||
if self.trendline_n < 3:
|
||||
raise ValueError("trendline_n 必须大于或等于 3")
|
||||
|
||||
log_message = (
|
||||
f"TrendlineBreakoutStrategy (V3 Optimized) 初始化:\n"
|
||||
f"交易标的={self.main_symbol}, 交易量={self.trade_volume}\n"
|
||||
f"趋势线周期={self.trendline_n}, ATR周期={self.atr_period}, ATR倍数={self.atr_multiplier}"
|
||||
)
|
||||
self.log(log_message)
|
||||
|
||||
|
||||
def _calculate_atr(self, bar_history: List[Bar]) -> Optional[float]:
|
||||
# (此函数与上一版本完全相同,保持不变)
|
||||
if len(bar_history) < self.atr_period + 1: return None
|
||||
highs = np.array([b.high for b in bar_history])
|
||||
lows = np.array([b.low for b in bar_history])
|
||||
closes = np.array([b.close for b in bar_history])
|
||||
atr = talib.ATR(highs, lows, closes, timeperiod=self.atr_period)
|
||||
return atr[-1] if not np.isnan(atr[-1]) else None
|
||||
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.pos_meta.clear()
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
bar_history = self.get_bar_history()
|
||||
min_bars_required = self.trendline_n + 2
|
||||
if len(bar_history) < min_bars_required:
|
||||
return
|
||||
|
||||
self.cancel_all_pending_orders(symbol)
|
||||
pos = self.get_current_positions().get(symbol, 0)
|
||||
|
||||
# 1. 优先处理平仓逻辑 (逻辑不变)
|
||||
meta = self.pos_meta.get(symbol)
|
||||
if meta and pos != 0:
|
||||
current_atr = self._calculate_atr(bar_history[:-1])
|
||||
if current_atr:
|
||||
trailing_stop = meta['trailing_stop']
|
||||
direction = meta['direction']
|
||||
last_close = bar_history[-1].close
|
||||
if direction == "BUY":
|
||||
new_stop_level = last_close - current_atr * self.atr_multiplier
|
||||
trailing_stop = max(trailing_stop, new_stop_level)
|
||||
else: # SELL
|
||||
new_stop_level = last_close + current_atr * self.atr_multiplier
|
||||
trailing_stop = min(trailing_stop, new_stop_level)
|
||||
self.pos_meta[symbol]['trailing_stop'] = trailing_stop
|
||||
if (direction == "BUY" and open_price <= trailing_stop) or \
|
||||
(direction == "SELL" and open_price >= trailing_stop):
|
||||
self.log(f"ATR滑动止损触发: 价格 {open_price:.2f} 触及止损位 {trailing_stop:.2f}")
|
||||
self.send_market_order("CLOSE_LONG" if direction == "BUY" else "CLOSE_SHORT", abs(pos))
|
||||
del self.pos_meta[symbol]
|
||||
return
|
||||
|
||||
# 2. 开仓逻辑 (调用优化后的方法)
|
||||
if pos == 0:
|
||||
prices_for_trendline = np.array([b.close for b in bar_history[-self.trendline_n - 1:-1]])
|
||||
|
||||
# --- 调用新的独立方法 ---
|
||||
trendline_val_upper, trendline_val_lower = calculate_latest_trendline_values(prices_for_trendline)
|
||||
|
||||
if trendline_val_upper is None or trendline_val_lower is None:
|
||||
return # 无法计算趋势线,跳过
|
||||
|
||||
prev_close = bar_history[-2].close
|
||||
last_close = bar_history[-1].close
|
||||
|
||||
current_atr = self._calculate_atr(bar_history[:-1])
|
||||
if not current_atr:
|
||||
return
|
||||
|
||||
# if "BUY" in self.order_direction and last_close > trendline_val_upper and self.indicators[0].is_condition_met(*self.get_indicator_tuple()):
|
||||
# self.log(f"做多信号: Close({last_close:.2f}) 上穿下趋势线({trendline_val_upper:.2f})")
|
||||
# self.send_open_order("BUY", open_price, self.trade_volume, current_atr)
|
||||
#
|
||||
# elif "SELL" in self.order_direction and last_close < trendline_val_lower and self.indicators[1].is_condition_met(*self.get_indicator_tuple()):
|
||||
# self.log(f"做空信号: Close({last_close:.2f}) 下穿上趋势线({trendline_val_lower:.2f})")
|
||||
# self.send_open_order("SELL", open_price, self.trade_volume, current_atr)
|
||||
|
||||
if "BUY" in self.order_direction and last_close > trendline_val_upper and self.indicators[0].is_condition_met(*self.get_indicator_tuple()):
|
||||
self.log(f"做多信号: Close({last_close:.2f}) 上穿下趋势线({trendline_val_upper:.2f})")
|
||||
self.send_open_order("BUY", open_price, self.trade_volume, current_atr)
|
||||
|
||||
elif "SELL" in self.order_direction and last_close < trendline_val_lower and self.indicators[1].is_condition_met(*self.get_indicator_tuple()):
|
||||
self.log(f"做空信号: Close({last_close:.2f}) 下穿上趋势线({trendline_val_lower:.2f})")
|
||||
self.send_open_order("SELL", open_price, self.trade_volume, current_atr)
|
||||
|
||||
|
||||
# send_open_order, send_market_order, on_rollover 等方法与上一版本完全相同,保持不变
|
||||
def send_open_order(self, direction: str, entry_price: float, volume: int, current_atr: float):
|
||||
if direction == "BUY":
|
||||
initial_stop = entry_price - current_atr * self.atr_multiplier
|
||||
else:
|
||||
initial_stop = entry_price + current_atr * self.atr_multiplier
|
||||
current_time = self.get_current_time()
|
||||
order_id = f"{self.symbol}_{direction}_{current_time.strftime('%Y%m%d%H%M%S')}"
|
||||
order_direction = "BUY" if direction == "BUY" else "SELL"
|
||||
order = Order(id=order_id, symbol=self.symbol, direction=order_direction, volume=volume, price_type="MARKET",
|
||||
submitted_time=current_time, offset="OPEN")
|
||||
self.send_order(order)
|
||||
self.pos_meta[self.symbol] = {"direction": direction, "volume": volume, "entry_price": entry_price,
|
||||
"trailing_stop": initial_stop}
|
||||
self.log(
|
||||
f"发送开仓订单: {direction} {volume}手 @ Market Price (执行价约 {entry_price:.2f}), 初始ATR止损位: {initial_stop:.2f}")
|
||||
|
||||
def send_market_order(self, direction: str, volume: int):
|
||||
current_time = self.get_current_time()
|
||||
order_id = f"{self.symbol}_{direction}_{current_time.strftime('%Y%m%d%H%M%S')}"
|
||||
order = Order(id=order_id, symbol=self.symbol, direction=direction, volume=volume, price_type="MARKET",
|
||||
submitted_time=current_time, offset="CLOSE")
|
||||
self.send_order(order)
|
||||
self.log(f"发送平仓订单: {direction} {volume}手 @ Market Price")
|
||||
|
||||
def on_rollover(self, old_symbol: str, new_symbol: str):
|
||||
super().on_rollover(old_symbol, new_symbol)
|
||||
self.cancel_all_pending_orders(new_symbol)
|
||||
self.pos_meta.clear()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,178 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
# 假设这些是你项目中的模块
|
||||
from src.core_data import Bar, Order
|
||||
from src.strategies.base_strategy import Strategy
|
||||
from src.algo.TrendLine import calculate_latest_trendline_values
|
||||
from src.algo.HawksProcess import calculate_hawkes_bands
|
||||
|
||||
|
||||
class TrendlineHawkesStrategy(Strategy):
|
||||
"""
|
||||
趋势线与霍克斯过程双重确认策略 (V2 - 支持逻辑反转):
|
||||
|
||||
入场信号 (双重确认):
|
||||
1. 趋势线事件: 收盘价突破上轨(标准模式做多)或下轨(标准模式做空)。
|
||||
2. 霍克斯确认: 同时,成交量霍克斯强度必须高于其近期高位分位数。
|
||||
|
||||
出场逻辑 (基于霍克斯过程):
|
||||
- 当成交量霍克斯强度从高位回落至近期低位分位数以下时,平仓。
|
||||
|
||||
逻辑反转 (`reverse_logic=True`):
|
||||
- 趋势线突破上轨时,开【空】仓。
|
||||
- 趋势线突破下轨时,开【多】仓。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
trade_volume: int = 1,
|
||||
order_direction: Optional[List[str]] = None,
|
||||
# --- 新增: 逻辑反转开关 ---
|
||||
reverse_logic: bool = False,
|
||||
# --- 趋势线参数 ---
|
||||
trendline_n: int = 50,
|
||||
# --- 霍克斯过程参数 ---
|
||||
hawkes_kappa: float = 0.1,
|
||||
hawkes_lookback: int = 50,
|
||||
hawkes_entry_percent: float = 0.95,
|
||||
hawkes_exit_percent: float = 0.50,
|
||||
enable_log: bool = True,
|
||||
):
|
||||
super().__init__(context, main_symbol, enable_log)
|
||||
self.main_symbol = main_symbol
|
||||
self.trade_volume = trade_volume
|
||||
self.order_direction = order_direction or ["BUY", "SELL"]
|
||||
|
||||
# --- 新增 ---
|
||||
self.reverse_logic = reverse_logic
|
||||
|
||||
self.trendline_n = trendline_n
|
||||
self.hawkes_kappa = hawkes_kappa
|
||||
self.hawkes_lookback = hawkes_lookback
|
||||
self.hawkes_entry_percent = hawkes_entry_percent
|
||||
self.hawkes_exit_percent = hawkes_exit_percent
|
||||
|
||||
self.pos_meta: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
if self.trendline_n < 3:
|
||||
raise ValueError("trendline_n 必须大于或等于 3")
|
||||
|
||||
log_message = (
|
||||
f"TrendlineHawkesStrategy 初始化:\n"
|
||||
f"【逻辑模式】: {'反转 (Reversal)' if self.reverse_logic else '标准 (Breakout)'}\n"
|
||||
f"趋势线周期={self.trendline_n}\n"
|
||||
f"霍克斯参数: kappa={self.hawkes_kappa}, lookback={self.hawkes_lookback}, "
|
||||
f"entry_pct={self.hawkes_entry_percent}, exit_pct={self.hawkes_exit_percent}"
|
||||
)
|
||||
self.log(log_message)
|
||||
|
||||
def on_init(self):
|
||||
# (此函数保持不变)
|
||||
super().on_init()
|
||||
self.pos_meta.clear()
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
bar_history = self.get_bar_history()
|
||||
min_bars_required = max(self.trendline_n + 2, self.hawkes_lookback + 2)
|
||||
if len(bar_history) < min_bars_required:
|
||||
return
|
||||
|
||||
self.cancel_all_pending_orders(symbol)
|
||||
pos = self.get_current_positions().get(symbol, 0)
|
||||
|
||||
# --- 数据准备 (与之前相同) ---
|
||||
close_prices = np.array([b.close for b in bar_history])
|
||||
volume_series = pd.Series(
|
||||
[b.volume for b in bar_history],
|
||||
index=pd.to_datetime([b.datetime for b in bar_history])
|
||||
)
|
||||
|
||||
vol_hawkes, hawkes_upper_band, hawkes_lower_band = calculate_hawkes_bands(
|
||||
volume_series, self.hawkes_lookback, self.hawkes_kappa,
|
||||
self.hawkes_entry_percent, self.hawkes_exit_percent
|
||||
)
|
||||
|
||||
latest_hawkes_value = vol_hawkes.iloc[-1]
|
||||
latest_hawkes_upper = hawkes_upper_band.iloc[-1]
|
||||
latest_hawkes_lower = hawkes_lower_band.iloc[-1]
|
||||
|
||||
# 1. 优先处理平仓逻辑 (逻辑保持不变)
|
||||
meta = self.pos_meta.get(symbol)
|
||||
if meta and pos != 0:
|
||||
if latest_hawkes_value < latest_hawkes_lower:
|
||||
self.log(f"霍克斯出场信号: 强度({latest_hawkes_value:.2f}) < 阈值({latest_hawkes_lower:.2f})")
|
||||
self.send_market_order("CLOSE_LONG" if meta['direction'] == "BUY" else "CLOSE_SHORT", abs(pos))
|
||||
del self.pos_meta[symbol]
|
||||
return
|
||||
|
||||
# 2. 开仓逻辑 (加入反转判断)
|
||||
if pos == 0:
|
||||
prices_for_trendline = close_prices[-self.trendline_n - 1:-1]
|
||||
trend_upper, trend_lower = calculate_latest_trendline_values(prices_for_trendline)
|
||||
|
||||
if trend_upper is None or trend_lower is None:
|
||||
return
|
||||
|
||||
prev_close = bar_history[-2].close
|
||||
last_close = bar_history[-1].close
|
||||
|
||||
# --- a) 定义基础的突破【事件】 ---
|
||||
upper_break_event = last_close > trend_upper and prev_close < trend_upper
|
||||
lower_break_event = last_close < trend_lower and prev_close > trend_lower
|
||||
|
||||
# --- b) 定义霍克斯【确认】---
|
||||
hawkes_confirmation = latest_hawkes_value > latest_hawkes_upper
|
||||
|
||||
# 只有当基础事件和霍克斯确认都发生时,才考虑开仓
|
||||
if hawkes_confirmation and (upper_break_event or lower_break_event):
|
||||
|
||||
# --- c) 【核心修改】根据 reverse_logic 决定最终交易方向 ---
|
||||
trade_direction = None
|
||||
|
||||
if upper_break_event: # 价格向上突破上轨
|
||||
# 标准模式:做多 (动量)
|
||||
# 反转模式:做空 (力竭反转)
|
||||
trade_direction = "SELL" if self.reverse_logic else "BUY"
|
||||
|
||||
elif lower_break_event: # 价格向下突破下轨
|
||||
# 标准模式:做空 (动量)
|
||||
# 反转模式:做多 (恐慌探底反转)
|
||||
trade_direction = "BUY" if self.reverse_logic else "SELL"
|
||||
|
||||
# d) 执行交易
|
||||
if trade_direction and trade_direction in self.order_direction:
|
||||
event_type = "向上突破" if upper_break_event else "向下突破"
|
||||
logic_type = "反转" if self.reverse_logic else "标准"
|
||||
self.log(
|
||||
f"{logic_type}模式 {trade_direction} 信号: "
|
||||
f"价格{event_type} & 霍克斯强度({latest_hawkes_value:.2f}) > 阈值({latest_hawkes_upper:.2f})"
|
||||
)
|
||||
self.send_open_order(trade_direction, open_price, self.trade_volume)
|
||||
|
||||
# send_open_order, send_market_order, on_rollover 等方法保持不变
|
||||
def send_open_order(self, direction: str, entry_price: float, volume: int):
|
||||
current_time = self.get_current_time()
|
||||
order_id = f"{self.symbol}_{direction}_{current_time.strftime('%Y%m%d%H%M%S')}"
|
||||
order_direction = "BUY" if direction == "BUY" else "SELL"
|
||||
order = Order(id=order_id, symbol=self.symbol, direction=order_direction, volume=volume, price_type="MARKET",
|
||||
submitted_time=current_time, offset="OPEN")
|
||||
self.send_order(order)
|
||||
self.pos_meta[self.symbol] = {"direction": direction, "volume": volume, "entry_price": entry_price}
|
||||
self.log(f"发送开仓订单: {direction} {volume}手 @ Market Price (执行价约 {entry_price:.2f})")
|
||||
|
||||
def send_market_order(self, direction: str, volume: int):
|
||||
current_time = self.get_current_time()
|
||||
order_id = f"{self.symbol}_{direction}_{current_time.strftime('%Y%m%d%H%M%S')}"
|
||||
order = Order(id=order_id, symbol=self.symbol, direction=direction, volume=volume, price_type="MARKET",
|
||||
submitted_time=current_time, offset="CLOSE")
|
||||
self.send_order(order)
|
||||
self.log(f"发送平仓订单: {direction} {volume}手 @ Market Price")
|
||||
|
||||
def on_rollover(self, old_symbol: str, new_symbol: str):
|
||||
super().on_rollover(old_symbol, new_symbol)
|
||||
self.cancel_all_pending_orders(new_symbol)
|
||||
self.pos_meta.clear()
|
||||
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