add files

This commit is contained in:
songkunling
2025-08-11 18:06:53 +08:00
parent 3f115f1f14
commit 21ce1831ec
23 changed files with 2429 additions and 290 deletions
+7
View File
@@ -0,0 +1,7 @@
# 风险管理模块
此模块包含所有风险管理相关的组件:
- `market_state.py`: 市场状态分析器,基于resilient_trend逻辑
- `position_manager.py`: 仓位管理器,基于profit_protect逻辑
- `__init__.py`: 风险管理控制器,统一接口
+93
View File
@@ -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("重置所有风险管理状态")
+312
View File
@@ -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')
+105
View File
@@ -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("重置所有持仓状态")