mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-28 19:27:43 +00:00
重构项目架构,新增 MT5 代理服务
- 重构核心模块:DataProvider 依赖注入、RiskController 门面、信号注册表 - 新增 FastAPI 代理服务 (run/server.py),支持局域网远程调用 MT5 - 新增 RemoteDataProvider + AttrDict,远端无缝替代 LiveDataProvider - 新增序列化模块,MT5 对象转 JSON 兼容格式 - 重构入口点至 run/ 包,支持 python -m run.realtime/server/backtest/optimize - 更新 CLAUDE.md 文档 Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
"""统一回测引擎 — 消除 start_backtest.py 和 optimizer.py 中的回测循环重复"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from logger import logger
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from core.data import MultiTimeframeDataStore, BacktestDataProvider
|
||||
from core.risk.controller import RiskController
|
||||
from core.risk.market_state import MarketStateAnalyzer
|
||||
from core.signal.registry import StrategyRegistry
|
||||
from core.signal.combiner import SignalCombiner
|
||||
from execution.weights import DynamicWeightManager
|
||||
from config import (
|
||||
SYMBOL, TIMEFRAME, INITIAL_CAPITAL, SIGNAL_THRESHOLDS, DEFAULT_WEIGHTS,
|
||||
BACKTEST_CONFIG, RISK_CONFIG,
|
||||
MARKET_STATE_CONFIG, TREND_INDICATOR_WEIGHTS, TREND_THRESHOLDS, CONFIDENCE_THRESHOLDS
|
||||
)
|
||||
from utils.constants import PERIOD_H1
|
||||
|
||||
|
||||
class BacktestEngine:
|
||||
"""统一回测引擎
|
||||
|
||||
支持两种模式:
|
||||
- standard: 使用 DEFAULT_WEIGHTS 的固定权重回测
|
||||
- dynamic: 使用 MarketStateAnalyzer 的动态权重回测
|
||||
"""
|
||||
|
||||
def __init__(self, symbol: str = SYMBOL, timeframe: int = TIMEFRAME,
|
||||
initial_capital: float = INITIAL_CAPITAL,
|
||||
use_dynamic_weights: bool = False):
|
||||
self.symbol = symbol
|
||||
self.timeframe = timeframe
|
||||
self.initial_capital = initial_capital
|
||||
self.use_dynamic_weights = use_dynamic_weights
|
||||
|
||||
# 延迟初始化
|
||||
self.multi_tf = None
|
||||
self.data_provider = None
|
||||
self.risk_controller = None
|
||||
self.registry = StrategyRegistry()
|
||||
|
||||
def load_data(self, rates: np.ndarray) -> None:
|
||||
"""加载M1数据并初始化组件"""
|
||||
if rates is None or len(rates) == 0:
|
||||
raise ValueError("无法加载历史数据")
|
||||
|
||||
self.multi_tf = MultiTimeframeDataStore()
|
||||
self.multi_tf.load_m1_data(rates)
|
||||
self.data_provider = BacktestDataProvider(self.multi_tf, self.initial_capital)
|
||||
|
||||
# 预计算市场状态(动态权重模式)
|
||||
analyzer = None
|
||||
if self.use_dynamic_weights:
|
||||
analyzer = MarketStateAnalyzer(
|
||||
timeframe=PERIOD_H1,
|
||||
market_state_params=MARKET_STATE_CONFIG,
|
||||
trend_weights=TREND_INDICATOR_WEIGHTS,
|
||||
trend_thresholds=TREND_THRESHOLDS,
|
||||
confidence_thresholds=CONFIDENCE_THRESHOLDS,
|
||||
)
|
||||
analyzer.precompute_from_multitf(self.multi_tf)
|
||||
|
||||
self.risk_controller = RiskController(
|
||||
self.data_provider,
|
||||
trade_direction=BACKTEST_CONFIG.get('trade_direction', 'both'),
|
||||
risk_config=RISK_CONFIG,
|
||||
market_state_analyzer=analyzer,
|
||||
)
|
||||
|
||||
self.weight_manager = DynamicWeightManager(
|
||||
self.data_provider,
|
||||
market_state_analyzer=analyzer or MarketStateAnalyzer(),
|
||||
)
|
||||
|
||||
def _instantiate_strategies(self, params_dict: dict = None) -> dict:
|
||||
"""使用给定参数实例化所有策略"""
|
||||
return self.registry.instantiate_all(self.symbol, self.timeframe, params_dict)
|
||||
|
||||
def precompute_signals(self, strategies: dict) -> pd.DataFrame:
|
||||
"""预生成所有策略信号"""
|
||||
logger.info("开始预生成所有策略信号...")
|
||||
signals_df = pd.DataFrame(index=self.multi_tf.main_df.index)
|
||||
|
||||
for config_key, strategy in tqdm(strategies.items(), desc="生成策略信号"):
|
||||
try:
|
||||
sig = strategy.run_backtest(self.multi_tf.main_df)
|
||||
if sig is not None and len(sig) > 0:
|
||||
signals_df[config_key] = sig
|
||||
logger.debug(f"策略 {config_key} 信号生成成功")
|
||||
else:
|
||||
logger.warning(f"策略 {config_key} 返回空信号")
|
||||
signals_df[config_key] = pd.Series(0, index=signals_df.index)
|
||||
except Exception as e:
|
||||
logger.error(f"策略 {config_key} 执行失败: {e}")
|
||||
signals_df[config_key] = pd.Series(0, index=signals_df.index)
|
||||
|
||||
logger.info(f"信号预生成完成: {len(signals_df.columns)} 个策略")
|
||||
return signals_df
|
||||
|
||||
def run(self, rates: np.ndarray,
|
||||
strategy_params: dict = None,
|
||||
weights: dict = None,
|
||||
buy_threshold: float = None,
|
||||
sell_threshold: float = None) -> dict:
|
||||
"""执行完整回测
|
||||
|
||||
Args:
|
||||
rates: MT5原始M1数据(numpy数组)
|
||||
strategy_params: {config_key: {param_name: value}} 策略参数覆写
|
||||
weights: {config_key: weight} 策略权重,None则使用 DEFAULT_WEIGHTS
|
||||
buy_threshold: 买入阈值
|
||||
sell_threshold: 卖出阈值
|
||||
|
||||
Returns:
|
||||
dict: 交易摘要
|
||||
"""
|
||||
self.load_data(rates)
|
||||
|
||||
buy_th = buy_threshold or SIGNAL_THRESHOLDS.get('buy_threshold', 1.5)
|
||||
sell_th = sell_threshold or SIGNAL_THRESHOLDS.get('sell_threshold', -1.5)
|
||||
|
||||
# 实例化策略并预计算信号
|
||||
strategies = self._instantiate_strategies(strategy_params)
|
||||
signals_df = self.precompute_signals(strategies)
|
||||
|
||||
# 信号组合
|
||||
if weights is not None:
|
||||
use_weights = weights
|
||||
else:
|
||||
use_weights = DEFAULT_WEIGHTS
|
||||
|
||||
combined_signals = SignalCombiner.combine_vectorized(
|
||||
signals_df, use_weights, buy_th, sell_th
|
||||
)
|
||||
|
||||
logger.info("回测主循环开始...")
|
||||
total_bars = len(self.multi_tf.main_df)
|
||||
|
||||
for bar_index in tqdm(range(total_bars), desc="回测执行"):
|
||||
try:
|
||||
current_price = self.data_provider.get_current_price(self.symbol)
|
||||
if not current_price:
|
||||
self.data_provider.tick()
|
||||
continue
|
||||
|
||||
# 动态权重模式:每bar重新计算权重并重新组合信号
|
||||
if self.use_dynamic_weights and self.weight_manager.analyzer._precomputed_states is not None:
|
||||
bar_weights = self.weight_manager.get_weights_for_bar(bar_index)
|
||||
bar_signals = {col: signals_df[col].iloc[bar_index] for col in signals_df.columns}
|
||||
current_signal = SignalCombiner.combine_at_bar(
|
||||
bar_signals, bar_weights, buy_th, sell_th
|
||||
)
|
||||
else:
|
||||
current_signal = combined_signals.iloc[bar_index]
|
||||
if current_signal == 1:
|
||||
self.risk_controller.process_trading_signal("buy", current_price, 1.0)
|
||||
elif current_signal == -1:
|
||||
self.risk_controller.process_trading_signal("sell", current_price, 1.0)
|
||||
|
||||
self.risk_controller.monitor_positions(current_price, dry_run=True)
|
||||
self.data_provider.tick()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"回测循环在索引 {bar_index} 出错: {e}")
|
||||
self.data_provider.tick()
|
||||
continue
|
||||
|
||||
# 生成报告
|
||||
summary = self.risk_controller.position_manager.get_trade_summary()
|
||||
self._print_report(summary)
|
||||
|
||||
# 保存交易记录
|
||||
try:
|
||||
self.risk_controller.save_trade_history("backtest_trades")
|
||||
except Exception as e:
|
||||
logger.error(f"保存交易记录失败: {e}")
|
||||
|
||||
return summary
|
||||
|
||||
def _print_report(self, summary: dict) -> None:
|
||||
logger.info("=" * 80)
|
||||
logger.info("回测性能报告")
|
||||
logger.info(f"总交易次数: {summary.get('total_trades', 0)}")
|
||||
logger.info(f"胜率: {summary.get('win_rate', 0):.2f}%")
|
||||
logger.info(f"总盈亏: ${summary.get('total_profit_loss', 0):.2f}")
|
||||
logger.info("=" * 80)
|
||||
@@ -1,95 +0,0 @@
|
||||
import pandas as pd
|
||||
from tqdm import tqdm
|
||||
from core.risk import RiskController
|
||||
from config import SIGNAL_THRESHOLDS, BACKTEST_CONFIG, SPREAD
|
||||
from logger import logger
|
||||
|
||||
class BacktestEngine:
|
||||
def __init__(self, df, trade_direction="both"):
|
||||
self.df = df
|
||||
self.trade_direction = trade_direction
|
||||
self.spread = BACKTEST_CONFIG.get("spread", SPREAD)
|
||||
|
||||
def run_strategy(self, strategy):
|
||||
signals = strategy.run_backtest(self.df)
|
||||
buy_count = (signals == 1).sum()
|
||||
sell_count = (signals == -1).sum()
|
||||
logger.info(f"策略 {strategy.__class__.__module__} 信号统计 - 买入: {buy_count}, 卖出: {sell_count}")
|
||||
return signals
|
||||
|
||||
def combine_signals(self, signals_list, weights):
|
||||
df_signals = pd.concat(signals_list, axis=1).fillna(0)
|
||||
weighted_signals = df_signals * weights
|
||||
combined = weighted_signals.sum(axis=1)
|
||||
|
||||
def apply_threshold(score):
|
||||
if score > SIGNAL_THRESHOLDS["buy_threshold"]:
|
||||
return 1
|
||||
elif score < SIGNAL_THRESHOLDS["sell_threshold"]:
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
|
||||
combined_signal = combined.apply(apply_threshold)
|
||||
buy_signals = (combined_signal == 1).sum()
|
||||
sell_signals = (combined_signal == -1).sum()
|
||||
logger.info(f"组合信号统计 - 买入: {buy_signals}, 卖出: {sell_signals}")
|
||||
return combined_signal
|
||||
|
||||
def run_backtest(self, signals, symbol="XAUUSD"):
|
||||
df = self.df.copy()
|
||||
df['signal'] = signals.shift(1).fillna(0)
|
||||
|
||||
risk_controller = RiskController(self.trade_direction)
|
||||
logger.info(f"回测开始: 交易方向={self.trade_direction}")
|
||||
|
||||
# 使用tqdm创建进度条
|
||||
for i in tqdm(range(1, len(df)), desc=f"Backtesting ({len(df)} bars)"):
|
||||
current_signal = df['signal'].iloc[i]
|
||||
# 计算考虑双向点差的买卖价格
|
||||
close_price = df['close'].iloc[i]
|
||||
spread_points = self.spread
|
||||
spread_half = spread_points * 0.01 / 2 # XAUUSD: 1点 = 0.01,双向点差各一半
|
||||
|
||||
current_price = {
|
||||
'bid': close_price - spread_half, # 卖出价格(中间价 - 点差/2)
|
||||
'ask': close_price + spread_half, # 买入价格(中间价 + 点差/2)
|
||||
'last': close_price # 最后成交价(中间价)
|
||||
}
|
||||
|
||||
direction = None
|
||||
if current_signal == 1:
|
||||
direction = "buy"
|
||||
elif current_signal == -1:
|
||||
direction = "sell"
|
||||
|
||||
if direction:
|
||||
risk_controller.process_trading_signal(
|
||||
direction, current_price, abs(current_signal), dry_run=True
|
||||
)
|
||||
|
||||
risk_controller.monitor_positions(current_price, dry_run=True)
|
||||
|
||||
risk_controller.position_manager.force_close_all_positions(df['close'].iloc[-1], dry_run=True)
|
||||
|
||||
logger.info("回测完成,生成性能报告...")
|
||||
summary = risk_controller.position_manager.get_trade_summary()
|
||||
|
||||
logger.info("=" * 80)
|
||||
logger.info("回测性能报告")
|
||||
logger.info("=" * 80)
|
||||
logger.info(f" 总交易次数: {summary['total_trades']}")
|
||||
logger.info(f" 盈利次数: {summary['winning_trades']}")
|
||||
logger.info(f" 亏损次数: {summary['losing_trades']}")
|
||||
logger.info(f" 胜率: {summary['win_rate']:.2f}%")
|
||||
logger.info("-" * 40)
|
||||
logger.info(f" 总盈亏: ${summary['total_profit_loss']:.2f}")
|
||||
logger.info(f" 平均每笔交易盈亏: ${summary['avg_profit_loss']:.2f}")
|
||||
logger.info(f" 最大盈利: ${summary['max_profit']:.2f}")
|
||||
logger.info(f" 最大亏损: ${summary['max_loss']:.2f}")
|
||||
logger.info("=" * 80)
|
||||
|
||||
risk_controller.position_manager.save_to_csv("backtest_trades.csv")
|
||||
risk_controller.position_manager.save_to_json("backtest_trades.json")
|
||||
|
||||
return summary
|
||||
@@ -1,67 +0,0 @@
|
||||
from strategies import ma_cross, rsi, bollinger, mean_reversion, momentum_breakout, macd, kdj, turtle, daily_breakout, wave_theory
|
||||
from config import DEFAULT_WEIGHTS, SYMBOL, TIMEFRAME
|
||||
from logger import logger
|
||||
from core.risk.market_state import MarketStateAnalyzer
|
||||
|
||||
class DynamicWeightManager:
|
||||
"""
|
||||
动态权重管理器
|
||||
"""
|
||||
|
||||
def __init__(self, data_provider, market_state_params=None, trend_weights=None, trend_thresholds=None, confidence_thresholds=None):
|
||||
self.data_provider = data_provider
|
||||
self.market_state_analyzer = MarketStateAnalyzer(
|
||||
data_provider,
|
||||
market_state_params=market_state_params,
|
||||
trend_weights=trend_weights,
|
||||
trend_thresholds=trend_thresholds,
|
||||
confidence_thresholds=confidence_thresholds
|
||||
)
|
||||
|
||||
# 策略类和它们的初始化参数的映射
|
||||
self.strategy_blueprints = {
|
||||
'ma_cross': (ma_cross.MACrossStrategy, {}),
|
||||
'rsi': (rsi.RSIStrategy, {}),
|
||||
'bollinger': (bollinger.BollingerStrategy, {}),
|
||||
'mean_reversion': (mean_reversion.MeanReversionStrategy, {}),
|
||||
'momentum_breakout': (momentum_breakout.MomentumBreakoutStrategy, {}),
|
||||
'macd': (macd.MACDStrategy, {}),
|
||||
'kdj': (kdj.KDJStrategy, {}),
|
||||
'turtle': (turtle.TurtleStrategy, {}),
|
||||
'daily_breakout': (daily_breakout.DailyBreakoutStrategy, {}),
|
||||
'wave_theory': (wave_theory.WaveTheoryStrategy, {})
|
||||
}
|
||||
|
||||
self.strategy_instances = self._create_strategy_instances()
|
||||
|
||||
self.current_weights = None
|
||||
self.current_market_state = "none"
|
||||
self.current_confidence = 0.0
|
||||
|
||||
def _create_strategy_instances(self):
|
||||
instances = {}
|
||||
for name, (strategy_class, params) in self.strategy_blueprints.items():
|
||||
instances[name] = strategy_class(self.data_provider, SYMBOL, TIMEFRAME, **params)
|
||||
return instances
|
||||
|
||||
def get_current_strategies_and_weights(self):
|
||||
market_state, confidence = self.market_state_analyzer.get_market_state()
|
||||
dynamic_weights = self.market_state_analyzer.get_strategy_weights(market_state, confidence)
|
||||
|
||||
self.current_weights = dynamic_weights
|
||||
self.current_market_state = market_state
|
||||
self.current_confidence = confidence
|
||||
|
||||
strategies_with_weights = []
|
||||
for name, weight in dynamic_weights.items():
|
||||
if name in self.strategy_instances:
|
||||
strategies_with_weights.append((self.strategy_instances[name], weight))
|
||||
|
||||
return strategies_with_weights
|
||||
|
||||
def get_weight_info(self):
|
||||
return {
|
||||
'market_state': self.current_market_state,
|
||||
'confidence': self.current_confidence,
|
||||
'weights': self.current_weights
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
"""遗传算法优化器(重写版)
|
||||
|
||||
关键改进:
|
||||
1. 权重基因通过 individual_weights → MarketStateAnalyzer.get_strategy_weights() 真正生效
|
||||
2. 使用 StrategyRegistry 单例消,除重复的策略列表
|
||||
3. 使用 MultiTimeframeDataStore 支持正确的多周期市场状态分析
|
||||
4. evaluate_fitness 逐 bar 调用 get_market_state(bar_index) 获取每根bar的动态权重
|
||||
"""
|
||||
|
||||
import random
|
||||
import numpy as np
|
||||
from deap import base, creator, tools, algorithms
|
||||
import multiprocessing
|
||||
import pandas as pd
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
from tqdm import tqdm
|
||||
|
||||
SEED = 42
|
||||
random.seed(SEED)
|
||||
np.random.seed(SEED)
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from core.data import MultiTimeframeDataStore, BacktestDataProvider
|
||||
from core.risk.market_state import MarketStateAnalyzer
|
||||
from core.risk.position import PositionManager
|
||||
from core.signal.registry import StrategyRegistry
|
||||
from core.signal.combiner import SignalCombiner
|
||||
from config import (
|
||||
SYMBOL, TIMEFRAME, OPTIMIZER_COUNT, OPTIMIZER_START_DATE, OPTIMIZER_END_DATE,
|
||||
USE_DATE_RANGE, INITIAL_CAPITAL, SIGNAL_THRESHOLDS, DEFAULT_WEIGHTS,
|
||||
RISK_CONFIG, GENETIC_OPTIMIZER_CONFIG,
|
||||
MARKET_STATE_CONFIG, TREND_INDICATOR_WEIGHTS, TREND_THRESHOLDS, CONFIDENCE_THRESHOLDS
|
||||
)
|
||||
from utils.constants import PERIOD_H1
|
||||
from core.utils import get_rates, initialize, shutdown
|
||||
from logger import logger
|
||||
|
||||
# 导入策略模块确保 StrategyRegistry 已注册
|
||||
import strategies # noqa: F401
|
||||
|
||||
# ── 全局缓存 ──
|
||||
_multi_tf: MultiTimeframeDataStore | None = None
|
||||
_cached_signals: pd.DataFrame | None = None
|
||||
_registry: StrategyRegistry | None = None
|
||||
|
||||
|
||||
def init_worker():
|
||||
"""多进程worker初始化:抑制日志噪音"""
|
||||
import logging
|
||||
logging.getLogger().setLevel(logging.WARNING)
|
||||
for name in ["StrategyLogger", "PositionManager", "RiskController", "DataProvider"]:
|
||||
logging.getLogger(name).setLevel(logging.WARNING)
|
||||
|
||||
|
||||
# ── 参数定义(与旧版一致,保持兼容)──
|
||||
PARAMETER_DEFINITIONS = [
|
||||
# MACrossStrategy
|
||||
{'name': 'ma_cross_short_window', 'type': 'int', 'min': 3, 'max': 15, 'strategy': 'MACrossStrategy'},
|
||||
{'name': 'ma_cross_long_window', 'type': 'int', 'min': 10, 'max': 60, 'strategy': 'MACrossStrategy'},
|
||||
# RSIStrategy
|
||||
{'name': 'rsi_period', 'type': 'int', 'min': 7, 'max': 21, 'strategy': 'RSIStrategy'},
|
||||
{'name': 'rsi_overbought', 'type': 'int', 'min': 65, 'max': 80, 'strategy': 'RSIStrategy'},
|
||||
{'name': 'rsi_oversold', 'type': 'int', 'min': 20, 'max': 35, 'strategy': 'RSIStrategy'},
|
||||
# BollingerStrategy
|
||||
{'name': 'bollinger_period', 'type': 'int', 'min': 10, 'max': 50, 'strategy': 'BollingerStrategy'},
|
||||
{'name': 'bollinger_std_dev', 'type': 'float', 'min': 1.5, 'max': 3.0, 'strategy': 'BollingerStrategy'},
|
||||
# MACDStrategy
|
||||
{'name': 'macd_fast_ema', 'type': 'int', 'min': 8, 'max': 20, 'strategy': 'MACDStrategy'},
|
||||
{'name': 'macd_slow_ema', 'type': 'int', 'min': 20, 'max': 35, 'strategy': 'MACDStrategy'},
|
||||
{'name': 'macd_signal_period', 'type': 'int', 'min': 5, 'max': 15, 'strategy': 'MACDStrategy'},
|
||||
# MeanReversionStrategy
|
||||
{'name': 'mean_reversion_period', 'type': 'int', 'min': 10, 'max': 50, 'strategy': 'MeanReversionStrategy'},
|
||||
{'name': 'mean_reversion_std_dev', 'type': 'float', 'min': 1.5, 'max': 3.0, 'strategy': 'MeanReversionStrategy'},
|
||||
# MomentumBreakoutStrategy
|
||||
{'name': 'momentum_breakout_period', 'type': 'int', 'min': 10, 'max': 50, 'strategy': 'MomentumBreakoutStrategy'},
|
||||
{'name': 'momentum_breakout_momentum_period', 'type': 'int', 'min': 5, 'max': 30, 'strategy': 'MomentumBreakoutStrategy'},
|
||||
# KDJStrategy
|
||||
{'name': 'kdj_period', 'type': 'int', 'min': 5, 'max': 21, 'strategy': 'KDJStrategy'},
|
||||
# TurtleStrategy
|
||||
{'name': 'turtle_period', 'type': 'int', 'min': 10, 'max': 50, 'strategy': 'TurtleStrategy'},
|
||||
# WaveTheoryStrategy
|
||||
{'name': 'wave_ema_short', 'type': 'int', 'min': 3, 'max': 10, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_ema_medium', 'type': 'int', 'min': 8, 'max': 20, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_ema_long', 'type': 'int', 'min': 20, 'max': 50, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_period', 'type': 'int', 'min': 10, 'max': 40, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_range_period', 'type': 'int', 'min': 10, 'max': 40, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_adx_period', 'type': 'int', 'min': 10, 'max': 25, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_momentum_period', 'type': 'int', 'min': 7, 'max': 21, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_range_threshold', 'type': 'float', 'min': 0.002, 'max': 0.01, 'strategy': 'WaveTheoryStrategy'},
|
||||
{'name': 'wave_adx_threshold', 'type': 'int', 'min': 15, 'max': 35, 'strategy': 'WaveTheoryStrategy'},
|
||||
# DailyBreakoutStrategy
|
||||
{'name': 'daily_breakout_bars_count', 'type': 'int', 'min': 720, 'max': 2880, 'strategy': 'DailyBreakoutStrategy'},
|
||||
# Signal Thresholds
|
||||
{'name': 'buy_threshold', 'type': 'float', 'min': 0.5, 'max': 3.0, 'strategy': 'signal'},
|
||||
{'name': 'sell_threshold', 'type': 'float', 'min': -3.0, 'max': -0.5, 'strategy': 'signal'},
|
||||
# Risk Management
|
||||
{'name': 'stop_loss_pct', 'type': 'float', 'min': -0.05, 'max': -0.01, 'strategy': 'risk'},
|
||||
{'name': 'profit_retracement_pct', 'type': 'float', 'min': 0.05, 'max': 0.20, 'strategy': 'risk'},
|
||||
{'name': 'min_profit_for_trailing', 'type': 'float', 'min': 0.005, 'max': 0.02, 'strategy': 'risk'},
|
||||
{'name': 'take_profit_pct', 'type': 'float', 'min': 0.10, 'max': 0.50, 'strategy': 'risk'},
|
||||
{'name': 'max_holding_minutes', 'type': 'int', 'min': 30, 'max': 180, 'strategy': 'risk'},
|
||||
{'name': 'min_profit_for_time_exit', 'type': 'float', 'min': 0.002, 'max': 0.01, 'strategy': 'risk'},
|
||||
# ★ Strategy Weights — 这些基因现在真正影响适应度
|
||||
{'name': 'weight_MACrossStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_RSIStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_BollingerStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_MeanReversionStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_MomentumBreakoutStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_MACDStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_KDJStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_TurtleStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_DailyBreakoutStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
{'name': 'weight_WaveTheoryStrategy', 'type': 'float', 'min': 0.0, 'max': 2.0, 'strategy': 'weight'},
|
||||
# Market State Analysis parameters
|
||||
{'name': 'market_trend_period', 'type': 'int', 'min': 20, 'max': 100, 'strategy': 'market_state'},
|
||||
{'name': 'market_retracement_tolerance', 'type': 'float', 'min': 0.1, 'max': 0.5, 'strategy': 'market_state'},
|
||||
{'name': 'market_volume_period', 'type': 'int', 'min': 10, 'max': 50, 'strategy': 'market_state'},
|
||||
{'name': 'market_volume_ma_period', 'type': 'int', 'min': 5, 'max': 30, 'strategy': 'market_state'},
|
||||
# Trend Indicator Weights
|
||||
{'name': 'trend_price_breakout_weight', 'type': 'float', 'min': 0.1, 'max': 0.5, 'strategy': 'trend_weights'},
|
||||
{'name': 'trend_volume_confirmation_weight', 'type': 'float', 'min': 0.1, 'max': 0.5, 'strategy': 'trend_weights'},
|
||||
{'name': 'trend_momentum_oscillator_weight', 'type': 'float', 'min': 0.1, 'max': 0.5, 'strategy': 'trend_weights'},
|
||||
{'name': 'trend_moving_average_weight', 'type': 'float', 'min': 0.1, 'max': 0.5, 'strategy': 'trend_weights'},
|
||||
# Trend Thresholds
|
||||
{'name': 'trend_strong_threshold', 'type': 'float', 'min': 0.4, 'max': 0.8, 'strategy': 'trend_thresholds'},
|
||||
{'name': 'trend_weak_threshold', 'type': 'float', 'min': 0.2, 'max': 0.5, 'strategy': 'trend_thresholds'},
|
||||
{'name': 'trend_volume_spike', 'type': 'float', 'min': 1.0, 'max': 3.0, 'strategy': 'trend_thresholds'},
|
||||
{'name': 'trend_oversold', 'type': 'int', 'min': 20, 'max': 40, 'strategy': 'trend_thresholds'},
|
||||
{'name': 'trend_overbought', 'type': 'int', 'min': 60, 'max': 80, 'strategy': 'trend_thresholds'},
|
||||
# Confidence Thresholds
|
||||
{'name': 'confidence_high', 'type': 'float', 'min': 0.5, 'max': 0.9, 'strategy': 'confidence'},
|
||||
{'name': 'confidence_medium', 'type': 'float', 'min': 0.3, 'max': 0.7, 'strategy': 'confidence'},
|
||||
]
|
||||
|
||||
# class_name → config_key 映射(从 StrategyRegistry 获取)
|
||||
_class_name_to_config_key = {
|
||||
"MACrossStrategy": "ma_cross",
|
||||
"RSIStrategy": "rsi",
|
||||
"BollingerStrategy": "bollinger",
|
||||
"MeanReversionStrategy": "mean_reversion",
|
||||
"MomentumBreakoutStrategy": "momentum_breakout",
|
||||
"MACDStrategy": "macd",
|
||||
"KDJStrategy": "kdj",
|
||||
"TurtleStrategy": "turtle",
|
||||
"DailyBreakoutStrategy": "daily_breakout",
|
||||
"WaveTheoryStrategy": "wave_theory",
|
||||
}
|
||||
|
||||
|
||||
def _parse_individual(individual):
|
||||
"""解析个体基因为命名参数字典,并裁剪到定义范围"""
|
||||
parsed = {}
|
||||
for idx, param_def in enumerate(PARAMETER_DEFINITIONS):
|
||||
val = individual[idx]
|
||||
if param_def['type'] == 'int':
|
||||
val = int(round(val))
|
||||
# 裁剪到 [min, max] 防止变异越界
|
||||
val = max(param_def['min'], min(param_def['max'], val))
|
||||
parsed[param_def['name']] = val
|
||||
return parsed
|
||||
|
||||
|
||||
def _extract_strategy_params(parsed: dict) -> dict:
|
||||
"""从解析后的基因提取策略参数字典"""
|
||||
strategy_params = {}
|
||||
for param_def in PARAMETER_DEFINITIONS:
|
||||
strategy_name = param_def.get('strategy')
|
||||
if strategy_name in ('weight', 'signal', 'risk', 'market_state',
|
||||
'trend_weights', 'trend_thresholds', 'confidence'):
|
||||
continue
|
||||
if strategy_name not in strategy_params:
|
||||
strategy_params[strategy_name] = {}
|
||||
|
||||
# 参数名映射(保持与旧版兼容)
|
||||
param_name = param_def['name']
|
||||
if param_name == 'ma_cross_short_window':
|
||||
param_name = 'short_window'
|
||||
elif param_name == 'ma_cross_long_window':
|
||||
param_name = 'long_window'
|
||||
elif param_name == 'rsi_period':
|
||||
param_name = 'period'
|
||||
elif param_name == 'rsi_overbought':
|
||||
param_name = 'overbought'
|
||||
elif param_name == 'rsi_oversold':
|
||||
param_name = 'oversold'
|
||||
elif param_name == 'bollinger_period':
|
||||
param_name = 'period'
|
||||
elif param_name == 'bollinger_std_dev':
|
||||
param_name = 'std_dev'
|
||||
elif param_name == 'macd_fast_ema':
|
||||
param_name = 'fast_ema'
|
||||
elif param_name == 'macd_slow_ema':
|
||||
param_name = 'slow_ema'
|
||||
elif param_name == 'macd_signal_period':
|
||||
param_name = 'signal_period'
|
||||
elif param_name == 'mean_reversion_period':
|
||||
param_name = 'period'
|
||||
elif param_name == 'mean_reversion_std_dev':
|
||||
param_name = 'std_dev'
|
||||
elif param_name == 'momentum_breakout_period':
|
||||
param_name = 'period'
|
||||
elif param_name == 'momentum_breakout_momentum_period':
|
||||
param_name = 'momentum_period'
|
||||
elif param_name == 'kdj_period':
|
||||
param_name = 'period'
|
||||
elif param_name == 'turtle_period':
|
||||
param_name = 'period'
|
||||
elif param_name.startswith("wave_"):
|
||||
param_name = param_name[5:]
|
||||
if param_name == "period":
|
||||
param_name = "wave_period"
|
||||
elif param_name == 'daily_breakout_bars_count':
|
||||
param_name = 'bars_count'
|
||||
|
||||
strategy_params[strategy_name][param_name] = parsed[param_def['name']]
|
||||
return strategy_params
|
||||
|
||||
|
||||
def _extract_weight_genes(parsed: dict) -> dict:
|
||||
"""★ 提取权重基因 → {config_key: weight}
|
||||
|
||||
这些权重会通过 individual_weights 参数传递给
|
||||
MarketStateAnalyzer.get_strategy_weights(),使权重基因真正生效。
|
||||
"""
|
||||
weight_genes = {}
|
||||
for param_def in PARAMETER_DEFINITIONS:
|
||||
if param_def.get('strategy') != 'weight':
|
||||
continue
|
||||
class_name = param_def['name'].replace('weight_', '')
|
||||
config_key = _class_name_to_config_key.get(class_name, class_name.lower())
|
||||
weight_genes[config_key] = parsed[param_def['name']]
|
||||
return weight_genes
|
||||
|
||||
|
||||
def _precompute_h1_states(multi_tf: MultiTimeframeDataStore, analyzer: MarketStateAnalyzer) -> list:
|
||||
"""预计算H1市场状态序列 → 映射到M1时间轴"""
|
||||
h1_df = multi_tf.ensure_timeframe(PERIOD_H1)
|
||||
if h1_df is None or len(h1_df) == 0:
|
||||
return [("none", 0.0)] * multi_tf.length
|
||||
|
||||
m1_index = multi_tf.main_df.index
|
||||
h1_index = h1_df.index
|
||||
|
||||
# 计算每个H1 bar的状态
|
||||
h1_states = []
|
||||
for i in range(len(h1_df)):
|
||||
lookback = analyzer.trend_period + 10
|
||||
start = max(0, i - lookback)
|
||||
df_slice = h1_df.iloc[start:i + 1]
|
||||
state, conf = analyzer._calculate_state_from_df(df_slice)
|
||||
h1_states.append((state, conf))
|
||||
|
||||
# 映射到M1时间轴
|
||||
m1_states = []
|
||||
h1_pos = 0
|
||||
first_h1_time = h1_index[0]
|
||||
|
||||
for m1_time in m1_index:
|
||||
if m1_time < first_h1_time:
|
||||
m1_states.append(("none", 0.0))
|
||||
continue
|
||||
while h1_pos < len(h1_index) - 1 and m1_time >= h1_index[h1_pos + 1]:
|
||||
h1_pos += 1
|
||||
if h1_pos < len(h1_states):
|
||||
m1_states.append(h1_states[h1_pos])
|
||||
else:
|
||||
m1_states.append(("none", 0.0))
|
||||
|
||||
return m1_states
|
||||
|
||||
|
||||
def evaluate_fitness(individual, multi_tf: MultiTimeframeDataStore,
|
||||
_signals_df_unused=None) -> tuple:
|
||||
"""★ 重写的适应度函数 — 所有权重基因、策略参数和风控参数真正生效
|
||||
|
||||
三个关键基因全部生效:
|
||||
1. 策略参数 → 重新实例化策略并 run_backtest → 影响信号序列
|
||||
2. 权重基因 → per-bar get_strategy_weights(individual_weights=...) → 影响信号组合
|
||||
3. 风控参数 → PositionManager 在回测中真正使用
|
||||
"""
|
||||
parsed = _parse_individual(individual)
|
||||
|
||||
# 1. 提取策略参数
|
||||
strategy_params = _extract_strategy_params(parsed)
|
||||
|
||||
# 2. ★ 提取权重基因(核心修复)
|
||||
weight_genes = _extract_weight_genes(parsed)
|
||||
|
||||
# 3. 提取风控参数
|
||||
risk_params = {
|
||||
'stop_loss_pct': parsed.get('stop_loss_pct', RISK_CONFIG.get('stop_loss_pct', -0.01)),
|
||||
'profit_retracement_pct': parsed.get('profit_retracement_pct', RISK_CONFIG.get('profit_retracement_pct', 0.10)),
|
||||
'min_profit_for_trailing': parsed.get('min_profit_for_trailing', RISK_CONFIG.get('min_profit_for_trailing', 0.01)),
|
||||
'take_profit_pct': parsed.get('take_profit_pct', RISK_CONFIG.get('take_profit_pct', 0.20)),
|
||||
'max_holding_minutes': parsed.get('max_holding_minutes', RISK_CONFIG.get('max_holding_minutes', 60)),
|
||||
'min_profit_for_time_exit': parsed.get('min_profit_for_time_exit', RISK_CONFIG.get('min_profit_for_time_exit', 0.005)),
|
||||
'max_daily_loss': parsed.get('max_daily_loss', RISK_CONFIG.get('max_daily_loss', -0.30)),
|
||||
}
|
||||
|
||||
# 4. 提取市场状态和趋势参数
|
||||
market_params = {
|
||||
'trend_period': parsed.get('market_trend_period', MARKET_STATE_CONFIG.get('trend_period', 50)),
|
||||
'retracement_tolerance': parsed.get('market_retracement_tolerance', MARKET_STATE_CONFIG.get('retracement_tolerance', 0.30)),
|
||||
'volume_period': parsed.get('market_volume_period', MARKET_STATE_CONFIG.get('volume_period', 20)),
|
||||
'volume_ma_period': parsed.get('market_volume_ma_period', MARKET_STATE_CONFIG.get('volume_ma_period', 10)),
|
||||
'hourly_data_count': 100,
|
||||
}
|
||||
trend_weights = {
|
||||
'price_breakout': parsed.get('trend_price_breakout_weight', 0.35),
|
||||
'volume_confirmation': parsed.get('trend_volume_confirmation_weight', 0.25),
|
||||
'momentum oscillator': parsed.get('trend_momentum_oscillator_weight', 0.20),
|
||||
'moving_average': parsed.get('trend_moving_average_weight', 0.20),
|
||||
}
|
||||
trend_thresholds = {
|
||||
'strong_trend': parsed.get('trend_strong_threshold', 0.6),
|
||||
'weak_trend': parsed.get('trend_weak_threshold', 0.3),
|
||||
'volume_spike': parsed.get('trend_volume_spike', 1.5),
|
||||
'oversold': parsed.get('trend_oversold', 30),
|
||||
'overbought': parsed.get('trend_overbought', 70),
|
||||
}
|
||||
confidence_thresholds = {
|
||||
'high_confidence': parsed.get('confidence_high', 0.7),
|
||||
'medium_confidence': parsed.get('confidence_medium', 0.4),
|
||||
}
|
||||
|
||||
# 5. 构建 MarketStateAnalyzer 并预计算H1状态
|
||||
analyzer = MarketStateAnalyzer(
|
||||
timeframe=PERIOD_H1,
|
||||
market_state_params=market_params,
|
||||
trend_weights=trend_weights,
|
||||
trend_thresholds=trend_thresholds,
|
||||
confidence_thresholds=confidence_thresholds,
|
||||
)
|
||||
analyzer._precomputed_states = _precompute_h1_states(multi_tf, analyzer)
|
||||
|
||||
# 5.5. ★ 使用个体的策略参数重新实例化策略并重新计算信号
|
||||
# 将 class_name → {params} 映射为 config_key → {params}
|
||||
strategy_params_by_key = {
|
||||
_class_name_to_config_key.get(cn, cn.lower()): params
|
||||
for cn, params in strategy_params.items()
|
||||
}
|
||||
registry = StrategyRegistry()
|
||||
sig_instances = registry.instantiate_all(SYMBOL, TIMEFRAME, strategy_params_by_key)
|
||||
signals_df = pd.DataFrame(index=multi_tf.main_df.index)
|
||||
for config_key, strat in sig_instances.items():
|
||||
try:
|
||||
sig = strat.run_backtest(multi_tf.main_df)
|
||||
signals_df[config_key] = sig if sig is not None else pd.Series(0, index=signals_df.index)
|
||||
except Exception:
|
||||
signals_df[config_key] = pd.Series(0, index=signals_df.index)
|
||||
|
||||
# 6. 回测环境
|
||||
data_provider = BacktestDataProvider(multi_tf, INITIAL_CAPITAL)
|
||||
pm = PositionManager(data_provider, trade_direction="both", risk_config=risk_params,
|
||||
persist_peaks=False)
|
||||
|
||||
buy_th = parsed.get('buy_threshold', 1.5)
|
||||
sell_th = parsed.get('sell_threshold', -1.5)
|
||||
total_bars = multi_tf.length
|
||||
|
||||
# 7. ★ 逐 bar 回测(权重基因真正影响信号组合)
|
||||
for bar_index in range(total_bars):
|
||||
current_price = data_provider.get_current_price(SYMBOL)
|
||||
if not current_price:
|
||||
data_provider.tick()
|
||||
continue
|
||||
|
||||
# 获取当前 bar 的市场状态
|
||||
state, conf = analyzer.get_market_state(bar_index)
|
||||
|
||||
# ★ 权重基因在这里生效
|
||||
weights = analyzer.get_strategy_weights(
|
||||
state, conf, individual_weights=weight_genes
|
||||
)
|
||||
|
||||
# 组合当前 bar 的信号(使用个体参数重新计算的信号)
|
||||
bar_signals = {
|
||||
col: signals_df[col].iloc[bar_index]
|
||||
for col in signals_df.columns
|
||||
}
|
||||
final_signal = SignalCombiner.combine_at_bar(bar_signals, weights, buy_th, sell_th)
|
||||
|
||||
# 执行交易
|
||||
if final_signal == 1:
|
||||
pm.open_position("buy", current_price, 1.0, dry_run=True)
|
||||
elif final_signal == -1:
|
||||
pm.open_position("sell", current_price, 1.0, dry_run=True)
|
||||
|
||||
pm.monitor_positions(current_price, dry_run=True)
|
||||
data_provider.tick()
|
||||
|
||||
# 8. 适应度 = 总盈亏
|
||||
total_pnl = pm.total_equity - INITIAL_CAPITAL
|
||||
return (total_pnl,)
|
||||
|
||||
|
||||
def load_historical_data():
|
||||
"""一次性加载M1数据并返回 MultiTimeframeDataStore"""
|
||||
initialize()
|
||||
rates = (
|
||||
get_rates(SYMBOL, TIMEFRAME, OPTIMIZER_COUNT, OPTIMIZER_START_DATE, OPTIMIZER_END_DATE)
|
||||
if USE_DATE_RANGE else
|
||||
get_rates(SYMBOL, TIMEFRAME, OPTIMIZER_COUNT)
|
||||
)
|
||||
shutdown()
|
||||
|
||||
if rates is None or len(rates) == 0:
|
||||
raise RuntimeError("获取历史数据失败")
|
||||
|
||||
if len(rates) > OPTIMIZER_COUNT:
|
||||
rates = rates[-OPTIMIZER_COUNT:]
|
||||
|
||||
store = MultiTimeframeDataStore()
|
||||
store.load_m1_data(rates)
|
||||
|
||||
# 预生成H1数据
|
||||
store.ensure_timeframe(PERIOD_H1)
|
||||
|
||||
return store
|
||||
|
||||
|
||||
def precompute_all_signals(multi_tf: MultiTimeframeDataStore,
|
||||
strategy_params: dict = None) -> pd.DataFrame:
|
||||
"""使用默认参数预计算所有策略的回测信号"""
|
||||
registry = StrategyRegistry()
|
||||
strategies = registry.instantiate_all(SYMBOL, TIMEFRAME, strategy_params)
|
||||
signals_df = pd.DataFrame(index=multi_tf.main_df.index)
|
||||
|
||||
for config_key, strategy in strategies.items():
|
||||
try:
|
||||
sig = strategy.run_backtest(multi_tf.main_df)
|
||||
signals_df[config_key] = sig if sig is not None else pd.Series(0, index=signals_df.index)
|
||||
except Exception:
|
||||
signals_df[config_key] = pd.Series(0, index=signals_df.index)
|
||||
|
||||
return signals_df
|
||||
|
||||
|
||||
def run_optimizer():
|
||||
"""运行遗传算法优化(所有基因真正生效)"""
|
||||
try:
|
||||
# 1. 加载数据(一次性)
|
||||
logger.info("加载历史数据...")
|
||||
multi_tf = load_historical_data()
|
||||
logger.info(f"数据加载完成: {multi_tf.length} 条M1数据")
|
||||
|
||||
# 2. DEAP 设置(策略信号在 evaluate_fitness 内部逐个体重新计算)
|
||||
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
|
||||
creator.create("Individual", list, fitness=creator.FitnessMax)
|
||||
|
||||
toolbox = base.Toolbox()
|
||||
|
||||
for i, param_def in enumerate(PARAMETER_DEFINITIONS):
|
||||
if param_def['type'] == 'int':
|
||||
toolbox.register(f"attr_param_{i}", random.randint, param_def['min'], param_def['max'])
|
||||
else:
|
||||
toolbox.register(f"attr_param_{i}", random.uniform, param_def['min'], param_def['max'])
|
||||
|
||||
toolbox.register("individual", tools.initIterate, creator.Individual,
|
||||
lambda: [toolbox.__getattribute__(f"attr_param_{j}")()
|
||||
for j in range(len(PARAMETER_DEFINITIONS))])
|
||||
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
|
||||
|
||||
toolbox.register("evaluate", evaluate_fitness,
|
||||
multi_tf=multi_tf)
|
||||
toolbox.register("mate", tools.cxTwoPoint)
|
||||
toolbox.register("mutate", tools.mutGaussian,
|
||||
mu=GENETIC_OPTIMIZER_CONFIG["mutation_mu"],
|
||||
sigma=GENETIC_OPTIMIZER_CONFIG["mutation_sigma"],
|
||||
indpb=GENETIC_OPTIMIZER_CONFIG["mutation_indpb"])
|
||||
toolbox.register("select", tools.selTournament,
|
||||
tournsize=GENETIC_OPTIMIZER_CONFIG["tournament_size"])
|
||||
|
||||
# 多进程
|
||||
if GENETIC_OPTIMIZER_CONFIG["enable_multiprocessing"]:
|
||||
pool = multiprocessing.Pool(
|
||||
processes=GENETIC_OPTIMIZER_CONFIG["processes"],
|
||||
initializer=init_worker
|
||||
)
|
||||
toolbox.register("map", pool.map)
|
||||
else:
|
||||
toolbox.register("map", map)
|
||||
|
||||
# 4. 统计
|
||||
stats = tools.Statistics(lambda ind: ind.fitness.values)
|
||||
stats.register("avg", np.mean)
|
||||
stats.register("std", np.std)
|
||||
stats.register("min", np.min)
|
||||
stats.register("max", np.max)
|
||||
|
||||
# 5. 运行
|
||||
population = toolbox.population(n=GENETIC_OPTIMIZER_CONFIG["population_size"])
|
||||
ngen = GENETIC_OPTIMIZER_CONFIG["generations"]
|
||||
cxpb = GENETIC_OPTIMIZER_CONFIG["crossover_probability"]
|
||||
mutpb = GENETIC_OPTIMIZER_CONFIG["mutation_probability"]
|
||||
|
||||
logger.info(f"开始进化: 种群 {len(population)}, {ngen} 代")
|
||||
generation_info = []
|
||||
|
||||
for gen in range(ngen):
|
||||
offspring = toolbox.select(population, len(population))
|
||||
offspring = algorithms.varAnd(offspring, toolbox, cxpb, mutpb)
|
||||
fits = toolbox.map(toolbox.evaluate, offspring)
|
||||
|
||||
for fit, ind in zip(fits, offspring):
|
||||
ind.fitness.values = fit
|
||||
|
||||
population[:] = offspring
|
||||
|
||||
best = tools.selBest(population, k=1)[0]
|
||||
best_fit = best.fitness.values[0]
|
||||
avg_fit = np.mean([ind.fitness.values[0] for ind in population])
|
||||
|
||||
if GENETIC_OPTIMIZER_CONFIG.get("save_generation_info", True):
|
||||
generation_info.append({
|
||||
'generation': gen + 1,
|
||||
'best_fitness': best_fit,
|
||||
'avg_fitness': avg_fit,
|
||||
})
|
||||
|
||||
if GENETIC_OPTIMIZER_CONFIG.get("verbose", True):
|
||||
logger.info(f"代数 {gen + 1}/{ngen}: 最佳={best_fit:.2f}, 平均={avg_fit:.2f}")
|
||||
|
||||
# 6. 结果
|
||||
if GENETIC_OPTIMIZER_CONFIG["enable_multiprocessing"]:
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
best_individual = tools.selBest(population, k=1)[0]
|
||||
best_fitness = best_individual.fitness.values[0]
|
||||
|
||||
logger.info(f"优化完成: 最佳适应度={best_fitness:.2f}")
|
||||
save_optimization_results(best_individual, best_fitness, generation_info)
|
||||
|
||||
return _parse_individual(best_individual), best_fitness
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
logger.error(f"优化器错误: {e}\n{traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
def save_optimization_results(best_individual, best_fitness, generation_info):
|
||||
"""保存优化结果"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
parsed = _parse_individual(best_individual)
|
||||
|
||||
# JSON
|
||||
import json
|
||||
results = {
|
||||
'best_fitness': best_fitness,
|
||||
'best_params': parsed,
|
||||
'generation_info': generation_info,
|
||||
}
|
||||
json_path = f"optimization_results_{timestamp}.json"
|
||||
with open(json_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2, default=str)
|
||||
logger.info(f"结果已保存: {json_path}")
|
||||
|
||||
# TXT报告
|
||||
txt_path = f"optimization_report_{timestamp}.txt"
|
||||
with open(txt_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"优化完成时间: {datetime.now()}\n")
|
||||
f.write(f"最佳适应度: {best_fitness:.2f}\n\n")
|
||||
f.write("最佳参数:\n")
|
||||
for key, value in parsed.items():
|
||||
f.write(f" {key}: {value}\n")
|
||||
f.write("\n代数统计:\n")
|
||||
for gi in generation_info:
|
||||
f.write(f" 代数 {gi['generation']}: 最佳={gi['best_fitness']:.2f}, 平均={gi['avg_fitness']:.2f}\n")
|
||||
logger.info(f"报告已保存: {txt_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_optimizer()
|
||||
@@ -5,7 +5,7 @@ from datetime import datetime
|
||||
from logger import logger
|
||||
from config import SYMBOL, TIMEFRAME, REALTIME_CONFIG, SIGNAL_THRESHOLDS
|
||||
from core.risk import RiskController
|
||||
from execution.dynamic_weights import DynamicWeightManager
|
||||
from execution.weights import DynamicWeightManager
|
||||
|
||||
class RealtimeTrader:
|
||||
"""实时交易器 (已重构为依赖注入)"""
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""动态权重管理器(重写版)
|
||||
|
||||
关键改进:
|
||||
1. 使用 StrategyRegistry(单例)获取策略列表,消除 blueprint 重复
|
||||
2. 回测/优化器模式支持逐 bar 获取权重(get_weights_for_bar)
|
||||
3. 优化器模式接受 individual_weights 参数并传递给 MarketStateAnalyzer
|
||||
"""
|
||||
|
||||
import strategies # noqa: F401 — 触发策略注册
|
||||
from logger import logger
|
||||
from core.risk.market_state import MarketStateAnalyzer
|
||||
from core.signal.registry import StrategyRegistry
|
||||
from config import SYMBOL, TIMEFRAME
|
||||
|
||||
|
||||
class DynamicWeightManager:
|
||||
"""动态权重管理器"""
|
||||
|
||||
def __init__(self, data_provider,
|
||||
market_state_analyzer: MarketStateAnalyzer = None):
|
||||
self.data_provider = data_provider
|
||||
self.analyzer = market_state_analyzer or MarketStateAnalyzer(data_provider)
|
||||
self.registry = StrategyRegistry()
|
||||
|
||||
# 初始化策略实例(仅实盘模式首次使用)
|
||||
self._strategies_initialized = False
|
||||
|
||||
def _ensure_strategies(self):
|
||||
if not self._strategies_initialized:
|
||||
self.registry.instantiate_all(SYMBOL, TIMEFRAME,
|
||||
data_provider=self.data_provider)
|
||||
self._strategies_initialized = True
|
||||
|
||||
# ── 实盘模式 ──
|
||||
|
||||
def get_current_strategies_and_weights(self) -> list:
|
||||
"""获取当前(实时)策略实例和权重列表"""
|
||||
self._ensure_strategies()
|
||||
weights = self.get_current_weights()
|
||||
|
||||
result = []
|
||||
for config_key, weight in weights.items():
|
||||
instance = self.registry.get_instance(config_key)
|
||||
if instance is not None:
|
||||
result.append((instance, weight))
|
||||
return result
|
||||
|
||||
def get_current_weights(self) -> dict:
|
||||
"""获取当前实时权重"""
|
||||
market_state, confidence = self.analyzer.get_market_state()
|
||||
return self.analyzer.get_strategy_weights(market_state, confidence)
|
||||
|
||||
# ── 回测/优化器模式 ──
|
||||
|
||||
def get_weights_for_bar(self, bar_index: int,
|
||||
individual_weights: dict = None) -> dict:
|
||||
"""获取第 bar_index 个 bar 的策略权重
|
||||
|
||||
Args:
|
||||
bar_index: 当前M1 bar索引
|
||||
individual_weights: 优化器传入 {config_key: weight},非None时直接使用
|
||||
|
||||
Returns:
|
||||
{config_key: weight}
|
||||
"""
|
||||
market_state, confidence = self.analyzer.get_market_state(bar_index)
|
||||
return self.analyzer.get_strategy_weights(
|
||||
market_state, confidence, individual_weights
|
||||
)
|
||||
|
||||
def get_weight_info(self) -> dict:
|
||||
market_state, confidence = self.analyzer.get_market_state()
|
||||
market_weights = self.analyzer.get_strategy_weights(market_state, confidence)
|
||||
return {
|
||||
'market_state': market_state,
|
||||
'confidence': confidence,
|
||||
'weights': market_weights
|
||||
}
|
||||
Reference in New Issue
Block a user