1、trend + hawks 策略
This commit is contained in:
782
data/ analysis/MA.ipynb
Normal file
782
data/ analysis/MA.ipynb
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
ma_channel_event_probability.png
Normal file
BIN
ma_channel_event_probability.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 189 KiB |
@@ -1,55 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
from src.analysis.result_analyzer import ResultAnalyzer
|
|
||||||
|
|
||||||
# 导入 TqsdkEngine,而不是原来的 BacktestEngine
|
|
||||||
from src.indicators.indicators import RSI, HistoricalRange, BollingerBandwidth
|
|
||||||
from src.tqsdk_real_engine import TqsdkEngine
|
|
||||||
|
|
||||||
# 导入你的策略类
|
|
||||||
from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy
|
|
||||||
|
|
||||||
from tqsdk import TqApi, TqBacktest, TqAuth, TqKq, TqAccount
|
|
||||||
|
|
||||||
# --- 配置参数 ---
|
|
||||||
# Tqsdk 的本地数据文件路径,注意 Tqsdk 要求文件名为 KQ_m@交易所_品种名_周期.csv
|
|
||||||
|
|
||||||
# 主力合约的 symbol
|
|
||||||
symbol = 'KQ.m@CZCE.MA'
|
|
||||||
strategy_parameters = {
|
|
||||||
'main_symbol': 'MA', # 根据您的数据文件中的品种名称调整
|
|
||||||
'trade_volume': 2,
|
|
||||||
'lag': 7,
|
|
||||||
# 'range_factor': 1.8, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 2.8, # 示例值
|
|
||||||
# 'range_factor': 1.6, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 2.1, # 示例值
|
|
||||||
'range_factor_l': 1.8, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_l': 2.8, # 示例值
|
|
||||||
'range_factor_s': 1.6, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_s': 2.1, # 示例值
|
|
||||||
'max_position': 10,
|
|
||||||
'enable_log': True,
|
|
||||||
'stop_loss_points': 20,
|
|
||||||
'use_indicator': True,
|
|
||||||
# 'indicator': HistoricalRange(11, 25, 20),
|
|
||||||
# 'indicator': BollingerBandwidth(window=20, nbdev=2.0, down_bound=1.9, up_bound=3.25),
|
|
||||||
'indicator_l': HistoricalRange(11, 25, 20),
|
|
||||||
'indicator_s': BollingerBandwidth(window=20, nbdev=2.0, down_bound=1.9, up_bound=3.25),
|
|
||||||
}
|
|
||||||
|
|
||||||
# api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
api = TqApi(TqAccount('H宏源期货', '903308830', 'lzr520102'), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
|
|
||||||
# --- 1. 初始化回测引擎并运行 ---
|
|
||||||
print("\n初始化 Tqsdk 回测引擎...")
|
|
||||||
engine = TqsdkEngine(
|
|
||||||
strategy_class=SimpleLimitBuyStrategy,
|
|
||||||
strategy_params=strategy_parameters,
|
|
||||||
api=api,
|
|
||||||
symbol=symbol,
|
|
||||||
duration_seconds=60 * 60,
|
|
||||||
roll_over_mode=True, # 启用换月模式检测
|
|
||||||
history_length=50,
|
|
||||||
close_bar_delta=timedelta(minutes=58)
|
|
||||||
)
|
|
||||||
engine.run() # 这是一个同步方法,内部会运行 asyncio 循环
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,180 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
# is_met = self.indicators[0].is_condition_met(*self.get_indicator_tuple())
|
||||||
|
# if "BUY" in self.order_direction and last_close > trendline_val_upper and is_met:
|
||||||
|
# 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 is_met:
|
||||||
|
# self.log(f"做空信号: Close({last_close:.2f}) 下穿上趋势线({trendline_val_lower:.2f})")
|
||||||
|
# self.send_open_order("SELL", open_price, self.trade_volume, current_atr)
|
||||||
|
|
||||||
|
# prob = self.indicators[0].get_latest_value(*self.get_indicator_tuple())
|
||||||
|
# if "BUY" in self.order_direction and last_close > trendline_val_upper:
|
||||||
|
# if prob is None or prob[0] < prob[1]:
|
||||||
|
# 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:
|
||||||
|
# if prob is None or prob[2] < prob[3]:
|
||||||
|
# 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
@@ -1,58 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
from src.analysis.result_analyzer import ResultAnalyzer
|
|
||||||
|
|
||||||
# 导入 TqsdkEngine,而不是原来的 BacktestEngine
|
|
||||||
from src.indicators.indicators import RSI, HistoricalRange, BollingerBandwidth
|
|
||||||
from src.tqsdk_real_engine import TqsdkEngine
|
|
||||||
|
|
||||||
# 导入你的策略类
|
|
||||||
from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy
|
|
||||||
|
|
||||||
from tqsdk import TqApi, TqBacktest, TqAuth, TqKq, TqAccount
|
|
||||||
|
|
||||||
# --- 配置参数 ---
|
|
||||||
# Tqsdk 的本地数据文件路径,注意 Tqsdk 要求文件名为 KQ_m@交易所_品种名_周期.csv
|
|
||||||
|
|
||||||
# 主力合约的 symbol
|
|
||||||
symbol = "KQ.m@CZCE.SA"
|
|
||||||
strategy_parameters = {
|
|
||||||
'main_symbol': "SA", # 根据您的数据文件中的品种名称调整
|
|
||||||
'trade_volume': 1,
|
|
||||||
# 'lag': 7,
|
|
||||||
# 'lag': 1,
|
|
||||||
# 'range_factor': 1.6, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 2.4, # 示例值
|
|
||||||
# 'range_factor': 2.5, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 4.5, # 示例值
|
|
||||||
'range_factor_l': 1.6, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_l': 2.4, # 示例值
|
|
||||||
'range_factor_s': 2.5, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_s': 4.5, # 示例值
|
|
||||||
'max_position': 10,
|
|
||||||
'enable_log': True,
|
|
||||||
'stop_loss_points': 10,
|
|
||||||
'use_indicator': True,
|
|
||||||
# 'indicator': RSI(window=25, down_bound=47, up_bound=67),
|
|
||||||
# 'indicator': BollingerBandwidth(window=10, nbdev=1.5, down_bound=2.5, up_bound=6),
|
|
||||||
'indicator_l': RSI(window=25, down_bound=47, up_bound=67),
|
|
||||||
'indicator_s': BollingerBandwidth(window=10, nbdev=1.5, down_bound=2.5, up_bound=6),
|
|
||||||
'lag_l': 7,
|
|
||||||
'lag_s': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
# api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
api = TqApi(TqAccount('H宏源期货', '903308830', 'lzr520102'), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
|
|
||||||
# --- 1. 初始化回测引擎并运行 ---
|
|
||||||
print("\n初始化 Tqsdk 回测引擎...")
|
|
||||||
engine = TqsdkEngine(
|
|
||||||
strategy_class=SimpleLimitBuyStrategy,
|
|
||||||
strategy_params=strategy_parameters,
|
|
||||||
api=api,
|
|
||||||
symbol=symbol,
|
|
||||||
duration_seconds=60 * 60,
|
|
||||||
roll_over_mode=True, # 启用换月模式检测
|
|
||||||
history_length=300,
|
|
||||||
close_bar_delta=timedelta(minutes=58)
|
|
||||||
)
|
|
||||||
engine.run() # 这是一个同步方法,内部会运行 asyncio 循环
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
from src.analysis.result_analyzer import ResultAnalyzer
|
|
||||||
|
|
||||||
# 导入 TqsdkEngine,而不是原来的 BacktestEngine
|
|
||||||
from src.indicators.indicators import RSI, HistoricalRange, BollingerBandwidth, NormalizedATR
|
|
||||||
from src.tqsdk_real_engine import TqsdkEngine
|
|
||||||
|
|
||||||
# 导入你的策略类
|
|
||||||
from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy
|
|
||||||
|
|
||||||
from tqsdk import TqApi, TqBacktest, TqAuth, TqKq, TqAccount
|
|
||||||
|
|
||||||
# --- 配置参数 ---
|
|
||||||
# Tqsdk 的本地数据文件路径,注意 Tqsdk 要求文件名为 KQ_m@交易所_品种名_周期.csv
|
|
||||||
|
|
||||||
# 主力合约的 symbol
|
|
||||||
symbol = "KQ.m@SHFE.fu"
|
|
||||||
strategy_parameters = {
|
|
||||||
'main_symbol': "fu", # 根据您的数据文件中的品种名称调整
|
|
||||||
'trade_volume': 3,
|
|
||||||
'lag': 7,
|
|
||||||
# 'lag': 7,
|
|
||||||
# 'range_factor': 0.7, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 0.3, # 示例值
|
|
||||||
# 'range_factor': 1.9, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 0.2, # 示例值
|
|
||||||
'range_factor_l': 0.7, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_l': 0.3, # 示例值
|
|
||||||
'range_factor_s': 1.9, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_s': 0.2, # 示例值
|
|
||||||
'max_position': 10,
|
|
||||||
'enable_log': True,
|
|
||||||
'stop_loss_points': 20,
|
|
||||||
'use_indicator': True,
|
|
||||||
# 'indicator': HistoricalRange(shift_window=0, down_bound=5, up_bound=20),
|
|
||||||
# 'indicator': RSI(window=5, down_bound=40, up_bound=70),
|
|
||||||
# 'indicator': HistoricalRange(shift_window=0, down_bound=20, up_bound=100),
|
|
||||||
'indicator_l': HistoricalRange(shift_window=0, down_bound=5, up_bound=20),
|
|
||||||
'indicator_s': HistoricalRange(shift_window=0, down_bound=20, up_bound=100),
|
|
||||||
# 'lag_l': 1,
|
|
||||||
# 'lag_s': 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
# api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
api = TqApi(TqAccount('H宏源期货', '903308830', 'lzr520102'), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
|
|
||||||
# --- 1. 初始化回测引擎并运行 ---
|
|
||||||
print("\n初始化 Tqsdk 回测引擎...")
|
|
||||||
engine = TqsdkEngine(
|
|
||||||
strategy_class=SimpleLimitBuyStrategy,
|
|
||||||
strategy_params=strategy_parameters,
|
|
||||||
api=api,
|
|
||||||
symbol=symbol,
|
|
||||||
duration_seconds=60 * 60,
|
|
||||||
roll_over_mode=True, # 启用换月模式检测
|
|
||||||
history_length=50,
|
|
||||||
close_bar_delta=timedelta(minutes=58)
|
|
||||||
)
|
|
||||||
engine.run() # 这是一个同步方法,内部会运行 asyncio 循环
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
from src.analysis.result_analyzer import ResultAnalyzer
|
|
||||||
|
|
||||||
# 导入 TqsdkEngine,而不是原来的 BacktestEngine
|
|
||||||
from src.indicators.indicators import RSI, HistoricalRange
|
|
||||||
from src.tqsdk_real_engine import TqsdkEngine
|
|
||||||
|
|
||||||
# 导入你的策略类
|
|
||||||
from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy
|
|
||||||
|
|
||||||
from tqsdk import TqApi, TqBacktest, TqAuth, TqKq, TqAccount
|
|
||||||
|
|
||||||
# --- 配置参数 ---
|
|
||||||
# Tqsdk 的本地数据文件路径,注意 Tqsdk 要求文件名为 KQ_m@交易所_品种名_周期.csv
|
|
||||||
|
|
||||||
# 主力合约的 symbol
|
|
||||||
main_symbol = "KQ.m@DCE.jm"
|
|
||||||
strategy_parameters = {
|
|
||||||
'symbol': 'jm', # 根据您的数据文件中的品种名称调整
|
|
||||||
'trade_volume': 1,
|
|
||||||
'lag': 1,
|
|
||||||
# 'range_factor': 1.3, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 4.8, # 示例值
|
|
||||||
# 'range_factor': 1.1, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 4.9, # 示例值
|
|
||||||
'range_factor_l': 1.3, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_l': 4.8, # 示例值
|
|
||||||
'range_factor_s': 1.1, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_s': 4.9, # 示例值
|
|
||||||
'max_position': 10,
|
|
||||||
'enable_log': True,
|
|
||||||
'stop_loss_points': 20,
|
|
||||||
'use_indicator': True,
|
|
||||||
# 'indicator': RSI(5, 63, 95),
|
|
||||||
# 'indicator': RSI(5, 5, 25),
|
|
||||||
'indicator_l': RSI(5, 63, 95),
|
|
||||||
'indicator_s': RSI(5, 0, 100),
|
|
||||||
}
|
|
||||||
|
|
||||||
# api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
api = TqApi(TqAccount('H宏源期货', '903308830', 'lzr520102'), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
|
|
||||||
# --- 1. 初始化回测引擎并运行 ---
|
|
||||||
print("\n初始化 Tqsdk 回测引擎...")
|
|
||||||
engine = TqsdkEngine(
|
|
||||||
strategy_class=SimpleLimitBuyStrategy,
|
|
||||||
strategy_params=strategy_parameters,
|
|
||||||
api=api,
|
|
||||||
symbol=main_symbol,
|
|
||||||
duration_seconds=60 * 60,
|
|
||||||
roll_over_mode=True, # 启用换月模式检测
|
|
||||||
history_length=50,
|
|
||||||
close_bar_delta=timedelta(minutes=58)
|
|
||||||
)
|
|
||||||
engine.run() # 这是一个同步方法,内部会运行 asyncio 循环
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
from src.analysis.result_analyzer import ResultAnalyzer
|
|
||||||
|
|
||||||
# 导入 TqsdkEngine,而不是原来的 BacktestEngine
|
|
||||||
from src.indicators.indicators import RSI, HistoricalRange, BollingerBandwidth, NormalizedATR
|
|
||||||
from src.tqsdk_real_engine import TqsdkEngine
|
|
||||||
|
|
||||||
# 导入你的策略类
|
|
||||||
from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy
|
|
||||||
|
|
||||||
from tqsdk import TqApi, TqBacktest, TqAuth, TqKq, TqAccount
|
|
||||||
|
|
||||||
# --- 配置参数 ---
|
|
||||||
# Tqsdk 的本地数据文件路径,注意 Tqsdk 要求文件名为 KQ_m@交易所_品种名_周期.csv
|
|
||||||
|
|
||||||
# 主力合约的 symbol
|
|
||||||
symbol = "KQ.m@SHFE.rb"
|
|
||||||
strategy_parameters = {
|
|
||||||
'main_symbol': 'rb', # 根据您的数据文件中的品种名称调整
|
|
||||||
'trade_volume': 2,
|
|
||||||
# 'lag': 1,
|
|
||||||
# 'lag': 7,
|
|
||||||
# 'range_factor': 2.1, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 1.6, # 示例值
|
|
||||||
# 'range_factor': 0.2, # 示例值,需要通过网格搜索优化
|
|
||||||
# 'profit_factor': 3.8, # 示例值
|
|
||||||
'range_factor_l': 2.1, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_l': 1.6, # 示例值
|
|
||||||
'range_factor_s': 0.2, # 示例值,需要通过网格搜索优化
|
|
||||||
'profit_factor_s': 3.8, # 示例值
|
|
||||||
'max_position': 10,
|
|
||||||
'enable_log': True,
|
|
||||||
'stop_loss_points': 20,
|
|
||||||
'use_indicator': True,
|
|
||||||
# 'indicator': RSI(14, 44, 70),
|
|
||||||
# 'indicator': NormalizedATR(window=5, down_bound=0.62, up_bound=0.76),
|
|
||||||
'indicator_l': RSI(14, 44, 70),
|
|
||||||
'indicator_s': NormalizedATR(window=5, down_bound=0.62, up_bound=0.76),
|
|
||||||
'lag_l': 1,
|
|
||||||
'lag_s': 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
# api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
api = TqApi(TqAccount('H宏源期货', '903308830', 'lzr520102'), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
|
||||||
|
|
||||||
# --- 1. 初始化回测引擎并运行 ---
|
|
||||||
print("\n初始化 Tqsdk 回测引擎...")
|
|
||||||
engine = TqsdkEngine(
|
|
||||||
strategy_class=SimpleLimitBuyStrategy,
|
|
||||||
strategy_params=strategy_parameters,
|
|
||||||
api=api,
|
|
||||||
symbol=symbol,
|
|
||||||
duration_seconds=60 * 60,
|
|
||||||
roll_over_mode=True, # 启用换月模式检测
|
|
||||||
history_length=50,
|
|
||||||
close_bar_delta=timedelta(minutes=58)
|
|
||||||
)
|
|
||||||
engine.run() # 这是一个同步方法,内部会运行 asyncio 循环
|
|
||||||
44
src/algo/HawksProcess.py
Normal file
44
src/algo/HawksProcess.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 您可以创建一个新的辅助文件,例如 src/algo/hawkes_process.py
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
# from numba import njit
|
||||||
|
|
||||||
|
|
||||||
|
# @njit
|
||||||
|
def hawkes_intensity(data: np.ndarray, kappa: float) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
【Numba加速版】计算霍克斯过程强度。
|
||||||
|
"""
|
||||||
|
alpha = np.exp(-kappa)
|
||||||
|
output = np.zeros(len(data))
|
||||||
|
if len(data) == 0:
|
||||||
|
return output
|
||||||
|
|
||||||
|
# 初始化第一个点
|
||||||
|
output[0] = data[0] if not np.isnan(data[0]) else 0.0
|
||||||
|
|
||||||
|
for i in range(1, len(data)):
|
||||||
|
if np.isnan(data[i]):
|
||||||
|
output[i] = output[i - 1] * alpha # 如果数据无效,强度依然会衰减
|
||||||
|
else:
|
||||||
|
output[i] = output[i - 1] * alpha + data[i]
|
||||||
|
|
||||||
|
return output * kappa
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_hawkes_bands(volume_series: pd.Series, lookback: int, kappa: float, high_percent: float,
|
||||||
|
low_percent: float):
|
||||||
|
"""
|
||||||
|
计算霍克斯强度及其动态上下轨。
|
||||||
|
"""
|
||||||
|
# 1. 计算霍克斯强度
|
||||||
|
vol_hawkes = hawkes_intensity(volume_series.values, kappa)
|
||||||
|
vol_hawkes_series = pd.Series(vol_hawkes, index=volume_series.index)
|
||||||
|
|
||||||
|
# 2. 计算滚动分位数作为动态上下轨
|
||||||
|
rolling_hawkes = vol_hawkes_series.rolling(lookback)
|
||||||
|
upper_band = rolling_hawkes.quantile(high_percent)
|
||||||
|
lower_band = rolling_hawkes.quantile(low_percent)
|
||||||
|
|
||||||
|
return vol_hawkes_series, upper_band, lower_band
|
||||||
79
src/algo/TrendLine.py
Normal file
79
src/algo/TrendLine.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import numpy as np
|
||||||
|
from typing import Tuple, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_latest_trendline_values(prices: np.ndarray) -> Tuple[Optional[float], Optional[float]]:
|
||||||
|
"""
|
||||||
|
【独立优化方法】
|
||||||
|
根据给定的价格序列,仅计算并返回上、下趋势线在最后一个点的值。
|
||||||
|
|
||||||
|
:param prices: 价格序列 (长度为 n)
|
||||||
|
:return: (latest_upper_value, latest_lower_value)
|
||||||
|
"""
|
||||||
|
n = len(prices)
|
||||||
|
if n < 2:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
x = np.arange(n)
|
||||||
|
|
||||||
|
# --- 计算上趋势线 ---
|
||||||
|
high_point_idx = np.argmax(prices)
|
||||||
|
high_point_price = prices[high_point_idx]
|
||||||
|
best_upper_slope = None
|
||||||
|
min_upper_distance_sum = float('inf')
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
if i == high_point_idx:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 避免除以零
|
||||||
|
if (i - high_point_idx) == 0: continue
|
||||||
|
|
||||||
|
candidate_slope = (prices[i] - high_point_price) / (i - high_point_idx)
|
||||||
|
intercept = high_point_price - candidate_slope * high_point_idx
|
||||||
|
# 循环内部仍然需要生成整条候选线用于检查约束条件
|
||||||
|
candidate_line = candidate_slope * x + intercept
|
||||||
|
|
||||||
|
if np.all(candidate_line >= prices - 1e-9):
|
||||||
|
distance_sum = np.sum(candidate_line - prices)
|
||||||
|
if distance_sum < min_upper_distance_sum:
|
||||||
|
min_upper_distance_sum = distance_sum
|
||||||
|
best_upper_slope = candidate_slope
|
||||||
|
|
||||||
|
if best_upper_slope is None:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# 优化点:找到最优斜率后,只计算最后一个点的值
|
||||||
|
upper_intercept = high_point_price - best_upper_slope * high_point_idx
|
||||||
|
latest_upper_value = best_upper_slope * (n - 1) + upper_intercept
|
||||||
|
|
||||||
|
# --- 计算下趋势线 ---
|
||||||
|
low_point_idx = np.argmin(prices)
|
||||||
|
low_point_price = prices[low_point_idx]
|
||||||
|
best_lower_slope = None
|
||||||
|
min_lower_distance_sum = float('inf')
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
if i == low_point_idx:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (i - low_point_idx) == 0: continue
|
||||||
|
|
||||||
|
candidate_slope = (prices[i] - low_point_price) / (i - low_point_idx)
|
||||||
|
intercept = low_point_price - candidate_slope * low_point_idx
|
||||||
|
candidate_line = candidate_slope * x + intercept
|
||||||
|
|
||||||
|
if np.all(candidate_line <= prices + 1e-9):
|
||||||
|
distance_sum = np.sum(prices - candidate_line)
|
||||||
|
if distance_sum < min_lower_distance_sum:
|
||||||
|
min_lower_distance_sum = distance_sum
|
||||||
|
best_lower_slope = candidate_slope
|
||||||
|
|
||||||
|
if best_lower_slope is None:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
# 优化点:只计算最后一个点的值
|
||||||
|
lower_intercept = low_point_price - best_lower_slope * low_point_idx
|
||||||
|
latest_lower_value = best_lower_slope * (n - 1) + lower_intercept
|
||||||
|
|
||||||
|
return latest_upper_value, latest_lower_value
|
||||||
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)
|
self._send_market_order('SELL', self.volume, latest_bar)
|
||||||
|
|
||||||
def _exit_logic(self, pos: int, latest_bar: Bar):
|
def _exit_logic(self, pos: int, latest_bar: Bar):
|
||||||
|
# 1. ATR追踪止损 (优先,保持不变)
|
||||||
if pos > 0:
|
if pos > 0:
|
||||||
new_stop_price = latest_bar.close - self.atr * self.atr_stop_multiplier
|
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
|
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:
|
if latest_bar.high >= self.trailing_stop_price:
|
||||||
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
||||||
return
|
return
|
||||||
dominance = self.dp_long - self.dp_short
|
|
||||||
if pos > 0 and dominance < 0:
|
# 2. 策略性离场 (非对称逻辑)
|
||||||
self._send_market_order('CLOSE_LONG', abs(pos), latest_bar)
|
if pos > 0:
|
||||||
elif pos < 0 and dominance > 0:
|
# --- 多头出场逻辑 (保持不变): 寻找多头优势的丧失 ---
|
||||||
self._send_market_order('CLOSE_SHORT', abs(pos), latest_bar)
|
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):
|
def _send_market_order(self, direction: str, vol: int, latest_bar: Bar):
|
||||||
offset = 'OPEN' if direction in ('BUY', 'SELL') else 'CLOSE'
|
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
220
test/test.ipynb
Normal file
220
test/test.ipynb
Normal file
File diff suppressed because one or more lines are too long
BIN
trade_filter_model_v2.joblib
Normal file
BIN
trade_filter_model_v2.joblib
Normal file
Binary file not shown.
BIN
trade_filter_scaler_v2.joblib
Normal file
BIN
trade_filter_scaler_v2.joblib
Normal file
Binary file not shown.
Reference in New Issue
Block a user