Files
lianghua/src/quant/reason.py
T

72 lines
2.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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), {}