1、trend + hawks 策略

This commit is contained in:
2025-09-24 23:14:14 +08:00
parent bc93a547f0
commit f43a6b2822
33 changed files with 20646 additions and 1609 deletions

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View File

@@ -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

View File

@@ -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

View File

@@ -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 循环

View File

@@ -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 循环

View File

@@ -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 循环

View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -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

File diff suppressed because one or more lines are too long

View 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

View File

@@ -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

View File

@@ -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

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.