2025-12-16 00:31:37 +08:00
|
|
|
import pandas as pd
|
|
|
|
|
import numpy as np
|
|
|
|
|
from typing import Dict, List, Optional
|
|
|
|
|
import warnings
|
|
|
|
|
warnings.filterwarnings('ignore')
|
|
|
|
|
|
|
|
|
|
class AdvancedMarketAnalysis:
|
|
|
|
|
"""
|
|
|
|
|
高级市场分析工具类
|
|
|
|
|
基于MQL5文章中的最佳实践,利用Python的Pandas和NumPy库
|
|
|
|
|
提供更强大的技术分析和机器学习功能
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.indicators_cache = {}
|
|
|
|
|
|
|
|
|
|
def calculate_technical_indicators(self, df: pd.DataFrame) -> Dict[str, float]:
|
|
|
|
|
"""
|
|
|
|
|
计算综合技术指标
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: 包含OHLCV数据的DataFrame
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
技术指标字典
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < 20:
|
|
|
|
|
return self._get_default_indicators()
|
|
|
|
|
|
|
|
|
|
indicators = {}
|
|
|
|
|
|
|
|
|
|
# 移动平均线
|
|
|
|
|
indicators['sma_20'] = df['close'].tail(20).mean()
|
|
|
|
|
indicators['sma_50'] = df['close'].tail(50).mean() if len(df) >= 50 else indicators['sma_20']
|
|
|
|
|
|
|
|
|
|
# 指数移动平均线
|
|
|
|
|
indicators['ema_12'] = df['close'].ewm(span=12).mean().iloc[-1]
|
|
|
|
|
indicators['ema_26'] = df['close'].ewm(span=26).mean().iloc[-1]
|
|
|
|
|
|
|
|
|
|
# MACD
|
|
|
|
|
ema_12 = df['close'].ewm(span=12).mean()
|
|
|
|
|
ema_26 = df['close'].ewm(span=26).mean()
|
|
|
|
|
macd_line = ema_12 - ema_26
|
|
|
|
|
macd_signal = macd_line.ewm(span=9).mean()
|
|
|
|
|
indicators['macd'] = macd_line.iloc[-1]
|
|
|
|
|
indicators['macd_signal'] = macd_signal.iloc[-1]
|
|
|
|
|
indicators['macd_histogram'] = indicators['macd'] - indicators['macd_signal']
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
indicators['rsi'] = 100 - (100 / (1 + rs.iloc[-1])) if not pd.isna(loss.iloc[-1]) and loss.iloc[-1] != 0 else 50
|
|
|
|
|
|
|
|
|
|
# Bollinger Bands
|
|
|
|
|
bb_period = 20
|
|
|
|
|
bb_std = 2
|
|
|
|
|
sma = df['close'].rolling(window=bb_period).mean()
|
|
|
|
|
std = df['close'].rolling(window=bb_period).std()
|
|
|
|
|
indicators['bb_upper'] = sma.iloc[-1] + (std.iloc[-1] * bb_std)
|
|
|
|
|
indicators['bb_lower'] = sma.iloc[-1] - (std.iloc[-1] * bb_std)
|
|
|
|
|
indicators['bb_middle'] = sma.iloc[-1]
|
|
|
|
|
|
|
|
|
|
# ATR (平均真实波幅)
|
|
|
|
|
high_low = df['high'] - df['low']
|
|
|
|
|
high_close_prev = abs(df['high'] - df['close'].shift())
|
|
|
|
|
low_close_prev = abs(df['low'] - df['close'].shift())
|
|
|
|
|
true_range = pd.concat([high_low, high_close_prev, low_close_prev], axis=1).max(axis=1)
|
|
|
|
|
indicators['atr'] = true_range.rolling(window=14).mean().iloc[-1]
|
|
|
|
|
|
|
|
|
|
# 成交量分析
|
|
|
|
|
indicators['volume_sma'] = df['volume'].tail(20).mean()
|
|
|
|
|
indicators['current_volume'] = df['volume'].iloc[-1]
|
|
|
|
|
indicators['volume_ratio'] = indicators['current_volume'] / indicators['volume_sma'] if indicators['volume_sma'] > 0 else 1
|
|
|
|
|
|
|
|
|
|
# 价格动量
|
|
|
|
|
indicators['momentum_5'] = (df['close'].iloc[-1] / df['close'].iloc[-6] - 1) * 100
|
|
|
|
|
indicators['momentum_10'] = (df['close'].iloc[-1] / df['close'].iloc[-11] - 1) * 100
|
|
|
|
|
|
|
|
|
|
return indicators
|
|
|
|
|
|
|
|
|
|
def detect_market_regime(self, df: pd.DataFrame) -> Dict[str, any]:
|
|
|
|
|
"""
|
|
|
|
|
检测市场状态(趋势/震荡/高波动)
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: 价格数据DataFrame
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
市场状态分析结果
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < 50:
|
|
|
|
|
return {"regime": "unknown", "confidence": 0.5, "description": "数据不足"}
|
|
|
|
|
|
|
|
|
|
# 计算波动率
|
|
|
|
|
returns = df['close'].pct_change().dropna()
|
|
|
|
|
volatility = returns.std() * np.sqrt(252) # 年化波动率
|
|
|
|
|
|
|
|
|
|
# 计算趋势强度
|
|
|
|
|
price_change = (df['close'].iloc[-1] / df['close'].iloc[0] - 1) * 100
|
|
|
|
|
|
|
|
|
|
# 使用ADX判断趋势强度
|
|
|
|
|
highs = df['high']
|
|
|
|
|
lows = df['low']
|
|
|
|
|
|
|
|
|
|
# 计算+DI和-DI
|
|
|
|
|
plus_dm = highs.diff()
|
|
|
|
|
minus_dm = -lows.diff()
|
|
|
|
|
|
|
|
|
|
plus_dm[plus_dm < 0] = 0
|
|
|
|
|
minus_dm[minus_dm < 0] = 0
|
|
|
|
|
|
|
|
|
|
tr = pd.concat([
|
|
|
|
|
highs - lows,
|
|
|
|
|
abs(highs - df['close'].shift()),
|
|
|
|
|
abs(lows - df['close'].shift())
|
|
|
|
|
], axis=1).max(axis=1)
|
|
|
|
|
|
|
|
|
|
atr = tr.rolling(window=14).mean()
|
|
|
|
|
plus_di = 100 * (plus_dm.rolling(window=14).mean() / atr)
|
|
|
|
|
minus_di = 100 * (minus_dm.rolling(window=14).mean() / atr)
|
|
|
|
|
|
|
|
|
|
dx = 100 * abs(plus_di - minus_di) / (plus_di + minus_di)
|
|
|
|
|
adx = dx.rolling(window=14).mean()
|
|
|
|
|
|
|
|
|
|
current_adx = adx.iloc[-1] if not pd.isna(adx.iloc[-1]) else 0
|
|
|
|
|
|
|
|
|
|
# 判断市场状态
|
|
|
|
|
if volatility > 0.3: # 高波动
|
|
|
|
|
regime = "high_volatility"
|
|
|
|
|
confidence = min(volatility / 0.5, 0.9)
|
|
|
|
|
description = "高波动市场"
|
|
|
|
|
elif current_adx > 25: # 强趋势
|
|
|
|
|
regime = "trending"
|
|
|
|
|
confidence = min(current_adx / 50, 0.9)
|
|
|
|
|
description = "趋势市场"
|
|
|
|
|
else: # 震荡
|
|
|
|
|
regime = "ranging"
|
|
|
|
|
confidence = 0.7
|
|
|
|
|
description = "震荡市场"
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"regime": regime,
|
|
|
|
|
"confidence": confidence,
|
|
|
|
|
"description": description,
|
|
|
|
|
"volatility": volatility,
|
|
|
|
|
"adx": current_adx,
|
|
|
|
|
"price_change": price_change
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def generate_support_resistance(self, df: pd.DataFrame, lookback_period: int = 100) -> Dict[str, List[float]]:
|
|
|
|
|
"""
|
|
|
|
|
生成支撑阻力位
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: 价格数据DataFrame
|
|
|
|
|
lookback_period: 回溯周期
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
支撑阻力位字典
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < lookback_period:
|
|
|
|
|
lookback_period = len(df)
|
|
|
|
|
|
|
|
|
|
recent_data = df.tail(lookback_period)
|
|
|
|
|
|
|
|
|
|
# 使用局部极值点识别支撑阻力
|
|
|
|
|
highs = recent_data['high']
|
|
|
|
|
lows = recent_data['low']
|
|
|
|
|
|
|
|
|
|
# 寻找局部高点和低点
|
|
|
|
|
support_levels = []
|
|
|
|
|
resistance_levels = []
|
|
|
|
|
|
|
|
|
|
# 简单方法:使用近期高点和低点
|
|
|
|
|
recent_high = highs.max()
|
|
|
|
|
recent_low = lows.min()
|
|
|
|
|
|
|
|
|
|
# 生成多个支撑阻力位
|
|
|
|
|
price_range = recent_high - recent_low
|
|
|
|
|
|
|
|
|
|
# 支撑位(低于当前价格)
|
|
|
|
|
current_price = df['close'].iloc[-1]
|
|
|
|
|
for i in range(1, 4):
|
|
|
|
|
level = current_price - (price_range * 0.1 * i)
|
|
|
|
|
if level > recent_low:
|
2025-12-24 13:00:01 +08:00
|
|
|
support_levels.append(float(round(level, 4)))
|
2025-12-16 00:31:37 +08:00
|
|
|
|
|
|
|
|
# 阻力位(高于当前价格)
|
|
|
|
|
for i in range(1, 4):
|
|
|
|
|
level = current_price + (price_range * 0.1 * i)
|
|
|
|
|
if level < recent_high:
|
2025-12-24 13:00:01 +08:00
|
|
|
resistance_levels.append(float(round(level, 4)))
|
2025-12-16 00:31:37 +08:00
|
|
|
|
|
|
|
|
# 确保至少有一个支撑阻力位
|
|
|
|
|
if not support_levels:
|
2025-12-24 13:00:01 +08:00
|
|
|
support_levels = [float(round(recent_low, 4))]
|
2025-12-16 00:31:37 +08:00
|
|
|
if not resistance_levels:
|
2025-12-24 13:00:01 +08:00
|
|
|
resistance_levels = [float(round(recent_high, 4))]
|
2025-12-16 00:31:37 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"support_levels": sorted(support_levels),
|
|
|
|
|
"resistance_levels": sorted(resistance_levels),
|
|
|
|
|
"current_price": current_price
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def calculate_risk_metrics(self, df: pd.DataFrame) -> Dict[str, float]:
|
|
|
|
|
"""
|
|
|
|
|
计算风险指标
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: 价格数据DataFrame
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
风险指标字典
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < 30:
|
|
|
|
|
return self._get_default_risk_metrics()
|
|
|
|
|
|
|
|
|
|
returns = df['close'].pct_change().dropna()
|
|
|
|
|
|
|
|
|
|
metrics = {
|
|
|
|
|
"volatility": returns.std() * np.sqrt(252), # 年化波动率
|
|
|
|
|
"sharpe_ratio": returns.mean() / returns.std() * np.sqrt(252) if returns.std() > 0 else 0,
|
|
|
|
|
"max_drawdown": self._calculate_max_drawdown(df['close']),
|
|
|
|
|
"var_95": np.percentile(returns, 5) * 100, # 95% VaR
|
|
|
|
|
"expected_shortfall": returns[returns <= np.percentile(returns, 5)].mean() * 100,
|
|
|
|
|
"skewness": returns.skew(),
|
|
|
|
|
"kurtosis": returns.kurtosis()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return metrics
|
|
|
|
|
|
2025-12-25 11:21:20 +08:00
|
|
|
def analyze_crt_strategy(self, df: pd.DataFrame, range_period: int = 15, confirm_period: int = 1, min_manipulation_pct: float = 5.0) -> Dict[str, any]:
|
|
|
|
|
"""
|
|
|
|
|
基于 CRT (Candle Range Theory) 的策略分析
|
|
|
|
|
逻辑来源: CRT蜡烛区间理论EA.mq5
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: OHLCV DataFrame (通常是较高时间周期的,如 M15)
|
|
|
|
|
range_period: 定义区间的周期数
|
|
|
|
|
confirm_period: 确认突破的周期数
|
|
|
|
|
min_manipulation_pct: 最小操纵深度百分比
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
策略信号和区间信息
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < range_period + confirm_period + 5:
|
|
|
|
|
return {"signal": "hold", "strength": 0, "reasons": []}
|
|
|
|
|
|
|
|
|
|
# 1. 识别区间 (Accumulation Phase)
|
|
|
|
|
# 假设当前时间点为 t,区间定义为 t-1 的 High/Low
|
|
|
|
|
# MQL5逻辑: Range High/Low based on PREVIOUS candle of Range Timeframe
|
|
|
|
|
|
|
|
|
|
prev_candle = df.iloc[-2] # 前一根已完成 K 线
|
|
|
|
|
range_high = prev_candle['high']
|
|
|
|
|
range_low = prev_candle['low']
|
|
|
|
|
range_open = prev_candle['open']
|
|
|
|
|
range_close = prev_candle['close']
|
|
|
|
|
|
|
|
|
|
# 判断区间方向 (Sentiment)
|
|
|
|
|
is_bullish_range = range_close > range_open
|
|
|
|
|
|
|
|
|
|
# 2. 检测突破和操纵 (Manipulation Phase)
|
|
|
|
|
# 当前正在形成的 K 线 (或最近几根) 是否突破了区间
|
|
|
|
|
current_candle = df.iloc[-1]
|
|
|
|
|
current_high = current_candle['high']
|
|
|
|
|
current_low = current_candle['low']
|
|
|
|
|
current_close = current_candle['close']
|
|
|
|
|
|
|
|
|
|
breakout = False
|
|
|
|
|
manipulation = False
|
|
|
|
|
manipulation_depth = 0.0
|
|
|
|
|
|
|
|
|
|
range_size = range_high - range_low
|
|
|
|
|
if range_size == 0: range_size = 0.0001
|
|
|
|
|
|
|
|
|
|
# 操纵逻辑:
|
|
|
|
|
# 如果是看涨区间 (预期向上),操纵通常是先向下假突破 (Run Stops below Low)
|
|
|
|
|
# 如果是看跌区间 (预期向下),操纵通常是先向上假突破 (Run Stops above High)
|
|
|
|
|
|
|
|
|
|
signal = "hold"
|
|
|
|
|
strength = 0
|
|
|
|
|
reasons = []
|
|
|
|
|
|
|
|
|
|
if is_bullish_range:
|
|
|
|
|
# 预期向上,检查下方操纵
|
|
|
|
|
if current_low < range_low:
|
|
|
|
|
breakout = True
|
|
|
|
|
manipulation_depth = range_low - current_low
|
|
|
|
|
pct = (manipulation_depth / range_size) * 100
|
|
|
|
|
|
|
|
|
|
# 检查是否收回 (Distribution Phase Start)
|
|
|
|
|
# 价格重新回到区间内或收盘价高于 range_low
|
|
|
|
|
if current_close > range_low:
|
|
|
|
|
manipulation = True
|
|
|
|
|
if pct >= min_manipulation_pct:
|
|
|
|
|
signal = "buy"
|
|
|
|
|
strength = 80
|
|
|
|
|
reasons.append(f"CRT Bullish: Downward Manipulation ({pct:.1f}%) & Reclaim")
|
|
|
|
|
|
|
|
|
|
else: # Bearish Range
|
|
|
|
|
# 预期向下,检查上方操纵
|
|
|
|
|
if current_high > range_high:
|
|
|
|
|
breakout = True
|
|
|
|
|
manipulation_depth = current_high - range_high
|
|
|
|
|
pct = (manipulation_depth / range_size) * 100
|
|
|
|
|
|
|
|
|
|
# 检查是否收回
|
|
|
|
|
if current_close < range_high:
|
|
|
|
|
manipulation = True
|
|
|
|
|
if pct >= min_manipulation_pct:
|
|
|
|
|
signal = "sell"
|
|
|
|
|
strength = 80
|
|
|
|
|
reasons.append(f"CRT Bearish: Upward Manipulation ({pct:.1f}%) & Reclaim")
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"signal": signal,
|
|
|
|
|
"strength": strength,
|
|
|
|
|
"reasons": reasons,
|
|
|
|
|
"range_info": {
|
|
|
|
|
"high": range_high,
|
|
|
|
|
"low": range_low,
|
|
|
|
|
"direction": "bullish" if is_bullish_range else "bearish",
|
|
|
|
|
"manipulation_pct": (manipulation_depth / range_size * 100) if breakout else 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
def analyze_rvgi_cci_strategy(self, df: pd.DataFrame, sma_period: int = 30, cci_period: int = 14, rvi_smooth: int = 4) -> Dict[str, any]:
|
|
|
|
|
"""
|
|
|
|
|
基于 RVGI + CCI + SMA 的复合策略分析
|
|
|
|
|
逻辑来源: RVGI_CCI_SMA_Panel_EA.mq5
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: OHLCV DataFrame
|
|
|
|
|
sma_period: SMA 周期
|
|
|
|
|
cci_period: CCI 周期
|
|
|
|
|
rvi_smooth: RVI 平滑周期
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
策略信号和指标值
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < max(sma_period, cci_period) + 5:
|
|
|
|
|
return {"signal": "hold", "strength": 0, "reasons": []}
|
|
|
|
|
|
|
|
|
|
closes = df['close']
|
|
|
|
|
highs = df['high']
|
|
|
|
|
lows = df['low']
|
|
|
|
|
opens = df['open']
|
|
|
|
|
|
|
|
|
|
# 1. 计算指标
|
|
|
|
|
|
|
|
|
|
# SMA
|
|
|
|
|
sma = closes.rolling(window=sma_period).mean()
|
|
|
|
|
|
|
|
|
|
# CCI (Commodity Channel Index)
|
|
|
|
|
tp = (highs + lows + closes) / 3
|
|
|
|
|
sma_tp = tp.rolling(window=cci_period).mean()
|
|
|
|
|
mad = tp.rolling(window=cci_period).apply(lambda x: np.mean(np.abs(x - np.mean(x))), raw=True)
|
|
|
|
|
# 避免除以零
|
|
|
|
|
mad = mad.replace(0, 0.000001)
|
|
|
|
|
cci = (tp - sma_tp) / (0.015 * mad)
|
|
|
|
|
|
|
|
|
|
# RVI (Relative Vigor Index) - 完整版计算
|
|
|
|
|
# 逻辑来源: MQL5 RVI 计算公式
|
|
|
|
|
|
|
|
|
|
# 1. 计算 Numerator (Value1) 和 Denominator (Value2) 的基础值
|
|
|
|
|
# Value1 = ((Close - Open) + 2*(Close[1]-Open[1]) + 2*(Close[2]-Open[2]) + (Close[3]-Open[3])) / 6
|
|
|
|
|
# Value2 = ((High - Low) + 2*(High[1]-Low[1]) + 2*(High[2]-Low[2]) + (High[3]-Low[3])) / 6
|
|
|
|
|
|
|
|
|
|
co = closes - opens
|
|
|
|
|
hl = highs - lows
|
|
|
|
|
|
|
|
|
|
# 使用 shift 获取前 N 根 K 线的数据
|
|
|
|
|
# 注意: rolling 的 window 包含当前行,shift(1) 是上一行
|
|
|
|
|
|
|
|
|
|
# 计算 Numerator (Value1)
|
|
|
|
|
num_val = (co + 2 * co.shift(1) + 2 * co.shift(2) + co.shift(3)) / 6
|
|
|
|
|
|
|
|
|
|
# 计算 Denominator (Value2)
|
|
|
|
|
den_val = (hl + 2 * hl.shift(1) + 2 * hl.shift(2) + hl.shift(3)) / 6
|
|
|
|
|
|
|
|
|
|
# 2. 对 Num 和 Den 进行 SMA 平滑 (通常周期为 10)
|
|
|
|
|
# 注意: 这里使用传入的 rvi_smooth 还是固定周期? MQL5 标准 RVI 指标通常 Period=10
|
|
|
|
|
# 但这里的函数参数是 rvi_smooth,通常指 Signal 线的平滑?
|
|
|
|
|
# 让我们假设 rvi_smooth 用于 Signal 线,而 RVI 主线的 SMA 周期通常是 10
|
|
|
|
|
# 为了灵活,我们可以引入 rvi_period 参数,默认为 10
|
|
|
|
|
rvi_period = 10
|
|
|
|
|
|
|
|
|
|
rvi_num = num_val.rolling(window=rvi_period).mean()
|
|
|
|
|
rvi_den = den_val.rolling(window=rvi_period).mean()
|
|
|
|
|
|
|
|
|
|
# 避免除以零
|
|
|
|
|
rvi_den = rvi_den.replace(0, 0.000001)
|
|
|
|
|
|
|
|
|
|
rvi_main = rvi_num / rvi_den
|
|
|
|
|
|
|
|
|
|
# 3. 计算 Signal Line
|
|
|
|
|
# Signal = (RVI + 2*RVI[1] + 2*RVI[2] + RVI[3]) / 6
|
|
|
|
|
rvi_signal = (rvi_main + 2 * rvi_main.shift(1) + 2 * rvi_main.shift(2) + rvi_main.shift(3)) / 6
|
|
|
|
|
|
|
|
|
|
# 处理可能的 NaN (由于 shift 和 rolling 导致的前面数据缺失)
|
|
|
|
|
rvi_main = rvi_main.fillna(0)
|
|
|
|
|
rvi_signal = rvi_signal.fillna(0)
|
|
|
|
|
|
|
|
|
|
# 2. 获取当前值 (最新的已完成 K 线,即 iloc[-2] 或 iloc[-1] 取决于是否包含当前 K)
|
|
|
|
|
# 假设 df 包含当前正在形成的 K 线,我们通常看上一根 K 线的收盘确认信号
|
|
|
|
|
curr = -1
|
|
|
|
|
prev = -2
|
|
|
|
|
|
|
|
|
|
price = closes.iloc[curr]
|
|
|
|
|
sma_val = sma.iloc[curr]
|
|
|
|
|
|
|
|
|
|
cci_now = cci.iloc[curr]
|
|
|
|
|
|
|
|
|
|
rvi_m_now = rvi_main.iloc[curr]
|
|
|
|
|
rvi_s_now = rvi_signal.iloc[curr]
|
|
|
|
|
rvi_m_prev = rvi_main.iloc[prev]
|
|
|
|
|
rvi_s_prev = rvi_signal.iloc[prev]
|
|
|
|
|
|
|
|
|
|
# 3. 信号逻辑
|
|
|
|
|
|
|
|
|
|
# 价格位置
|
|
|
|
|
price_above_sma = price > sma_val
|
|
|
|
|
price_below_sma = price < sma_val
|
|
|
|
|
|
|
|
|
|
# RVI 交叉
|
|
|
|
|
# 金叉: Main 上穿 Signal
|
|
|
|
|
rvi_cross_up = (rvi_m_prev <= rvi_s_prev) and (rvi_m_now > rvi_s_now)
|
|
|
|
|
# 死叉: Main 下穿 Signal
|
|
|
|
|
rvi_cross_down = (rvi_m_prev >= rvi_s_prev) and (rvi_m_now < rvi_s_now)
|
|
|
|
|
|
|
|
|
|
# CCI 状态
|
|
|
|
|
cci_buy = cci_now <= -100 # 超卖
|
|
|
|
|
cci_sell = cci_now >= 100 # 超买
|
|
|
|
|
|
|
|
|
|
signal = "hold"
|
|
|
|
|
strength = 0
|
|
|
|
|
reasons = []
|
|
|
|
|
|
|
|
|
|
# Sell Signal: Price > SMA + CCI >= 100 + RVI Cross Down
|
|
|
|
|
if price_above_sma and cci_sell and rvi_cross_down:
|
|
|
|
|
signal = "sell"
|
|
|
|
|
strength = 75
|
|
|
|
|
reasons.append("RVGI 死叉 + CCI 超买 + 价格在 SMA 之上")
|
|
|
|
|
|
|
|
|
|
# Buy Signal: Price < SMA + CCI <= -100 + RVI Cross Up
|
|
|
|
|
elif price_below_sma and cci_buy and rvi_cross_up:
|
|
|
|
|
signal = "buy"
|
|
|
|
|
strength = 75
|
|
|
|
|
reasons.append("RVGI 金叉 + CCI 超卖 + 价格在 SMA 之下")
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"signal": signal,
|
|
|
|
|
"strength": strength,
|
|
|
|
|
"reasons": reasons,
|
|
|
|
|
"indicators": {
|
|
|
|
|
"sma": sma_val,
|
|
|
|
|
"cci": cci_now,
|
|
|
|
|
"rvi_main": rvi_m_now,
|
|
|
|
|
"rvi_signal": rvi_s_now
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
def analyze_ifvg(self, df: pd.DataFrame, min_gap_points: int = 100) -> Dict[str, any]:
|
|
|
|
|
"""
|
|
|
|
|
分析 IFVG (Inverse Fair Value Gap) - 基于 MQL5 策略逻辑移植
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: OHLC DataFrame
|
|
|
|
|
min_gap_points: 最小跳空点数 (默认100点 = 1.0对于黄金)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
包含信号和活跃区域的字典
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < 5:
|
|
|
|
|
return {"signal": "hold", "strength": 0, "reasons": [], "active_zones": []}
|
|
|
|
|
|
|
|
|
|
# 数据准备
|
|
|
|
|
opens = df['open'].values
|
|
|
|
|
highs = df['high'].values
|
|
|
|
|
lows = df['low'].values
|
|
|
|
|
closes = df['close'].values
|
|
|
|
|
times = df.index
|
|
|
|
|
|
|
|
|
|
# 点值估算 (假设是黄金/外汇,0.01)
|
|
|
|
|
point = 0.01
|
|
|
|
|
min_gap = min_gap_points * point
|
|
|
|
|
|
|
|
|
|
fvgs = []
|
|
|
|
|
# 结构: {'type': 'bullish'/'bearish', 'top': float, 'bottom': float, 'start_time': datetime,
|
|
|
|
|
# 'mitigated': bool, 'inverted': bool, 'inverted_time': datetime}
|
|
|
|
|
|
|
|
|
|
# 遍历历史数据检测 FVG
|
|
|
|
|
# 从第3根K线开始 (索引2)
|
|
|
|
|
for i in range(2, len(df)):
|
|
|
|
|
# 1. 检测新 FVG
|
|
|
|
|
|
|
|
|
|
# Bullish FVG (Gap Up): Low[i] > High[i-2]
|
|
|
|
|
if lows[i] > highs[i-2] and (lows[i] - highs[i-2]) > min_gap:
|
|
|
|
|
fvgs.append({
|
|
|
|
|
'id': f"bull_{i}",
|
|
|
|
|
'type': 'bullish', # 原始方向
|
|
|
|
|
'top': lows[i],
|
|
|
|
|
'bottom': highs[i-2],
|
|
|
|
|
'start_time': times[i],
|
|
|
|
|
'mitigated': False,
|
|
|
|
|
'inverted': False,
|
|
|
|
|
'inverted_time': None
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Bearish FVG (Gap Down): Low[i-2] > High[i]
|
|
|
|
|
elif lows[i-2] > highs[i] and (lows[i-2] - highs[i]) > min_gap:
|
|
|
|
|
fvgs.append({
|
|
|
|
|
'id': f"bear_{i}",
|
|
|
|
|
'type': 'bearish', # 原始方向
|
|
|
|
|
'top': lows[i-2],
|
|
|
|
|
'bottom': highs[i],
|
|
|
|
|
'start_time': times[i],
|
|
|
|
|
'mitigated': False,
|
|
|
|
|
'inverted': False,
|
|
|
|
|
'inverted_time': None
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# 2. 更新现有 FVG 状态 (基于当前 K 线 i)
|
|
|
|
|
current_low = lows[i]
|
|
|
|
|
current_high = highs[i]
|
|
|
|
|
current_close = closes[i]
|
|
|
|
|
|
|
|
|
|
for fvg in fvgs:
|
|
|
|
|
# 如果已经反转,暂不需要进一步状态更新(除非失效,这里暂不处理失效)
|
|
|
|
|
if fvg['inverted']:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 检查 Mitigation (缓解/触碰)
|
|
|
|
|
# MQL5逻辑: breakFar check
|
|
|
|
|
if not fvg['mitigated']:
|
|
|
|
|
if fvg['type'] == 'bullish':
|
|
|
|
|
if current_low < fvg['bottom']: # 价格跌破缺口下沿
|
|
|
|
|
fvg['mitigated'] = True
|
|
|
|
|
else: # bearish
|
|
|
|
|
if current_high > fvg['top']: # 价格突破缺口上沿
|
|
|
|
|
fvg['mitigated'] = True
|
|
|
|
|
|
|
|
|
|
# 检查 Inversion (反转信号)
|
|
|
|
|
# 必须先被 Mitigated,然后收盘价完全突破
|
|
|
|
|
if fvg['mitigated']:
|
|
|
|
|
if fvg['type'] == 'bullish':
|
|
|
|
|
# 原本是看涨缺口,被跌破并收盘在下方 -> 变为看跌阻力 (Bearish Inverted FVG)
|
|
|
|
|
if current_close < fvg['bottom']:
|
|
|
|
|
fvg['inverted'] = True
|
|
|
|
|
fvg['inverted_time'] = times[i]
|
|
|
|
|
else: # bearish
|
|
|
|
|
# 原本是看跌缺口,被突破并收盘在上方 -> 变为看涨支撑 (Bullish Inverted FVG)
|
|
|
|
|
if current_close > fvg['top']:
|
|
|
|
|
fvg['inverted'] = True
|
|
|
|
|
fvg['inverted_time'] = times[i]
|
|
|
|
|
|
|
|
|
|
# 3. 生成最新信号
|
|
|
|
|
# 检查最后一根 K 线是否触发了新的反转
|
|
|
|
|
last_time = times[-1]
|
|
|
|
|
signal = "hold"
|
|
|
|
|
strength = 0
|
|
|
|
|
reasons = []
|
|
|
|
|
|
|
|
|
|
latest_inversions = [f for f in fvgs if f['inverted'] and f['inverted_time'] == last_time]
|
|
|
|
|
|
|
|
|
|
for inv in latest_inversions:
|
|
|
|
|
if inv['type'] == 'bearish':
|
|
|
|
|
# Bearish FVG Inverted -> Bullish Signal (Buy)
|
|
|
|
|
signal = "buy"
|
|
|
|
|
strength = 80 # IFVG 是强信号
|
|
|
|
|
reasons.append(f"IFVG Bullish Inversion (原看跌缺口被突破)")
|
|
|
|
|
elif inv['type'] == 'bullish':
|
|
|
|
|
# Bullish FVG Inverted -> Bearish Signal (Sell)
|
|
|
|
|
signal = "sell"
|
|
|
|
|
strength = 80
|
|
|
|
|
reasons.append(f"IFVG Bearish Inversion (原看涨缺口被跌破)")
|
|
|
|
|
|
|
|
|
|
# 过滤出最近的活跃区域用于可视化
|
|
|
|
|
active_zones = [f for f in fvgs if f['start_time'] > times[-50]] # 仅保留最近50根K线的
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"signal": signal,
|
|
|
|
|
"strength": strength,
|
|
|
|
|
"reasons": reasons,
|
|
|
|
|
"active_zones": active_zones
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 00:31:37 +08:00
|
|
|
def _calculate_max_drawdown(self, prices: pd.Series) -> float:
|
|
|
|
|
"""计算最大回撤"""
|
|
|
|
|
cumulative_returns = (1 + prices.pct_change()).cumprod()
|
|
|
|
|
running_max = cumulative_returns.expanding().max()
|
|
|
|
|
drawdown = (cumulative_returns - running_max) / running_max
|
|
|
|
|
return drawdown.min() * 100
|
|
|
|
|
|
|
|
|
|
def _get_default_indicators(self) -> Dict[str, float]:
|
|
|
|
|
"""获取默认技术指标"""
|
|
|
|
|
return {
|
|
|
|
|
"sma_20": 0, "sma_50": 0, "ema_12": 0, "ema_26": 0,
|
|
|
|
|
"macd": 0, "macd_signal": 0, "macd_histogram": 0,
|
|
|
|
|
"rsi": 50, "bb_upper": 0, "bb_lower": 0, "bb_middle": 0,
|
|
|
|
|
"atr": 0, "volume_sma": 0, "current_volume": 0, "volume_ratio": 1,
|
|
|
|
|
"momentum_5": 0, "momentum_10": 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def generate_signal_from_indicators(self, indicators: Dict[str, float]) -> Dict[str, any]:
|
|
|
|
|
"""根据技术指标生成交易信号
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
indicators: 技术指标字典
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
交易信号信息
|
|
|
|
|
"""
|
|
|
|
|
signal = "hold"
|
|
|
|
|
strength = 0
|
|
|
|
|
reasons = []
|
|
|
|
|
|
|
|
|
|
# MACD信号
|
|
|
|
|
if indicators['macd'] > indicators['macd_signal'] and indicators['macd_histogram'] > 0:
|
|
|
|
|
strength += 25
|
|
|
|
|
reasons.append("MACD金叉")
|
|
|
|
|
elif indicators['macd'] < indicators['macd_signal'] and indicators['macd_histogram'] < 0:
|
|
|
|
|
strength -= 25
|
|
|
|
|
reasons.append("MACD死叉")
|
|
|
|
|
|
|
|
|
|
# RSI信号
|
|
|
|
|
if indicators['rsi'] < 30:
|
|
|
|
|
strength += 20
|
|
|
|
|
reasons.append("RSI超卖")
|
|
|
|
|
elif indicators['rsi'] > 70:
|
|
|
|
|
strength -= 20
|
|
|
|
|
reasons.append("RSI超买")
|
|
|
|
|
|
|
|
|
|
# 移动平均线信号
|
|
|
|
|
if indicators['ema_12'] > indicators['ema_26']:
|
|
|
|
|
strength += 15
|
|
|
|
|
reasons.append("EMA金叉")
|
|
|
|
|
elif indicators['ema_12'] < indicators['ema_26']:
|
|
|
|
|
strength -= 15
|
|
|
|
|
reasons.append("EMA死叉")
|
|
|
|
|
|
|
|
|
|
# 布林带信号
|
|
|
|
|
current_price = indicators.get('current_price', indicators['sma_20'])
|
|
|
|
|
if current_price < indicators['bb_lower']:
|
|
|
|
|
strength += 15
|
|
|
|
|
reasons.append("价格触及布林带下轨")
|
|
|
|
|
elif current_price > indicators['bb_upper']:
|
|
|
|
|
strength -= 15
|
|
|
|
|
reasons.append("价格触及布林带上轨")
|
|
|
|
|
|
|
|
|
|
# 成交量确认
|
|
|
|
|
if indicators['volume_ratio'] > 1.5:
|
|
|
|
|
strength += 10
|
|
|
|
|
reasons.append("成交量放大")
|
|
|
|
|
|
|
|
|
|
# 确定最终信号
|
|
|
|
|
if strength >= 40:
|
|
|
|
|
signal = "buy"
|
|
|
|
|
elif strength <= -40:
|
|
|
|
|
signal = "sell"
|
|
|
|
|
else:
|
|
|
|
|
signal = "hold"
|
|
|
|
|
strength = 0
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"signal": signal,
|
|
|
|
|
"strength": abs(strength),
|
|
|
|
|
"reasons": reasons,
|
|
|
|
|
"confidence": min(abs(strength) / 100, 1.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _get_default_risk_metrics(self) -> Dict[str, float]:
|
|
|
|
|
"""获取默认风险指标"""
|
|
|
|
|
return {
|
|
|
|
|
"volatility": 0.2, "sharpe_ratio": 0, "max_drawdown": 0,
|
|
|
|
|
"var_95": -2, "expected_shortfall": -3, "skewness": 0, "kurtosis": 0
|
|
|
|
|
}
|
2025-12-21 16:16:17 +08:00
|
|
|
|
|
|
|
|
def generate_analysis_summary(self, df: pd.DataFrame) -> Dict[str, any]:
|
|
|
|
|
"""
|
|
|
|
|
生成综合分析摘要
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df: 价格数据DataFrame
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
分析摘要字典
|
|
|
|
|
"""
|
|
|
|
|
if len(df) < 20:
|
|
|
|
|
return {
|
|
|
|
|
"summary": "数据不足,无法生成分析摘要",
|
|
|
|
|
"recommendation": "hold",
|
|
|
|
|
"confidence": 0.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 计算各种分析指标
|
|
|
|
|
indicators = self.calculate_technical_indicators(df)
|
|
|
|
|
market_regime = self.detect_market_regime(df)
|
|
|
|
|
risk_metrics = self.calculate_risk_metrics(df)
|
|
|
|
|
support_resistance = self.generate_support_resistance(df)
|
|
|
|
|
signal_info = self.generate_signal_from_indicators(indicators)
|
|
|
|
|
|
|
|
|
|
# 生成综合分析摘要
|
|
|
|
|
current_price = df['close'].iloc[-1]
|
|
|
|
|
price_change_1d = (df['close'].iloc[-1] / df['close'].iloc[-2] - 1) * 100
|
|
|
|
|
price_change_5d = (df['close'].iloc[-1] / df['close'].iloc[-6] - 1) * 100
|
|
|
|
|
|
|
|
|
|
# 生成交易建议
|
|
|
|
|
if signal_info['signal'] == 'buy' and signal_info['strength'] > 60:
|
|
|
|
|
recommendation = "强烈买入"
|
|
|
|
|
elif signal_info['signal'] == 'buy':
|
|
|
|
|
recommendation = "买入"
|
|
|
|
|
elif signal_info['signal'] == 'sell' and signal_info['strength'] > 60:
|
|
|
|
|
recommendation = "强烈卖出"
|
|
|
|
|
elif signal_info['signal'] == 'sell':
|
|
|
|
|
recommendation = "卖出"
|
|
|
|
|
else:
|
|
|
|
|
recommendation = "持有"
|
|
|
|
|
|
|
|
|
|
# 生成市场状态描述
|
|
|
|
|
regime_descriptions = {
|
|
|
|
|
"trending": "市场处于趋势状态,适合趋势跟踪策略",
|
|
|
|
|
"ranging": "市场处于震荡状态,适合区间交易策略",
|
|
|
|
|
"high_volatility": "市场波动性较高,注意风险管理",
|
|
|
|
|
"unknown": "市场状态不明确,建议谨慎操作"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
regime_desc = regime_descriptions.get(market_regime['regime'], "市场状态分析中")
|
|
|
|
|
|
|
|
|
|
# 构建摘要
|
|
|
|
|
summary = {
|
|
|
|
|
"summary": f"当前价格: {current_price:.4f} ({price_change_1d:+.2f}% 1日, {price_change_5d:+.2f}% 5日)",
|
|
|
|
|
"market_regime": market_regime['description'],
|
|
|
|
|
"regime_analysis": regime_desc,
|
|
|
|
|
"recommendation": recommendation,
|
|
|
|
|
"confidence": signal_info['confidence'],
|
|
|
|
|
"risk_level": "高" if risk_metrics['volatility'] > 0.3 else "中" if risk_metrics['volatility'] > 0.15 else "低",
|
|
|
|
|
"key_indicators": {
|
|
|
|
|
"RSI": f"{indicators['rsi']:.1f}",
|
|
|
|
|
"MACD": f"{indicators['macd']:.4f}",
|
|
|
|
|
"ATR": f"{indicators['atr']:.4f}",
|
|
|
|
|
"波动率": f"{risk_metrics['volatility']:.2%}"
|
|
|
|
|
},
|
|
|
|
|
"support_levels": support_resistance['support_levels'],
|
|
|
|
|
"resistance_levels": support_resistance['resistance_levels'],
|
|
|
|
|
"timestamp": pd.Timestamp.now().isoformat()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return summary
|
2025-12-16 00:31:37 +08:00
|
|
|
|
|
|
|
|
# 使用示例
|
|
|
|
|
def create_sample_data():
|
|
|
|
|
"""创建示例数据用于测试"""
|
|
|
|
|
dates = pd.date_range('2024-01-01', periods=100, freq='D')
|
|
|
|
|
np.random.seed(42)
|
|
|
|
|
|
|
|
|
|
# 生成随机价格数据
|
|
|
|
|
returns = np.random.normal(0.001, 0.02, 100)
|
|
|
|
|
prices = 100 * (1 + returns).cumprod()
|
|
|
|
|
|
|
|
|
|
df = pd.DataFrame({
|
|
|
|
|
'open': prices * 0.999,
|
|
|
|
|
'high': prices * 1.005,
|
|
|
|
|
'low': prices * 0.995,
|
|
|
|
|
'close': prices,
|
|
|
|
|
'volume': np.random.randint(100000, 1000000, 100)
|
|
|
|
|
}, index=dates)
|
|
|
|
|
|
|
|
|
|
return df
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
# 测试功能
|
|
|
|
|
analyzer = AdvancedMarketAnalysis()
|
|
|
|
|
sample_data = create_sample_data()
|
|
|
|
|
|
|
|
|
|
print("技术指标:")
|
|
|
|
|
indicators = analyzer.calculate_technical_indicators(sample_data)
|
|
|
|
|
for key, value in indicators.items():
|
|
|
|
|
print(f"{key}: {value:.4f}")
|
|
|
|
|
|
|
|
|
|
print("\n市场状态:")
|
|
|
|
|
regime = analyzer.detect_market_regime(sample_data)
|
|
|
|
|
for key, value in regime.items():
|
|
|
|
|
print(f"{key}: {value}")
|
|
|
|
|
|
|
|
|
|
print("\n支撑阻力位:")
|
|
|
|
|
levels = analyzer.generate_support_resistance(sample_data)
|
|
|
|
|
for key, value in levels.items():
|
|
|
|
|
print(f"{key}: {value}")
|
|
|
|
|
|
|
|
|
|
print("\n风险指标:")
|
|
|
|
|
risk = analyzer.calculate_risk_metrics(sample_data)
|
|
|
|
|
for key, value in risk.items():
|
|
|
|
|
print(f"{key}: {value:.4f}")
|