feat(data): 添加个股资金流向接口并重构速率限制配置

- 新增 moneyflow 资金流向数据同步模块
- 实现接口级速率限制配置(sync_config.py)
- 更新流动性相关因子定义
- 添加非对称量化损失函数
This commit is contained in:
2026-04-03 23:57:47 +08:00
parent c143815443
commit 9e7d4241c6
18 changed files with 1473 additions and 334 deletions

View File

@@ -84,3 +84,65 @@ class EnsembleQuantLoss(nn.Module):
total_loss += self.alpha * h_loss + (1.0 - self.alpha) * ic_loss
return total_loss / self.ensemble_size
class AsymmetricQuantLoss(nn.Module):
"""Asymmetric Quant Loss (非对称 Huber + IC)
保留全截面计算以维持稳定梯度的前提下,对多头关注的错误进行加权惩罚:
1. 过度高估烂股票 (买入陷阱) -> 加重惩罚
2. 严重低估好股票 (错失金股) -> 加重惩罚
"""
def __init__(self, alpha: float = 0.5, ensemble_size: int = 32):
super().__init__()
self.alpha = alpha
self.ensemble_size = ensemble_size
# 我们不使用内置的 HuberLoss而是手动写以支持 element-wise 加权
self.delta = 1.0 # Huber threshold (可以设得比较小,比如对于收益率设为 0.05)
def forward(self, preds: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
batch_size = preds.shape[0]
total_loss = 0.0
if batch_size < 10:
# 极小 batch 退回简单 MSE
return ((preds - target.unsqueeze(1)) ** 2).mean()
target_mean = target.mean()
target_std = target.std(unbiased=False) + 1e-8
target_norm = (target - target_mean) / target_std
for i in range(self.ensemble_size):
pred_i = preds[:, i]
# --- 1. 非对称 Huber 损失 ---
error = pred_i - target
abs_error = torch.abs(error)
# 标准 Huber 计算
quadratic = torch.clamp(abs_error, max=self.delta)
linear = abs_error - quadratic
base_huber = 0.5 * quadratic**2 + self.delta * linear
# 【核心逻辑】:非对称权重
# 如果 target > 0 且 pred < target (错过了涨的好票) -> 权重 1.5
# 如果 target < 0 且 pred > 0 (把跌的票预测成了涨的,容易实盘买入踩雷) -> 权重 2.0
# 其他情况 (比如烂票预测得更烂) -> 权重 1.0 正常学习
weights = torch.ones_like(target)
weights[(target > 0) & (error < 0)] = 1.5
weights[(target < 0) & (pred_i > 0)] = 2.0
weighted_huber = (base_huber * weights).mean()
# --- 2. IC 损失 (维持全截面排序能力) ---
pred_mean = pred_i.mean()
pred_std = pred_i.std(unbiased=False) + 1e-8
pred_norm = (pred_i - pred_mean) / pred_std
ic = (pred_norm * target_norm).mean()
ic_loss = 1.0 - ic
total_loss += self.alpha * weighted_huber + (1.0 - self.alpha) * ic_loss
return total_loss / self.ensemble_size

View File

@@ -19,7 +19,10 @@ from tabm import TabM
from src.training.components.base import BaseModel
from src.training.components.models.cross_section_sampler import CrossSectionSampler
from src.training.components.models.ensemble_quant_loss import EnsembleQuantLoss
from src.training.components.models.ensemble_quant_loss import (
EnsembleQuantLoss,
AsymmetricQuantLoss,
)
from src.training.registry import register_model
@@ -235,8 +238,8 @@ class TabMModel(BaseModel):
optimizer, T_max=epochs, eta_min=1e-6
)
# 使用 EnsembleQuantLoss 替代 MSE
self.criterion = EnsembleQuantLoss(
# 使用 AsymmetricQuantLoss (非对称 Huber + IC)
self.criterion = AsymmetricQuantLoss(
alpha=self.params.get("loss_alpha", 0.5), ensemble_size=ensemble_size
)