Download model/technical_indicators.py from sgfw/kronos: direct link, hf CLI and curl.
- Browser
- Download file 50.2 kB
-
https://huggingface.co/spaces/sgfw/kronos/resolve/main/model/technical_indicators.py
- Command line
-
hf download hf://spaces/sgfw/kronos/model/technical_indicators.py
-
curl -L -o technical_indicators.py https://huggingface.co/spaces/sgfw/kronos/resolve/main/model/technical_indicators.py
50.2 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| 多维度技术指标联动研判模块 | |
| 包含12个固定参数指标、5大研判维度和统一信号共振逻辑 | |
| 固定参数指标清单: | |
| MACD(5,10,3) KDJ(3,3,3) EMA5/10/20/60 BOLL(20,2) | |
| RSI(6,14) CCI(20) 成交量+5/10日均量 OBV DMI(14,6) SAR(0.02,0.2) MFI(14) | |
| 5大研判维度: | |
| 1. 趋势判定:MACD + 多级EMA + BOLL | |
| 2. 拐点判定:KDJ + RSI + CCI | |
| 3. 行情验证:成交量 + OBV | |
| 4. 强度确认:DMI + MFI | |
| 5. 风控配套:SAR | |
| 信号生效原则:多指标同向共振优先采信,背离则暂缓观望 | |
| """ | |
| import numpy as np | |
| import pandas as pd | |
| from typing import Dict, List, Tuple, Optional | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # === 兼容层:优先尝试 talib,失败则用自带兼容模块 === | |
| try: | |
| import talib | |
| _HAS_TALIB = True | |
| except ImportError: | |
| _HAS_TALIB = False | |
| import importlib.util, sys, os | |
| _talib_path = os.path.join(os.path.dirname(__file__), 'talib.py') | |
| if os.path.exists(_talib_path): | |
| _spec = importlib.util.spec_from_file_location('talib_compat', _talib_path) | |
| talib = importlib.util.module_from_spec(_spec) | |
| sys.modules['talib'] = talib | |
| _spec.loader.exec_module(talib) | |
| else: | |
| raise ImportError("talib 或 talib.py 兼容层均不可用") | |
| # ============================================================================ | |
| # 第一部分:TechnicalIndicators —— 12个固定参数指标计算 | |
| # ============================================================================ | |
| class TechnicalIndicators: | |
| """技术指标计算类 —— 严格按固定参数执行""" | |
| def __init__(self): | |
| self.indicators = {} | |
| def calculate_all_indicators(self, df: pd.DataFrame) -> pd.DataFrame: | |
| """计算全部12类技术指标""" | |
| df = df.copy() | |
| if 'timestamps' in df.columns: | |
| df = df.sort_values('timestamps') | |
| df = self._calc_ema_system(df) # EMA5/10/20/60 | |
| df = self._calc_macd(df) # MACD(5,10,3) | |
| df = self._calc_kdj(df) # KDJ(3,3,3) | |
| df = self._calc_boll(df) # BOLL(20,2) | |
| df = self._calc_rsi(df) # RSI(6,14) | |
| df = self._calc_cci(df) # CCI(20) | |
| df = self._calc_volume(df) # 成交量+5/10日均量 | |
| df = self._calc_obv(df) # OBV | |
| df = self._calc_dmi(df) # DMI(14,6) | |
| df = self._calc_sar(df) # SAR(0.02,0.2) | |
| df = self._calc_mfi(df) # MFI(14) | |
| df = self._calc_auxiliary(df) # 辅助指标(ATR/ROC/MOM等) | |
| return df | |
| # ---------- 1. EMA 均线系统 ---------- | |
| def _calc_ema_system(self, df): | |
| c = df['close'].values.astype(float) | |
| for p in (5, 10, 20, 60): | |
| df[f'ema_{p}'] = talib.EMA(c, timeperiod=p) | |
| # SMA 作为辅助参考 | |
| for p in (5, 10, 20): | |
| df[f'sma_{p}'] = talib.SMA(c, timeperiod=p) | |
| return df | |
| # ---------- 2. MACD(5,10,3) ---------- | |
| def _calc_macd(self, df): | |
| c = df['close'].values.astype(float) | |
| df['macd'], df['macd_signal'], df['macd_hist'] = talib.MACD( | |
| c, fastperiod=5, slowperiod=10, signalperiod=3) | |
| return df | |
| # ---------- 3. KDJ(3,3,3) ---------- | |
| def _calc_kdj(self, df): | |
| h = df['high'].values.astype(float) | |
| l = df['low'].values.astype(float) | |
| c = df['close'].values.astype(float) | |
| df['k'], df['d'] = talib.STOCH( | |
| h, l, c, fastk_period=3, slowk_period=3, slowd_period=3) | |
| df['j'] = 3 * df['k'] - 2 * df['d'] | |
| return df | |
| # ---------- 4. BOLL(20,2) ---------- | |
| def _calc_boll(self, df): | |
| c = df['close'].values.astype(float) | |
| df['bb_upper'], df['bb_middle'], df['bb_lower'] = talib.BBANDS( | |
| c, timeperiod=20, nbdevup=2, nbdevdn=2) | |
| df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_middle'] | |
| df['bb_position'] = (c - df['bb_lower']) / (df['bb_upper'] - df['bb_lower']) | |
| return df | |
| # ---------- 5. RSI(6,14) ---------- | |
| def _calc_rsi(self, df): | |
| c = df['close'].values.astype(float) | |
| df['rsi_6'] = talib.RSI(c, timeperiod=6) | |
| df['rsi_14'] = talib.RSI(c, timeperiod=14) | |
| return df | |
| # ---------- 6. CCI(20) ---------- | |
| def _calc_cci(self, df): | |
| h = df['high'].values.astype(float) | |
| l = df['low'].values.astype(float) | |
| c = df['close'].values.astype(float) | |
| df['cci'] = talib.CCI(h, l, c, timeperiod=20) | |
| return df | |
| # ---------- 7. 成交量体系 ---------- | |
| def _calc_volume(self, df): | |
| v = df['volume'].values.astype(float) | |
| df['vol_ma5'] = talib.SMA(v, timeperiod=5) | |
| df['vol_ma10'] = talib.SMA(v, timeperiod=10) | |
| df['vol_ratio5'] = v / df['vol_ma5'] | |
| df['vol_ratio10'] = v / df['vol_ma10'] | |
| return df | |
| # ---------- 8. OBV ---------- | |
| def _calc_obv(self, df): | |
| c = df['close'].values.astype(float) | |
| v = df['volume'].values.astype(float) | |
| df['obv'] = talib.OBV(c, v) | |
| # OBV 均线 | |
| df['obv_ma10'] = talib.EMA(df['obv'].values.astype(float), timeperiod=10) | |
| return df | |
| # ---------- 9. DMI(14,6) ---------- | |
| def _calc_dmi(self, df): | |
| h = df['high'].values.astype(float) | |
| l = df['low'].values.astype(float) | |
| c = df['close'].values.astype(float) | |
| df['adx'] = talib.ADX(h, l, c, timeperiod=14) | |
| df['plus_di'] = talib.PLUS_DI(h, l, c, timeperiod=14) | |
| df['minus_di'] = talib.MINUS_DI(h, l, c, timeperiod=14) | |
| # ADX 平滑(6周期EMA)用于趋势确认 | |
| df['adx_smooth'] = talib.EMA(df['adx'].fillna(0).values.astype(float), timeperiod=6) | |
| df['di_diff'] = df['plus_di'] - df['minus_di'] # 多空力量差 | |
| return df | |
| # ---------- 10. SAR(0.02,0.2) ---------- | |
| def _calc_sar(self, df): | |
| h = df['high'].values.astype(float) | |
| l = df['low'].values.astype(float) | |
| df['sar'] = talib.SAR(h, l, acceleration=0.02, maximum=0.2) | |
| df['sar_signal'] = np.where(df['close'] > df['sar'], 1, -1) # 1多头 -1空头 | |
| return df | |
| # ---------- 11. MFI(14) ---------- | |
| def _calc_mfi(self, df): | |
| h = df['high'].values.astype(float) | |
| l = df['low'].values.astype(float) | |
| c = df['close'].values.astype(float) | |
| v = df['volume'].values.astype(float) | |
| df['mfi'] = talib.MFI(h, l, c, v, timeperiod=14) | |
| return df | |
| # ---------- 辅助指标 ---------- | |
| def _calc_auxiliary(self, df): | |
| h = df['high'].values.astype(float) | |
| l = df['low'].values.astype(float) | |
| c = df['close'].values.astype(float) | |
| v = df['volume'].values.astype(float) | |
| df['atr'] = talib.ATR(h, l, c, timeperiod=14) | |
| df['willr'] = talib.WILLR(h, l, c, timeperiod=14) | |
| df['roc_10'] = talib.ROC(c, timeperiod=10) | |
| df['mom_10'] = talib.MOM(c, timeperiod=10) | |
| df['cmo'] = talib.CMO(c, timeperiod=14) | |
| # AD 累积/派发 | |
| df['ad'] = talib.AD(h, l, c, v) | |
| # 威廉%R 辅助 | |
| df['aroon_up'], df['aroon_down'] = talib.AROON(h, l, timeperiod=14) | |
| df['ultosc'] = talib.ULTOSC(h, l, c, timeperiod1=7, timeperiod2=14, timeperiod3=28) | |
| # 量价关系 | |
| df['price_change'] = df['close'].pct_change() | |
| df['volume_change'] = df['volume'].pct_change() | |
| return df | |
| # ============================================================================ | |
| # 第二部分:保留原有 VolumePriceAnalyzer / WaveTheoryAnalyzer(向后兼容) | |
| # ============================================================================ | |
| class VolumePriceAnalyzer: | |
| """量价分析器""" | |
| def __init__(self): | |
| self.indicators = TechnicalIndicators() | |
| def analyze_volume_price_relationship(self, df): | |
| analysis = {} | |
| price_change = df['close'].pct_change() | |
| volume_change = df['volume'].pct_change() | |
| corr_val = price_change.corr(volume_change) | |
| analysis['volume_price_correlation'] = 0.0 if (corr_val is None or (isinstance(corr_val, float) and np.isnan(corr_val))) else corr_val | |
| analysis['volume_price_divergence'] = self._detect_divergence(df) | |
| analysis['volume_anomaly_score'] = self._calculate_volume_anomaly_score(df) | |
| analysis['volume_price_sync_score'] = self._calculate_sync_score(df) | |
| return analysis | |
| def _detect_divergence(self, df): | |
| pt = df['close'].rolling(window=10).apply(lambda x: np.polyfit(range(len(x)), x, 1)[0]) | |
| vt = df['volume'].rolling(window=10).apply(lambda x: np.polyfit(range(len(x)), x, 1)[0]) | |
| d = np.corrcoef(pt.dropna(), vt.dropna())[0, 1] | |
| return d if not np.isnan(d) else 0.0 | |
| def _calculate_volume_anomaly_score(self, df): | |
| vm = df['volume'].rolling(20).mean() | |
| vs = df['volume'].rolling(20).std() | |
| a = np.abs((df['volume'] - vm) / vs) | |
| return a.mean() if not a.isna().all() else 0.0 | |
| def _calculate_sync_score(self, df): | |
| pd_dir = np.sign(df['close'].diff()) | |
| vd_dir = np.sign(df['volume'].diff()) | |
| s = (pd_dir == vd_dir).mean() | |
| return s if not np.isnan(s) else 0.0 | |
| class WaveTheoryAnalyzer: | |
| """波浪理论分析器""" | |
| def __init__(self): | |
| self.indicators = TechnicalIndicators() | |
| def analyze_wave_patterns(self, df): | |
| analysis = {} | |
| analysis['wave_count'] = self._count_waves(df) | |
| analysis['wave_strength'] = self._calculate_wave_strength(df) | |
| analysis['fibonacci_levels'] = self._calculate_fibonacci_analysis(df) | |
| analysis['wave_phase'] = self._identify_wave_phase(df) | |
| return analysis | |
| def _count_waves(self, df): | |
| c = df['close'].values | |
| waves, trend = 0, 0 | |
| for i in range(1, len(c)): | |
| if c[i] > c[i-1]: | |
| if trend <= 0: waves += 1; trend = 1 | |
| else: | |
| if trend >= 0: waves += 1; trend = -1 | |
| return waves | |
| def _calculate_wave_strength(self, df): | |
| c = df['close'].values | |
| changes = np.diff(c) | |
| cc, mc = 0, 0 | |
| for ch in changes: | |
| if ch > 0: | |
| cc += 1; mc = max(mc, cc) | |
| else: | |
| cc = 0 | |
| return mc / len(changes) if len(changes) > 0 else 0.0 | |
| def _calculate_fibonacci_analysis(self, df): | |
| c = df['close'].values | |
| rh, rl = np.max(c[-20:]), np.min(c[-20:]) | |
| return {k: rl + (rh - rl) * v for k, v in | |
| [('fib_236', 0.236), ('fib_382', 0.382), ('fib_500', 0.5), | |
| ('fib_618', 0.618), ('fib_786', 0.786)]} | |
| def _identify_wave_phase(self, df): | |
| c = df['close'].values | |
| if len(c) < 10: return "insufficient_data" | |
| rt = np.polyfit(range(10), c[-10:], 1)[0] | |
| if rt > 0.01: return "impulse_wave" | |
| elif rt < -0.01: return "corrective_wave" | |
| return "consolidation" | |
| # ============================================================================ | |
| # 第三部分:MultiDimensionSignalAnalyzer —— 多维度联动研判 + 信号共振 | |
| # ============================================================================ | |
| class MultiDimensionSignalAnalyzer: | |
| """ | |
| 多维度技术指标联动研判体系 | |
| 5大研判维度: | |
| 1. 趋势判定 (Trend) : MACD(5,10,3) + EMA5/10/20/60 + BOLL(20,2) | |
| 2. 拐点判定 (Inflection) : KDJ(3,3,3) + RSI(6,14) + CCI(20) | |
| 3. 行情验证 (Verification): 成交量+5/10日均量 + OBV | |
| 4. 强度确认 (Strength) : DMI(14,6) + MFI(14) | |
| 5. 风控配套 (RiskControl) : SAR(0.02,0.2) | |
| 信号生效原则: | |
| - 多指标同向共振 → 采信 | |
| - 指标相互背离 → 暂缓观望 | |
| """ | |
| def __init__(self): | |
| self.calc = TechnicalIndicators() | |
| def analyze(self, df: pd.DataFrame) -> Dict: | |
| """ | |
| 执行完整的多维度联动研判 | |
| Parameters | |
| ---------- | |
| df : DataFrame | |
| OHLCV 数据(需包含 open/high/low/close/volume 列) | |
| Returns | |
| ------- | |
| dict 包含 dimensions、resonance、overall_signal、indicators_detail | |
| """ | |
| df = self.calc.calculate_all_indicators(df.copy()) | |
| last = df.iloc[-1] | |
| prev = df.iloc[-2] if len(df) >= 2 else last | |
| # 提取最新值 | |
| iv = self._extract_latest(df, last, prev) | |
| # 5大维度逐一研判 | |
| d_trend = self._judge_trend(iv) | |
| d_inflection = self._judge_inflection(iv) | |
| d_verification = self._judge_verification(iv, df) | |
| d_strength = self._judge_strength(iv) | |
| d_risk = self._judge_risk(iv) | |
| dimensions = { | |
| 'trend': d_trend, | |
| 'inflection': d_inflection, | |
| 'verification': d_verification, | |
| 'strength': d_strength, | |
| 'risk_control': d_risk, | |
| } | |
| # 信号共振 | |
| resonance = self._calc_resonance(dimensions) | |
| # 综合研判 | |
| overall = self._calc_overall(dimensions, resonance) | |
| # 指标详情(供前端展示) | |
| detail = self._build_detail(iv, dimensions) | |
| return { | |
| 'dimensions': dimensions, | |
| 'resonance': resonance, | |
| 'overall_signal': overall, | |
| 'indicators_detail': detail, | |
| } | |
| # ------------------------------------------------------------------ | |
| # 提取最新指标值 | |
| # ------------------------------------------------------------------ | |
| def _extract_latest(self, df, last, prev): | |
| """从 DataFrame 中提取最新和前一日的指标值""" | |
| def _v(col): | |
| return last.get(col, np.nan) | |
| def _p(col): | |
| return prev.get(col, np.nan) | |
| return { | |
| # 价格 | |
| 'close': _v('close'), 'open': _v('open'), | |
| 'high': _v('high'), 'low': _v('low'), | |
| # EMA | |
| 'ema5': _v('ema_5'), 'ema10': _v('ema_10'), | |
| 'ema20': _v('ema_20'), 'ema60': _v('ema_60'), | |
| # MACD | |
| 'macd': _v('macd'), 'macd_signal': _v('macd_signal'), | |
| 'macd_hist': _v('macd_hist'), | |
| 'macd_prev': _p('macd'), 'macd_signal_prev': _p('macd_signal'), | |
| # KDJ | |
| 'k': _v('k'), 'd': _v('d'), 'j': _v('j'), | |
| 'k_prev': _p('k'), 'd_prev': _p('d'), | |
| # BOLL | |
| 'bb_upper': _v('bb_upper'), 'bb_middle': _v('bb_middle'), | |
| 'bb_lower': _v('bb_lower'), 'bb_width': _v('bb_width'), | |
| 'bb_position': _v('bb_position'), | |
| # RSI | |
| 'rsi6': _v('rsi_6'), 'rsi14': _v('rsi_14'), | |
| # CCI | |
| 'cci': _v('cci'), | |
| # 成交量 | |
| 'volume': _v('volume'), 'vol_ma5': _v('vol_ma5'), 'vol_ma10': _v('vol_ma10'), | |
| 'vol_ratio5': _v('vol_ratio5'), 'vol_ratio10': _v('vol_ratio10'), | |
| 'close_prev': _p('close'), | |
| # OBV | |
| 'obv': _v('obv'), 'obv_ma10': _v('obv_ma10'), | |
| # DMI | |
| 'adx': _v('adx'), 'adx_smooth': _v('adx_smooth'), | |
| 'plus_di': _v('plus_di'), 'minus_di': _v('minus_di'), | |
| 'di_diff': _v('di_diff'), | |
| # SAR | |
| 'sar': _v('sar'), 'sar_signal': _v('sar_signal'), | |
| # MFI | |
| 'mfi': _v('mfi'), | |
| # 辅助 | |
| 'atr': _v('atr'), | |
| } | |
| # ------------------------------------------------------------------ | |
| # 维度1:趋势判定 (MACD + EMA + BOLL) | |
| # ------------------------------------------------------------------ | |
| def _judge_trend(self, iv): | |
| """趋势判定:MACD + 多级EMA均线定方向,BOLL确认运行格局""" | |
| signals = [] | |
| # --- MACD --- | |
| macd_score = 0 | |
| macd_text = [] | |
| # MACD柱状图方向 | |
| if not np.isnan(iv['macd_hist']): | |
| if iv['macd_hist'] > 0: | |
| macd_score += 1 | |
| macd_text.append('MACD柱>0,多头动能') | |
| else: | |
| macd_score -= 1 | |
| macd_text.append('MACD柱<0,空头动能') | |
| # MACD金叉/死叉 | |
| if not np.isnan(iv['macd']) and not np.isnan(iv['macd_signal']): | |
| if not np.isnan(iv['macd_prev']) and not np.isnan(iv['macd_signal_prev']): | |
| prev_diff = iv['macd_prev'] - iv['macd_signal_prev'] | |
| curr_diff = iv['macd'] - iv['macd_signal'] | |
| if prev_diff <= 0 and curr_diff > 0: | |
| macd_score += 2 | |
| macd_text.append('MACD金叉确认') | |
| elif prev_diff >= 0 and curr_diff < 0: | |
| macd_score -= 2 | |
| macd_text.append('MACD死叉确认') | |
| if iv['macd'] > iv['macd_signal']: | |
| macd_score += 0.5 | |
| else: | |
| macd_score -= 0.5 | |
| # MACD零轴位置 | |
| if not np.isnan(iv['macd']): | |
| if iv['macd'] > 0: | |
| macd_score += 1 | |
| macd_text.append('MACD运行在零轴上方') | |
| else: | |
| macd_score -= 1 | |
| macd_text.append('MACD运行在零轴下方') | |
| signals.append({ | |
| 'indicator': 'MACD(5,10,3)', | |
| 'score': macd_score, | |
| 'max_score': 4, | |
| 'signal': 'bullish' if macd_score > 0 else 'bearish' if macd_score < 0 else 'neutral', | |
| 'details': macd_text | |
| }) | |
| # --- EMA均线系统 --- | |
| ema_score = 0 | |
| ema_text = [] | |
| close = iv['close'] | |
| # 多头排列判定:EMA5 > EMA10 > EMA20 > EMA60 | |
| emas = [('EMA5', iv['ema5']), ('EMA10', iv['ema10']), | |
| ('EMA20', iv['ema20']), ('EMA60', iv['ema60'])] | |
| valid_emas = [(n, v) for n, v in emas if not np.isnan(v)] | |
| if len(valid_emas) >= 2: | |
| is_bull = all(valid_emas[i][1] >= valid_emas[i+1][1] | |
| for i in range(len(valid_emas)-1)) | |
| is_bear = all(valid_emas[i][1] <= valid_emas[i+1][1] | |
| for i in range(len(valid_emas)-1)) | |
| if is_bull: | |
| ema_score += 2 | |
| ema_text.append('均线多头排列') | |
| elif is_bear: | |
| ema_score -= 2 | |
| ema_text.append('均线空头排列') | |
| # 价格相对EMA5位置 | |
| if not np.isnan(iv['ema5']): | |
| if close > iv['ema5']: | |
| ema_score += 0.5 | |
| ema_text.append('价格站上EMA5') | |
| else: | |
| ema_score -= 0.5 | |
| ema_text.append('价格跌破EMA5') | |
| # EMA5/EMA10金叉死叉(短期) | |
| if not np.isnan(iv['ema5']) and not np.isnan(iv['ema10']): | |
| if iv['ema5'] > iv['ema10']: | |
| ema_score += 0.5 | |
| ema_text.append('EMA5>EMA10短线偏多') | |
| else: | |
| ema_score -= 0.5 | |
| ema_text.append('EMA5<EMA10短线偏空') | |
| # EMA20支撑/压力 | |
| if not np.isnan(iv['ema20']): | |
| if close > iv['ema20']: | |
| ema_score += 0.5 | |
| ema_text.append('价格在EMA20上方运行') | |
| else: | |
| ema_score -= 0.5 | |
| ema_text.append('价格在EMA20下方运行') | |
| signals.append({ | |
| 'indicator': 'EMA均线系统', | |
| 'score': ema_score, | |
| 'max_score': 3.5, | |
| 'signal': 'bullish' if ema_score > 0 else 'bearish' if ema_score < 0 else 'neutral', | |
| 'details': ema_text | |
| }) | |
| # --- BOLL --- | |
| boll_score = 0 | |
| boll_text = [] | |
| if not np.isnan(iv['bb_position']): | |
| bp = iv['bb_position'] | |
| if bp > 0.8: | |
| boll_score -= 1 | |
| boll_text.append('价格接近上轨,注意回调风险') | |
| elif bp > 0.5: | |
| boll_score += 0.5 | |
| boll_text.append('价格在中上轨区间,偏强运行') | |
| elif bp < 0.2: | |
| boll_score += 1 | |
| boll_text.append('价格接近下轨,关注反弹机会') | |
| elif bp < 0.5: | |
| boll_score -= 0.5 | |
| boll_text.append('价格在中下轨区间,偏弱运行') | |
| # 轨道收窄/扩张 | |
| if not np.isnan(iv['bb_width']): | |
| # 简化:用bb_width值判断 | |
| bw = iv['bb_width'] | |
| if bw > 0.15: | |
| boll_text.append('布林带宽较大,波动剧烈') | |
| elif bw < 0.05: | |
| boll_text.append('布林带极度收窄,变盘在即') | |
| signals.append({ | |
| 'indicator': 'BOLL(20,2)', | |
| 'score': boll_score, | |
| 'max_score': 1.5, | |
| 'signal': 'bullish' if boll_score > 0 else 'bearish' if boll_score < 0 else 'neutral', | |
| 'details': boll_text | |
| }) | |
| # 维度总分 | |
| total = sum(s['score'] for s in signals) | |
| max_total = sum(s['max_score'] for s in signals) | |
| return { | |
| 'name': '趋势判定', | |
| 'description': 'MACD + 多级EMA均线定方向,BOLL确认运行格局', | |
| 'signals': signals, | |
| 'score': total, | |
| 'max_score': max_total, | |
| 'verdict': 'bullish' if total > max_total * 0.3 else 'bearish' if total < -max_total * 0.3 else 'neutral', | |
| 'verdict_text': '趋势偏多' if total > max_total * 0.3 else '趋势偏空' if total < -max_total * 0.3 else '趋势不明/震荡', | |
| } | |
| # ------------------------------------------------------------------ | |
| # 维度2:拐点判定 (KDJ + RSI + CCI) | |
| # ------------------------------------------------------------------ | |
| def _judge_inflection(self, iv): | |
| """拐点判定:KDJ+RSI+CCI组合捕捉短期高低点反转""" | |
| signals = [] | |
| # --- KDJ(3,3,3) --- | |
| kdj_score = 0 | |
| kdj_text = [] | |
| if not np.isnan(iv['k']) and not np.isnan(iv['d']): | |
| k, d, j = iv['k'], iv['d'], iv['j'] | |
| # 超买超卖 | |
| if k > 80 and d > 80: | |
| kdj_score -= 1.5 | |
| kdj_text.append('KDJ高位超买区(K={:.0f},D={:.0f})'.format(k, d)) | |
| elif k < 20 and d < 20: | |
| kdj_score += 1.5 | |
| kdj_text.append('KDJ低位超卖区(K={:.0f},D={:.0f})'.format(k, d)) | |
| elif k > 50: | |
| kdj_score += 0.5 | |
| kdj_text.append('KDJ处于强势区间') | |
| elif k < 50: | |
| kdj_score -= 0.5 | |
| kdj_text.append('KDJ处于弱势区间') | |
| # J值极端 | |
| if not np.isnan(j): | |
| if j > 100: | |
| kdj_score -= 1 | |
| kdj_text.append('J值>100,严重超买') | |
| elif j < 0: | |
| kdj_score += 1 | |
| kdj_text.append('J值<0,严重超卖') | |
| # 金叉死叉 | |
| if not np.isnan(iv['k_prev']) and not np.isnan(iv['d_prev']): | |
| if iv['k_prev'] <= iv['d_prev'] and k > d: | |
| kdj_score += 1.5 | |
| kdj_text.append('KDJ低位金叉,反转信号') | |
| elif iv['k_prev'] >= iv['d_prev'] and k < d: | |
| kdj_score -= 1.5 | |
| kdj_text.append('KDJ高位死叉,见顶信号') | |
| signals.append({ | |
| 'indicator': 'KDJ(3,3,3)', | |
| 'score': kdj_score, | |
| 'max_score': 4, | |
| 'signal': 'bullish' if kdj_score > 0 else 'bearish' if kdj_score < 0 else 'neutral', | |
| 'details': kdj_text | |
| }) | |
| # --- RSI(6,14) --- | |
| rsi_score = 0 | |
| rsi_text = [] | |
| # RSI6 短线 | |
| if not np.isnan(iv['rsi6']): | |
| if iv['rsi6'] > 80: | |
| rsi_score -= 1 | |
| rsi_text.append('RSI6={:.1f},严重超买'.format(iv['rsi6'])) | |
| elif iv['rsi6'] > 70: | |
| rsi_score -= 0.5 | |
| rsi_text.append('RSI6={:.1f},超买区间'.format(iv['rsi6'])) | |
| elif iv['rsi6'] < 20: | |
| rsi_score += 1 | |
| rsi_text.append('RSI6={:.1f},严重超卖'.format(iv['rsi6'])) | |
| elif iv['rsi6'] < 30: | |
| rsi_score += 0.5 | |
| rsi_text.append('RSI6={:.1f},超卖区间'.format(iv['rsi6'])) | |
| else: | |
| rsi_text.append('RSI6={:.1f},正常区间'.format(iv['rsi6'])) | |
| # RSI14 中线 | |
| if not np.isnan(iv['rsi14']): | |
| if iv['rsi14'] > 70: | |
| rsi_score -= 0.5 | |
| rsi_text.append('RSI14={:.1f},偏超买'.format(iv['rsi14'])) | |
| elif iv['rsi14'] < 30: | |
| rsi_score += 0.5 | |
| rsi_text.append('RSI14={:.1f},偏超卖'.format(iv['rsi14'])) | |
| # RSI背离辅助(简化:用RSI6与RSI14的方向差异) | |
| if not np.isnan(iv['rsi6']) and not np.isnan(iv['rsi14']): | |
| if iv['rsi6'] > 70 and iv['rsi14'] < 50: | |
| rsi_text.append('RSI6/RSI14背离,注意假突破') | |
| elif iv['rsi6'] < 30 and iv['rsi14'] > 50: | |
| rsi_text.append('RSI6/RSI14背离,注意假跌破') | |
| signals.append({ | |
| 'indicator': 'RSI(6,14)', | |
| 'score': rsi_score, | |
| 'max_score': 2, | |
| 'signal': 'bullish' if rsi_score > 0 else 'bearish' if rsi_score < 0 else 'neutral', | |
| 'details': rsi_text | |
| }) | |
| # --- CCI(20) --- | |
| cci_score = 0 | |
| cci_text = [] | |
| if not np.isnan(iv['cci']): | |
| c = iv['cci'] | |
| if c > 200: | |
| cci_score -= 1 | |
| cci_text.append('CCI={:.0f},极端强势多头'.format(c)) | |
| elif c > 100: | |
| cci_score -= 0.5 | |
| cci_text.append('CCI={:.0f},强势多头区间'.format(c)) | |
| elif c < -200: | |
| cci_score += 1 | |
| cci_text.append('CCI={:.0f},极端弱势空头'.format(c)) | |
| elif c < -100: | |
| cci_score += 0.5 | |
| cci_text.append('CCI={:.0f},弱势空头区间'.format(c)) | |
| else: | |
| cci_text.append('CCI={:.0f},正常波动区间'.format(c)) | |
| signals.append({ | |
| 'indicator': 'CCI(20)', | |
| 'score': cci_score, | |
| 'max_score': 1.5, | |
| 'signal': 'bullish' if cci_score > 0 else 'bearish' if cci_score < 0 else 'neutral', | |
| 'details': cci_text | |
| }) | |
| total = sum(s['score'] for s in signals) | |
| max_total = sum(s['max_score'] for s in signals) | |
| return { | |
| 'name': '拐点判定', | |
| 'description': 'KDJ+RSI+CCI组合捕捉短期高低点反转', | |
| 'signals': signals, | |
| 'score': total, | |
| 'max_score': max_total, | |
| 'verdict': 'bullish' if total > max_total * 0.3 else 'bearish' if total < -max_total * 0.3 else 'neutral', | |
| 'verdict_text': '拐点偏多/超卖反弹' if total > max_total * 0.3 else '拐点偏空/超买回调' if total < -max_total * 0.3 else '无明显拐点信号', | |
| } | |
| # ------------------------------------------------------------------ | |
| # 维度3:行情验证 (成交量 + OBV) | |
| # ------------------------------------------------------------------ | |
| def _judge_verification(self, iv, df): | |
| """行情验证:成交量+OBV验证趋势真实性""" | |
| signals = [] | |
| # --- 成交量体系 --- | |
| vol_score = 0 | |
| vol_text = [] | |
| close = iv['close'] | |
| close_prev = iv['close_prev'] | |
| # 量比判断 | |
| if not np.isnan(iv['vol_ratio5']): | |
| vr = iv['vol_ratio5'] | |
| if vr > 2.0: | |
| vol_text.append('5日量比={:.1f},显著放量'.format(vr)) | |
| if close > close_prev: | |
| vol_score += 1.5 # 放量上涨 | |
| else: | |
| vol_score -= 1.0 # 放量下跌 | |
| elif vr > 1.3: | |
| vol_text.append('5日量比={:.1f},温和放量'.format(vr)) | |
| if close > close_prev: | |
| vol_score += 0.5 | |
| elif vr < 0.5: | |
| vol_text.append('5日量比={:.1f},显著缩量'.format(vr)) | |
| # 缩量下跌视为惜售,缩量上涨视为动力不足 | |
| if close < close_prev: | |
| vol_score += 0.5 # 缩量下跌=惜售 | |
| else: | |
| vol_score -= 0.5 | |
| else: | |
| vol_text.append('5日量比={:.1f},成交量正常'.format(vr)) | |
| # 10日量比 | |
| if not np.isnan(iv['vol_ratio10']): | |
| vr10 = iv['vol_ratio10'] | |
| if vr10 > 1.5: | |
| vol_text.append('10日量比={:.1f},中期放量'.format(vr10)) | |
| signals.append({ | |
| 'indicator': '成交量体系', | |
| 'score': vol_score, | |
| 'max_score': 2, | |
| 'signal': 'bullish' if vol_score > 0 else 'bearish' if vol_score < 0 else 'neutral', | |
| 'details': vol_text | |
| }) | |
| # --- OBV --- | |
| obv_score = 0 | |
| obv_text = [] | |
| if not np.isnan(iv['obv']) and not np.isnan(iv['obv_ma10']): | |
| obv = iv['obv'] | |
| obv_ma = iv['obv_ma10'] | |
| if obv > obv_ma: | |
| obv_score += 1 | |
| obv_text.append('OBV位于均线上方,资金流入') | |
| else: | |
| obv_score -= 1 | |
| obv_text.append('OBV位于均线下方,资金流出') | |
| # OBV趋势与价格趋势一致性 | |
| if not np.isnan(close_prev) and close > close_prev: | |
| if obv > obv_ma: | |
| obv_text.append('量价齐升,趋势健康') | |
| obv_score += 0.5 | |
| else: | |
| obv_text.append('价涨量缩,需警惕量价背离') | |
| obv_score -= 0.5 | |
| elif not np.isnan(close_prev) and close < close_prev: | |
| if obv < obv_ma: | |
| obv_text.append('量价齐跌,空头确认') | |
| obv_score -= 0.5 | |
| else: | |
| obv_text.append('价跌量升,主力吸筹可能') | |
| obv_score += 0.5 | |
| signals.append({ | |
| 'indicator': 'OBV能量潮', | |
| 'score': obv_score, | |
| 'max_score': 1.5, | |
| 'signal': 'bullish' if obv_score > 0 else 'bearish' if obv_score < 0 else 'neutral', | |
| 'details': obv_text | |
| }) | |
| total = sum(s['score'] for s in signals) | |
| max_total = sum(s['max_score'] for s in signals) | |
| return { | |
| 'name': '行情验证', | |
| 'description': '成交量+OBV验证趋势真实性,排除无量假突破/假回落', | |
| 'signals': signals, | |
| 'score': total, | |
| 'max_score': max_total, | |
| 'verdict': 'bullish' if total > max_total * 0.2 else 'bearish' if total < -max_total * 0.2 else 'neutral', | |
| 'verdict_text': '量能验证通过,趋势可信' if total > max_total * 0.2 else '量能不支持,警惕假信号' if total < -max_total * 0.2 else '量能中性', | |
| } | |
| # ------------------------------------------------------------------ | |
| # 维度4:强度确认 (DMI + MFI) | |
| # ------------------------------------------------------------------ | |
| def _judge_strength(self, iv): | |
| """强度确认:DMI+MFI确认行情动能强弱与资金认可度""" | |
| signals = [] | |
| # --- DMI(14,6) --- | |
| dmi_score = 0 | |
| dmi_text = [] | |
| if not np.isnan(iv['adx']): | |
| adx = iv['adx'] | |
| # ADX > 25 趋势明确 | |
| if adx > 50: | |
| dmi_text.append('ADX={:.1f},极强趋势'.format(adx)) | |
| dmi_score += 1.5 | |
| elif adx > 25: | |
| dmi_text.append('ADX={:.1f},趋势明确'.format(adx)) | |
| dmi_score += 1 | |
| elif adx > 20: | |
| dmi_text.append('ADX={:.1f},趋势形成中'.format(adx)) | |
| dmi_score += 0.5 | |
| else: | |
| dmi_text.append('ADX={:.1f},无明确趋势/震荡市'.format(adx)) | |
| # +DI/-DI 判断多空力量 | |
| if not np.isnan(iv['plus_di']) and not np.isnan(iv['minus_di']): | |
| pdi, mdi = iv['plus_di'], iv['minus_di'] | |
| if pdi > mdi: | |
| dmi_score += 1 | |
| dmi_text.append('+DI({:.1f})>-DI({:.1f}),多头主导'.format(pdi, mdi)) | |
| else: | |
| dmi_score -= 1 | |
| dmi_text.append('-DI({:.1f})>+DI({:.1f}),空头主导'.format(mdi, pdi)) | |
| # DI差值 | |
| if not np.isnan(iv['di_diff']): | |
| diff = iv['di_diff'] | |
| if diff > 20: | |
| dmi_text.append('多空力量差={:.1f},多方优势明显'.format(diff)) | |
| dmi_score += 0.5 | |
| elif diff < -20: | |
| dmi_text.append('多空力量差={:.1f},空方优势明显'.format(diff)) | |
| dmi_score -= 0.5 | |
| signals.append({ | |
| 'indicator': 'DMI(14,6)', | |
| 'score': dmi_score, | |
| 'max_score': 3, | |
| 'signal': 'bullish' if dmi_score > 0 else 'bearish' if dmi_score < 0 else 'neutral', | |
| 'details': dmi_text | |
| }) | |
| # --- MFI(14) --- | |
| mfi_score = 0 | |
| mfi_text = [] | |
| if not np.isnan(iv['mfi']): | |
| mfi = iv['mfi'] | |
| if mfi > 80: | |
| mfi_score -= 1 | |
| mfi_text.append('MFI={:.1f},资金过度流入/超买'.format(mfi)) | |
| elif mfi > 60: | |
| mfi_score += 0.5 | |
| mfi_text.append('MFI={:.1f},资金持续流入'.format(mfi)) | |
| elif mfi < 20: | |
| mfi_score += 1 | |
| mfi_text.append('MFI={:.1f},资金过度流出/超卖'.format(mfi)) | |
| elif mfi < 40: | |
| mfi_score -= 0.5 | |
| mfi_text.append('MFI={:.1f},资金持续流出'.format(mfi)) | |
| else: | |
| mfi_text.append('MFI={:.1f},资金流向中性'.format(mfi)) | |
| signals.append({ | |
| 'indicator': 'MFI(14)', | |
| 'score': mfi_score, | |
| 'max_score': 1.5, | |
| 'signal': 'bullish' if mfi_score > 0 else 'bearish' if mfi_score < 0 else 'neutral', | |
| 'details': mfi_text | |
| }) | |
| total = sum(s['score'] for s in signals) | |
| max_total = sum(s['max_score'] for s in signals) | |
| return { | |
| 'name': '强度确认', | |
| 'description': 'DMI+MFI确认行情动能强弱与资金认可度', | |
| 'signals': signals, | |
| 'score': total, | |
| 'max_score': max_total, | |
| 'verdict': 'bullish' if total > max_total * 0.3 else 'bearish' if total < -max_total * 0.3 else 'neutral', | |
| 'verdict_text': '动能强劲,资金认可' if total > max_total * 0.3 else '动能衰竭,资金撤离' if total < -max_total * 0.3 else '动能中性', | |
| } | |
| # ------------------------------------------------------------------ | |
| # 维度5:风控配套 (SAR) | |
| # ------------------------------------------------------------------ | |
| def _judge_risk(self, iv): | |
| """风控配套:SAR自动锚定趋势止盈止损点位""" | |
| signals = [] | |
| sar_score = 0 | |
| sar_text = [] | |
| if not np.isnan(iv['sar']) and not np.isnan(iv['sar_signal']): | |
| close = iv['close'] | |
| sar = iv['sar'] | |
| if iv['sar_signal'] == 1: | |
| # SAR在价格下方 = 多头趋势 | |
| sar_score += 2 | |
| sar_text.append('SAR={:.2f}在价格下方,多头趋势运行'.format(sar)) | |
| sar_text.append('SAR可作为止损参考位: {:.2f}'.format(sar)) | |
| else: | |
| # SAR在价格上方 = 空头趋势 | |
| sar_score -= 2 | |
| sar_text.append('SAR={:.2f}在价格上方,空头趋势运行'.format(sar)) | |
| sar_text.append('SAR可作为止盈/止损参考位: {:.2f}'.format(sar)) | |
| # SAR与价格距离(衡量风险敞口) | |
| distance = abs(close - sar) / close * 100 | |
| if distance > 5: | |
| sar_text.append('价格偏离SAR {:.1f}%,趋势强劲但注意回撤'.format(distance)) | |
| elif distance < 1: | |
| sar_text.append('价格接近SAR {:.1f}%,趋势可能反转'.format(distance)) | |
| signals.append({ | |
| 'indicator': 'SAR(0.02,0.2)', | |
| 'score': sar_score, | |
| 'max_score': 2, | |
| 'signal': 'bullish' if sar_score > 0 else 'bearish' if sar_score < 0 else 'neutral', | |
| 'details': sar_text | |
| }) | |
| return { | |
| 'name': '风控配套', | |
| 'description': 'SAR自动锚定趋势止盈止损点位,辅助风控量化', | |
| 'signals': signals, | |
| 'score': sar_score, | |
| 'max_score': 2, | |
| 'verdict': 'bullish' if sar_score > 0 else 'bearish' if sar_score < 0 else 'neutral', | |
| 'verdict_text': 'SAR多头运行,可持仓' if sar_score > 0 else 'SAR空头运行,宜止损' if sar_score < 0 else 'SAR中性', | |
| } | |
| # ------------------------------------------------------------------ | |
| # 信号共振计算 | |
| # ------------------------------------------------------------------ | |
| def _calc_resonance(self, dimensions): | |
| """ | |
| 多指标同向共振优先采信,指标背离则暂缓判定 | |
| 共振等级: | |
| - 强共振:5个维度全部同向 | |
| - 弱共振:4个维度同向 | |
| - 共振:3个维度同向 | |
| - 分歧:2/3 维度反向 | |
| - 强分歧:各维度方向混乱 | |
| - 背离:方向完全不一致 | |
| """ | |
| verdicts = {k: v['verdict'] for k, v in dimensions.items()} | |
| bullish_count = sum(1 for v in verdicts.values() if v == 'bullish') | |
| bearish_count = sum(1 for v in verdicts.values() if v == 'bearish') | |
| neutral_count = sum(1 for v in verdicts.values() if v == 'neutral') | |
| total = len(verdicts) # 5 | |
| if bullish_count == total: | |
| level = 'strong_bullish' | |
| text = '5维度强共振看多' | |
| elif bearish_count == total: | |
| level = 'strong_bearish' | |
| text = '5维度强共振看空' | |
| elif bullish_count >= 4: | |
| level = 'bullish' | |
| text = '4维度共振看多' | |
| elif bearish_count >= 4: | |
| level = 'bearish' | |
| text = '4维度共振看空' | |
| elif bullish_count >= 3: | |
| level = 'weak_bullish' | |
| text = '3维度偏多共振' | |
| elif bearish_count >= 3: | |
| level = 'weak_bearish' | |
| text = '3维度偏空共振' | |
| elif neutral_count >= 3: | |
| level = 'neutral' | |
| text = '多维度中性,方向不明' | |
| elif bullish_count == 2 and bearish_count == 2: | |
| level = 'divergence' | |
| text = '指标分歧,多空对峙' | |
| elif bullish_count == 2 and bearish_count == 1: | |
| level = 'slight_bullish' | |
| text = '略偏多,但缺乏共振确认' | |
| elif bearish_count == 2 and bullish_count == 1: | |
| level = 'slight_bearish' | |
| text = '略偏空,但缺乏共振确认' | |
| else: | |
| level = 'divergence' | |
| text = '指标严重分歧,暂缓操作' | |
| # 共振分数(-100 到 +100) | |
| score_map = { | |
| 'strong_bullish': 90, 'bullish': 70, 'weak_bullish': 40, | |
| 'slight_bullish': 20, 'neutral': 0, 'slight_bearish': -20, | |
| 'weak_bearish': -40, 'bearish': -70, 'strong_bearish': -90, | |
| 'divergence': 0, | |
| } | |
| return { | |
| 'level': level, | |
| 'text': text, | |
| 'score': score_map.get(level, 0), | |
| 'bullish_count': bullish_count, | |
| 'bearish_count': bearish_count, | |
| 'neutral_count': neutral_count, | |
| 'distribution': {k: v for k, v in verdicts.items()}, | |
| } | |
| # ------------------------------------------------------------------ | |
| # 综合研判 | |
| # ------------------------------------------------------------------ | |
| def _calc_overall(self, dimensions, resonance): | |
| """综合5维度和共振得出最终信号""" | |
| # 加权总分 | |
| weights = { | |
| 'trend': 0.30, # 趋势最重要 | |
| 'inflection': 0.20, # 拐点次之 | |
| 'verification': 0.20, # 行情验证 | |
| 'strength': 0.15, # 强度确认 | |
| 'risk_control': 0.15, # 风控 | |
| } | |
| weighted_score = 0 | |
| for dim_name, dim_data in dimensions.items(): | |
| w = weights.get(dim_name, 0.2) | |
| max_s = dim_data['max_score'] if dim_data['max_score'] > 0 else 1 | |
| normalized = dim_data['score'] / max_s # -1 到 +1 | |
| weighted_score += normalized * w | |
| # -1 到 +1 映射到分数 | |
| total_score = round(weighted_score * 100, 1) | |
| # 结合共振等级修正 | |
| res_level = resonance['level'] | |
| if 'strong' in res_level: | |
| # 强共振放大信号 | |
| total_score = total_score * 1.2 | |
| elif 'divergence' in res_level: | |
| # 分歧缩小信号 | |
| total_score = total_score * 0.5 | |
| total_score = max(-100, min(100, round(total_score, 1))) | |
| # 最终信号 | |
| if total_score > 40: | |
| signal = 'bullish' | |
| signal_text = '多头信号' | |
| elif total_score > 15: | |
| signal = 'slightly_bullish' | |
| signal_text = '偏多信号' | |
| elif total_score > -15: | |
| signal = 'neutral' | |
| signal_text = '观望信号' | |
| elif total_score > -40: | |
| signal = 'slightly_bearish' | |
| signal_text = '偏空信号' | |
| else: | |
| signal = 'bearish' | |
| signal_text = '空头信号' | |
| # 操作建议 | |
| if signal == 'bullish': | |
| action = '积极做多,注意SAR止损位' | |
| elif signal == 'slightly_bullish': | |
| action = '可轻仓参与,严格风控' | |
| elif signal == 'neutral': | |
| action = '暂缓操作,等待明确信号' | |
| elif signal == 'slightly_bearish': | |
| action = '减仓观望,不宜追高' | |
| else: | |
| action = '回避风险,考虑止损离场' | |
| return { | |
| 'signal': signal, | |
| 'signal_text': signal_text, | |
| 'score': total_score, | |
| 'action': action, | |
| 'resonance_level': res_level, | |
| 'resonance_text': resonance['text'], | |
| } | |
| # ------------------------------------------------------------------ | |
| # 指标详情构建(供前端展示) | |
| # ------------------------------------------------------------------ | |
| def _build_detail(self, iv, dimensions): | |
| """构建前端展示用的指标数值详情""" | |
| return { | |
| 'MACD': { | |
| 'DIF': self._safe_round(iv['macd'], 4), | |
| 'DEA': self._safe_round(iv['macd_signal'], 4), | |
| 'MACD柱': self._safe_round(iv['macd_hist'], 4), | |
| }, | |
| 'KDJ': { | |
| 'K': self._safe_round(iv['k'], 2), | |
| 'D': self._safe_round(iv['d'], 2), | |
| 'J': self._safe_round(iv['j'], 2), | |
| }, | |
| 'EMA': { | |
| 'EMA5': self._safe_round(iv['ema5'], 2), | |
| 'EMA10': self._safe_round(iv['ema10'], 2), | |
| 'EMA20': self._safe_round(iv['ema20'], 2), | |
| 'EMA60': self._safe_round(iv['ema60'], 2), | |
| }, | |
| 'BOLL': { | |
| '上轨': self._safe_round(iv['bb_upper'], 2), | |
| '中轨': self._safe_round(iv['bb_middle'], 2), | |
| '下轨': self._safe_round(iv['bb_lower'], 2), | |
| '位置': self._safe_round(iv['bb_position'], 3), | |
| }, | |
| 'RSI': { | |
| 'RSI6': self._safe_round(iv['rsi6'], 2), | |
| 'RSI14': self._safe_round(iv['rsi14'], 2), | |
| }, | |
| 'CCI': { | |
| 'CCI': self._safe_round(iv['cci'], 2), | |
| }, | |
| '成交量': { | |
| '5日均量': self._safe_round(iv['vol_ma5'], 0), | |
| '10日均量': self._safe_round(iv['vol_ma10'], 0), | |
| '5日量比': self._safe_round(iv['vol_ratio5'], 2), | |
| }, | |
| 'OBV': { | |
| 'OBV': self._safe_round(iv['obv'], 0), | |
| 'OBV_MA10': self._safe_round(iv['obv_ma10'], 0), | |
| }, | |
| 'DMI': { | |
| 'ADX': self._safe_round(iv['adx'], 2), | |
| '+DI': self._safe_round(iv['plus_di'], 2), | |
| '-DI': self._safe_round(iv['minus_di'], 2), | |
| }, | |
| 'SAR': { | |
| 'SAR': self._safe_round(iv['sar'], 2), | |
| '信号': '多头' if iv.get('sar_signal') == 1 else '空头', | |
| }, | |
| 'MFI': { | |
| 'MFI': self._safe_round(iv['mfi'], 2), | |
| }, | |
| } | |
| def _safe_round(val, digits): | |
| if val is None or (isinstance(val, float) and np.isnan(val)): | |
| return None | |
| return round(float(val), digits) | |
| # ============================================================================ | |
| # 向后兼容的增强入口 | |
| # ============================================================================ | |
| def enhance_kronos_features(df: pd.DataFrame) -> pd.DataFrame: | |
| """为Kronos模型增强特征(保持向后兼容)""" | |
| indicators = TechnicalIndicators() | |
| enhanced_df = indicators.calculate_all_indicators(df) | |
| vp_analyzer = VolumePriceAnalyzer() | |
| vp = vp_analyzer.analyze_volume_price_relationship(enhanced_df) | |
| for k, v in vp.items(): | |
| enhanced_df[f'vp_{k}'] = v | |
| w_analyzer = WaveTheoryAnalyzer() | |
| wa = w_analyzer.analyze_wave_patterns(enhanced_df) | |
| for k, v in wa.items(): | |
| if isinstance(v, dict): | |
| for sk, sv in v.items(): | |
| enhanced_df[f'wave_{k}_{sk}'] = sv | |
| else: | |
| enhanced_df[f'wave_{k}'] = v | |
| return enhanced_df | |
| def run_multidimension_analysis(df: pd.DataFrame) -> Dict: | |
| """ | |
| 执行多维度联动研判(供路由调用) | |
| Parameters | |
| ---------- | |
| df : DataFrame OHLCV 数据 | |
| Returns | |
| ------- | |
| dict 完整的多维度研判结果 | |
| """ | |
| analyzer = MultiDimensionSignalAnalyzer() | |
| return analyzer.analyze(df) | |
| # ============================================================================ | |
| # 测试 | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| dates = pd.date_range('2024-01-01', periods=120, freq='D') | |
| np.random.seed(42) | |
| base = 100 | |
| prices = [base] | |
| for i in range(119): | |
| change = np.random.normal(0, 0.02) | |
| prices.append(max(0.01, prices[-1] * (1 + change))) | |
| df = pd.DataFrame({ | |
| 'timestamps': dates, | |
| 'open': prices, | |
| 'high': [p * (1 + abs(np.random.normal(0, 0.015))) for p in prices], | |
| 'low': [p * (1 - abs(np.random.normal(0, 0.015))) for p in prices], | |
| 'close': prices, | |
| 'volume': np.random.normal(1000000, 300000, 120), | |
| }) | |
| # 测试多维度研判 | |
| result = run_multidimension_analysis(df) | |
| print("=" * 60) | |
| print("多维度技术指标联动研判结果") | |
| print("=" * 60) | |
| for dim_name, dim_data in result['dimensions'].items(): | |
| print(f"\n【{dim_data['name']}】 {dim_data['description']}") | |
| print(f" 判定: {dim_data['verdict_text']} (得分: {dim_data['score']:.1f}/{dim_data['max_score']:.1f})") | |
| for sig in dim_data['signals']: | |
| print(f" - {sig['indicator']}: {sig['signal']} ({sig['score']:.1f}/{sig['max_score']:.1f})") | |
| for d in sig['details']: | |
| print(f" {d}") | |
| print(f"\n{'=' * 60}") | |
| print(f"【信号共振】 {result['resonance']['text']}") | |
| print(f" 共振等级: {result['resonance']['level']}") | |
| print(f" 共振分数: {result['resonance']['score']}") | |
| print(f" 分布: 看多={result['resonance']['bullish_count']} " | |
| f"看空={result['resonance']['bearish_count']} " | |
| f"中性={result['resonance']['neutral_count']}") | |
| print(f"\n【综合研判】 {result['overall_signal']['signal_text']}") | |
| print(f" 综合分数: {result['overall_signal']['score']}") | |
| print(f" 操作建议: {result['overall_signal']['action']}") | |
| print(f"\n【指标详情】") | |
| for name, vals in result['indicators_detail'].items(): | |
| val_str = ' | '.join(f"{k}={v}" for k, v in vals.items() if v is not None) | |
| print(f" {name}: {val_str}") | |