This commit is contained in:
sam 2025-09-30 08:53:33 +08:00
parent d88bf0375a
commit 8083c9ffab
4 changed files with 186 additions and 6 deletions

3
.gitignore vendored
View File

@ -26,6 +26,9 @@ app/data/*.json
# Streamlit temporary files
.streamlit/
# Local references
Refer-TradingAgents-CN
# System files
.DS_Store
Thumbs.db

View File

@ -259,8 +259,6 @@ class BacktestEngine:
decision_callback(ts_code, trade_date, context, decision)
except Exception: # noqa: BLE001
LOGGER.exception("决策回调执行失败", extra=LOG_EXTRA)
# TODO: translate decisions into fills, holdings, and NAV updates.
_ = state
return records
def record_agent_state(self, context: AgentContext, decision: Decision) -> None:
@ -700,12 +698,156 @@ def run_backtest(
) -> BacktestResult:
engine = BacktestEngine(cfg)
result = engine.run(decision_callback=decision_callback)
with db_session() as conn:
_ = conn
# Implementation should persist bt_nav, bt_trades, and bt_report rows.
_persist_backtest_results(cfg, result)
return result
def _persist_backtest_results(cfg: BtConfig, result: BacktestResult) -> None:
"""Persist backtest configuration, NAV path, trades and summary metrics."""
nav_rows: List[tuple] = []
trade_rows: List[tuple] = []
summary_payload: Dict[str, object] = {}
if result.nav_series:
first_nav = float(result.nav_series[0].get("nav", 0.0) or 0.0)
peak_nav = first_nav
prev_nav: Optional[float] = None
max_drawdown = 0.0
for entry in result.nav_series:
trade_date = str(entry.get("trade_date", ""))
nav_val = float(entry.get("nav", 0.0) or 0.0)
cash = float(entry.get("cash", 0.0) or 0.0)
market_value = float(entry.get("market_value", 0.0) or 0.0)
realized = float(entry.get("realized_pnl", 0.0) or 0.0)
unrealized = float(entry.get("unrealized_pnl", 0.0) or 0.0)
if nav_val > peak_nav:
peak_nav = nav_val
drawdown = (peak_nav - nav_val) / peak_nav if peak_nav else 0.0
max_drawdown = max(max_drawdown, drawdown)
if prev_nav is None or prev_nav == 0.0:
ret_val = 0.0
else:
ret_val = (nav_val / prev_nav) - 1.0
prev_nav = nav_val
info_payload = {
"cash": cash,
"market_value": market_value,
"realized_pnl": realized,
"unrealized_pnl": unrealized,
}
nav_rows.append(
(
cfg.id,
trade_date,
nav_val,
float(ret_val),
None,
None,
float(drawdown),
json.dumps(info_payload, ensure_ascii=False),
)
)
last_nav = float(result.nav_series[-1].get("nav", 0.0) or 0.0)
total_return = (last_nav / first_nav - 1.0) if first_nav else 0.0
summary_payload.update(
{
"start_nav": first_nav,
"end_nav": last_nav,
"total_return": total_return,
"max_drawdown": max_drawdown,
"days": len(result.nav_series),
}
)
if result.trades:
for trade in result.trades:
trade_date = str(trade.get("trade_date", ""))
ts_code = str(trade.get("ts_code", ""))
side = str(trade.get("action", "")).lower()
price = float(trade.get("price", 0.0) or 0.0)
qty = float(trade.get("quantity", 0.0) or 0.0)
reason_payload = {
"confidence": trade.get("confidence"),
"target_weight": trade.get("target_weight"),
"value": trade.get("value"),
}
trade_rows.append(
(
cfg.id,
ts_code,
trade_date,
side,
price,
qty,
json.dumps(reason_payload, ensure_ascii=False),
)
)
summary_payload["trade_count"] = len(trade_rows)
cfg_payload = {
"id": cfg.id,
"name": cfg.name,
"start_date": cfg.start_date.isoformat(),
"end_date": cfg.end_date.isoformat(),
"universe": cfg.universe,
"params": cfg.params,
"method": cfg.method,
}
with db_session() as conn:
conn.execute(
"""
INSERT OR REPLACE INTO bt_config (id, name, start_date, end_date, universe, params)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
cfg.id,
cfg.name,
cfg.start_date.isoformat(),
cfg.end_date.isoformat(),
",".join(cfg.universe),
json.dumps(cfg.params, ensure_ascii=False),
),
)
conn.execute("DELETE FROM bt_nav WHERE cfg_id = ?", (cfg.id,))
conn.execute("DELETE FROM bt_trades WHERE cfg_id = ?", (cfg.id,))
conn.execute("DELETE FROM bt_report WHERE cfg_id = ?", (cfg.id,))
if nav_rows:
conn.executemany(
"""
INSERT INTO bt_nav (cfg_id, trade_date, nav, ret, pos_count, turnover, dd, info)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
nav_rows,
)
if trade_rows:
conn.executemany(
"""
INSERT INTO bt_trades (cfg_id, ts_code, trade_date, side, price, qty, reason)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
trade_rows,
)
summary_payload.setdefault("universe", cfg.universe)
summary_payload.setdefault("method", cfg.method)
conn.execute(
"""
INSERT OR REPLACE INTO bt_report (cfg_id, summary)
VALUES (?, ?)
""",
(cfg.id, json.dumps(summary_payload, ensure_ascii=False, default=str)),
)
def _candidate_status(action: AgentAction, requires_review: bool) -> str:
mapping = {
AgentAction.SELL: "exit",

35
docs/TODO.md Normal file
View File

@ -0,0 +1,35 @@
# 项目待办清单
> 用于跟踪现阶段尚未完成或需要后续完善的工作,便于规划优先级。
## 1. UI 与日志增强
- 今日计划页增加“一键重评估”入口,以及日志钻取 / 历史对比视图(对齐 README 中的架构目标)。
- 回测页面支持多版本实验管理(对比不同提示/温度的收益曲线),与 `tuning_results` 记录联动。
- Streamlit 聚焦监控场景,补充实时指标面板、异常日志钻取与“仅监控不干预”模式的一键复评策略。
## 2. 数据与特征层
- 实现 `app/features/factors.py` 中的 `compute_factors()`,补齐因子计算与持久化流程。
- 完成 `app/ingest/rss.py` 的 RSS 拉取与写库逻辑,打通新闻与情绪数据源。
- 强化 `DataBroker` 的取数校验、缓存与回退策略,确保行情/特征补数统一自动化,减少人工兜底。
- 围绕动量、估值、流动性等核心信号扩展轻量高质量因子集,全部由程序生成,满足端到端自动决策需求。
## 3. 决策优化与强化学习
- 扩展 `DecisionEnv` 的动作空间提示版本、部门温度、function 调用策略等),不仅限于代理权重调节。
- 引入 Bandit / 贝叶斯优化或 RL 算法探索动作空间,并将 `portfolio_snapshots`、`portfolio_trades` 指标纳入奖励约束。
- 构建实时持仓/成交数据写入链路,使线上监控与离线调参共用同一数据源。
- 借鉴 TradingAgents-CN 的做法:拆分环境与策略、提供训练脚本/配置,并输出丰富的评估指标(如 Sharpe、Sortino、基准对比
- 完善 `BacktestEngine` 的成交撮合、风险阈值与指标输出,让回测信号直接对接执行端,形成无人值守的自动闭环。
## 4. 测试与验证
- 补充部门上下文构造、多模型调用、回测指标生成等核心路径的单元 / 集成测试。
- 建立决策流程的回归测试用例,确保提示模板或配置调整后行为可复现。
- 编写示例 Notebook / end-to-end 教程,参照 TradingAgents-CN 的教学方式,覆盖“数据→回测→调参→评估”全流程。
- 针对数据摄取、策略主干与回测指标建立自动化验证管线,作为无人干预运行的质量护栏。
## 5. 文档同步
- 随功能推进,更新 README 与讨论文档,确保描述与实际实现保持一致。
## 6. LLM 协同与配置
- 精简 Provider 列表、强化 function-calling 架构,完善降级和重试策略,并用配置化的角色提示与数据 Scope 提高模型行为可控性。
最后更新2025-09-29

View File

@ -33,7 +33,7 @@
## 已完成的日志改进
- `agent_utils` 表新增 `_telemetry``_department_telemetry` JSON 字段(存于 `utils` 列内部),记录每个部门的 provider、模型、温度、回合数、工具调用列表与 token 统计,可在 Streamlit “部门意见”详情页展开查看。
- `app/data/logs/agent_*.log` 会追加 `telemetry` 行,保存每轮函数调用的摘要,方便离线分析提示版本与 LLM 配置对决策的影响。
- Streamlit 侧边栏监听 `llm.metrics` 的实时事件,并以 ~0.75 秒节流频率刷新“系统监控”,既保证日志到达后快速更新,也避免刷屏造成 UI 闪烁
- Streamlit 侧边栏监听 `llm.metrics` 的实时事件,并使用原位组件刷新“系统监控”,避免增量追加或节流导致的失效,同时确保实时数据推送稳定
- 新增投资管理数据层SQLite 中创建 `investment_pool`、`portfolio_positions`、`portfolio_trades`、`portfolio_snapshots` 四张表;`app/utils/portfolio.py` 提供访问接口,今日计划页可实时展示候选池、持仓与成交。
- 回测引擎 `record_agent_state()` 现同步写入 `investment_pool`,将每日全局决策的置信度、部门标签与目标权重落库,作为后续提示参数调优与候选池管理的基础数据。
- `app/backtest/decision_env.py` 引入 `DecisionEnv`,用单步 RL/Gym 风格接口封装回测:动作 → 权重映射 → 回测 → 奖励(收益 - 0.5×回撤),同时输出 NAV、交易与行动权重方便与 Bandit/PPO 等算法对接。