""" 负向指标 (5维) — calculate_negative_features(df) 注意: #53 trend_consistency_20d 与 #39 同名不同义,更新字典时会覆盖 #39 """ import numpy as np import pandas as pd def calculate_negative_features(ctx) -> pd.DataFrame: """计算负向指标 (5维)""" kline = ctx.kline.copy() if kline.empty: return pd.DataFrame() kline = kline.sort_values(['stock_code', 'trade_date']) candidates = ctx.candidates features = {} grouped = kline.groupby('stock_code') for code, group in grouped: if code not in candidates: continue g = group.tail(120) if len(g) < 20: continue closes = g['close'].values opens = g['open'].values highs = g['high'].values lows = g['low'].values volumes = g['volume'].values feat = {} # 53. trend_consistency_20d (负向版本): |mean(return>0) - 0.5| × 100 # 衡量偏离均衡的程度,越接近50%越弱 rets_20 = np.diff(closes[-21:]) / np.where(closes[-21:-1] > 0, closes[-21:-1], 1) feat['trend_consistency_20d'] = abs(np.mean(rets_20 > 0) - 0.5) * 100 # 54. max_consecutive_direction_20d: 最大连续同向天数 rets_sign = np.sign(np.diff(closes[-21:])) max_consec = 0 curr_consec = 0 curr_sign = 0 for s in rets_sign: if s != 0 and s == curr_sign: curr_consec += 1 elif s != 0: curr_sign = s curr_consec = 1 else: curr_consec = 0 max_consec = max(max_consec, curr_consec) feat['max_consecutive_direction_20d'] = max_consec # 55. gap_risk_20d: mean(|open_t - close_{t-1}|/close_{t-1} > 0.02) × 100 gap_count = 0 n = 0 for i in range(max(0, len(g) - 20), len(g)): if i > 0 and closes[i - 1] > 0: gap_pct = abs(opens[i] - closes[i - 1]) / closes[i - 1] if gap_pct > 0.02: gap_count += 1 n += 1 feat['gap_risk_20d'] = gap_count / n * 100 if n > 0 else 0 # 56. liquidity_drying_up_20d: min(vol_20d)/mean(vol_60d) vol_20 = volumes[-20:] vol_60 = volumes[-60:] if len(volumes) >= 60 else volumes feat['liquidity_drying_up_20d'] = ( np.min(vol_20) / np.mean(vol_60) if np.mean(vol_60) > 0 else 1 ) # 57. price_stagnation_20d: (max(high_20d)-min(low_20d))/close × 100 h20 = np.max(highs[-20:]) l20 = np.min(lows[-20:]) feat['price_stagnation_20d'] = ( (h20 - l20) / closes[-1] * 100 if closes[-1] > 0 else 0 ) features[code] = feat return pd.DataFrame.from_dict(features, orient='index')