模型,评分,修复网格策略市场状态监听
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
情绪弹性特征 (4维) — calculate_relaxed_emotion_features(df, market_regime)
|
||||
180 日窗口,筛选 advance_ratio < 0.20 恐慌日 + advance_ratio > 0.55 贪婪日。
|
||||
"""
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from core.scoring.config import PANIC_ADVANCE_RATIO, GREED_ADVANCE_RATIO, WINDOW_180D
|
||||
|
||||
|
||||
def calculate_relaxed_emotion_features(ctx) -> pd.DataFrame:
|
||||
"""计算情绪弹性特征 (4维)"""
|
||||
kline = ctx.kline.copy()
|
||||
mkt = ctx.market_regime.copy() if ctx.market_regime is not None else pd.DataFrame()
|
||||
|
||||
if kline.empty or mkt.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
# 筛选恐慌日和贪婪日
|
||||
panic_dates = set()
|
||||
greed_dates = set()
|
||||
|
||||
for _, row in mkt.iterrows():
|
||||
td = row['trade_date']
|
||||
ar = row.get('advance_ratio', 0.5)
|
||||
if ar < PANIC_ADVANCE_RATIO:
|
||||
panic_dates.add(td.date() if hasattr(td, 'date') else td)
|
||||
if ar > GREED_ADVANCE_RATIO:
|
||||
greed_dates.add(td.date() if hasattr(td, 'date') else td)
|
||||
|
||||
kline = kline.sort_values(['stock_code', 'trade_date'])
|
||||
kline['td'] = (kline['trade_date'].dt.date
|
||||
if hasattr(kline['trade_date'], 'dt')
|
||||
else pd.to_datetime(kline['trade_date']).dt.date)
|
||||
|
||||
candidates = ctx.candidates
|
||||
|
||||
# 计算全市场平均振幅 (用于恐慌/贪婪 ratio)
|
||||
kline['amp'] = (kline['high'] - kline['low']) / np.where(
|
||||
kline['open'] > 0, kline['open'], 1
|
||||
)
|
||||
market_amp = kline.groupby('td')['amp'].mean().to_dict()
|
||||
|
||||
features = {}
|
||||
grouped = kline.groupby('stock_code')
|
||||
|
||||
for code, group in grouped:
|
||||
if code not in candidates:
|
||||
continue
|
||||
if len(group) < 20:
|
||||
continue
|
||||
|
||||
g = group.sort_values('trade_date').tail(WINDOW_180D)
|
||||
closes = g['close'].values
|
||||
opens = g['open'].values
|
||||
highs = g['high'].values
|
||||
lows = g['low'].values
|
||||
dates = g['td'].values
|
||||
amps = (highs - lows) / np.where(opens > 0, opens, 1)
|
||||
|
||||
feat = {}
|
||||
|
||||
# 恐慌日分析
|
||||
panic_indices = [i for i, d in enumerate(dates) if d in panic_dates]
|
||||
if panic_indices:
|
||||
panic_amp_ratios = []
|
||||
panic_drop_ratios = []
|
||||
panic_rebounds = []
|
||||
|
||||
for pi in panic_indices:
|
||||
td = dates[pi]
|
||||
mkt_amp_val = market_amp.get(td, amps[pi])
|
||||
if mkt_amp_val > 0:
|
||||
panic_amp_ratios.append(amps[pi] / mkt_amp_val)
|
||||
|
||||
# 恐慌日跌幅
|
||||
if pi > 0 and closes[pi - 1] > 0:
|
||||
drop = (closes[pi] - closes[pi - 1]) / closes[pi - 1]
|
||||
# 全市场跌幅: 用 advance_ratio 估算
|
||||
ar = _get_advance_ratio(mkt, td)
|
||||
market_drop = -0.02 if ar < PANIC_ADVANCE_RATIO else -0.005
|
||||
if market_drop != 0:
|
||||
panic_drop_ratios.append(drop / market_drop)
|
||||
|
||||
# 恐慌次日反弹
|
||||
if pi + 1 < len(g) and closes[pi] > 0:
|
||||
panic_rebounds.append(highs[pi + 1] / closes[pi] - 1)
|
||||
|
||||
# 64. relaxed_panic_amplitude_ratio
|
||||
feat['relaxed_panic_amplitude_ratio'] = (
|
||||
np.median(panic_amp_ratios) if panic_amp_ratios else 1.0
|
||||
)
|
||||
|
||||
# 65. relaxed_panic_rebound_strength
|
||||
feat['relaxed_panic_rebound_strength'] = (
|
||||
np.median(panic_rebounds) if panic_rebounds else 0.0
|
||||
)
|
||||
else:
|
||||
feat['relaxed_panic_amplitude_ratio'] = 1.0
|
||||
feat['relaxed_panic_rebound_strength'] = 0.0
|
||||
|
||||
# 贪婪日分析
|
||||
greed_indices = [i for i, d in enumerate(dates) if d in greed_dates]
|
||||
if greed_indices:
|
||||
greed_gains = []
|
||||
greed_amp_ratios = []
|
||||
|
||||
for gi in greed_indices:
|
||||
td = dates[gi]
|
||||
if gi > 0 and closes[gi - 1] > 0:
|
||||
gain = (closes[gi] - closes[gi - 1]) / closes[gi - 1]
|
||||
# 全市场收益
|
||||
ar = _get_advance_ratio(mkt, td)
|
||||
market_gain = 0.01 if ar > GREED_ADVANCE_RATIO else 0.003
|
||||
if market_gain > 0:
|
||||
greed_gains.append(gain / market_gain)
|
||||
|
||||
mkt_amp_val = market_amp.get(td, amps[gi])
|
||||
if mkt_amp_val > 0:
|
||||
greed_amp_ratios.append(amps[gi] / mkt_amp_val)
|
||||
|
||||
# 66. relaxed_greed_relative_gain
|
||||
feat['relaxed_greed_relative_gain'] = (
|
||||
np.median(greed_gains) if greed_gains else 0.0
|
||||
)
|
||||
|
||||
# 67. relaxed_greed_amplitude_ratio
|
||||
feat['relaxed_greed_amplitude_ratio'] = (
|
||||
np.median(greed_amp_ratios) if greed_amp_ratios else 1.0
|
||||
)
|
||||
else:
|
||||
feat['relaxed_greed_relative_gain'] = 0.0
|
||||
feat['relaxed_greed_amplitude_ratio'] = 1.0
|
||||
|
||||
features[code] = feat
|
||||
|
||||
return pd.DataFrame.from_dict(features, orient='index')
|
||||
|
||||
|
||||
def _get_advance_ratio(mkt_df, trade_date):
|
||||
"""获取指定日期的 advance_ratio"""
|
||||
if mkt_df is None or mkt_df.empty:
|
||||
return 0.5
|
||||
td = trade_date
|
||||
match = mkt_df[mkt_df['trade_date'] == td]
|
||||
if not match.empty:
|
||||
return match.iloc[0].get('advance_ratio', 0.5)
|
||||
return 0.5
|
||||
Reference in New Issue
Block a user