195 lines
7.2 KiB
Python
195 lines
7.2 KiB
Python
"""Extended factor implementations for the quant system.
|
|
|
|
This module contains additional high-quality factors that extend the default factor set.
|
|
All factors are designed to be lightweight and programmatically generated to meet
|
|
end-to-end automated decision-making requirements.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Dict, List, Sequence
|
|
|
|
from app.core.indicators import momentum, volatility, rolling_mean, normalize
|
|
|
|
|
|
@dataclass
|
|
class FactorSpec:
|
|
"""Specification for a factor computation.
|
|
|
|
Attributes:
|
|
name: Factor name identifier
|
|
window: Required lookback window (0 for snapshot-only factors)
|
|
"""
|
|
name: str
|
|
window: int
|
|
|
|
|
|
# Extended factors focusing on momentum, value, and liquidity signals
|
|
EXTENDED_FACTORS: List[FactorSpec] = [
|
|
# 增强动量因子
|
|
FactorSpec("mom_10_30", 0), # 10日与30日动量差
|
|
FactorSpec("mom_5_20_rank", 0), # 相对排名动量因子
|
|
FactorSpec("mom_dynamic", 0), # 动态窗口动量因子
|
|
# 波动率相关因子
|
|
FactorSpec("volat_5", 5), # 短期波动率
|
|
FactorSpec("volat_ratio", 0), # 长短期波动率比率
|
|
# 换手率扩展因子
|
|
FactorSpec("turn_60", 60), # 长期换手率
|
|
FactorSpec("turn_rank", 0), # 换手率相对排名
|
|
# 价格均线比率因子
|
|
FactorSpec("price_ma_10_ratio", 0), # 当前价格与10日均线比率
|
|
FactorSpec("price_ma_20_ratio", 0), # 当前价格与20日均线比率
|
|
FactorSpec("price_ma_60_ratio", 0), # 当前价格与60日均线比率
|
|
# 成交量均线比率因子
|
|
FactorSpec("volume_ma_5_ratio", 0), # 当前成交量与5日均线比率
|
|
FactorSpec("volume_ma_20_ratio", 0), # 当前成交量与20日均线比率
|
|
# 高级估值因子
|
|
FactorSpec("val_ps_score", 0), # PS估值评分
|
|
FactorSpec("val_multiscore", 0), # 综合估值评分
|
|
FactorSpec("val_dividend_score", 0), # 股息率估值评分
|
|
# 市场状态因子
|
|
FactorSpec("market_regime", 0), # 市场状态因子
|
|
FactorSpec("trend_strength", 0), # 趋势强度因子
|
|
]
|
|
|
|
|
|
def compute_extended_factor_values(
|
|
close_series: Sequence[float],
|
|
volume_series: Sequence[float],
|
|
turnover_series: Sequence[float],
|
|
latest_fields: Dict[str, float],
|
|
) -> Dict[str, float]:
|
|
"""Compute values for extended factors.
|
|
|
|
Args:
|
|
close_series: Closing prices series (most recent first)
|
|
volume_series: Trading volume series (most recent first)
|
|
turnover_series: Turnover rate series (most recent first)
|
|
latest_fields: Latest available fields including valuation ratios
|
|
|
|
Returns:
|
|
Dictionary mapping factor names to computed values
|
|
"""
|
|
if not close_series:
|
|
return {}
|
|
|
|
results: Dict[str, float] = {}
|
|
|
|
# 增强动量因子
|
|
# 10日与30日动量差
|
|
if len(close_series) >= 30:
|
|
mom_10 = momentum(close_series, 10)
|
|
mom_30 = momentum(close_series, 30)
|
|
if mom_10 is not None and mom_30 is not None:
|
|
results["mom_10_30"] = mom_10 - mom_30
|
|
|
|
# 相对排名动量因子
|
|
# 这里需要市场数据来计算相对排名,暂时使用简化版本
|
|
if len(close_series) >= 20:
|
|
mom_20 = momentum(close_series, 20)
|
|
if mom_20 is not None:
|
|
# 简化处理:将动量标准化
|
|
results["mom_5_20_rank"] = min(1.0, max(0.0, (mom_20 + 0.2) / 0.4))
|
|
|
|
# 动态窗口动量因子
|
|
# 根据波动率动态调整窗口
|
|
if len(close_series) >= 20:
|
|
volat_20 = volatility(close_series, 20)
|
|
mom_20 = momentum(close_series, 20)
|
|
if volat_20 is not None and mom_20 is not None and volat_20 > 0:
|
|
# 波动率调整后的动量
|
|
results["mom_dynamic"] = mom_20 / volat_20
|
|
|
|
# 波动率相关因子
|
|
# 短期波动率
|
|
if len(close_series) >= 5:
|
|
results["volat_5"] = volatility(close_series, 5)
|
|
|
|
# 长短期波动率比率
|
|
if len(close_series) >= 20 and len(close_series) >= 5:
|
|
volat_5 = volatility(close_series, 5)
|
|
volat_20 = volatility(close_series, 20)
|
|
if volat_5 is not None and volat_20 is not None and volat_20 > 0:
|
|
results["volat_ratio"] = volat_5 / volat_20
|
|
|
|
# 换手率扩展因子
|
|
# 长期换手率
|
|
if len(turnover_series) >= 60:
|
|
results["turn_60"] = rolling_mean(turnover_series, 60)
|
|
|
|
# 换手率相对排名
|
|
if len(turnover_series) >= 20:
|
|
turn_20 = rolling_mean(turnover_series, 20)
|
|
if turn_20 is not None:
|
|
# 简化处理:将换手率标准化
|
|
results["turn_rank"] = min(1.0, max(0.0, turn_20 / 5.0)) # 假设5%为高换手率
|
|
|
|
# 价格均线比率因子
|
|
if len(close_series) >= 10:
|
|
ma_10 = rolling_mean(close_series, 10)
|
|
if ma_10 is not None and ma_10 > 0:
|
|
results["price_ma_10_ratio"] = close_series[0] / ma_10
|
|
|
|
if len(close_series) >= 20:
|
|
ma_20 = rolling_mean(close_series, 20)
|
|
if ma_20 is not None and ma_20 > 0:
|
|
results["price_ma_20_ratio"] = close_series[0] / ma_20
|
|
|
|
if len(close_series) >= 60:
|
|
ma_60 = rolling_mean(close_series, 60)
|
|
if ma_60 is not None and ma_60 > 0:
|
|
results["price_ma_60_ratio"] = close_series[0] / ma_60
|
|
|
|
# 成交量均线比率因子
|
|
if len(volume_series) >= 5:
|
|
vol_ma_5 = rolling_mean(volume_series, 5)
|
|
if vol_ma_5 is not None and vol_ma_5 > 0:
|
|
results["volume_ma_5_ratio"] = volume_series[0] / vol_ma_5
|
|
|
|
if len(volume_series) >= 20:
|
|
vol_ma_20 = rolling_mean(volume_series, 20)
|
|
if vol_ma_20 is not None and vol_ma_20 > 0:
|
|
results["volume_ma_20_ratio"] = volume_series[0] / vol_ma_20
|
|
|
|
# 高级估值因子
|
|
ps = latest_fields.get("daily_basic.ps")
|
|
if ps is not None and ps > 0:
|
|
# PS估值评分
|
|
results["val_ps_score"] = 2.5 / (2.5 + ps) # 使用2.5作为scale参数
|
|
|
|
pe = latest_fields.get("daily_basic.pe")
|
|
pb = latest_fields.get("daily_basic.pb")
|
|
# 综合估值评分
|
|
scores = []
|
|
if pe is not None and pe > 0:
|
|
scores.append(12.0 / (12.0 + pe)) # PE评分
|
|
if pb is not None and pb > 0:
|
|
scores.append(2.5 / (2.5 + pb)) # PB评分
|
|
if ps is not None and ps > 0:
|
|
scores.append(2.5 / (2.5 + ps)) # PS评分
|
|
|
|
if scores:
|
|
results["val_multiscore"] = sum(scores) / len(scores)
|
|
|
|
dv_ratio = latest_fields.get("daily_basic.dv_ratio")
|
|
if dv_ratio is not None:
|
|
# 股息率估值评分
|
|
results["val_dividend_score"] = min(1.0, max(0.0, dv_ratio / 5.0)) # 假设5%为高股息率
|
|
|
|
# 市场状态因子
|
|
# 简单的市场状态指标:基于价格位置
|
|
if len(close_series) >= 60:
|
|
ma_60 = rolling_mean(close_series, 60)
|
|
if ma_60 is not None and ma_60 > 0:
|
|
results["market_regime"] = close_series[0] / ma_60
|
|
|
|
# 趋势强度因子
|
|
if len(close_series) >= 20:
|
|
mom_20 = momentum(close_series, 20)
|
|
volat_20 = volatility(close_series, 20)
|
|
if mom_20 is not None and volat_20 is not None and volat_20 > 0:
|
|
# 趋势强度:动量与波动率的比率
|
|
results["trend_strength"] = abs(mom_20) / volat_20
|
|
|
|
return results |