mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-08-02 13:47:43 +00:00
add files
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# 风险管理模块
|
||||
|
||||
此模块包含所有风险管理相关的组件:
|
||||
|
||||
- `market_state.py`: 市场状态分析器,基于resilient_trend逻辑
|
||||
- `position_manager.py`: 仓位管理器,基于profit_protect逻辑
|
||||
- `__init__.py`: 风险管理控制器,统一接口
|
||||
@@ -0,0 +1,93 @@
|
||||
from .market_state import MarketStateAnalyzer
|
||||
from .position_manager import PositionManager
|
||||
from logger import logger
|
||||
from config import RISK_CONFIG
|
||||
|
||||
class RiskController:
|
||||
"""
|
||||
风险管理控制器,统一管理市场状态分析和仓位管理
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.market_state_analyzer = MarketStateAnalyzer()
|
||||
self.position_manager = PositionManager()
|
||||
|
||||
# 从配置文件读取风险控制参数
|
||||
self.max_position_size = RISK_CONFIG.get("max_position_size", 0.1)
|
||||
self.max_daily_loss = RISK_CONFIG.get("max_daily_loss", -0.05)
|
||||
self.daily_pnl = 0.0 # 日内盈亏
|
||||
|
||||
def get_market_state(self):
|
||||
"""
|
||||
获取当前市场状态
|
||||
"""
|
||||
return self.market_state_analyzer.get_market_state()
|
||||
|
||||
def get_dynamic_weights(self):
|
||||
"""
|
||||
获取动态策略权重
|
||||
"""
|
||||
market_state, confidence = self.get_market_state()
|
||||
logger.info(f"当前市场状态: {market_state}, 置信度: {confidence:.2f}")
|
||||
|
||||
weights = self.market_state_analyzer.get_strategy_weights(market_state, confidence)
|
||||
logger.info(f"动态权重配置: {weights}")
|
||||
|
||||
return weights, market_state, confidence
|
||||
|
||||
def check_risk_management(self, current_price):
|
||||
"""
|
||||
检查风险管理条件
|
||||
"""
|
||||
return self.position_manager.check_risk_management(current_price)
|
||||
|
||||
def execute_risk_action(self, action, reason):
|
||||
"""
|
||||
执行风险管理操作
|
||||
"""
|
||||
self.position_manager.execute_risk_action(action, reason)
|
||||
|
||||
def update_position_entry(self, entry_price, position_type="long"):
|
||||
"""
|
||||
更新持仓入场信息
|
||||
"""
|
||||
self.position_manager.update_position_entry(entry_price, position_type)
|
||||
|
||||
def should_allow_trade(self, signal_type):
|
||||
"""
|
||||
根据风险控制决定是否允许交易
|
||||
"""
|
||||
# 检查日亏损限制
|
||||
if self.daily_pnl <= self.max_daily_loss:
|
||||
logger.warning(f"日亏损已达{self.daily_pnl:.2%},暂停交易")
|
||||
return False
|
||||
|
||||
# 检查持仓数量限制
|
||||
current_positions = len(self.position_manager.positions)
|
||||
if current_positions >= 1: # 限制同时只持有一个仓位
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def update_daily_pnl(self, pnl_change):
|
||||
"""
|
||||
更新日内盈亏
|
||||
"""
|
||||
self.daily_pnl += pnl_change
|
||||
logger.info(f"更新日内盈亏: {self.daily_pnl:.2%}")
|
||||
|
||||
def reset_daily_stats(self):
|
||||
"""
|
||||
重置日内统计
|
||||
"""
|
||||
self.daily_pnl = 0.0
|
||||
logger.info("重置日内统计")
|
||||
|
||||
def reset_all_states(self):
|
||||
"""
|
||||
重置所有状态
|
||||
"""
|
||||
self.market_state_analyzer.reset_state()
|
||||
self.position_manager.reset_positions()
|
||||
self.reset_daily_stats()
|
||||
logger.info("重置所有风险管理状态")
|
||||
@@ -0,0 +1,312 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import MetaTrader5 as mt5
|
||||
from logger import logger
|
||||
from utils import get_rates
|
||||
from config import MARKET_STATE_CONFIG, SYMBOL, DEFAULT_WEIGHTS, TREND_INDICATOR_WEIGHTS, TREND_THRESHOLDS
|
||||
|
||||
class MarketStateAnalyzer:
|
||||
"""
|
||||
市场状态分析器,基于resilient_trend逻辑
|
||||
判断当前市场状态:uptrend, downtrend, none
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.symbol = SYMBOL
|
||||
self.timeframe = mt5.TIMEFRAME_H1 # 使用小时线数据
|
||||
self.hourly_data_count = 100 # 获取最近100小时的数据
|
||||
self.trend_period = MARKET_STATE_CONFIG.get("trend_period", 50)
|
||||
self.retracement_tolerance = MARKET_STATE_CONFIG.get("retracement_tolerance", 0.30)
|
||||
self.volume_period = MARKET_STATE_CONFIG.get("volume_period", 20)
|
||||
self.volume_ma_period = MARKET_STATE_CONFIG.get("volume_ma_period", 10)
|
||||
|
||||
# 市场状态变量
|
||||
self.current_trend = "none" # none, uptrend, downtrend
|
||||
self.trend_peak = 0.0
|
||||
self.trend_trough = float('inf')
|
||||
|
||||
# 趋势指标权重
|
||||
self.indicator_weights = TREND_INDICATOR_WEIGHTS
|
||||
self.thresholds = TREND_THRESHOLDS
|
||||
|
||||
def calculate_volume_indicators(self, df):
|
||||
"""计算成交量指标"""
|
||||
# MT5使用tick_volume和real_volume,这里使用tick_volume作为成交量
|
||||
df['volume'] = df['tick_volume'] # 将tick_volume映射为volume以便后续计算
|
||||
|
||||
# 成交量移动平均
|
||||
df['volume_ma'] = df['volume'].rolling(self.volume_ma_period).mean()
|
||||
|
||||
# 成交量相对强度
|
||||
df['volume_ratio'] = df['volume'] / df['volume_ma']
|
||||
|
||||
# 价量相关性 (最近n期)
|
||||
volume_corr = df['close'].rolling(self.volume_period).corr(df['volume'])
|
||||
|
||||
return df, volume_corr.iloc[-1] if len(volume_corr) > 0 else 0
|
||||
|
||||
def calculate_momentum_indicators(self, df):
|
||||
"""计算动量指标"""
|
||||
# RSI
|
||||
delta = df['close'].diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
|
||||
rs = gain / loss
|
||||
df['rsi'] = 100 - (100 / (1 + rs))
|
||||
|
||||
# MACD
|
||||
exp1 = df['close'].ewm(span=12).mean()
|
||||
exp2 = df['close'].ewm(span=26).mean()
|
||||
df['macd'] = exp1 - exp2
|
||||
df['macd_signal'] = df['macd'].ewm(span=9).mean()
|
||||
df['macd_histogram'] = df['macd'] - df['macd_signal']
|
||||
|
||||
# 移动平均线
|
||||
df['ma20'] = df['close'].rolling(20).mean()
|
||||
df['ma50'] = df['close'].rolling(50).mean()
|
||||
|
||||
return df
|
||||
|
||||
def calculate_price_breakout_score(self, df):
|
||||
"""计算价格突破得分 (0-1)"""
|
||||
current_price = df['close'].iloc[-1]
|
||||
high_period = df['high'].rolling(self.trend_period).max().iloc[-2]
|
||||
low_period = df['low'].rolling(self.trend_period).min().iloc[-2]
|
||||
|
||||
# 计算价格在区间中的位置
|
||||
price_position = (current_price - low_period) / (high_period - low_period)
|
||||
|
||||
# 突破确认
|
||||
if current_price > high_period:
|
||||
return 0.8 # 向上突破
|
||||
elif current_price < low_period:
|
||||
return 0.2 # 向下突破
|
||||
else:
|
||||
return price_position # 在区间内的位置
|
||||
|
||||
def calculate_volume_score(self, df, volume_corr):
|
||||
"""计算成交量确认得分 (0-1)"""
|
||||
current_volume = df['volume'].iloc[-1]
|
||||
volume_ma = df['volume_ma'].iloc[-1]
|
||||
volume_ratio = current_volume / volume_ma if volume_ma > 0 else 1
|
||||
|
||||
score = 0.5 # 基础分数
|
||||
|
||||
# 成交量突增确认
|
||||
if volume_ratio > self.thresholds['volume_spike']:
|
||||
score += 0.3
|
||||
|
||||
# 价量相关性
|
||||
if volume_corr > 0.5: # 正相关,价量齐升
|
||||
score += 0.2
|
||||
elif volume_corr < -0.5: # 负相关,价跌量增
|
||||
score -= 0.2
|
||||
|
||||
return np.clip(score, 0, 1)
|
||||
|
||||
def calculate_momentum_score(self, df):
|
||||
"""计算动量指标得分 (0-1)"""
|
||||
current_rsi = df['rsi'].iloc[-1] if not pd.isna(df['rsi'].iloc[-1]) else 50
|
||||
current_macd_hist = df['macd_histogram'].iloc[-1] if not pd.isna(df['macd_histogram'].iloc[-1]) else 0
|
||||
|
||||
score = 0.5 # 基础分数
|
||||
|
||||
# RSI判断
|
||||
if current_rsi > self.thresholds['overbought']:
|
||||
score += 0.2 # 超买,可能继续上涨
|
||||
elif current_rsi < self.thresholds['oversold']:
|
||||
score -= 0.2 # 超卖,可能继续下跌
|
||||
|
||||
# MACD直方图判断
|
||||
if current_macd_hist > 0:
|
||||
score += 0.2 # MACD正向动能
|
||||
else:
|
||||
score -= 0.2 # MACD负向动能
|
||||
|
||||
return np.clip(score, 0, 1)
|
||||
|
||||
def calculate_ma_score(self, df):
|
||||
"""计算移动平均线得分 (0-1)"""
|
||||
current_price = df['close'].iloc[-1]
|
||||
ma20 = df['ma20'].iloc[-1] if not pd.isna(df['ma20'].iloc[-1]) else current_price
|
||||
ma50 = df['ma50'].iloc[-1] if not pd.isna(df['ma50'].iloc[-1]) else current_price
|
||||
|
||||
score = 0.5 # 基础分数
|
||||
|
||||
# 价格与均线关系
|
||||
if current_price > ma20 > ma50:
|
||||
score += 0.3 # 多头排列
|
||||
elif current_price < ma20 < ma50:
|
||||
score -= 0.3 # 空头排列
|
||||
|
||||
# 均线方向
|
||||
if len(df) >= 3:
|
||||
ma20_slope = df['ma20'].iloc[-1] - df['ma20'].iloc[-3]
|
||||
ma50_slope = df['ma50'].iloc[-1] - df['ma50'].iloc[-3]
|
||||
|
||||
if ma20_slope > 0 and ma50_slope > 0:
|
||||
score += 0.2 # 均线向上
|
||||
elif ma20_slope < 0 and ma50_slope < 0:
|
||||
score -= 0.2 # 均线向下
|
||||
|
||||
return np.clip(score, 0, 1)
|
||||
|
||||
def get_market_state(self):
|
||||
"""
|
||||
获取当前市场状态 - 使用加权多指标分析
|
||||
返回: (state, confidence)
|
||||
state: "uptrend", "downtrend", "none", "ranging"
|
||||
confidence: 0.0-1.0 的置信度
|
||||
"""
|
||||
logger.info(f"开始获取市场状态数据: {self.symbol}, 时间周期: {self.timeframe}, 数据量: {self.hourly_data_count}")
|
||||
|
||||
rates = get_rates(self.symbol, self.timeframe, self.hourly_data_count)
|
||||
|
||||
# 检查数据获取状态
|
||||
if rates is None:
|
||||
logger.error(f"获取市场数据失败: {self.symbol}")
|
||||
return "none", 0.0
|
||||
|
||||
logger.info(f"成功获取数据,数据量: {len(rates)}, 需要最小数据量: {max(self.trend_period, 50)}")
|
||||
|
||||
if len(rates) < max(self.trend_period, 50):
|
||||
logger.warning(f"数据量不足: 当前{len(rates)}, 需要{max(self.trend_period, 50)}")
|
||||
return "none", 0.0
|
||||
|
||||
df = pd.DataFrame(rates)
|
||||
|
||||
# 计算各项指标
|
||||
df, volume_corr = self.calculate_volume_indicators(df)
|
||||
df = self.calculate_momentum_indicators(df)
|
||||
|
||||
# 计算各项得分
|
||||
price_score = self.calculate_price_breakout_score(df)
|
||||
volume_score = self.calculate_volume_score(df, volume_corr)
|
||||
momentum_score = self.calculate_momentum_score(df)
|
||||
ma_score = self.calculate_ma_score(df)
|
||||
|
||||
logger.info(f"各项指标得分: 价格突破={price_score:.3f}, 成交量确认={volume_score:.3f}, "
|
||||
f"动量指标={momentum_score:.3f}, 移动平均={ma_score:.3f}")
|
||||
|
||||
# 加权计算趋势强度
|
||||
trend_strength = (
|
||||
price_score * self.indicator_weights['price_breakout'] +
|
||||
volume_score * self.indicator_weights['volume_confirmation'] +
|
||||
momentum_score * self.indicator_weights['momentum oscillator'] +
|
||||
ma_score * self.indicator_weights['moving_average']
|
||||
)
|
||||
|
||||
logger.info(f"趋势强度计算结果: {trend_strength:.3f}")
|
||||
logger.info(f"阈值参考: 强趋势={self.thresholds['strong_trend']}, 弱趋势={self.thresholds['weak_trend']}")
|
||||
|
||||
# 判断市场状态
|
||||
if trend_strength >= self.thresholds['strong_trend']:
|
||||
state = "uptrend"
|
||||
confidence = min(0.9, trend_strength)
|
||||
logger.info(f"判断为上涨趋势: 趋势强度{trend_strength:.3f} >= 强趋势阈值{self.thresholds['strong_trend']}")
|
||||
elif trend_strength <= (1 - self.thresholds['strong_trend']):
|
||||
state = "downtrend"
|
||||
confidence = min(0.9, 1 - trend_strength)
|
||||
logger.info(f"判断为下跌趋势: 趋势强度{trend_strength:.3f} <= {1 - self.thresholds['strong_trend']:.3f}")
|
||||
elif trend_strength >= self.thresholds['weak_trend']:
|
||||
state = "ranging"
|
||||
confidence = 0.6
|
||||
logger.info(f"判断为震荡市场: 趋势强度{trend_strength:.3f} 在弱趋势阈值{self.thresholds['weak_trend']}和强趋势阈值{self.thresholds['strong_trend']}之间")
|
||||
else:
|
||||
state = "none"
|
||||
confidence = 0.4
|
||||
logger.info(f"判断为无明确趋势: 趋势强度{trend_strength:.3f} 低于弱趋势阈值{self.thresholds['weak_trend']}")
|
||||
|
||||
# 更新趋势状态(用于状态机逻辑)
|
||||
self.update_trend_state(df, trend_strength, state)
|
||||
|
||||
logger.debug(f"市场状态分析: {state}, 置信度: {confidence:.2f}, "
|
||||
f"趋势强度: {trend_strength:.2f}, "
|
||||
f"价格得分: {price_score:.2f}, 成交量得分: {volume_score:.2f}, "
|
||||
f"动量得分: {momentum_score:.2f}, 均线得分: {ma_score:.2f}")
|
||||
|
||||
return state, confidence
|
||||
|
||||
def update_trend_state(self, df, trend_strength, new_state):
|
||||
"""更新趋势状态(保持原有状态机逻辑的兼容性)"""
|
||||
current_price = df['close'].iloc[-1]
|
||||
|
||||
if new_state == "uptrend":
|
||||
self.current_trend = "uptrend"
|
||||
self.trend_peak = max(self.trend_peak, current_price)
|
||||
elif new_state == "downtrend":
|
||||
self.current_trend = "downtrend"
|
||||
self.trend_trough = min(self.trend_trough, current_price)
|
||||
else:
|
||||
# 检查是否需要反转趋势
|
||||
if self.current_trend == "uptrend":
|
||||
if current_price < self.trend_peak * (1 - self.retracement_tolerance):
|
||||
self.current_trend = "none"
|
||||
elif self.current_trend == "downtrend":
|
||||
if current_price > self.trend_trough * (1 + self.retracement_tolerance):
|
||||
self.current_trend = "none"
|
||||
|
||||
def get_strategy_weights(self, market_state, confidence):
|
||||
"""
|
||||
根据市场状态返回推荐的策略权重配置
|
||||
"""
|
||||
weight_configs = {
|
||||
"uptrend": {
|
||||
"ma_cross": 2.5,
|
||||
"momentum_breakout": 2.2,
|
||||
"turtle": 2.0,
|
||||
"macd": 1.8,
|
||||
"rsi": 1.2,
|
||||
"bollinger": 1.0,
|
||||
"kdj": 0.8,
|
||||
"mean_reversion": 0.5,
|
||||
"daily_breakout": 1.5,
|
||||
"wave_theory": 1.8 # 趋势市中波浪理论权重适中
|
||||
},
|
||||
"downtrend": {
|
||||
"ma_cross": 2.5,
|
||||
"turtle": 2.2,
|
||||
"momentum_breakout": 2.0,
|
||||
"macd": 1.8,
|
||||
"rsi": 1.5,
|
||||
"bollinger": 1.2,
|
||||
"kdj": 1.0,
|
||||
"mean_reversion": 0.8,
|
||||
"daily_breakout": 1.0,
|
||||
"wave_theory": 1.8 # 趋势市中波浪理论权重适中
|
||||
},
|
||||
"ranging": {
|
||||
"rsi": 2.5,
|
||||
"bollinger": 2.2,
|
||||
"mean_reversion": 2.0,
|
||||
"kdj": 1.8,
|
||||
"wave_theory": 3.0, # 震荡市中波浪理论权重最高
|
||||
"ma_cross": 1.2,
|
||||
"macd": 1.0,
|
||||
"turtle": 0.8,
|
||||
"momentum_breakout": 0.5,
|
||||
"daily_breakout": 1.5
|
||||
},
|
||||
"none": DEFAULT_WEIGHTS
|
||||
}
|
||||
|
||||
base_weights = weight_configs.get(market_state, weight_configs["none"])
|
||||
|
||||
# 根据置信度调整权重
|
||||
if confidence > 0.7:
|
||||
# 高置信度,使用推荐权重
|
||||
return base_weights
|
||||
elif confidence > 0.4:
|
||||
# 中等置信度,混合默认权重
|
||||
default_weights = weight_configs["none"]
|
||||
return {k: (base_weights[k] * 0.7 + default_weights[k] * 0.3)
|
||||
for k in base_weights}
|
||||
else:
|
||||
# 低置信度,使用默认权重
|
||||
return weight_configs["none"]
|
||||
|
||||
def reset_state(self):
|
||||
"""重置市场状态"""
|
||||
self.current_trend = "none"
|
||||
self.trend_peak = 0.0
|
||||
self.trend_trough = float('inf')
|
||||
@@ -0,0 +1,105 @@
|
||||
import pandas as pd
|
||||
from logger import logger
|
||||
from utils import has_open_position, close_all
|
||||
from config import RISK_CONFIG, SYMBOL
|
||||
|
||||
class PositionManager:
|
||||
"""
|
||||
仓位管理器,基于profit_protect逻辑
|
||||
处理止损、止盈、追踪止损等风险管理
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.symbol = SYMBOL
|
||||
|
||||
# 从配置文件读取风险管理参数
|
||||
self.stop_loss_pct = RISK_CONFIG.get("stop_loss_pct", -0.10)
|
||||
self.profit_retracement_pct = RISK_CONFIG.get("profit_retracement_pct", 0.30)
|
||||
self.min_profit_for_trailing = RISK_CONFIG.get("min_profit_for_trailing", 0.05)
|
||||
self.take_profit_pct = RISK_CONFIG.get("take_profit_pct", 0.20)
|
||||
|
||||
# 持仓状态
|
||||
self.positions = {} # symbol: {entry_price, entry_time, peak_profit}
|
||||
|
||||
def check_risk_management(self, current_price):
|
||||
"""
|
||||
检查是否需要触发风险管理操作
|
||||
返回: action ("close_long", "close_short", "none"), reason
|
||||
"""
|
||||
# 在回测环境中,使用self.positions来判断持仓状态
|
||||
# 而不是使用has_open_position()(该函数依赖MT5实时数据)
|
||||
position_info = self.positions.get(self.symbol)
|
||||
if not position_info:
|
||||
return "none", "no_position"
|
||||
|
||||
entry_price = position_info['entry_price']
|
||||
current_profit_pct = (current_price - entry_price) / entry_price
|
||||
|
||||
# 更新最高利润
|
||||
self.positions[self.symbol]['peak_profit'] = max(
|
||||
position_info.get('peak_profit', 0),
|
||||
current_profit_pct
|
||||
)
|
||||
peak_profit = self.positions[self.symbol]['peak_profit']
|
||||
|
||||
# 检查止损条件 - 仅基于买入成本判断亏损,不考虑盈利回撤
|
||||
if current_profit_pct < 0 and current_profit_pct <= self.stop_loss_pct:
|
||||
reason = f"止损触发:当前亏损{current_profit_pct:.2%}"
|
||||
action = "close_long" if current_profit_pct < 0 else "close_short"
|
||||
return action, reason
|
||||
|
||||
# 检查固定止盈条件
|
||||
if current_profit_pct >= self.take_profit_pct:
|
||||
reason = f"止盈触发:当前盈利{current_profit_pct:.2%}"
|
||||
action = "close_long" if current_profit_pct > 0 else "close_short"
|
||||
return action, reason
|
||||
|
||||
# 检查追踪止损条件 - 只有在利润达到min_profit_for_trailing以上才激活
|
||||
if peak_profit > self.min_profit_for_trailing:
|
||||
retracement_from_peak = peak_profit - current_profit_pct
|
||||
if peak_profit > 0:
|
||||
retracement_pct = retracement_from_peak / peak_profit
|
||||
if retracement_pct >= self.profit_retracement_pct:
|
||||
reason = f"追踪止损:最高盈利{peak_profit:.2%},回撤{retracement_pct:.2%}"
|
||||
action = "close_long" if current_profit_pct > 0 else "close_short"
|
||||
return action, reason
|
||||
|
||||
return "none", "no_action_needed"
|
||||
|
||||
def execute_risk_action(self, action, reason):
|
||||
"""
|
||||
执行风险管理操作
|
||||
"""
|
||||
if action == "close_long":
|
||||
logger.info(f"执行平多仓:{reason}")
|
||||
close_all(self.symbol)
|
||||
self.positions.pop(self.symbol, None)
|
||||
elif action == "close_short":
|
||||
logger.info(f"执行平空仓:{reason}")
|
||||
close_all(self.symbol)
|
||||
self.positions.pop(self.symbol, None)
|
||||
|
||||
def update_position_entry(self, entry_price, position_type="long"):
|
||||
"""
|
||||
更新持仓入场信息
|
||||
"""
|
||||
self.positions[self.symbol] = {
|
||||
'entry_price': entry_price,
|
||||
'entry_time': pd.Timestamp.now(),
|
||||
'position_type': position_type,
|
||||
'peak_profit': 0.0
|
||||
}
|
||||
logger.info(f"记录持仓入场:价格={entry_price:.2f}, 类型={position_type}")
|
||||
|
||||
def get_position_info(self):
|
||||
"""
|
||||
获取当前持仓信息
|
||||
"""
|
||||
return self.positions.get(self.symbol, None)
|
||||
|
||||
def reset_positions(self):
|
||||
"""
|
||||
重置所有持仓状态
|
||||
"""
|
||||
self.positions.clear()
|
||||
logger.info("重置所有持仓状态")
|
||||
Reference in New Issue
Block a user