Files
mt5_python_ea_suite/execution/optimize.py
T

619 lines
27 KiB
Python
Raw Normal View History

"""遗传算法优化器(重写版)
关键改进:
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,
DATA_PROVIDER_MODE, REMOTE_SERVER_HOST, REMOTE_SERVER_PORT
)
from utils.constants import PERIOD_H1
from core.utils import get_rates, initialize, shutdown
from core.data.remote import RemoteDataProvider
from logger import logger
# 导入策略模块确保 StrategyRegistry 已注册
import strategies # noqa: F401
# ── 全局缓存 ──
_multi_tf: MultiTimeframeDataStore | None = None
_cached_signals: pd.DataFrame | None = None
_registry: StrategyRegistry | None = None
# MT5 结构化数组 dtype(用于远程 API JSON → numpy 转换)
_MT5_RATES_DTYPE = np.dtype([
('time', 'i8'),
('open', 'f8'),
('high', 'f8'),
('low', 'f8'),
('close', 'f8'),
('tick_volume', 'i8'),
('spread', 'i4'),
('real_volume', 'i8'),
])
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': -1.00, 'max': -0.10, 'strategy': 'risk'},
{'name': 'profit_retracement_pct', 'type': 'float', 'min': 0.10, 'max': 0.80, 'strategy': 'risk'},
{'name': 'min_profit_for_trailing', 'type': 'float', 'min': 0.30, 'max': 2.00, 'strategy': 'risk'},
{'name': 'take_profit_pct', 'type': 'float', 'min': 0.50, 'max': 3.00, 'strategy': 'risk'},
{'name': 'max_holding_minutes', 'type': 'int', 'min': 30, 'max': 180, 'strategy': 'risk'},
{'name': 'min_profit_for_time_exit', 'type': 'float', 'min': 0.02, 'max': 0.20, '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 _json_to_mt5_rates(rates_data):
"""将远程API JSON rates 转换为 MT5 兼容的 numpy 结构化数组"""
if not rates_data:
return None
records = []
for r in rates_data:
records.append((
r['time'], r['open'], r['high'], r['low'], r['close'],
r.get('tick_volume', 0), r.get('spread', 0), r.get('real_volume', 0),
))
return np.array(records, dtype=_MT5_RATES_DTYPE)
def load_historical_data():
"""一次性加载M1数据并返回 MultiTimeframeDataStore"""
if DATA_PROVIDER_MODE == "remote":
provider = RemoteDataProvider(host=REMOTE_SERVER_HOST, port=REMOTE_SERVER_PORT)
if not provider.initialize():
raise RuntimeError("远程MT5 API初始化失败,请检查 Windows MT5 是否运行")
rates_json = provider.get_historical_data(SYMBOL, TIMEFRAME, OPTIMIZER_COUNT)
provider.shutdown()
if not rates_json:
raise RuntimeError("远程获取历史数据失败")
rates = _json_to_mt5_rates(rates_json)
else:
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()