171 lines
8.2 KiB
Python
171 lines
8.2 KiB
Python
import numpy as np
|
|
import pandas as pd
|
|
from typing import Optional, Dict, Any, List
|
|
from collections import deque
|
|
|
|
# 假设这些是你项目中的模块
|
|
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 PriceRangeVolumeIntensityStrategy(Strategy):
|
|
"""
|
|
价格区间成交量强度策略 (V11 - 市场冲击强度版):
|
|
- 【核心修改】策略的强度指标从单纯的 `volume` 升级为 `volume * (bar.high - bar.low)`。
|
|
- 这个新指标能更准确地衡量市场的“冲击力”或“有效动能”,过滤掉高换手但价格停滞的无效信号。
|
|
- 继承了 V10 版本的高效无状态 deque 实现,仅改变了指标的输入源。
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
context: Any,
|
|
main_symbol: str,
|
|
strategy_mode: str,
|
|
trade_volume: int = 1,
|
|
trendline_n: int = 50,
|
|
intensity_kappa: float = 0.1,
|
|
intensity_lookback: int = 24,
|
|
intensity_entry_percent: float = 0.95,
|
|
intensity_exit_percent: float = 0.50,
|
|
order_direction: Optional[List[str]] = None,
|
|
enable_log: bool = True,
|
|
):
|
|
super().__init__(context, main_symbol, enable_log)
|
|
self.main_symbol = main_symbol
|
|
self.trade_volume = trade_volume
|
|
|
|
if strategy_mode not in ['TREND', 'REVERSION']:
|
|
raise ValueError(f"strategy_mode 必须是 'TREND' 或 'REVERSION', 但收到了 '{strategy_mode}'")
|
|
self.strategy_mode = strategy_mode
|
|
|
|
self.params = {
|
|
"order_direction": order_direction if order_direction is not None else ["BUY", "SELL"],
|
|
"trendline_n": trendline_n,
|
|
"intensity_kappa": intensity_kappa,
|
|
"intensity_lookback": intensity_lookback,
|
|
"intensity_entry_percent": intensity_entry_percent,
|
|
"intensity_exit_percent": intensity_exit_percent,
|
|
}
|
|
|
|
self.weights = np.exp(-self.params['intensity_kappa'] * np.arange(self.params['intensity_lookback'], 0, -1))
|
|
self.intensity_window: Optional[deque] = None
|
|
self.pos_meta: Dict[str, Dict[str, Any]] = {}
|
|
|
|
print(f"PriceRangeVolumeIntensityStrategy (V11) initialized.")
|
|
print(f"--- Strategy Mode SET TO: {self.strategy_mode} ---")
|
|
print(f"Intensity metric: volume * (high - low)")
|
|
|
|
def _calculate_single_intensity(self, metric_slice: np.ndarray) -> float:
|
|
"""根据一段“市场冲击”指标切片,计算最新的一个强度值。"""
|
|
intensity_unscaled = np.dot(metric_slice, self.weights)
|
|
return intensity_unscaled * self.params['intensity_kappa']
|
|
|
|
def on_init(self):
|
|
super().on_init()
|
|
self.pos_meta.clear()
|
|
self.intensity_window = None
|
|
|
|
def on_open_bar(self, open_price: float, symbol: str):
|
|
bar_history = self.get_bar_history()
|
|
lookback = self.params['intensity_lookback']
|
|
|
|
min_bars_required = max(self.params['trendline_n'] + 2, 2 * lookback)
|
|
if len(bar_history) < min_bars_required:
|
|
return
|
|
|
|
# --- 【核心修改】计算新的“市场冲击”指标序列 ---
|
|
# 替代了之前的 all_volumes
|
|
market_impact_metric = np.array(
|
|
[(b.high - b.low) * 1 for b in bar_history],
|
|
dtype=float
|
|
)
|
|
# 简单的零值处理,防止 (high-low) 为 0 或负数(异常数据)导致的问题
|
|
market_impact_metric[market_impact_metric <= 0] = 1e-9
|
|
|
|
# --- 策略预热 (Warm-up) 与 增量更新 (逻辑不变,数据源已更新) ---
|
|
if self.intensity_window is None:
|
|
self.intensity_window = deque(maxlen=lookback)
|
|
print("Warming up the market impact intensity window...")
|
|
for i in range(len(market_impact_metric) - lookback, len(market_impact_metric)):
|
|
metric_slice = market_impact_metric[i - lookback : i]
|
|
intensity_value = self._calculate_single_intensity(metric_slice)
|
|
self.intensity_window.append(intensity_value)
|
|
print("Warm-up complete.")
|
|
else:
|
|
latest_metric_slice = market_impact_metric[-lookback:]
|
|
latest_intensity_value = self._calculate_single_intensity(latest_metric_slice)
|
|
self.intensity_window.append(latest_intensity_value)
|
|
|
|
self.cancel_all_pending_orders(symbol)
|
|
pos = self.get_current_positions().get(symbol, 0)
|
|
|
|
# --- 平仓与开仓逻辑 (完全不变,因为它们依赖于 intensity_window 的结果) ---
|
|
latest_intensity = self.intensity_window[-1]
|
|
|
|
# 1. 平仓逻辑
|
|
meta = self.pos_meta.get(symbol)
|
|
if meta and pos != 0:
|
|
exit_threshold = np.quantile(list(self.intensity_window), self.params['intensity_exit_percent'])
|
|
if latest_intensity < exit_threshold:
|
|
self.log(f"[{self.strategy_mode}模式] 市场冲击强度平仓信号,平仓。")
|
|
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:
|
|
signal = self._calculate_entry_signal(
|
|
self.strategy_mode, bar_history, self.params, self.intensity_window
|
|
)
|
|
if signal and signal in self.params['order_direction']:
|
|
self.log(f"[{self.strategy_mode}模式] 市场冲击强度开仓信号: {signal}")
|
|
self.send_open_order(signal, open_price, self.trade_volume)
|
|
|
|
# _calculate_entry_signal 函数完全不变,因为它只消费 intensity_window
|
|
def _calculate_entry_signal(self, mode: str, bar_history: List[Bar], params: Dict, intensity_window: deque) -> \
|
|
Optional[str]:
|
|
latest_intensity_value = intensity_window[-1]
|
|
entry_threshold = np.quantile(list(intensity_window), params['intensity_entry_percent'])
|
|
if not (latest_intensity_value > entry_threshold):
|
|
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)
|
|
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"
|
|
if lower_break_event: return "SELL" if mode == 'TREND' else "BUY"
|
|
return None
|
|
|
|
# 其他辅助函数 (send_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"发送开仓订单 ({self.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()
|
|
self.intensity_window = None |