卡尔曼策略新增md文件
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,196 @@
|
||||
import numpy as np
|
||||
import talib
|
||||
from typing import Optional, Any, List
|
||||
|
||||
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 AreaReversalStrategy(Strategy):
|
||||
"""
|
||||
面积反转策略(含跟踪止损出场)
|
||||
逻辑:
|
||||
- 面积扩张 + 强度达标 + 局部见顶 + 面积收缩 → 等待反向突破开仓
|
||||
- 出场:跟踪止损(回调出场)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
enable_log: bool,
|
||||
trade_volume: int,
|
||||
ma_period: int = 14,
|
||||
area_window: int = 14,
|
||||
strength_window: int = 50,
|
||||
breakout_window: int = 20,
|
||||
quantile_threshold: float = 0.5,
|
||||
top_k: int = 3,
|
||||
trailing_points: float = 100.0, # 跟踪止损点数
|
||||
trailing_percent: float = None, # 或用百分比(如 0.01 = 1%)
|
||||
order_direction: Optional[List[str]] = None,
|
||||
indicators: Optional[List[Indicator]] = None,
|
||||
):
|
||||
super().__init__(context, main_symbol, enable_log)
|
||||
if order_direction is None:
|
||||
order_direction = ["BUY", "SELL"]
|
||||
if indicators is None:
|
||||
indicators = [Empty(), Empty()]
|
||||
|
||||
self.trade_volume = trade_volume
|
||||
self.ma_period = ma_period
|
||||
self.area_window = area_window
|
||||
self.strength_window = strength_window
|
||||
self.breakout_window = breakout_window
|
||||
self.quantile_threshold = quantile_threshold
|
||||
self.top_k = top_k
|
||||
self.trailing_points = trailing_points
|
||||
self.trailing_percent = trailing_percent
|
||||
self.order_direction = order_direction
|
||||
self.indicators = indicators
|
||||
|
||||
# 跟踪止损状态
|
||||
self.entry_price = None
|
||||
self.highest_high = None # 多头持仓期间最高价
|
||||
self.lowest_low = None # 空头持仓期间最低价
|
||||
|
||||
self.order_id_counter = 0
|
||||
self.min_bars_needed = max(
|
||||
ma_period,
|
||||
area_window * 3,
|
||||
strength_window,
|
||||
breakout_window
|
||||
) + 10
|
||||
self.log("AreaReversalStrategy with Trailing Stop Initialized")
|
||||
|
||||
def _calculate_areas(self, closes: np.array, ma: np.array) -> np.array:
|
||||
diffs = np.abs(closes - ma)
|
||||
areas = talib.SUM(diffs, self.area_window)
|
||||
return areas
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
bar_history = self.get_bar_history()
|
||||
|
||||
if len(bar_history) < self.min_bars_needed or not self.trading:
|
||||
return
|
||||
|
||||
position = self.get_current_positions().get(self.symbol, 0)
|
||||
current_bar = bar_history[-1]
|
||||
|
||||
# === 计算指标 ===
|
||||
closes = np.array([b.close for b in bar_history], dtype=float)
|
||||
ma = talib.SMA(closes, self.ma_period)
|
||||
areas = self._calculate_areas(closes, ma)
|
||||
|
||||
A1 = areas[-1]
|
||||
A2 = areas[-2] if len(areas) >= 2 else 0
|
||||
|
||||
# 强度评估窗口
|
||||
historical_areas = areas[-(self.strength_window + 1):-1]
|
||||
if len(historical_areas) < self.strength_window:
|
||||
return
|
||||
|
||||
# === 面积信号条件 ===
|
||||
area_contracting = (A1 < A2) and (A2 > 0)
|
||||
threshold = np.nanpercentile(historical_areas, self.quantile_threshold * 100)
|
||||
strength_satisfied = (A2 >= threshold)
|
||||
top_k_values = np.partition(historical_areas, -self.top_k)[-self.top_k:]
|
||||
local_peak = (A2 >= np.min(top_k_values))
|
||||
|
||||
area_signal = area_contracting and strength_satisfied and local_peak
|
||||
|
||||
# === 突破判断 ===
|
||||
recent_bars = bar_history[-self.breakout_window:]
|
||||
highest = max(b.high for b in recent_bars)
|
||||
lowest = min(b.low for b in recent_bars)
|
||||
|
||||
# =============== 开仓逻辑 ===============
|
||||
if position == 0 and area_signal:
|
||||
if "BUY" in self.order_direction and current_bar.high >= highest:
|
||||
self.send_market_order("BUY", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.highest_high = current_bar.high
|
||||
self.lowest_low = None
|
||||
self.log(f"🚀 Long Entry | A2={A2:.4f}")
|
||||
|
||||
elif "SELL" in self.order_direction and current_bar.low <= lowest:
|
||||
self.send_market_order("SELL", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.lowest_low = current_bar.low
|
||||
self.highest_high = None
|
||||
self.log(f"⬇️ Short Entry | A2={A2:.4f}")
|
||||
|
||||
# =============== 跟踪止损出场逻辑 ===============
|
||||
elif position != 0 and self.entry_price is not None:
|
||||
if position > 0:
|
||||
# 更新最高价
|
||||
if self.highest_high is None or current_bar.high > self.highest_high:
|
||||
self.highest_high = current_bar.high
|
||||
|
||||
# 计算止损价
|
||||
if self.trailing_percent is not None:
|
||||
trailing_offset = self.highest_high * self.trailing_percent
|
||||
else:
|
||||
trailing_offset = self.trailing_points
|
||||
|
||||
stop_loss_price = self.highest_high - trailing_offset
|
||||
|
||||
if current_bar.low <= stop_loss_price:
|
||||
self.close_position("CLOSE_LONG", position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (Long Trailing Stop) @ {stop_loss_price:.5f}")
|
||||
|
||||
else: # position < 0
|
||||
# 更新最低价
|
||||
if self.lowest_low is None or current_bar.low < self.lowest_low:
|
||||
self.lowest_low = current_bar.low
|
||||
|
||||
# 计算止损价
|
||||
if self.trailing_percent is not None:
|
||||
trailing_offset = self.lowest_low * self.trailing_percent
|
||||
else:
|
||||
trailing_offset = self.trailing_points
|
||||
|
||||
stop_loss_price = self.lowest_low + trailing_offset
|
||||
|
||||
if current_bar.high >= stop_loss_price:
|
||||
self.close_position("CLOSE_SHORT", -position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (Short Trailing Stop) @ {stop_loss_price:.5f}")
|
||||
|
||||
def _reset_state(self):
|
||||
"""重置跟踪止损状态"""
|
||||
self.entry_price = None
|
||||
self.highest_high = None
|
||||
self.lowest_low = None
|
||||
|
||||
# --- 模板方法 ---
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.cancel_all_pending_orders(self.main_symbol)
|
||||
self._reset_state()
|
||||
|
||||
def close_position(self, direction: str, volume: int):
|
||||
self.send_market_order(direction, volume, offset="CLOSE")
|
||||
|
||||
def send_market_order(self, direction: str, volume: int, offset: str):
|
||||
order_id = f"{self.symbol}_{direction}_MARKET_{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)
|
||||
|
||||
def on_rollover(self, old_symbol: str, new_symbol: str):
|
||||
super().on_rollover(old_symbol, new_symbol)
|
||||
self._reset_state()
|
||||
self.log("Rollover: Reset trailing stop state.")
|
||||
@@ -0,0 +1,255 @@
|
||||
import numpy as np
|
||||
import talib
|
||||
from typing import Optional, Any, List
|
||||
|
||||
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 AreaReversalStrategy(Strategy):
|
||||
"""
|
||||
面积反转策略(开仓逻辑不变,出场替换为 ATR 动态跟踪止损)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
enable_log: bool,
|
||||
trade_volume: int,
|
||||
ma_period: int = 14,
|
||||
area_window: int = 14,
|
||||
strength_window: int = 50,
|
||||
breakout_window: int = 20,
|
||||
quantile_threshold: float = 0.4,
|
||||
top_k: int = 3,
|
||||
# --- 原有跟踪止损(保留为后备)---
|
||||
trailing_points: float = 100.0,
|
||||
trailing_percent: float = None,
|
||||
# --- 新增 ATR 动态止损参数 ---
|
||||
atr_period: int = 14,
|
||||
initial_atr_mult: float = 3.0, # 初始止损 = 1.0 * ATR
|
||||
max_atr_mult: float = 9.0, # 最大止损 = 3.0 * ATR
|
||||
scale_threshold_mult: float = 1.0, # 盈利达 initial_atr_mult * ATR 时开始扩大
|
||||
use_atr_trailing: bool = True, # 是否启用 ATR 止损
|
||||
# --- 其他 ---
|
||||
order_direction: Optional[List[str]] = None,
|
||||
indicators: Optional[List[Indicator]] = None,
|
||||
):
|
||||
super().__init__(context, main_symbol, enable_log)
|
||||
if order_direction is None:
|
||||
order_direction = ["BUY", "SELL"]
|
||||
if indicators is None:
|
||||
indicators = [Empty(), Empty()]
|
||||
|
||||
self.trade_volume = trade_volume
|
||||
self.ma_period = ma_period
|
||||
self.area_window = area_window
|
||||
self.strength_window = strength_window
|
||||
self.breakout_window = breakout_window
|
||||
self.quantile_threshold = quantile_threshold
|
||||
self.top_k = top_k
|
||||
self.trailing_points = trailing_points
|
||||
self.trailing_percent = trailing_percent
|
||||
self.atr_period = atr_period
|
||||
self.initial_atr_mult = initial_atr_mult
|
||||
self.max_atr_mult = max_atr_mult
|
||||
self.scale_threshold_mult = scale_threshold_mult
|
||||
self.use_atr_trailing = use_atr_trailing
|
||||
self.order_direction = order_direction
|
||||
self.indicators = indicators
|
||||
|
||||
# 状态(新增 entry_atr)
|
||||
self.entry_price = None
|
||||
self.highest_high = None
|
||||
self.lowest_low = None
|
||||
self.entry_atr = None # 入场时的 ATR 值
|
||||
|
||||
self.order_id_counter = 0
|
||||
self.min_bars_needed = max(
|
||||
ma_period,
|
||||
area_window * 3,
|
||||
strength_window,
|
||||
breakout_window,
|
||||
atr_period
|
||||
) + 10
|
||||
self.log("AreaReversalStrategy with ATR Trailing Stop Initialized")
|
||||
|
||||
def _calculate_areas(self, closes: np.array, ma: np.array) -> np.array:
|
||||
diffs = np.abs(closes - ma)
|
||||
areas = talib.SUM(diffs, self.area_window)
|
||||
return areas
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
bar_history = self.get_bar_history()
|
||||
|
||||
if len(bar_history) < self.min_bars_needed or not self.trading:
|
||||
return
|
||||
|
||||
position = self.get_current_positions().get(self.symbol, 0)
|
||||
current_bar = bar_history[-1]
|
||||
|
||||
# === 提取价格序列(新增 highs, lows 用于 ATR)===
|
||||
closes = np.array([b.close for b in bar_history], dtype=float)
|
||||
highs = np.array([b.high for b in bar_history], dtype=float)
|
||||
lows = np.array([b.low for b in bar_history], dtype=float)
|
||||
|
||||
# === 计算指标 ===
|
||||
ma = talib.SMA(closes, self.ma_period)
|
||||
areas = self._calculate_areas(closes, ma)
|
||||
|
||||
# 新增:计算 ATR
|
||||
if self.use_atr_trailing:
|
||||
atr = talib.ATR(highs, lows, closes, self.atr_period)
|
||||
current_atr = atr[-1]
|
||||
else:
|
||||
current_atr = None
|
||||
|
||||
A1 = areas[-1]
|
||||
A2 = areas[-2] if len(areas) >= 2 else 0
|
||||
|
||||
historical_areas = areas[-(self.strength_window + 1):-1]
|
||||
if len(historical_areas) < self.strength_window:
|
||||
return
|
||||
|
||||
# === 面积信号条件(完全不变)===
|
||||
area_contracting = (A1 < A2) and (A2 > 0)
|
||||
threshold = np.nanpercentile(historical_areas, self.quantile_threshold * 100)
|
||||
strength_satisfied = (A2 >= threshold)
|
||||
top_k_values = np.partition(historical_areas, -self.top_k)[-self.top_k:]
|
||||
local_peak = (A2 >= np.min(top_k_values))
|
||||
area_signal = area_contracting and strength_satisfied and local_peak
|
||||
|
||||
# === 突破判断(完全不变)===
|
||||
recent_bars = bar_history[-self.breakout_window:]
|
||||
highest = max(b.high for b in recent_bars)
|
||||
lowest = min(b.low for b in recent_bars)
|
||||
|
||||
# =============== 开仓逻辑(完全不变)==============
|
||||
if position == 0 and area_signal:
|
||||
if "BUY" in self.order_direction and current_bar.high >= highest:
|
||||
self.send_market_order("BUY", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.highest_high = current_bar.high
|
||||
self.lowest_low = None
|
||||
if self.use_atr_trailing and current_atr is not None:
|
||||
self.entry_atr = current_atr # 记录入场 ATR
|
||||
self.log(f"🚀 Long Entry | A2={A2:.4f}")
|
||||
|
||||
elif "SELL" in self.order_direction and current_bar.low <= lowest:
|
||||
self.send_market_order("SELL", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.lowest_low = current_bar.low
|
||||
self.highest_high = None
|
||||
if self.use_atr_trailing and current_atr is not None:
|
||||
self.entry_atr = current_atr
|
||||
self.log(f"⬇️ Short Entry | A2={A2:.4f}")
|
||||
|
||||
# =============== 出场逻辑:ATR 动态跟踪止损 ===============
|
||||
elif position != 0 and self.entry_price is not None:
|
||||
if self.use_atr_trailing and self.entry_atr is not None:
|
||||
# --- ATR 动态止损 ---
|
||||
if position > 0:
|
||||
if self.highest_high is None or current_bar.high > self.highest_high:
|
||||
self.highest_high = current_bar.high
|
||||
|
||||
unrealized_pnl = current_bar.close - self.entry_price
|
||||
scale_threshold_pnl = self.scale_threshold_mult * self.initial_atr_mult * self.entry_atr
|
||||
|
||||
if unrealized_pnl <= 0:
|
||||
trail_mult = self.initial_atr_mult
|
||||
elif unrealized_pnl >= scale_threshold_pnl:
|
||||
trail_mult = self.max_atr_mult
|
||||
else:
|
||||
ratio = unrealized_pnl / scale_threshold_pnl
|
||||
trail_mult = self.initial_atr_mult + ratio * (self.max_atr_mult - self.initial_atr_mult)
|
||||
|
||||
stop_loss_price = self.highest_high - trail_mult * self.entry_atr
|
||||
if current_bar.low <= stop_loss_price:
|
||||
self.close_position("CLOSE_LONG", position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (ATR Trailing) | Mult={trail_mult:.2f}")
|
||||
|
||||
else: # short
|
||||
if self.lowest_low is None or current_bar.low < self.lowest_low:
|
||||
self.lowest_low = current_bar.low
|
||||
|
||||
unrealized_pnl = self.entry_price - current_bar.close
|
||||
scale_threshold_pnl = self.scale_threshold_mult * self.initial_atr_mult * self.entry_atr
|
||||
|
||||
if unrealized_pnl <= 0:
|
||||
trail_mult = self.initial_atr_mult
|
||||
elif unrealized_pnl >= scale_threshold_pnl:
|
||||
trail_mult = self.max_atr_mult
|
||||
else:
|
||||
ratio = unrealized_pnl / scale_threshold_pnl
|
||||
trail_mult = self.initial_atr_mult + ratio * (self.max_atr_mult - self.initial_atr_mult)
|
||||
|
||||
stop_loss_price = self.lowest_low + trail_mult * self.entry_atr
|
||||
if current_bar.high >= stop_loss_price:
|
||||
self.close_position("CLOSE_SHORT", -position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (ATR Trailing) | Mult={trail_mult:.2f}")
|
||||
|
||||
else:
|
||||
# --- 保留原有跟踪止损(后备)---
|
||||
if position > 0:
|
||||
if self.highest_high is None or current_bar.high > self.highest_high:
|
||||
self.highest_high = current_bar.high
|
||||
if self.trailing_percent is not None:
|
||||
offset = self.highest_high * self.trailing_percent
|
||||
else:
|
||||
offset = self.trailing_points
|
||||
stop_loss_price = self.highest_high - offset
|
||||
if current_bar.low <= stop_loss_price:
|
||||
self.close_position("CLOSE_LONG", position)
|
||||
self._reset_state()
|
||||
|
||||
else:
|
||||
if self.lowest_low is None or current_bar.low < self.lowest_low:
|
||||
self.lowest_low = current_bar.low
|
||||
if self.trailing_percent is not None:
|
||||
offset = self.lowest_low * self.trailing_percent
|
||||
else:
|
||||
offset = self.trailing_points
|
||||
stop_loss_price = self.lowest_low + offset
|
||||
if current_bar.high >= stop_loss_price:
|
||||
self.close_position("CLOSE_SHORT", -position)
|
||||
self._reset_state()
|
||||
|
||||
def _reset_state(self):
|
||||
self.entry_price = None
|
||||
self.highest_high = None
|
||||
self.lowest_low = None
|
||||
self.entry_atr = None
|
||||
|
||||
# --- 模板方法(不变)---
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.cancel_all_pending_orders(self.main_symbol)
|
||||
self._reset_state()
|
||||
|
||||
def close_position(self, direction: str, volume: int):
|
||||
self.send_market_order(direction, volume, offset="CLOSE")
|
||||
|
||||
def send_market_order(self, direction: str, volume: int, offset: str):
|
||||
order_id = f"{self.symbol}_{direction}_MARKET_{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)
|
||||
|
||||
def on_rollover(self, old_symbol: str, new_symbol: str):
|
||||
super().on_rollover(old_symbol, new_symbol)
|
||||
self._reset_state()
|
||||
self.log("Rollover: Reset trailing stop state.")
|
||||
File diff suppressed because one or more lines are too long
60
futures_trading_strategies/FG/AreaReversal/strategy.md
Normal file
60
futures_trading_strategies/FG/AreaReversal/strategy.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 面积反转策略(Area Reversal Strategy)
|
||||
|
||||
本策略源自主观交易员“猛将兄”的反转交易思想:**观测价格与均线之间的“面积”变化,当面积先扩张、再收缩,且伴随反向突破时,视为趋势动能耗尽、反转启动的信号**。我们将该主观逻辑完全量化,形成一套可复现、可验证的系统化策略。
|
||||
|
||||
## 📌 核心逻辑
|
||||
|
||||
1. **面积定义**
|
||||
- 单根K线偏离:`|close_t - MA_t|`
|
||||
- 窗口面积(长度 L):`A_t = Σ_{i=t-L+1}^{t} |close_i - MA_i|`
|
||||
- 面积越大,表示价格对均线的**偏离强度越强且越持续**
|
||||
|
||||
2. **开仓条件(三者需同时满足)**
|
||||
- **面积扩张后收缩**:`A₁ < A₂`(最新面积小于前一段)
|
||||
- **强度达标**:`A₂ ≥ 过去 W 个面积的 50% 分位数`
|
||||
- **局部见顶**:`A₂` 为过去 W 个面积中的前 3 大值之一
|
||||
- **反向突破**:价格突破前 N 根K线高/低点
|
||||
|
||||
3. **策略类型**
|
||||
- **反转策略**:捕捉趋势末端的动能衰竭点
|
||||
- **右侧入场**:需等待面积收缩 + 突破双重确认,避免左侧抄底
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Strategy 1:固定点数跟踪止损(Baseline)
|
||||
|
||||
- **出场逻辑**:
|
||||
入场后记录持仓期间最高价(多头)或最低价(空头),设置**固定点数回撤阈值**(如 100 跳)作为跟踪止损。
|
||||
- 止损价 = `最高价 - 100`(多头)
|
||||
- 止损价 = `最低价 + 100`(空头)
|
||||
|
||||
- **优点**:
|
||||
- 逻辑简单,回测稳定
|
||||
- 在强趋势行情中能有效捕获大段利润
|
||||
|
||||
- **缺点**:
|
||||
- **100 跳对多数品种过大**,导致回撤不可控
|
||||
- **调小后(如 50 跳)易被震荡行情洗出**,错过后续趋势
|
||||
- **无法自适应不同品种的波动特性**(黄金 vs 外汇 vs 股指)
|
||||
|
||||
> 💡 此版本为策略基线,用于验证面积信号本身的有效性。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Strategy 2:ATR 动态跟踪止损(Optimized)
|
||||
|
||||
- **核心改进**:
|
||||
引入 **ATR(平均真实波幅)** 动态调整止损距离,实现:
|
||||
- **初始止损小**(1.0 × ATR)→ 控制单笔风险
|
||||
- **盈利后止损逐步扩大**(线性过渡至 3.0 × ATR)→ 容忍趋势中的正常回撤
|
||||
- **完全自适应品种波动率**,统一参数适用于多资产
|
||||
```
|
||||
|
||||
- **优势**:
|
||||
- **解决“100跳太大,调小就失效”的困境**
|
||||
- 在趋势行情中**让利润充分奔跑**,在震荡行情中**自动收紧风险**
|
||||
- 保留原始开仓逻辑不变,仅优化风险管理
|
||||
|
||||
> ✅ Strategy 2 在不改变信号生成的前提下,显著提升策略的风险调整后收益,是面积反转策略的**工程化优化方向**。
|
||||
|
||||
---
|
||||
103
futures_trading_strategies/FG/AreaReversal/utils.py
Normal file
103
futures_trading_strategies/FG/AreaReversal/utils.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import multiprocessing
|
||||
from typing import Tuple, Dict, Any, Optional
|
||||
|
||||
from src.analysis.result_analyzer import ResultAnalyzer
|
||||
from src.backtest_engine import BacktestEngine
|
||||
from src.data_manager import DataManager
|
||||
|
||||
|
||||
# --- 单个回测任务函数 ---
|
||||
# 这个函数将在每个独立的进程中运行,因此它必须是自包含的
|
||||
def run_single_backtest(
|
||||
combination: Tuple[float, float], # 传入当前参数组合
|
||||
common_config: Dict[str, Any] # 传入公共配置 (如数据路径, 初始资金等)
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
运行单个参数组合的回测任务。
|
||||
此函数将在一个独立的进程中执行。
|
||||
"""
|
||||
p1_value, p2_value = combination
|
||||
|
||||
# 从 common_config 中获取必要的配置
|
||||
symbol = common_config['symbol']
|
||||
data_path = common_config['data_path']
|
||||
initial_capital = common_config['initial_capital']
|
||||
slippage_rate = common_config['slippage_rate']
|
||||
commission_rate = common_config['commission_rate']
|
||||
start_time = common_config['start_time']
|
||||
end_time = common_config['end_time']
|
||||
roll_over_mode = common_config['roll_over_mode']
|
||||
# bar_duration_seconds = common_config['bar_duration_seconds'] # 如果DataManager需要,可以再传
|
||||
param1_name = common_config['param1_name']
|
||||
param2_name = common_config['param2_name']
|
||||
|
||||
# 每个进程内部独立初始化 DataManager 和 BacktestEngine
|
||||
# 确保每个进程有自己的数据副本和模拟状态
|
||||
data_manager = DataManager(
|
||||
file_path=data_path,
|
||||
symbol=symbol,
|
||||
# bar_duration_seconds=bar_duration_seconds, # 如果DataManager需要,根据数据文件路径推断或者额外参数传入
|
||||
# start_date=start_time.date(), # DataManager 现在通过 file_path 和 symbol 处理数据
|
||||
# end_date=end_time.date(),
|
||||
)
|
||||
# data_manager.load_data() # DataManager 内部加载数据
|
||||
|
||||
strategy_parameters = {
|
||||
'main_symbol': common_config['main_symbol'],
|
||||
'trade_volume': 1,
|
||||
param1_name: p1_value, # 15分钟扫荡K线下影线占其总范围的最小比例。
|
||||
param2_name: p2_value, # 15分钟限价单的入场点位于扫荡K线低点到收盘价的斐波那契回撤比例。
|
||||
'order_direction': common_config['order_direction'],
|
||||
'enable_log': False, # 建议在调试和测试时开启日志
|
||||
}
|
||||
# 打印当前进程正在处理的组合信息
|
||||
# 注意:多进程打印会交错显示
|
||||
print(f"--- 正在运行组合: {strategy_parameters} (PID: {multiprocessing.current_process().pid}) ---")
|
||||
|
||||
try:
|
||||
# 初始化回测引擎
|
||||
engine = BacktestEngine(
|
||||
data_manager=data_manager,
|
||||
strategy_class=common_config['strategy'],
|
||||
strategy_params=strategy_parameters,
|
||||
initial_capital=initial_capital,
|
||||
slippage_rate=slippage_rate,
|
||||
commission_rate=commission_rate,
|
||||
roll_over_mode=True, # 保持换月模式
|
||||
start_time=common_config['start_time'],
|
||||
end_time=common_config['end_time']
|
||||
)
|
||||
# 运行回测,传入时间范围
|
||||
engine.run_backtest()
|
||||
|
||||
# 获取回测结果并分析
|
||||
results = engine.get_backtest_results()
|
||||
portfolio_snapshots = results["portfolio_snapshots"]
|
||||
trade_history = results["trade_history"]
|
||||
bars = results["all_bars"]
|
||||
initial_capital_result = results["initial_capital"]
|
||||
|
||||
if portfolio_snapshots:
|
||||
analyzer = ResultAnalyzer(portfolio_snapshots, trade_history, bars, initial_capital_result)
|
||||
|
||||
# analyzer.generate_report()
|
||||
# analyzer.plot_performance()
|
||||
metrics = analyzer.calculate_all_metrics()
|
||||
|
||||
# 将当前组合的参数和性能指标存储起来
|
||||
result_entry = {**strategy_parameters, **metrics}
|
||||
return result_entry
|
||||
else:
|
||||
print(
|
||||
f" 组合 {strategy_parameters} 没有生成投资组合快照,无法进行结果分析。(PID: {multiprocessing.current_process().pid})")
|
||||
# 返回一个包含参数和默认0值的结果,以便追踪失败组合
|
||||
return {**strategy_parameters, "total_return": 0.0, "annualized_return": 0.0, "sharpe_ratio": 0.0,
|
||||
"max_drawdown": 0.0, "error": "No portfolio snapshots"}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_trace = traceback.format_exc()
|
||||
print(
|
||||
f" 组合 {strategy_parameters} 运行失败: {e}\n{error_trace} (PID: {multiprocessing.current_process().pid})")
|
||||
# 返回错误信息,以便后续处理
|
||||
return {**strategy_parameters, "error": str(e), "traceback": error_trace}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
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_v2
|
||||
|
||||
|
||||
class DualModeTrendlineHawkesStrategy(Strategy):
|
||||
"""
|
||||
趋势线与霍克斯过程双模式策略 (V5 - 趋势/回归自适应版):
|
||||
- 支持两套独立的参数配置,分别对应趋势跟踪和均值回归逻辑。
|
||||
- 开平仓条件共享,但交易方向相反。
|
||||
- 内置冲突解决机制,用于处理两种模式同时发出开仓信号的情况。
|
||||
- 保持了V4版本高效的增量计算特性。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
trade_volume: int = 1,
|
||||
# 【核心修改】使用字典来配置两种模式
|
||||
trend_params: Dict[str, Any] = None,
|
||||
reversion_params: Dict[str, Any] = None,
|
||||
# 【新增】模式启用开关
|
||||
enabled_modes: Optional[List[str]] = None,
|
||||
# 【新增】信号冲突解决方案: 'TREND_PRIORITY', 'REVERSION_PRIORITY', 'NONE'
|
||||
conflict_resolution: str = 'TREND_PRIORITY',
|
||||
enable_log: bool = True,
|
||||
):
|
||||
super().__init__(context, main_symbol, enable_log)
|
||||
self.main_symbol = main_symbol
|
||||
self.trade_volume = trade_volume
|
||||
|
||||
# --- 【核心修改】参数结构化 ---
|
||||
# 提供默认参数,防止用户未提供
|
||||
default_params = {
|
||||
"order_direction": ["BUY", "SELL"],
|
||||
"trendline_n": 50,
|
||||
"hawkes_kappa": 0.1,
|
||||
"hawkes_lookback": 50,
|
||||
"hawkes_entry_percent": 0.95,
|
||||
"hawkes_exit_percent": 0.50,
|
||||
}
|
||||
self.trend_params = default_params.copy()
|
||||
if trend_params:
|
||||
self.trend_params.update(trend_params)
|
||||
|
||||
self.reversion_params = default_params.copy()
|
||||
if reversion_params:
|
||||
self.reversion_params.update(reversion_params)
|
||||
|
||||
self.enabled_modes = enabled_modes or ['TREND', 'REVERSION']
|
||||
self.conflict_resolution = conflict_resolution
|
||||
|
||||
self.pos_meta: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# --- 【核心修改】为每个模式维护独立的状态 ---
|
||||
# 趋势模式状态
|
||||
self._trend_last_hawkes_unscaled: float = 0.0
|
||||
self._trend_hawkes_window: np.ndarray = np.array([], dtype=np.float64)
|
||||
self._trend_hawkes_alpha = np.exp(-self.trend_params['hawkes_kappa'])
|
||||
|
||||
# 回归模式状态
|
||||
self._reversion_last_hawkes_unscaled: float = 0.0
|
||||
self._reversion_hawkes_window: np.ndarray = np.array([], dtype=np.float64)
|
||||
self._reversion_hawkes_alpha = np.exp(-self.reversion_params['hawkes_kappa'])
|
||||
|
||||
print("DualModeTrendlineHawkesStrategy initialized.")
|
||||
print(f"Enabled modes: {self.enabled_modes}")
|
||||
print(f"Conflict resolution: {self.conflict_resolution}")
|
||||
|
||||
# --- 辅助函数,用于状态管理 (可复用) ---
|
||||
def _initialize_hawkes_state(self, params: Dict, initial_volumes: np.ndarray) -> (float, np.ndarray):
|
||||
"""根据给定参数和历史成交量,初始化霍克斯状态。"""
|
||||
print(f"Initializing Hawkes state with lookback {params['hawkes_lookback']}...")
|
||||
alpha = np.exp(-params['hawkes_kappa'])
|
||||
kappa = params['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)
|
||||
|
||||
last_hawkes_unscaled = temp_hawkes_history[-1] if len(temp_hawkes_history) > 0 else 0.0
|
||||
hawkes_window = (temp_hawkes_history * kappa)[-params['hawkes_lookback']:]
|
||||
return last_hawkes_unscaled, hawkes_window
|
||||
|
||||
def _update_hawkes_state_incrementally(self, params: Dict, latest_volume: float, last_unscaled: float,
|
||||
window: np.ndarray) -> (float, np.ndarray):
|
||||
"""根据给定参数,增量更新霍克斯状态。"""
|
||||
alpha = np.exp(-params['hawkes_kappa'])
|
||||
kappa = params['hawkes_kappa']
|
||||
|
||||
new_hawkes_unscaled = last_unscaled * alpha + (latest_volume if not np.isnan(latest_volume) else 0.0)
|
||||
new_hawkes_scaled = new_hawkes_unscaled * kappa
|
||||
|
||||
new_window = np.roll(window, -1)
|
||||
new_window[-1] = new_hawkes_scaled
|
||||
|
||||
return new_hawkes_unscaled, new_window
|
||||
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.pos_meta.clear()
|
||||
# 重置所有状态
|
||||
self._trend_last_hawkes_unscaled = 0.0
|
||||
self._trend_hawkes_window = np.array([], dtype=np.float64)
|
||||
self._reversion_last_hawkes_unscaled = 0.0
|
||||
self._reversion_hawkes_window = np.array([], dtype=np.float64)
|
||||
self.pos_meta = self.context.load_state()
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
bar_history = self.get_bar_history()
|
||||
|
||||
# 确保有足够的数据来初始化两个模式
|
||||
min_bars_required = max(
|
||||
self.trend_params['trendline_n'] + 2, self.trend_params['hawkes_lookback'] + 2,
|
||||
self.reversion_params['trendline_n'] + 2, self.reversion_params['hawkes_lookback'] + 2
|
||||
)
|
||||
if len(bar_history) < min_bars_required:
|
||||
return
|
||||
|
||||
# --- 状态初始化与更新 ---
|
||||
# 首次运行时,为两个启用的模式初始化状态
|
||||
if self._trend_hawkes_window.size == 0 and 'TREND' in self.enabled_modes:
|
||||
initial_volumes = np.array([b.volume for b in bar_history], dtype=float)
|
||||
self._trend_last_hawkes_unscaled, self._trend_hawkes_window = self._initialize_hawkes_state(
|
||||
self.trend_params, initial_volumes[:-1]
|
||||
)
|
||||
if self._reversion_hawkes_window.size == 0 and 'REVERSION' in self.enabled_modes:
|
||||
initial_volumes = np.array([b.volume for b in bar_history], dtype=float)
|
||||
self._reversion_last_hawkes_unscaled, self._reversion_hawkes_window = self._initialize_hawkes_state(
|
||||
self.reversion_params, initial_volumes[:-1]
|
||||
)
|
||||
|
||||
# 增量更新两个模式的状态
|
||||
latest_volume = float(bar_history[-1].volume)
|
||||
if 'TREND' in self.enabled_modes:
|
||||
self._trend_last_hawkes_unscaled, self._trend_hawkes_window = self._update_hawkes_state_incrementally(
|
||||
self.trend_params, latest_volume, self._trend_last_hawkes_unscaled, self._trend_hawkes_window
|
||||
)
|
||||
if 'REVERSION' in self.enabled_modes:
|
||||
self._reversion_last_hawkes_unscaled, self._reversion_hawkes_window = self._update_hawkes_state_incrementally(
|
||||
self.reversion_params, latest_volume, self._reversion_last_hawkes_unscaled,
|
||||
self._reversion_hawkes_window
|
||||
)
|
||||
|
||||
self.cancel_all_pending_orders(symbol)
|
||||
pos = self.get_current_positions().get(symbol, 0)
|
||||
|
||||
meta = self.pos_meta.get(symbol)
|
||||
# --- 【核心修改】状态同步与异常处理 ---
|
||||
# 场景1: 有实际持仓,但策略无记录 (例如状态恢复失败)。这是最危险的情况。
|
||||
# 策略必须强制平仓以恢复到已知状态,避免“僵尸”持仓。
|
||||
if pos != 0 and not meta:
|
||||
self.log(f"警告:检测到实际持仓({pos})与策略状态(无记录)不一致!"
|
||||
f"可能由状态加载失败导致。将强制平仓以同步状态。", level='WARNING')
|
||||
direction_to_close = "CLOSE_LONG" if pos > 0 else "CLOSE_SHORT"
|
||||
self.send_market_order(direction_to_close, abs(pos))
|
||||
return
|
||||
|
||||
# 场景2: 无实际持仓,但策略仍有记录 (例如外部手动平仓或止损)。
|
||||
# 策略应清理过时的元数据。
|
||||
if pos == 0 and meta:
|
||||
self.log(f"信息:检测到策略状态({meta.get('direction')})与实际持仓(0)不一致。"
|
||||
f"可能是外部平仓导致。正在清理过时状态。", level='INFO')
|
||||
new_pos_meta = {k: v for k, v in self.pos_meta.items() if k != symbol}
|
||||
self.pos_meta = new_pos_meta
|
||||
self.save_state(new_pos_meta)
|
||||
meta = None # 必须更新meta变量以反映当前bar的真实状态
|
||||
|
||||
# --- 1. 平仓逻辑 ---
|
||||
if pos != 0:
|
||||
strategy_mode = meta.get('strategy_mode')
|
||||
params_to_use = self.trend_params if strategy_mode == 'TREND' else self.reversion_params
|
||||
window_to_use = self._trend_hawkes_window if strategy_mode == 'TREND' else self._reversion_hawkes_window
|
||||
|
||||
if window_to_use.size > 0:
|
||||
latest_hawkes_value = window_to_use[-1]
|
||||
latest_hawkes_lower = np.quantile(window_to_use, params_to_use['hawkes_exit_percent'])
|
||||
|
||||
if latest_hawkes_value < latest_hawkes_lower:
|
||||
self.log(f"[{strategy_mode}模式] 霍克斯出场信号触发,平仓。")
|
||||
self.send_market_order("CLOSE_LONG" if meta['direction'] == "BUY" else "CLOSE_SHORT", abs(pos))
|
||||
self.pos_meta = {}
|
||||
self.save_state(self.pos_meta)
|
||||
return
|
||||
|
||||
# --- 2. 开仓逻辑 ---
|
||||
if pos == 0 and self.trading:
|
||||
trend_signal = None
|
||||
reversion_signal = None
|
||||
|
||||
# 分别计算两个模式的信号
|
||||
if 'TREND' in self.enabled_modes:
|
||||
trend_signal = self._calculate_entry_signal(
|
||||
'TREND', bar_history, self.trend_params, self._trend_hawkes_window
|
||||
)
|
||||
if 'REVERSION' in self.enabled_modes:
|
||||
reversion_signal = self._calculate_entry_signal(
|
||||
'REVERSION', bar_history, self.reversion_params, self._reversion_hawkes_window
|
||||
)
|
||||
|
||||
final_direction = None
|
||||
winning_mode = None
|
||||
|
||||
# --- 信号冲突解决 ---
|
||||
if trend_signal and reversion_signal:
|
||||
self.log(f"信号冲突:趋势模式 ({trend_signal}) vs 回归模式 ({reversion_signal})")
|
||||
if self.conflict_resolution == 'TREND_PRIORITY':
|
||||
final_direction = trend_signal
|
||||
winning_mode = 'TREND'
|
||||
elif self.conflict_resolution == 'REVERSION_PRIORITY':
|
||||
final_direction = reversion_signal
|
||||
winning_mode = 'REVERSION'
|
||||
else: # 'NONE'
|
||||
self.log("冲突解决策略为'NONE',本次不开仓。")
|
||||
elif trend_signal:
|
||||
final_direction = trend_signal
|
||||
winning_mode = 'TREND'
|
||||
elif reversion_signal:
|
||||
final_direction = reversion_signal
|
||||
winning_mode = 'REVERSION'
|
||||
|
||||
# 执行最终决策
|
||||
if final_direction and winning_mode:
|
||||
params_to_use = self.trend_params if winning_mode == 'TREND' else self.reversion_params
|
||||
if final_direction in params_to_use['order_direction']:
|
||||
self.log(f"[{winning_mode}模式] 开仓信号确认: {final_direction}")
|
||||
self.send_open_order(final_direction, open_price, self.trade_volume, winning_mode)
|
||||
|
||||
def _calculate_entry_signal(self, mode: str, bar_history: List[Bar], params: Dict, hawkes_window: np.ndarray) -> \
|
||||
Optional[str]:
|
||||
"""计算单个模式的入场信号,返回 'BUY', 'SELL' 或 None。"""
|
||||
if hawkes_window.size == 0:
|
||||
return None
|
||||
|
||||
# 霍克斯确认
|
||||
latest_hawkes_value = hawkes_window[-1]
|
||||
latest_hawkes_upper = np.quantile(hawkes_window, params['hawkes_entry_percent'])
|
||||
hawkes_confirmation = latest_hawkes_value > latest_hawkes_upper
|
||||
self.log(f'latest_hawkes_value:{latest_hawkes_value}, latest_hawkes_upper:{latest_hawkes_upper}')
|
||||
|
||||
if not hawkes_confirmation:
|
||||
return None
|
||||
|
||||
# 趋势线突破事件
|
||||
close_prices = np.array([b.close for b in bar_history])
|
||||
prices_for_trendline = close_prices[-params['trendline_n'] - 1:-1]
|
||||
trend_upper, trend_lower = calculate_latest_trendline_values_v2(prices_for_trendline)
|
||||
self.log(f'trend_upper: {trend_upper}, trend_lower: {trend_lower}')
|
||||
|
||||
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
|
||||
|
||||
if upper_break_event:
|
||||
# 趋势模式:向上突破 -> 买入
|
||||
# 回归模式:向上突破 -> 卖出 (认为是假突破,价格将回归)
|
||||
return "BUY" if mode == 'TREND' else "SELL"
|
||||
elif lower_break_event:
|
||||
# 趋势模式:向下突破 -> 卖出
|
||||
# 回归模式:向下突破 -> 买入 (认为是超卖,价格将反弹)
|
||||
return "SELL" if mode == 'TREND' else "BUY"
|
||||
|
||||
return None
|
||||
|
||||
def send_open_order(self, direction: str, entry_price: float, volume: int, strategy_mode: str):
|
||||
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="LIMIT",
|
||||
submitted_time=current_time, offset="OPEN", limit_price=entry_price + (1 if direction == "BUY" else -1),)
|
||||
self.send_order(order)
|
||||
# 【核心修改】记录仓位属于哪个模式
|
||||
self.pos_meta[self.symbol] = {
|
||||
"direction": direction,
|
||||
"volume": volume,
|
||||
"entry_price": entry_price,
|
||||
"strategy_mode": strategy_mode
|
||||
}
|
||||
self.save_state(self.pos_meta)
|
||||
self.log(f"发送开仓订单 ({strategy_mode}): {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()
|
||||
@@ -0,0 +1,381 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"id": "522f09ca7b3fe929",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-10-24T08:54:09.381083Z",
|
||||
"start_time": "2025-10-24T08:54:09.363240Z"
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The autoreload extension is already loaded. To reload it, use:\n",
|
||||
" %reload_ext autoreload\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 9
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"id": "4f7e4b438cea750e",
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-10-24T08:54:09.402764Z",
|
||||
"start_time": "2025-10-24T08:54:09.387096Z"
|
||||
}
|
||||
},
|
||||
"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 futures_trading_strategies.FG.TrendlineBreakoutStrategy.DualModeTrendlineHawkesStrategy2 import DualModeTrendlineHawkesStrategy\n",
|
||||
"\n",
|
||||
"# --- 配置参数 ---\n",
|
||||
"# 获取当前脚本所在目录,假设数据文件在项目根目录下的 data 文件夹内\n",
|
||||
"data_file_path = 'D:/PyProject/NewQuant/data/data/KQ_m@CZCE_FG/KQ_m@CZCE_FG_min15.csv'\n"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 10
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-10-24T08:54:09.420838Z",
|
||||
"start_time": "2025-10-24T08:54:09.404769Z"
|
||||
}
|
||||
},
|
||||
"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_FG', # 确保与数据文件中的 symbol 匹配\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"# 回测时间范围\n",
|
||||
"start_time = datetime(2021, 1, 1)\n",
|
||||
"end_time = datetime(2024, 6, 1)\n",
|
||||
"\n",
|
||||
"start_time = datetime(2025, 9, 1)\n",
|
||||
"end_time = datetime(2025, 11, 1)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"indicators = INDICATOR_LIST\n",
|
||||
"indicators = []\n",
|
||||
"\n",
|
||||
"# 确保 DataManager 能够重置以进行多次回测\n",
|
||||
"# data_manager.reset() # 首次运行不需要重置"
|
||||
],
|
||||
"id": "9ee53c41eaaefabb",
|
||||
"outputs": [],
|
||||
"execution_count": 11
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2025-10-24T08:54:30.631596Z",
|
||||
"start_time": "2025-10-24T08:54:09.424852Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from src.tqsdk_engine import TqsdkEngine\n",
|
||||
"from tqsdk import TqApi, TqBacktest, TqAuth\n",
|
||||
"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': 'FG', # <-- 替换为你的交易品种代码,例如 'GC=F' (黄金期货), 'ZC=F' (玉米期货)\n",
|
||||
" 'trade_volume': 1,\n",
|
||||
" # 'indicators': [RateOfChange(10, -2.1, -0.5), ROC_MA(10, 10, -2.7, -0.4)],\n",
|
||||
" 'enable_log': False,\n",
|
||||
" 'trend_params': {\n",
|
||||
" \"trendline_n\": 10,\n",
|
||||
" \"hawkes_kappa\": 0.9,\n",
|
||||
" },\n",
|
||||
" 'reversion_params': {\n",
|
||||
" \"trendline_n\": 70,\n",
|
||||
" \"hawkes_kappa\": 0.1,\n",
|
||||
" },\n",
|
||||
" 'conflict_resolution': 'NONE'\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"# --- 2. 初始化回测引擎并运行 ---\n",
|
||||
"print(\"\\n初始化回测引擎...\")\n",
|
||||
"api = TqApi(\n",
|
||||
" backtest=TqBacktest(start_dt=start_time, end_dt=end_time),\n",
|
||||
" auth=TqAuth(\"emanresu\", \"dfgvfgdfgg\"),\n",
|
||||
")\n",
|
||||
"# --- 1. 初始化回测引擎并运行 ---\n",
|
||||
"print(\"\\n初始化 Tqsdk 回测引擎...\")\n",
|
||||
"engine = TqsdkEngine(\n",
|
||||
" strategy_class=DualModeTrendlineHawkesStrategy,\n",
|
||||
" strategy_params=strategy_parameters,\n",
|
||||
" api=api,\n",
|
||||
" symbol=global_config['symbol'],\n",
|
||||
" duration_seconds=60 * 15,\n",
|
||||
" roll_over_mode=True, # 启用换月模式检测\n",
|
||||
" start_time=start_time,\n",
|
||||
" end_time=end_time,\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",
|
||||
"数据加载成功: D:/PyProject/NewQuant/data/data/KQ_m@CZCE_FG/KQ_m@CZCE_FG_min15.csv\n",
|
||||
"数据范围从 2020-12-31 14:45:00 到 2025-10-24 14:30:00\n",
|
||||
"总计 26508 条记录。\n",
|
||||
"\n",
|
||||
"初始化回测引擎...\n",
|
||||
" INFO - TqSdk free 版剩余 0 天到期,如需续费或升级请访问 https://account.shinnytech.com/ 或联系相关工作人员。\n",
|
||||
"\n",
|
||||
"初始化 Tqsdk 回测引擎...\n",
|
||||
"内存仓储已初始化,管理ID: 'futures_trading_strategies.FG.TrendlineBreakoutStrategy.DualModeTrendlineHawkesStrategy2.DualModeTrendlineHawkesStrategy_ec6cb042fb4d776e22af01d6641dc528'\n",
|
||||
"TqsdkContext: 初始化完成。\n",
|
||||
"DualModeTrendlineHawkesStrategy initialized.\n",
|
||||
"Enabled modes: ['TREND', 'REVERSION']\n",
|
||||
"Conflict resolution: NONE\n",
|
||||
"TqsdkContext: 已设置引擎引用。\n",
|
||||
"TqsdkEngine: 初始化完成。\n",
|
||||
"\n",
|
||||
"开始运行回测...\n",
|
||||
"TqsdkEngine: 开始运行回测,从 2025-09-01 00:00:00 到 2025-11-01 00:00:00\n",
|
||||
"DualModeTrendlineHawkesStrategy 策略初始化回调被调用。\n",
|
||||
"Initializing Hawkes state with lookback 50...\n",
|
||||
"Initializing Hawkes state with lookback 50...\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20250904211500', price_type='LIMIT', limit_price=1148, stop_price=None, submitted_time=Timestamp('2025-09-04 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20250904211500', price_type='LIMIT', limit_price=1148, stop_price=None, submitted_time=Timestamp('2025-09-04 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_4aacb590c09871e5cfae4a8eb64c3c4d: 时间: 2025-09-04 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1148.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_4aacb590c09871e5cfae4a8eb64c3c4d: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20250904221500', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-04 22:15:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20250904221500', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-04 22:15:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_0f909d3561fe7c107a1b19326fa53a17: 时间: 2025-09-04 22:15:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1140.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_0f909d3561fe7c107a1b19326fa53a17: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20250909211500', price_type='LIMIT', limit_price=1171, stop_price=None, submitted_time=Timestamp('2025-09-09 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20250909211500', price_type='LIMIT', limit_price=1171, stop_price=None, submitted_time=Timestamp('2025-09-09 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_8db38b8aee8f78c3607d2d84cb4abb0b: 时间: 2025-09-09 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1171.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_8db38b8aee8f78c3607d2d84cb4abb0b: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20250909223000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-09 22:30:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20250909223000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-09 22:30:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_c462ad51e812c4f54117aa66ae1499e4: 时间: 2025-09-09 22:30:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1171.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_c462ad51e812c4f54117aa66ae1499e4: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20250918091500', price_type='LIMIT', limit_price=1217, stop_price=None, submitted_time=Timestamp('2025-09-18 09:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20250918091500', price_type='LIMIT', limit_price=1217, stop_price=None, submitted_time=Timestamp('2025-09-18 09:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_487e402458d1b25fb8a7e687ae4c291c: 时间: 2025-09-18 09:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1217.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_487e402458d1b25fb8a7e687ae4c291c: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20250918103000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-18 10:30:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20250918103000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-18 10:30:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_b3b97d0e6d4fdc323ec344f7d89ce1f0: 时间: 2025-09-18 10:30:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1225.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_b3b97d0e6d4fdc323ec344f7d89ce1f0: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20250918133000', price_type='LIMIT', limit_price=1207, stop_price=None, submitted_time=Timestamp('2025-09-18 13:30:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20250918133000', price_type='LIMIT', limit_price=1207, stop_price=None, submitted_time=Timestamp('2025-09-18 13:30:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_96c036710e7b53b554fc7c33b95b9c24: 时间: 2025-09-18 13:30:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1207.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_96c036710e7b53b554fc7c33b95b9c24: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20250918214500', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-18 21:45:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20250918214500', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-18 21:45:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_7865ded2b9c1bfa3442b1701742c4d58: 时间: 2025-09-18 21:45:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1205.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_7865ded2b9c1bfa3442b1701742c4d58: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20250919211500', price_type='LIMIT', limit_price=1225, stop_price=None, submitted_time=Timestamp('2025-09-19 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20250919211500', price_type='LIMIT', limit_price=1225, stop_price=None, submitted_time=Timestamp('2025-09-19 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_8ecdd284ec65ec961067a00e1e03e315: 时间: 2025-09-19 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1225.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_8ecdd284ec65ec961067a00e1e03e315: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20250919220000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-19 22:00:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20250919220000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-19 22:00:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_29eb0b749e58167aa45665d208253b36: 时间: 2025-09-19 22:00:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1223.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_29eb0b749e58167aa45665d208253b36: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20250924091500', price_type='LIMIT', limit_price=1203, stop_price=None, submitted_time=Timestamp('2025-09-24 09:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20250924091500', price_type='LIMIT', limit_price=1203, stop_price=None, submitted_time=Timestamp('2025-09-24 09:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_32cf408c5635c11b831b98b2fd635a66: 时间: 2025-09-24 09:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1203.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_32cf408c5635c11b831b98b2fd635a66: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20250924100000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-24 10:00:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20250924100000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-09-24 10:00:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_f046b95f5e3d13c223fa1f9c535d76bf: 时间: 2025-09-24 10:00:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1196.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_f046b95f5e3d13c223fa1f9c535d76bf: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20251010211500', price_type='LIMIT', limit_price=1185, stop_price=None, submitted_time=Timestamp('2025-10-10 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20251010211500', price_type='LIMIT', limit_price=1185, stop_price=None, submitted_time=Timestamp('2025-10-10 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_9a63d90f357be3164b448d24e95ea66d: 时间: 2025-10-10 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1185.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_9a63d90f357be3164b448d24e95ea66d: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20251010220000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-10-10 22:00:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20251010220000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-10-10 22:00:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_6dfce0e150315f78983eda0bcb0e9a91: 时间: 2025-10-10 22:00:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1184.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_6dfce0e150315f78983eda0bcb0e9a91: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20251016211500', price_type='LIMIT', limit_price=1129, stop_price=None, submitted_time=Timestamp('2025-10-16 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='SELL', volume=1, id='CZCE.FG601_SELL_20251016211500', price_type='LIMIT', limit_price=1129, stop_price=None, submitted_time=Timestamp('2025-10-16 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_6cd2d65278d526385068e2106d70854c: 时间: 2025-10-16 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1129.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_6cd2d65278d526385068e2106d70854c: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20251016221500', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-10-16 22:15:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_SHORT', volume=1, id='CZCE.FG601_CLOSE_SHORT_20251016221500', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-10-16 22:15:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_3299638e3209614bcfd7538e8fefa867: 时间: 2025-10-16 22:15:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1130.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_3299638e3209614bcfd7538e8fefa867: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20251017211500', price_type='LIMIT', limit_price=1125, stop_price=None, submitted_time=Timestamp('2025-10-17 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20251017211500', price_type='LIMIT', limit_price=1125, stop_price=None, submitted_time=Timestamp('2025-10-17 21:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_eb18a1a9237778c79666acd207f76702: 时间: 2025-10-17 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1125.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_eb18a1a9237778c79666acd207f76702: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20251017223000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-10-17 22:30:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='CLOSE_LONG', volume=1, id='CZCE.FG601_CLOSE_LONG_20251017223000', price_type='MARKET', limit_price=None, stop_price=None, submitted_time=Timestamp('2025-10-17 22:30:00+0800', tz='Asia/Shanghai'), offset='CLOSE')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_target_48e62f08faa15a6e629c20770e7fe2d0: 时间: 2025-10-17 22:30:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1113.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_target_48e62f08faa15a6e629c20770e7fe2d0: 全部成交\n",
|
||||
"Context: 订单已加入队列: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20251024111500', price_type='LIMIT', limit_price=1105, stop_price=None, submitted_time=Timestamp('2025-10-24 11:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
"Engine: 处理订单请求: Order(symbol='CZCE.FG601', direction='BUY', volume=1, id='CZCE.FG601_BUY_20251024111500', price_type='LIMIT', limit_price=1105, stop_price=None, submitted_time=Timestamp('2025-10-24 11:15:00+0800', tz='Asia/Shanghai'), offset='OPEN')\n",
|
||||
" INFO - 模拟交易下单 TQSIM, PYSDK_insert_cbe9a4c43285732cf01979eded2c27af: 时间: 2025-10-24 11:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1105.0\n",
|
||||
" INFO - 模拟交易委托单 TQSIM, PYSDK_insert_cbe9a4c43285732cf01979eded2c27af: 全部成交\n",
|
||||
" INFO - 回测结束\n",
|
||||
" INFO - 模拟交易成交记录, 账户: TQSIM\n",
|
||||
" INFO - 时间: 2025-09-04 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1148.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-04 22:15:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1140.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-09 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1171.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-09 22:30:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1171.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-18 09:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1217.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-18 10:30:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1225.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-18 13:30:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1207.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-18 21:45:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1205.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-19 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1225.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-19 22:00:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1223.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-24 09:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1203.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-09-24 10:00:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1196.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-10-10 21:15:59.999999, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1185.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-10-10 22:00:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1184.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-10-16 21:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: SELL, 手数: 1, 价格: 1129.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-10-16 22:15:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: BUY, 手数: 1, 价格: 1130.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-10-17 21:15:59.999999, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1125.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-10-17 22:30:00.000000, 合约: CZCE.FG601, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 1113.000,手续费: 3.00\n",
|
||||
" INFO - 时间: 2025-10-24 11:15:00.000000, 合约: CZCE.FG601, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 1105.000,手续费: 3.00\n",
|
||||
" INFO - 模拟交易账户资金, 账户: TQSIM\n",
|
||||
" INFO - 日期: 2025-09-01, 账户权益: 10000000.00, 可用资金: 10000000.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-02, 账户权益: 10000000.00, 可用资金: 10000000.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-03, 账户权益: 10000000.00, 可用资金: 10000000.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-04, 账户权益: 10000000.00, 可用资金: 10000000.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-05, 账户权益: 9999834.00, 可用资金: 9999834.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: -160.00, 市值: 0.00, 保证金: 0.00, 手续费: 6.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-08, 账户权益: 9999834.00, 可用资金: 9999834.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-09, 账户权益: 9999834.00, 可用资金: 9999834.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-10, 账户权益: 9999828.00, 可用资金: 9999828.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 6.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-11, 账户权益: 9999828.00, 可用资金: 9999828.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-12, 账户权益: 9999828.00, 可用资金: 9999828.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-15, 账户权益: 9999828.00, 可用资金: 9999828.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-16, 账户权益: 9999828.00, 可用资金: 9999828.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-17, 账户权益: 9999828.00, 可用资金: 9999828.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-18, 账户权益: 9999639.00, 可用资金: 9998541.00, 浮动盈亏: -20.00, 持仓盈亏: -20.00, 平仓盈亏: -160.00, 市值: 0.00, 保证金: 1098.00, 手续费: 9.00, 风险度: 0.01%\n",
|
||||
" INFO - 日期: 2025-09-19, 账户权益: 9999696.00, 可用资金: 9999696.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 60.00, 市值: 0.00, 保证金: 0.00, 手续费: 3.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-22, 账户权益: 9999650.00, 可用资金: 9999650.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: -40.00, 市值: 0.00, 保证金: 0.00, 手续费: 6.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-23, 账户权益: 9999650.00, 可用资金: 9999650.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-24, 账户权益: 9999504.00, 可用资金: 9999504.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: -140.00, 市值: 0.00, 保证金: 0.00, 手续费: 6.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-25, 账户权益: 9999504.00, 可用资金: 9999504.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-26, 账户权益: 9999504.00, 可用资金: 9999504.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-29, 账户权益: 9999504.00, 可用资金: 9999504.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-09-30, 账户权益: 9999504.00, 可用资金: 9999504.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-09, 账户权益: 9999504.00, 可用资金: 9999504.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-10, 账户权益: 9999504.00, 可用资金: 9999504.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-13, 账户权益: 9999518.00, 可用资金: 9999518.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 20.00, 市值: 0.00, 保证金: 0.00, 手续费: 6.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-14, 账户权益: 9999518.00, 可用资金: 9999518.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-15, 账户权益: 9999518.00, 可用资金: 9999518.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-16, 账户权益: 9999518.00, 可用资金: 9999518.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-17, 账户权益: 9999492.00, 可用资金: 9999492.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: -20.00, 市值: 0.00, 保证金: 0.00, 手续费: 6.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-20, 账户权益: 9999246.00, 可用资金: 9999246.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: -240.00, 市值: 0.00, 保证金: 0.00, 手续费: 6.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-21, 账户权益: 9999246.00, 可用资金: 9999246.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-22, 账户权益: 9999246.00, 可用资金: 9999246.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-23, 账户权益: 9999246.00, 可用资金: 9999246.00, 浮动盈亏: 0.00, 持仓盈亏: 0.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 0.00, 手续费: 0.00, 风险度: 0.00%\n",
|
||||
" INFO - 日期: 2025-10-24, 账户权益: 9998983.00, 可用资金: 9997885.00, 浮动盈亏: -260.00, 持仓盈亏: -260.00, 平仓盈亏: 0.00, 市值: 0.00, 保证金: 1098.00, 手续费: 3.00, 风险度: 0.01%\n",
|
||||
" INFO - 胜率: 33.33%, 盈亏额比例: 0.16, 收益率: -0.01%, 年化收益率: -0.07%, 最大回撤: 0.01%, 年化夏普率: -215.1636,年化索提诺比率: -15.7689\n",
|
||||
"回测结束:开始平仓所有剩余持仓...\n",
|
||||
"TqsdkEngine: 回测运行完毕。\n",
|
||||
"TqsdkEngine: API 已关闭。\n",
|
||||
"\n",
|
||||
"回测运行完毕。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ename": "KeyError",
|
||||
"evalue": "'initial_capital'",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001B[31m---------------------------------------------------------------------------\u001B[39m",
|
||||
"\u001B[31mKeyError\u001B[39m Traceback (most recent call last)",
|
||||
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[12]\u001B[39m\u001B[32m, line 53\u001B[39m\n\u001B[32m 51\u001B[39m portfolio_snapshots = results[\u001B[33m\"\u001B[39m\u001B[33mportfolio_snapshots\u001B[39m\u001B[33m\"\u001B[39m]\n\u001B[32m 52\u001B[39m trade_history = results[\u001B[33m\"\u001B[39m\u001B[33mtrade_history\u001B[39m\u001B[33m\"\u001B[39m]\n\u001B[32m---> \u001B[39m\u001B[32m53\u001B[39m initial_capital_result = \u001B[43mresults\u001B[49m\u001B[43m[\u001B[49m\u001B[33;43m\"\u001B[39;49m\u001B[33;43minitial_capital\u001B[39;49m\u001B[33;43m\"\u001B[39;49m\u001B[43m]\u001B[49m\n\u001B[32m 54\u001B[39m bars = results[\u001B[33m\"\u001B[39m\u001B[33mall_bars\u001B[39m\u001B[33m\"\u001B[39m]\n\u001B[32m 56\u001B[39m \u001B[38;5;66;03m# --- 4. 结果分析与可视化 ---\u001B[39;00m\n",
|
||||
"\u001B[31mKeyError\u001B[39m: 'initial_capital'"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 12
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"pos_meta": {
|
||||
"CZCE.FG405": {
|
||||
"direction": "BUY",
|
||||
"volume": 1,
|
||||
"entry_price": 1908.0,
|
||||
"strategy_mode": "TREND"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user