feat: 量化引擎加固 — 新增测试 + 数据/因子/回测层优化

- 新增 finance/tests/ 6 个测试套件(agents/backtest/dao_upsert/factors/features/fundamental_lookahead)
- 数据层: data_manager / dao 优化,新增 upsert 逻辑
- 因子层: 基本面因子抽象定位 _mapping、ROE/PE/PB 重构
- 回测层: vectorbt/engine 大改动(251 行),report 增强
- ML 层: features/backtest_integration 特征工程与回测优化
- CLI: agent_cli 重构
- config/settings 扩充配置项
This commit is contained in:
Simon
2026-08-31 14:01:06 +08:00
parent 6acf938caf
commit 73d191b43a
28 changed files with 1418 additions and 373 deletions
+16 -9
View File
@@ -10,6 +10,7 @@
"""
import json
import logging
import os
import time
from datetime import datetime, timedelta
@@ -21,6 +22,8 @@ import requests
# 确保 .env 已加载
import config.settings # noqa: F401
logger = logging.getLogger(__name__)
class NewsSource:
"""
@@ -87,7 +90,7 @@ class NewsSource:
frames.append(df)
time.sleep(self.request_delay)
except Exception as e:
print(" [WARN] AkShare 新闻获取失败 ({}): {}".format(ts_code, e))
logger.warning("[news_source] AkShare 新闻获取失败 (%s): %s", ts_code, e)
if self.use_xwlb:
try:
@@ -99,7 +102,7 @@ class NewsSource:
if not df.empty:
frames.append(df)
except Exception as e:
print(" [WARN] xwlb 新闻获取失败: {}".format(e))
logger.warning("[news_source] xwlb 新闻获取失败: %s", e)
if self.use_mcp:
try:
@@ -107,7 +110,7 @@ class NewsSource:
if not df.empty:
frames.append(df)
except Exception as e:
print(" [WARN] MCP 新闻获取失败: {}".format(e))
logger.warning("[news_source] MCP 新闻获取失败: %s", e)
if not frames:
return pd.DataFrame(columns=["date", "title", "content", "source", "url"])
@@ -133,7 +136,8 @@ class NewsSource:
symbol = ts_code.replace(".SZ", "").replace(".SH", "").replace(".BJ", "")
try:
df = ak.stock_news_em(symbol=symbol.zfill(6))
except Exception:
except Exception as e:
logger.warning("[news_source] AkShare 新闻接口失败 (%s): %s", ts_code, e)
return pd.DataFrame()
if df is None or df.empty:
@@ -204,7 +208,8 @@ class NewsSource:
df["url"] = ""
return df[["date", "title", "content", "source", "url"]]
except Exception:
except Exception as e:
logger.warning("[news_source] xwlb 新闻获取失败: %s", e)
return pd.DataFrame()
# ── MCP 数据源 ────────────────────────────────────────
@@ -232,7 +237,8 @@ class NewsSource:
df = pd.DataFrame(records)
df["source"] = "mcp_trendradar"
return df[["date", "title", "content", "source", "url"]]
except Exception:
except Exception as e:
logger.warning("[news_source] MCP 新闻获取失败: %s", e)
return pd.DataFrame()
def _mcp_initialize(self) -> str | None:
@@ -267,10 +273,10 @@ class NewsSource:
if session_id:
self._mcp_session_id = session_id
else:
print(" [WARN] MCP initialize 未返回 session-id")
logger.warning("[news_source] MCP initialize 未返回 session-id")
return session_id
except Exception as e:
print(" [WARN] MCP 连接失败: {}".format(e))
logger.warning("[news_source] MCP 连接失败: %s", e)
return None
def _mcp_call_tool(
@@ -299,7 +305,8 @@ class NewsSource:
data = json.loads(line[5:].strip())
return data.get("result", {})
return None
except Exception:
except Exception as e:
logger.warning("[news_source] MCP call_tool 失败: %s", e)
return None
@staticmethod
@@ -128,11 +128,12 @@ class SentimentEngine:
# 5. Qwen 情绪分析(有 API key 时才执行)
sentiment_df = self._analyze_news(news_df)
# 6. 计算因子
# 6. 计算因子(对齐注册表,产出全部 4 个注册情绪因子,含 news_sent_20)
factor_dfs = {}
if not sentiment_df.empty:
for factor_cls, kwargs in [
(NewsSentimentFactor, {"window": 5, "sentiment_df": sentiment_df}),
(NewsSentimentFactor, {"window": 20, "sentiment_df": sentiment_df}),
(SentimentConfidenceFactor, {"window": 5, "sentiment_df": sentiment_df}),
(SentimentMomentumFactor, {"period": 5, "sentiment_df": sentiment_df}),
]: