72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
推荐原因生成器:因子截面值 → 带具体数字和方向的中文推荐理由。
|
||
"""
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
|
||
def build_reason(code, kline_df, weights=None):
|
||
"""
|
||
从单股日K线(时间升序,≥30行)生成多维度推荐原因。
|
||
返回 (reason_str, factor_detail_dict)
|
||
"""
|
||
if kline_df is None or len(kline_df) < 30:
|
||
return '', {}
|
||
close = kline_df['close'].reset_index(drop=True)
|
||
vol = kline_df['volume'].reset_index(drop=True)
|
||
high = kline_df['high'].reset_index(drop=True)
|
||
low = kline_df['low'].reset_index(drop=True)
|
||
n = len(close)
|
||
c = close.iloc[-1]
|
||
|
||
parts = []
|
||
|
||
# 动量
|
||
if n > 21:
|
||
mom = (c / close.iloc[-21] - 1) * 100
|
||
tag = '强' if mom > 5 else ('偏强' if mom > 0 else '偏弱' if mom > -5 else '弱')
|
||
parts.append(f"20日动量{mom:+.1f}%({tag})")
|
||
|
||
# 趋势(MA20 偏离)
|
||
ma20 = close.rolling(20).mean().iloc[-1]
|
||
if ma20 and ma20 > 0:
|
||
dev = (c - ma20) / ma20 * 100
|
||
tag = '强势区' if dev > 3 else ('偏高水平' if dev > 0 else '偏低水平' if dev > -3 else '弱势区')
|
||
parts.append(f"距MA20 {dev:+.1f}%({tag})")
|
||
|
||
# 量能
|
||
v20 = vol.rolling(20).mean().iloc[-1]
|
||
if v20 and v20 > 0:
|
||
vr = vol.iloc[-1] / v20
|
||
if vr > 1.5:
|
||
parts.append(f"量比{vr:.1f}(放量)")
|
||
elif vr < 0.5:
|
||
parts.append(f"量比{vr:.1f}(极度缩量)")
|
||
|
||
# RSI
|
||
close_s = close
|
||
delta = close_s.diff()
|
||
gain = delta.clip(lower=0).rolling(14).mean()
|
||
loss = (-delta.clip(upper=0)).rolling(14).mean()
|
||
rs = gain / loss.replace(0, np.nan)
|
||
rsi = (100 - 100 / (1 + rs)).iloc[-1]
|
||
if rsi == rsi:
|
||
if rsi > 70:
|
||
parts.append(f"RSI={rsi:.0f}(超买)")
|
||
elif rsi < 30:
|
||
parts.append(f"RSI={rsi:.0f}(超卖)")
|
||
|
||
# MACD
|
||
ema12 = close.ewm(span=12, adjust=False).mean()
|
||
ema26 = close.ewm(span=26, adjust=False).mean()
|
||
dif = ema12 - ema26
|
||
dea = dif.ewm(span=9, adjust=False).mean()
|
||
hist = (dif - dea).iloc[-1]
|
||
if hist > 0:
|
||
parts.append("MACD多头")
|
||
else:
|
||
parts.append("MACD空头")
|
||
|
||
return ';'.join(parts), {}
|