卡尔曼策略新增md文件

This commit is contained in:
2025-11-07 16:37:16 +08:00
parent 2eec6452ee
commit 2ae9f2db9e
89 changed files with 39954 additions and 0 deletions

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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()

View File

@@ -0,0 +1,226 @@
{
"cells": [
{
"cell_type": "code",
"id": "522f09ca7b3fe929",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-21T13:27:14.609968Z",
"start_time": "2025-10-21T13:27:14.180365Z"
}
},
"source": [
"from datetime import datetime\n",
"\n",
"from src.data_processing import load_raw_data\n",
"%load_ext autoreload\n",
"%autoreload 2\n",
"\n",
"import sys\n",
"\n",
"if '/mnt/d/PyProject/NewQuant/' not in sys.path:\n",
" sys.path.append('/mnt/d/PyProject/NewQuant/')"
],
"outputs": [],
"execution_count": 1
},
{
"cell_type": "code",
"id": "4f7e4b438cea750e",
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-21T13:27:14.989537Z",
"start_time": "2025-10-21T13:27:14.615874Z"
}
},
"source": [
"from turtle import down\n",
"from src.analysis.result_analyzer import ResultAnalyzer\n",
"# 导入所有必要的模块\n",
"from src.data_manager import DataManager\n",
"from src.backtest_engine import BacktestEngine\n",
"from src.indicators.indicator_list import INDICATOR_LIST\n",
"from src.indicators.indicators import *\n",
"\n",
"# 导入您自己的 SMC 策略\n",
"from src.strategies.TrendlineBreakoutStrategy.TrendlineHawkesStrategyFast import TrendlineHawkesStrategy\n",
"\n",
"# --- 配置参数 ---\n",
"# 获取当前脚本所在目录,假设数据文件在项目根目录下的 data 文件夹内\n",
"data_file_path = '/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min15.csv'\n"
],
"outputs": [],
"execution_count": 2
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-21T13:27:15.060902Z",
"start_time": "2025-10-21T13:27:14.996119Z"
}
},
"cell_type": "code",
"source": [
"\n",
"initial_capital = 100000.0\n",
"slippage_rate = 0.000 # 假设每笔交易0.1%的滑点\n",
"commission_rate = 0.0000 # 假设每笔交易0.02%的佣金\n",
"\n",
"global_config = {\n",
" 'symbol': 'KQ_m@CZCE_MA', # 确保与数据文件中的 symbol 匹配\n",
"}\n",
"\n",
"# 回测时间范围\n",
"start_time = datetime(2021, 1, 1)\n",
"end_time = datetime(2024, 6, 1)\n",
"\n",
"start_time = datetime(2024, 1, 1)\n",
"end_time = datetime(2025, 8, 1)\n",
"\n",
"\n",
"indicators = INDICATOR_LIST\n",
"indicators = []\n",
"\n",
"# 确保 DataManager 能够重置以进行多次回测\n",
"# data_manager.reset() # 首次运行不需要重置"
],
"id": "9ee53c41eaaefabb",
"outputs": [],
"execution_count": 3
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2025-10-21T13:27:21.876001Z",
"start_time": "2025-10-21T13:27:15.070471Z"
}
},
"cell_type": "code",
"source": [
"from src.indicators.indicators import ROC_MA\n",
"\n",
"# --- 1. 初始化数据管理器 ---\n",
"print(\"初始化数据管理器...\")\n",
"data_manager = DataManager(file_path=data_file_path, symbol=global_config['symbol'], start_time=start_time,\n",
" end_time=end_time)\n",
"\n",
"strategy_parameters = {\n",
" 'main_symbol': 'MA', # <-- 替换为你的交易品种代码,例如 'GC=F' (黄金期货), 'ZC=F' (玉米期货)\n",
" 'trade_volume': 1,\n",
" 'trendline_n': 70,\n",
" 'hawkes_kappa': 0.1,\n",
" 'order_direction': ['SELL', 'BUY'],\n",
" 'reverse_logic': True,\n",
" # 'indicators': [RateOfChange(10, -2.1, -0.5), ROC_MA(10, 10, -2.7, -0.4)],\n",
" 'enable_log': False\n",
"}\n",
"\n",
"\n",
"\n",
"# --- 2. 初始化回测引擎并运行 ---\n",
"print(\"\\n初始化回测引擎...\")\n",
"engine = BacktestEngine(\n",
" data_manager=data_manager,\n",
" strategy_class=TrendlineHawkesStrategy, # <--- 更改为您的 SMC 策略类\n",
" # current_segment_symbol 参数已从 SMCPureH1LongStrategy 中移除,不需要设置\n",
" strategy_params=strategy_parameters,\n",
" initial_capital=initial_capital,\n",
" slippage_rate=slippage_rate,\n",
" commission_rate=commission_rate,\n",
" roll_over_mode=True,\n",
" start_time=start_time,\n",
" end_time=end_time,\n",
" indicators=indicators # 如果您的 SMC 策略不使用这些指标,也可以考虑移除\n",
")\n",
"\n",
"print(\"\\n开始运行回测...\")\n",
"engine.run_backtest()\n",
"print(\"\\n回测运行完毕。\")\n",
"\n",
"# --- 3. 获取回测结果 ---\n",
"results = engine.get_backtest_results()\n",
"portfolio_snapshots = results[\"portfolio_snapshots\"]\n",
"trade_history = results[\"trade_history\"]\n",
"initial_capital_result = results[\"initial_capital\"]\n",
"bars = results[\"all_bars\"]\n",
"\n",
"# --- 4. 结果分析与可视化 ---\n",
"if portfolio_snapshots:\n",
" analyzer = ResultAnalyzer(portfolio_snapshots, trade_history, bars, initial_capital_result, INDICATOR_LIST)\n",
"\n",
" analyzer.generate_report()\n",
" analyzer.plot_performance()\n",
" metrics = analyzer.calculate_all_metrics()\n",
" print(metrics)\n",
"\n",
" analyzer.analyze_indicators()\n",
"else:\n",
" print(\"\\n没有生成投资组合快照无法进行结果分析。\")"
],
"id": "f903fd2761d446cd",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"初始化数据管理器...\n",
"数据加载成功: /mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min15.csv\n",
"数据范围从 2020-12-31 14:45:00 到 2025-08-21 14:30:00\n",
"总计 25596 条记录。\n",
"\n",
"初始化回测引擎...\n",
"模拟器初始化:初始资金=100000.00, 滑点率=0.0, 佣金率=0.0\n",
"内存仓储已初始化管理ID: 'src.strategies.TrendlineBreakoutStrategy.TrendlineHawkesStrategyFast.TrendlineHawkesStrategy_13b1be9c188912b2ee8ccd9e5fb0718d'\n",
"\n",
"--- 回测引擎初始化完成 ---\n",
" 策略: TrendlineHawkesStrategy\n",
" 初始资金: 100000.00\n",
" 换月模式: 启用\n",
"\n",
"开始运行回测...\n",
"\n",
"--- 回测开始 ---\n",
"TrendlineHawkesStrategy 策略初始化回调被调用。\n",
"开始将 DataFrame 转换为 Bar 对象流...\n",
"首次运行,正在初始化霍克斯状态和滚动窗口...\n",
"状态初始化完成。\n"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001B[31m---------------------------------------------------------------------------\u001B[39m",
"\u001B[31mKeyboardInterrupt\u001B[39m Traceback (most recent call last)",
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[4]\u001B[39m\u001B[32m, line 38\u001B[39m\n\u001B[32m 23\u001B[39m engine = BacktestEngine(\n\u001B[32m 24\u001B[39m data_manager=data_manager,\n\u001B[32m 25\u001B[39m strategy_class=TrendlineHawkesStrategy, \u001B[38;5;66;03m# <--- 更改为您的 SMC 策略类\u001B[39;00m\n\u001B[32m (...)\u001B[39m\u001B[32m 34\u001B[39m indicators=indicators \u001B[38;5;66;03m# 如果您的 SMC 策略不使用这些指标,也可以考虑移除\u001B[39;00m\n\u001B[32m 35\u001B[39m )\n\u001B[32m 37\u001B[39m \u001B[38;5;28mprint\u001B[39m(\u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m开始运行回测...\u001B[39m\u001B[33m\"\u001B[39m)\n\u001B[32m---> \u001B[39m\u001B[32m38\u001B[39m \u001B[43mengine\u001B[49m\u001B[43m.\u001B[49m\u001B[43mrun_backtest\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[32m 39\u001B[39m \u001B[38;5;28mprint\u001B[39m(\u001B[33m\"\u001B[39m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[33m回测运行完毕。\u001B[39m\u001B[33m\"\u001B[39m)\n\u001B[32m 41\u001B[39m \u001B[38;5;66;03m# --- 3. 获取回测结果 ---\u001B[39;00m\n",
"\u001B[36mFile \u001B[39m\u001B[32m/mnt/d/PyProject/NewQuant/src/backtest_engine.py:166\u001B[39m, in \u001B[36mBacktestEngine.run_backtest\u001B[39m\u001B[34m(self)\u001B[39m\n\u001B[32m 163\u001B[39m \u001B[38;5;28mself\u001B[39m.strategy.on_open_bar(current_bar.open, current_bar.symbol)\n\u001B[32m 165\u001B[39m current_indicator_dict = {}\n\u001B[32m--> \u001B[39m\u001B[32m166\u001B[39m close_array = \u001B[43mnp\u001B[49m\u001B[43m.\u001B[49m\u001B[43marray\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m.\u001B[49m\u001B[43mclose_list\u001B[49m\u001B[43m)\u001B[49m\n\u001B[32m 167\u001B[39m open_array = np.array(\u001B[38;5;28mself\u001B[39m.open_list)\n\u001B[32m 168\u001B[39m high_array = np.array(\u001B[38;5;28mself\u001B[39m.high_list)\n",
"\u001B[31mKeyboardInterrupt\u001B[39m: "
]
}
],
"execution_count": 4
}
],
"metadata": {
"kernelspec": {
"display_name": "quant",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

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()

View File

@@ -0,0 +1,188 @@
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
class TrendlineHawkesStrategy(Strategy):
"""
趋势线与霍克斯过程双重确认策略 (V4 - 终极性能版):
- 霍克斯过程和滚动分位数都实现为高效的有状态增量计算。
- 使用固定长度的Numpy数组作为滚动窗口避免Pandas.rolling的开销和不一致性。
"""
def __init__(
self,
context: Any,
main_symbol: str,
# ... 参数与V3完全相同 ...
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)
# ... 参数赋值与V3完全相同 ...
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]] = {}
# --- 【核心修改】状态缓存重构 ---
# 只缓存上一个时间点的霍克斯强度值 (未缩放)
self._last_hawkes_unscaled: float = 0.0
# 只维护一个固定长度的滚动窗口,用于计算分位数
self._hawkes_window: np.ndarray = np.array([], dtype=np.float64)
# 衰减因子
self._hawkes_alpha = np.exp(-self.hawkes_kappa)
# ... 日志与V3相同 ...
def _initialize_state(self, initial_volumes: np.ndarray):
"""
仅在策略开始时调用一次,用于填充初始的滚动窗口。
"""
print("首次运行,正在初始化霍克斯状态和滚动窗口...")
alpha = self._hawkes_alpha
kappa = self.hawkes_kappa
# 完整计算一次历史强度,只为填充窗口
temp_hawkes_history = np.zeros_like(initial_volumes, dtype=np.float64)
if len(initial_volumes) > 0:
temp_hawkes_history[0] = initial_volumes[0] if not np.isnan(initial_volumes[0]) else 0.0
for i in range(1, len(initial_volumes)):
temp_hawkes_history[i] = temp_hawkes_history[i - 1] * alpha + (
initial_volumes[i] if not np.isnan(initial_volumes[i]) else 0.0)
# 记录最后一个点的强度值,作为下一次增量计算的起点
self._last_hawkes_unscaled = temp_hawkes_history[-1] if len(temp_hawkes_history) > 0 else 0.0
# 用历史强度值的最后 hawkes_lookback 个点来填充滚动窗口
self._hawkes_window = (temp_hawkes_history * kappa)[-self.hawkes_lookback:]
print("状态初始化完成。")
def _update_state_incrementally(self, latest_volume: float):
"""
【增量计算】在每个新的Bar上调用更新强度值和滚动窗口。
"""
# 1. 计算最新的霍克斯强度值 (未缩放)
new_hawkes_unscaled = self._last_hawkes_unscaled * self._hawkes_alpha + (
latest_volume if not np.isnan(latest_volume) else 0.0)
# 2. 更新上一个点的状态,为下一次计算做准备
self._last_hawkes_unscaled = new_hawkes_unscaled
# 3. 将新的缩放后的强度值推入滚动窗口
new_hawkes_scaled = new_hawkes_unscaled * self.hawkes_kappa
# np.roll 会高效地将数组元素移动,然后我们将新值放在第一个位置
# 这比 append + delete 的效率高得多
self._hawkes_window = np.roll(self._hawkes_window, -1)
self._hawkes_window[-1] = new_hawkes_scaled
def on_init(self):
super().on_init()
self.pos_meta.clear()
# 重置状态
self._last_hawkes_unscaled = 0.0
self._hawkes_window = np.array([], dtype=np.float64)
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
# --- 【核心修改】霍克斯过程的状态更新 ---
# 检查是否是第一次运行
if self._hawkes_window.size == 0:
initial_volumes = np.array([b.volume for b in bar_history], dtype=float)
self._initialize_state(initial_volumes[:-1]) # 用到上一根bar为止的数据初始化
# 增量更新当前bar的状态
self._update_state_incrementally(float(bar_history[-1].volume))
# --- 后续逻辑使用更新后的状态进行计算 ---
self.cancel_all_pending_orders(symbol)
pos = self.get_current_positions().get(symbol, 0)
# 【核心修改】直接在固定长度的窗口上计算分位数
# 这比pandas.rolling快几个数量级且结果稳定
latest_hawkes_value = self._hawkes_window[-1]
latest_hawkes_upper = np.quantile(self._hawkes_window, self.hawkes_entry_percent)
latest_hawkes_lower = np.quantile(self._hawkes_window, self.hawkes_exit_percent)
# 1. 平仓逻辑 (完全不变)
meta = self.pos_meta.get(symbol)
if meta and pos != 0:
if latest_hawkes_value < latest_hawkes_lower:
self.log(f"霍克斯出场信号...") # 日志简化
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:
close_prices = np.array([b.close for b in bar_history])
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 not None and trend_lower is not None:
prev_close = bar_history[-2].close
last_close = bar_history[-1].close
upper_break_event = last_close > trend_upper and prev_close < trend_upper
lower_break_event = last_close < trend_lower and prev_close > trend_lower
hawkes_confirmation = latest_hawkes_value > latest_hawkes_upper
if hawkes_confirmation and (upper_break_event or lower_break_event):
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"
if trade_direction and trade_direction in self.order_direction:
self.log(f"开仓信号确认...") # 日志简化
self.send_open_order(trade_direction, open_price, self.trade_volume)
# send_open_order, send_market_order, on_rollover 等方法保持不变
# ... (代码省略,与之前版本相同) ...
# 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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,278 @@
# =====================================================================================
# 以下是新增的 ValueMigrationStrategy 策略代码
# =====================================================================================
from collections import deque
from datetime import timedelta, time
import numpy as np
import pandas as pd
from typing import List, Any, Optional, Dict
import talib
from src.core_data import Bar, Order
from src.strategies.ValueMigrationStrategy.data_class import ProfileStats, calculate_profile_from_bars
from src.strategies.base_strategy import Strategy
# = ===================================================================
# 全局辅助函数 (Global Helper Functions)
# 将这些函数放在文件顶部,以便所有策略类都能调用
# =====================================================================
def compute_price_volume_distribution(bars: List[Bar], tick_size: float) -> Optional[pd.Series]:
"""
[全局函数] 从K线数据中计算出原始的价格-成交量分布。
"""
if not bars:
return None
data = []
# 为了性能我们只处理有限数量的bars防止内存问题
# 在实际应用中,更高效的实现是必要的
for bar in bars[-500:]: # 添加一个安全限制
price_range = np.arange(bar.low, bar.high + tick_size, tick_size)
if len(price_range) == 0 or bar.volume == 0: continue
# 将成交量近似分布到K线覆盖的每个tick上
volume_per_tick = bar.volume / len(price_range)
for price in price_range:
data.append({'price': price, 'volume': volume_per_tick})
if not data:
return None
df = pd.DataFrame(data)
if df.empty:
return None
return df.groupby('price')['volume'].sum().sort_index()
# 确保在文件顶部导入
from scipy.signal import find_peaks
def find_hvns_with_distance(price_volume_dist: pd.Series, distance_in_ticks: int) -> List[float]:
"""
[全局函数] 使用峰值查找算法根据峰值间的最小距离来识别HVNs。
Args:
price_volume_dist: 价格-成交量分布序列。
distance_in_ticks: 两个HVN之间必须间隔的最小tick数量。
Returns:
一个包含所有被识别出的HVN价格的列表。
"""
if price_volume_dist.empty or len(price_volume_dist) < 3:
return []
# distance参数确保找到的峰值之间至少相隔N个点
peaks_indices, _ = find_peaks(price_volume_dist.values, distance=distance_in_ticks)
if len(peaks_indices) == 0:
return [price_volume_dist.idxmax()] # 默认返回POC
hvn_prices = price_volume_dist.index[peaks_indices].tolist()
return hvn_prices
class ValueMigrationStrategy(Strategy):
# 确保在文件顶部导入
from scipy.signal import find_peaks
# =====================================================================================
# 以下是全新的、基于HVN回测逻辑的 HVNPullbackStrategy 策略代码
# =====================================================================================
"""
一个基于动态HVN突破后回测的量化交易策略。(适配无回调函数的框架)
该策略首先动态识别出市场中重要的成交量密集区(HVNs)。当价格
明确穿越一个HVN后它并不立即追逐而是预期价格会有一个短暂的
回测行为并在HVN附近的一个偏移位置挂限价单以更高概率顺势入场。
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
tick_size: float = 1,
profile_period: int = 100,
recalc_interval: int = 4,
hvn_distance_ticks: int = 1,
entry_offset_atr: float = 0.2,
stop_loss_atr: float = 1.0,
take_profit_atr: float = 1.0,
atr_period: int = 14,
order_direction=None,
indicators=[None, None],
):
super().__init__(context, main_symbol, enable_log)
if order_direction is None:
order_direction = ['BUY', 'SELL']
self.trade_volume = trade_volume
self.tick_size = tick_size
self.profile_period = profile_period
self.recalc_interval = recalc_interval
self.hvn_distance_ticks = hvn_distance_ticks
self.entry_offset_atr = entry_offset_atr
self.stop_loss_atr = stop_loss_atr
self.take_profit_atr = take_profit_atr
self.atr_period = atr_period
self.order_direction = order_direction
self.indicator_long = indicators[0]
self.indicator_short = indicators[1]
self.main_symbol = main_symbol
self.order_id_counter = 0
self._bar_counter = 0
self._cached_hvns: List[float] = []
self._last_order_id: Optional[str] = None
# 元数据存储:
self.position_meta: Dict[str, Any] = {} # 存储已成交持仓的止盈止损
self._pending_order_meta: Dict[str, Any] = {} # 存储未成交挂单的预设参数
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
self._bar_counter += 1
bar_history = self.get_bar_history()
required_len = max(self.profile_period, self.atr_period) + 1
if len(bar_history) < required_len:
return
# # --- 1. 取消上一根K线未成交的限价单 ---
# if self._last_order_id and self._last_order_id in self.get_pending_orders():
# self.cancel_order(self._last_order_id)
# self.log(f"已取消上一根K线的挂单: {self._last_order_id}")
# # 如果挂单被取消,清除对应的预设元数据
# if self._last_order_id in self._pending_order_meta:
# del self._pending_order_meta[self._last_order_id]
# self._last_order_id = None
self.cancel_all_pending_orders(self.symbol)
# --- 2. 管理现有持仓 (逻辑核心调整) ---
position_volume = self.get_current_positions().get(self.symbol, 0)
if position_volume != 0:
self.manage_open_position(position_volume, open_price)
return # 有持仓则不进行新的开仓评估
# --- 3. 周期性地计算并缓存所有的HVNs ---
if self._bar_counter % self.recalc_interval == 1:
profile_bars = bar_history[-self.profile_period:]
dist = compute_price_volume_distribution(profile_bars, self.tick_size)
if dist is not None and not dist.empty:
self._cached_hvns = find_hvns_with_distance(dist, self.hvn_distance_ticks)
self.log(f"New HVNs identified at: {[f'{p:.2f}' for p in self._cached_hvns]}")
if not self._cached_hvns: return
# --- 4. 评估新机会 (穿越后挂单逻辑) ---
self.evaluate_entry_signal(bar_history)
def manage_open_position(self, volume: int, current_price: float):
"""在on_open_bar中主动管理已开仓位的止盈止损。"""
# [关键逻辑]: 检测是否为新成交的持仓
if self.symbol not in self.position_meta:
# 这是一个新持仓。我们必须从挂单的元数据中恢复止盈止损参数。
# 这里假设只有一个挂单能成交。如果有多个,需要更复杂的匹配逻辑。
if not self._pending_order_meta:
self.log("Error: New position detected but no pending order meta found.")
# 紧急情况:立即平仓或设置默认止损
return
# 从挂单元数据中获取参数,并“过户”到持仓元数据
# 由于我们每次只挂一个单,取第一个即可
order_id = next(iter(self._pending_order_meta))
meta = self._pending_order_meta.pop(order_id) # 取出并从pending中删除
self.position_meta[self.symbol] = meta
self.log(f"新持仓确认。已设置TP/SL: {meta}")
# [常规逻辑]: 检查止盈止损
meta = self.position_meta[self.symbol]
sl_price = meta['sl_price']
tp_price = meta['tp_price']
if volume > 0: # 多头
if current_price <= sl_price:
self.log(f"多头止损触发 at {current_price:.2f}")
self.close_position("CLOSE_LONG", abs(volume))
elif current_price >= tp_price:
self.log(f"多头止盈触发 at {current_price:.2f}")
self.close_position("CLOSE_LONG", abs(volume))
elif volume < 0: # 空头
if current_price >= sl_price:
self.log(f"空头止损触发 at {current_price:.2f}")
self.close_position("CLOSE_SHORT", abs(volume))
elif current_price <= tp_price:
self.log(f"空头止盈触发 at {current_price:.2f}")
self.close_position("CLOSE_SHORT", abs(volume))
def evaluate_entry_signal(self, bar_history: List[Bar]):
prev_close = bar_history[-2].close
current_close = bar_history[-1].close
highs = np.array([b.high for b in bar_history], dtype=float)
lows = np.array([b.low for b in bar_history], dtype=float)
closes = np.array([b.close for b in bar_history], dtype=float)
current_atr = talib.ATR(highs, lows, closes, self.atr_period)[-1]
if current_atr < self.tick_size: return
for hvn in sorted(self._cached_hvns):
if "BUY" in self.order_direction and (prev_close < hvn < current_close):
if self.indicator_long is None or self.indicator_long.is_condition_met(*self.get_indicator_tuple()):
limit_price = hvn + self.entry_offset_atr * current_atr
self.log(f"价格向上穿越HVN({hvn:.2f}). 在 {limit_price:.2f} 挂限价买单。")
self.send_hvn_limit_order("BUY", limit_price, current_atr)
return
if "SELL" in self.order_direction and (prev_close > hvn > current_close):
if self.indicator_short is None or self.indicator_short.is_condition_met(
*self.get_indicator_tuple()):
limit_price = hvn - self.entry_offset_atr * current_atr
self.log(f"价格向下穿越HVN({hvn:.2f}). 在 {limit_price:.2f} 挂限价卖单。")
self.send_hvn_limit_order("SELL", limit_price, current_atr)
return
def send_hvn_limit_order(self, direction: str, limit_price: float, entry_atr: float):
# 预先计算止盈止损价格
sl_price = limit_price - self.stop_loss_atr * entry_atr if direction == "BUY" else limit_price + self.stop_loss_atr * entry_atr
tp_price = limit_price + self.take_profit_atr * entry_atr if direction == "BUY" else limit_price - self.take_profit_atr * entry_atr
order_id = f"{self.symbol}_{direction}_LIMIT_{self.order_id_counter}"
self.order_id_counter += 1
# 将这些参数存储到 pending_order_meta 中
self._pending_order_meta[order_id] = {'sl_price': sl_price, 'tp_price': tp_price}
order = Order(
id=order_id, symbol=self.symbol, direction=direction, volume=self.trade_volume,
price_type="LIMIT", limit_price=limit_price, submitted_time=self.get_current_time(),
offset="OPEN"
)
sent_order = self.send_order(order)
if sent_order:
self._last_order_id = sent_order.id
def close_position(self, direction: str, volume: int):
self.send_market_order(direction, volume)
if self.symbol in self.position_meta:
del self.position_meta[self.symbol] # 平仓后清理持仓元数据
def send_market_order(self, direction: str, volume: int, offset: str = "CLOSE"):
order_id = f"{self.symbol}_{direction}_{offset}_{self.get_current_time().strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
order = Order(
id=order_id, symbol=self.symbol, direction=direction, volume=volume,
price_type="MARKET", submitted_time=self.get_current_time(), offset=offset
)
self.send_order(order)

View File

@@ -0,0 +1,368 @@
# =====================================================================================
# 以下是新增的 ValueMigrationStrategy 策略代码
# =====================================================================================
from collections import deque
from datetime import timedelta, time
import numpy as np
import pandas as pd
from typing import List, Any, Optional, Dict
import talib
from src.core_data import Bar, Order
from src.strategies.ValueMigrationStrategy.data_class import ProfileStats, calculate_profile_from_bars
from src.strategies.base_strategy import Strategy
# = ===================================================================
# 全局辅助函数 (Global Helper Functions)
# 将这些函数放在文件顶部,以便所有策略类都能调用
# =====================================================================
def compute_price_volume_distribution(bars: List[Bar], tick_size: float) -> Optional[pd.Series]:
"""
[全局函数] 从K线数据中计算出原始的价格-成交量分布。
"""
if not bars:
return None
data = []
# 为了性能我们只处理有限数量的bars防止内存问题
# 在实际应用中,更高效的实现是必要的
for bar in bars[-500:]: # 添加一个安全限制
price_range = np.arange(bar.low, bar.high + tick_size, tick_size)
if len(price_range) == 0 or bar.volume == 0: continue
# 将成交量近似分布到K线覆盖的每个tick上
volume_per_tick = bar.volume / len(price_range)
for price in price_range:
data.append({'price': price, 'volume': volume_per_tick})
if not data:
return None
df = pd.DataFrame(data)
if df.empty:
return None
return df.groupby('price')['volume'].sum().sort_index()
# =====================================================================================
# 以下是最终的、以性能为首要考量的、超高速VP计算模块
# =====================================================================================
def compute_fast_volume_profile(bars: List[Bar], tick_size: float) -> Optional[pd.Series]:
"""
[全局核心函数] 使用“离散重心累加”法超高速地从K线数据中构建成交量剖面图。
该方法将每根K线的全部成交量一次性地归于其加权中心价
(HLC/3)所对齐的tick上。这在保持核心逻辑精确性的同时
实现了计算速度的数量级提升。
Args:
bars: 用于计算的K线历史数据。
tick_size: 合约的最小变动价位。
Returns:
一个代表成交量剖面图的Pandas Series或None。
"""
if not bars:
return None
# 使用字典进行累加这是Python中最快的操作之一
volume_dict = {}
for bar in bars:
if bar.volume == 0:
continue
# 1. 计算K线的加权中心价
center_price = (bar.high + bar.low + bar.close) / 3
# 2. 将中心价对齐到最近的tick
aligned_price = round(center_price / tick_size) * tick_size
# 3. 将该Bar的全部成交量一次性累加到对齐后的价格点上
volume_dict[aligned_price] = volume_dict.get(aligned_price, 0) + bar.volume
if not volume_dict:
return None
# 将最终结果转换为一个有序的Pandas Series
return pd.Series(volume_dict).sort_index()
# 确保在文件顶部导入
from scipy.signal import find_peaks
def find_hvns_with_distance(price_volume_dist: pd.Series, distance_in_ticks: int) -> List[float]:
"""
[全局函数] 使用峰值查找算法根据峰值间的最小距离来识别HVNs。
Args:
price_volume_dist: 价格-成交量分布序列。
distance_in_ticks: 两个HVN之间必须间隔的最小tick数量。
Returns:
一个包含所有被识别出的HVN价格的列表。
"""
if price_volume_dist.empty or len(price_volume_dist) < 3:
return []
# distance参数确保找到的峰值之间至少相隔N个点
peaks_indices, _ = find_peaks(price_volume_dist.values, distance=distance_in_ticks)
if len(peaks_indices) == 0:
return [price_volume_dist.idxmax()] # 默认返回POC
hvn_prices = price_volume_dist.index[peaks_indices].tolist()
return hvn_prices
def find_hvns_strict(price_volume_dist: pd.Series, window_radius: int) -> List[float]:
"""
[全局函数] 使用严格的“滚动窗口最大值”定义来识别HVNs。
一个点是HVN当且仅当它的成交量大于其左右各 `window_radius` 个点的成交量。
Args:
price_volume_dist: 价格-成交量分布序列。
window_radius: 定义了检查窗口的半径 (即您所说的 N)。
Returns:
一个包含所有被识别出的HVN价格的列表。
"""
if price_volume_dist.empty or window_radius == 0:
return [price_volume_dist.idxmax()] if not price_volume_dist.empty else []
# 1. 确保价格序列是连续的用0填充缺失的ticks
full_price_range = np.arange(price_volume_dist.index.min(),
price_volume_dist.index.max() + price_volume_dist.index.to_series().diff().min(),
price_volume_dist.index.to_series().diff().min())
continuous_dist = price_volume_dist.reindex(full_price_range, fill_value=0)
# 2. 计算滚动窗口最大值
window_size = 2 * window_radius + 1
rolling_max = continuous_dist.rolling(window=window_size, center=True).max()
# 3. 找到那些自身成交量就等于其窗口最大值的点
is_hvn = (continuous_dist == rolling_max) & (continuous_dist > 0)
hvn_prices = continuous_dist[is_hvn].index.tolist()
# 4. 处理平顶山如果连续多个点都是HVN只保留中间那个
if not hvn_prices:
return [price_volume_dist.idxmax()] # 如果找不到返回POC
final_hvns = []
i = 0
while i < len(hvn_prices):
# 找到一个连续HVN块
j = i
while j + 1 < len(hvn_prices) and (hvn_prices[j + 1] - hvn_prices[j]) < (
2 * price_volume_dist.index.to_series().diff().min()):
j += 1
# 取这个连续块的中间点
middle_index = i + (j - i) // 2
final_hvns.append(hvn_prices[middle_index])
i = j + 1
return final_hvns
# 确保在文件顶部导入
from scipy.signal import find_peaks
# =====================================================================================
# 以下是V2版本的、简化了状态管理的 HVNPullbackStrategy 代码
# =====================================================================================
class ValueMigrationStrategy(Strategy):
"""
一个基于动态HVN突破后回测的量化交易策略。(V2: 简化状态管理)
V2版本简化了内部状态管理移除了基于order_id的复杂元数据传递
使用更直接、更健壮的单一状态变量来处理挂单的止盈止损参数,
完美适配“单次单持仓”的策略逻辑。
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
tick_size: float = 1,
profile_period: int = 100,
recalc_interval: int = 4,
hvn_distance_ticks: int = 20,
entry_offset_atr: float = 0.0,
stop_loss_atr: float = 1.0,
take_profit_atr: float = 2.0,
atr_period: int = 14,
order_direction=None,
indicators=[None, None],
):
super().__init__(context, main_symbol, enable_log)
if order_direction is None:
order_direction = ['BUY', 'SELL']
self.trade_volume = trade_volume
self.tick_size = tick_size
self.profile_period = profile_period
self.recalc_interval = recalc_interval
self.hvn_distance_ticks = hvn_distance_ticks
self.entry_offset_atr = entry_offset_atr
self.stop_loss_atr = stop_loss_atr
self.take_profit_atr = take_profit_atr
self.atr_period = atr_period
self.order_direction = order_direction
self.indicator_long = indicators[0]
self.indicator_short = indicators[1]
self.main_symbol = main_symbol
self.order_id_counter = 0
self._bar_counter = 0
self._cached_hvns: List[float] = []
# --- V2: 简化的状态管理 ---
self._pending_sl_price: Optional[float] = None
self._pending_tp_price: Optional[float] = None
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
self._bar_counter += 1
bar_history = self.get_bar_history()
required_len = max(self.profile_period, self.atr_period) + 1
if len(bar_history) < required_len:
return
# --- 1. 取消所有挂单并重置挂单状态 ---
self.cancel_all_pending_orders(self.symbol)
# self._pending_sl_price = None
# self._pending_tp_price = None
# --- 2. 管理现有持仓 ---
position_volume = self.get_current_positions().get(self.symbol, 0)
if position_volume != 0:
self.manage_open_position(position_volume, open_price)
return
# --- 3. 周期性地计算HVNs ---
if self._bar_counter % self.recalc_interval == 1:
profile_bars = bar_history[-self.profile_period:]
dist = compute_price_volume_distribution(profile_bars, self.tick_size)
# dist = compute_fast_volume_profile(profile_bars, self.tick_size)
if dist is not None and not dist.empty:
# self._cached_hvns = find_hvns_with_distance(dist, self.hvn_distance_ticks)
self._cached_hvns = find_hvns_strict(dist, self.hvn_distance_ticks)
self.log(f"New HVNs identified at: {[f'{p:.2f}' for p in self._cached_hvns]}")
if not self._cached_hvns: return
# --- 4. 评估新机会 (挂单逻辑) ---
self.evaluate_entry_signal(bar_history)
def manage_open_position(self, volume: int, current_price: float):
"""主动管理已开仓位的止盈止损。"""
# # [V2 关键逻辑]: 检测是否为新持仓
# # 如果这是一个新持仓,并且我们有预设的止盈止损,就将其存入
# if self._pending_sl_price is not None and self._pending_tp_price is not None:
# meta = {'sl_price': self._pending_sl_price, 'tp_price': self._pending_tp_price}
# self.position_meta = meta
# self.log(f"新持仓确认。已设置TP/SL: {meta}")
# else:
# # 这种情况理论上不应发生,但作为保护
# self.log("Error: New position detected but no pending TP/SL values found.")
# self.close_position("CLOSE_LONG" if volume > 0 else "CLOSE_SHORT", abs(volume))
# return
# [常规逻辑]: 检查止盈止损
sl_price = self._pending_sl_price
tp_price = self._pending_tp_price
if volume > 0: # 多头
if current_price <= sl_price or current_price >= tp_price:
action = "止损" if current_price <= sl_price else "止盈"
self.log(f"多头{action}触发 at {current_price:.2f}")
self.close_position("CLOSE_LONG", abs(volume), current_price)
elif volume < 0: # 空头
if current_price >= sl_price or current_price <= tp_price:
action = "止损" if current_price >= sl_price else "止盈"
self.log(f"空头{action}触发 at {current_price:.2f}")
self.close_position("CLOSE_SHORT", abs(volume), current_price)
def evaluate_entry_signal(self, bar_history: List[Bar]):
prev_close = bar_history[-2].close
current_close = bar_history[-1].close
highs = np.array([b.high for b in bar_history], dtype=float)
lows = np.array([b.low for b in bar_history], dtype=float)
closes = np.array([b.close for b in bar_history], dtype=float)
current_atr = talib.ATR(highs, lows, closes, self.atr_period)[-1]
if current_atr < self.tick_size: return
for hvn in sorted(self._cached_hvns):
# (为了简洁,买卖逻辑合并)
direction = None
if "BUY" in self.order_direction and (prev_close < hvn < current_close):
direction = "SELL"
pass_filter = self.indicator_long is None or self.indicator_long.is_condition_met(
*self.get_indicator_tuple())
elif "SELL" in self.order_direction and (prev_close > hvn > current_close):
direction = "BUY"
pass_filter = self.indicator_short is None or self.indicator_short.is_condition_met(
*self.get_indicator_tuple())
else:
continue # 没有触发穿越
if direction and pass_filter:
offset = self.entry_offset_atr * current_atr
limit_price = hvn + offset if direction == "BUY" else hvn - offset
self.log(f"价格穿越HVN({hvn:.2f}). 在 {limit_price:.2f} 挂限价{direction}单。")
# self.send_hvn_limit_order(direction, limit_price + 1 if direction == 'BUY' else -1, current_atr)
self.send_hvn_limit_order(direction, limit_price, current_atr)
return # 每次只挂一个单
def send_hvn_limit_order(self, direction: str, limit_price: float, entry_atr: float):
print(limit_price, self.get_current_time())
# [V2 关键逻辑]: 直接更新实例变量
self._pending_sl_price = limit_price - self.stop_loss_atr * entry_atr if direction == "BUY" else limit_price + self.stop_loss_atr * entry_atr
self._pending_tp_price = limit_price + self.take_profit_atr * entry_atr if direction == "BUY" else limit_price - self.take_profit_atr * entry_atr
order_id = f"{self.symbol}_{direction}_LIMIT_{self.order_id_counter}"
self.order_id_counter += 1
# order = Order(
# id=order_id, symbol=self.symbol, direction=direction, volume=self.trade_volume,
# price_type="LIMIT", limit_price=limit_price, submitted_time=self.get_current_time(),
# offset="OPEN"
# )
order = Order(
id=order_id, symbol=self.symbol, direction=direction, volume=self.trade_volume,
price_type="STOP", stop_price=limit_price, submitted_time=self.get_current_time(),
offset="OPEN"
)
self.send_order(order)
def close_position(self, direction: str, volume: int, current_price: float):
self.send_market_order(direction, volume, current_price)
def send_market_order(self, direction: str, volume: int, current_price: float, offset: str = "CLOSE"):
order_id = f"{self.symbol}_{direction}_{offset}_{self.get_current_time().strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
order = Order(
id=order_id, symbol=self.symbol, direction=direction, volume=volume,
price_type="MARKET", submitted_time=self.get_current_time(), offset=offset, limit_price=current_price
)
self.send_order(order)

View File

@@ -0,0 +1,15 @@
{
"cells": [
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": "",
"id": "31b2956abfa86104"
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
import numpy as np
import pandas as pd
from dataclasses import dataclass
from scipy.stats import kurtosis, skew
from datetime import time, timedelta
from typing import List, Tuple, Optional
from src.core_data import Bar
@dataclass
class ProfileStats:
"""封装剖面图的所有核心统计量。"""
vah: float
val: float
poc: float
def calculate_profile_from_bars(bars: List[Bar], tick_size: float, va_percentage: int = 70) -> Optional[ProfileStats]:
"""
[全局核心函数] 从给定的K线列表计算剖面图统计量 (VAH, VAL, POC)。
"""
if not bars:
return None
data = []
for bar in bars:
price_range = np.arange(bar.low, bar.high + tick_size, tick_size)
if len(price_range) == 0 or bar.volume == 0: continue
volume_per_tick = bar.volume / len(price_range)
for price in price_range:
data.append({'price': price, 'volume': volume_per_tick})
if not data: return None
df = pd.DataFrame(data)
if df.empty: return None
price_volume_dist = df.groupby('price')['volume'].sum().sort_index()
if price_volume_dist.empty or len(price_volume_dist) < 3: return None
poc = price_volume_dist.idxmax()
total_volume = price_volume_dist.sum()
value_area_volume_target = total_volume * (va_percentage / 100.0)
current_va_volume = price_volume_dist.loc[poc]
vah, val = poc, poc
prices_above = price_volume_dist.index[price_volume_dist.index > poc]
prices_below = price_volume_dist.index[price_volume_dist.index < poc].sort_values(ascending=False)
idx_above, idx_below = 0, 0
while current_va_volume < value_area_volume_target:
vol_above = price_volume_dist.loc[prices_above[idx_above]] if idx_above < len(prices_above) else 0
vol_below = price_volume_dist.loc[prices_below[idx_below]] if idx_below < len(prices_below) else 0
if vol_above == 0 and vol_below == 0: break
if vol_above > vol_below:
current_va_volume += vol_above
vah = prices_above[idx_above]
idx_above += 1
else:
current_va_volume += vol_below
val = prices_below[idx_below]
idx_below += 1
return ProfileStats(vah=vah, val=val, poc=poc)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "initial_id",
"metadata": {
"ExecuteTime": {
"end_time": "2025-07-22T07:44:51.375234Z",
"start_time": "2025-07-22T07:44:51.352161Z"
},
"collapsed": true
},
"outputs": [],
"source": [
"from datetime import datetime\n",
"%load_ext autoreload\n",
"%autoreload 2\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "a559dfcf",
"metadata": {
"ExecuteTime": {
"end_time": "2025-07-22T07:44:56.927700Z",
"start_time": "2025-07-22T07:44:51.391111Z"
}
},
"outputs": [
{
"ename": "ModuleNotFoundError",
"evalue": "No module named 'src'",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mturtle\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m down\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01msrc\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01manalysis\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mresult_analyzer\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m ResultAnalyzer\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# 导入所有必要的模块\u001b[39;00m\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01msrc\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdata_manager\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m DataManager\n",
"\u001b[31mModuleNotFoundError\u001b[39m: No module named 'src'"
]
}
],
"source": [
"\n",
"from turtle import down\n",
"from src.analysis.result_analyzer import ResultAnalyzer\n",
"# 导入所有必要的模块\n",
"from src.data_manager import DataManager\n",
"from src.backtest_engine import BacktestEngine\n",
"from src.indicators.indicator_list import INDICATOR_LIST\n",
"from src.indicators.indicators import RSI, BollingerBandwidth, HistoricalRange, NormalizedATR, RateOfChange, StochasticOscillator\n",
"from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy\n",
"\n",
"\n",
"# --- 配置参数 ---\n",
"# 获取当前脚本所在目录,假设数据文件在项目根目录下的 data 文件夹内\n",
"# data_file_path = '/mnt/d/PyProject/NewQuant/data/data/SHFE_rb2510/SHFE_rb2510_min60.csv'\n",
"# data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv\"\n",
"# data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@SHFE_rb/KQ_m@SHFE_rb_min60.csv\"\n",
"data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv\"\n",
"\n",
"initial_capital = 100000.0\n",
"slippage_rate = 0.000 # 假设每笔交易0.1%的滑点\n",
"commission_rate = 0.0001 # 假设每笔交易0.02%的佣金\n",
"\n",
"global_config = {\n",
" 'symbol': 'KQ_m@CZCE_MA',\n",
"}\n",
"\n",
"# Short 可用\n",
"strategy_parameters = {\n",
" 'main_symbol': \"MA\", # 根据您的数据文件中的品种名称调整\n",
" 'trade_volume': 1,\n",
" 'lag': 7,\n",
" # 'range_factor': 1.8, # 示例值,需要通过网格搜索优化\n",
" # 'profit_factor': 2.8, # 示例值\n",
" # 'range_factor': 1.6, # 示例值,需要通过网格搜索优化\n",
" # 'profit_factor': 2.1, # 示例值\n",
" 'range_factor_l': 1.8, # 示例值,需要通过网格搜索优化\n",
" 'profit_factor_l': 2.8, # 示例值\n",
" 'range_factor_s': 1.6, # 示例值,需要通过网格搜索优化\n",
" 'profit_factor_s': 2.1, # 示例值\n",
" 'max_position': 10,\n",
" 'enable_log': True,\n",
" 'stop_loss_points': 20,\n",
" 'use_indicator': True,\n",
" # 'indicator': HistoricalRange(11, 25, 20),\n",
" # 'indicator': BollingerBandwidth(window=20, nbdev=2.0, down_bound=1.9, up_bound=3.25),\n",
" 'indicator_l': HistoricalRange(11, 25, 20),\n",
" 'indicator_s': BollingerBandwidth(window=20, nbdev=2.0, down_bound=1.9, up_bound=3.25),\n",
"}\n",
"start_time = datetime(2021, 1, 1)\n",
"end_time = datetime(2024, 6, 1)\n",
"\n",
"start_time = datetime(2024, 6, 1)\n",
"end_time = datetime(2025, 8, 1)\n",
"\n",
"\n",
"# --- 1. 初始化数据管理器 ---\n",
"print(\"初始化数据管理器...\")\n",
"data_manager = DataManager(file_path=data_file_path, symbol=global_config['symbol'], start_time=start_time, end_time=end_time)\n",
"# 确保 DataManager 能够重置以进行多次回测\n",
"# data_manager.reset() # 首次运行不需要重置\n",
"\n",
"# --- 2. 初始化回测引擎并运行 ---\n",
"print(\"\\n初始化回测引擎...\")\n",
"engine = BacktestEngine(\n",
" data_manager=data_manager,\n",
" strategy_class=SimpleLimitBuyStrategy,\n",
" # current_segment_symbol=strategy_parameters['symbol'],\n",
" strategy_params=strategy_parameters,\n",
" initial_capital=initial_capital,\n",
" slippage_rate=slippage_rate,\n",
" commission_rate=commission_rate,\n",
" roll_over_mode=True,\n",
" start_time=start_time,\n",
" end_time=end_time,\n",
" indicators=INDICATOR_LIST\n",
")\n",
"\n",
"print(\"\\n开始运行回测...\")\n",
"engine.run_backtest()\n",
"print(\"\\n回测运行完毕。\")\n",
"\n",
"# --- 3. 获取回测结果 ---\n",
"results = engine.get_backtest_results()\n",
"portfolio_snapshots = results[\"portfolio_snapshots\"]\n",
"trade_history = results[\"trade_history\"]\n",
"initial_capital_result = results[\"initial_capital\"]\n",
"bars = results[\"all_bars\"]\n",
"\n",
"# --- 4. 结果分析与可视化 ---\n",
"if portfolio_snapshots:\n",
" analyzer = ResultAnalyzer(portfolio_snapshots, trade_history, bars, initial_capital_result, INDICATOR_LIST)\n",
"\n",
" analyzer.generate_report()\n",
" analyzer.plot_performance()\n",
" metrics = analyzer.calculate_all_metrics()\n",
" print(metrics)\n",
"\n",
" analyzer.analyze_indicators()\n",
"else:\n",
" print(\"\\n没有生成投资组合快照无法进行结果分析。\")\n",
"\n",
"# --- 4. 结果分析与可视化 (待实现) ---\n",
"# if portfolio_snapshots:\n",
"# analyzer = ResultAnalyzer(portfolio_snapshots, trade_history, initial_capital_result)\n",
"# metrics = analyzer.calculate_all_metrics()\n",
"# print(\"\\n--- 绩效指标 ---\")\n",
"# for key, value in metrics.items():\n",
"# print(f\" {key}: {value:.4f}\")\n",
"#\n",
"# print(\"\\n--- 绘制绩效图表 ---\")\n",
"# analyzer.plot_performance()\n",
"# else:\n",
"# print(\"\\n没有生成投资组合快照无法进行结果分析。\")\n",
"\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "quant",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}