ty19880929/DeepTrade · CHANGELOG.md
更新日志
构建期从主仓 CHANGELOG.md 自动同步;网络异常时退化到仓内基线。
All notable changes to DeepTrade. Format follows Keep a Changelog and SemVer.
[v0.2.0] — 2026-05-09 — 框架瘦身(builtin 插件物理移除)· PR-8 cutover
本版本完成了框架与插件的物理解耦——deeptrade-quant wheel 不再携带任何插件代码。所有官方插件(limit-up-board / volume-anomaly / stdout-channel)必须通过 deeptrade plugin install <短名> 从注册表安装。
Removed
deeptrade/strategies_builtin/整目录(含limit_up_board/volume_anomaly)deeptrade/channels_builtin/整目录(含stdout)tests/strategies_builtin/整目录(140 个测试随插件代码迁移到DeepTradePluginOfficial)mypy.ini中针对 builtin 的ignore_errors(已无需要忽略的目标)
Changed
- README.md / docs/quick-start.md / docs/plugin-development.md /
guide/plugin/strategy/volume-anomaly.md:命令示例从本地 builtin 路径切换为短名(
deeptrade plugin install limit-up-board等)。 deeptrade/core/notifier.pydocstring:移除channels_builtin/ mirrors strategies_builtin/的过期引用。
Migration notes
- 从 0.1.x 升级:
pipx upgrade deeptrade-quant。已通过本地路径安装的 builtin 插件不会被升级删除(plugin 数据/代码独立于框架 wheel),可继续使用,也可执行deeptrade plugin upgrade <短名>切换到注册表版本。 - 历史快照可回溯:
git checkout archive/with-builtin-plugins-v0.1.0-preview取回含 builtin 的最后状态。 - 回滚发版:用户可
pipx install deeptrade-quant==0.1.0锁定上一稳定版本。
Wheel size impact
| 指标 | v0.1.0 | v0.2.0 |
|---|---|---|
| wheel 文件数 | 74 | 37 |
| Python 源文件(mypy 扫描) | 65 | 31 |
| 内置 .sql migration | 4 | 1(仅框架核心) |
v0.1.0 — 2026-05-09 — 框架与插件解耦 · PyPI 首个稳定发布(PR-1 ~ PR-6)
PyPI distribution name: deeptrade-quant(CLI 命令仍是 deeptrade)。
pipx install deeptrade-quant # or: uv tool install deeptrade-quant
deeptrade plugin search # browse the official registry
deeptrade plugin install limit-up-board
参考 docs/distribution-and-plugin-install-design.md。本版本是 §10 中
PR-1~PR-6 的合集;builtin 插件目录仍随 wheel 一起发布(兼容旧的本地路径
安装),下一个版本(PR-8 cutover)将完成瘦身。
Added
- 从 PyPI 分发:项目元数据、LICENSE、GitHub Actions(
ci.yml/release.yml)就位;tag v*触发 OIDC Trusted Publisher 直发 PyPI。 - 官方插件注册表客户端(
deeptrade.core.registry):从raw.githubusercontent.com/ty19880929/DeepTradePluginOfficial/main/registry/index.json拉取,ETag 缓存到~/.deeptrade/plugins/registry-cache.json;网络故障 时回退到本地缓存。 - GitHub tarball 拉取(
deeptrade.core.github_fetch):stdlib 实现 (urllib+tarfile),无第三方 HTTP 依赖;GITHUB_TOKEN环境变量 自动用于 rate-limit / 私有仓库(未来扩展);解压做 path-traversal 防护。 - 来源解析层(
deeptrade.core.plugin_source):SourceResolver把 用户输入(短名 / GitHub URL / 本地路径)统一解析为本地目录,强制min_framework_version校验。 - 新命令
deeptrade plugin search [keyword] [--no-cache]:列出注册 表中可用的插件;--no-cache旁路 ETag 缓存。 plugin info注册表 fallback:未安装但在注册表中时显示注册表条 目 + 安装命令提示。
Changed
deeptrade plugin install <SOURCE>现支持三种来源:短名(注册表)、 完整 git 仓库 URL、本地路径。判定顺序:本地目录存在 → git URL → 短 名。--ref <tag|branch|sha>可指定具体 ref(默认拉该插件最新 release tag)。deeptrade plugin upgrade <SOURCE>同上,且加入 SemVer 比较:- 待装 == 已装:
exit 0+ "已是最新版本 vX" - 待装 > 已装:执行升级
- 待装 < 已装:
exit 2+ 提示先uninstall --purge(禁止降级, 因为框架未建模 migration 回滚)
- 待装 == 已装:
PluginManager.upgrade()返回类型改为InstalledPlugin | UpgradeNoop。
Fixed
pyproject.toml删除了与packages = ["deeptrade"]冲突的[tool.hatch.build.targets.wheel.force-include]段(曾导致 wheel 内deeptrade/core/migrations/路径 duplicate name,让 PyPI 上传失败)。deeptrade.core.notifier:channel 实例 cast 到ChannelPlugin(mypy narrowing 修复)。- 一批预存在的 ruff lint(UP045 / F401 / I001 / W292 / B023)清理。
Notes
- builtin 插件(
limit-up-board/volume-anomaly/stdout-channel) 代码已迁移到DeepTradePluginOfficial独立仓库,并发布了首个 release tag(limit-up-board/v0.4.0等)。 本版本框架仍然包含旧 builtin 子树作为兼容期保留;下一版本(瘦身后) 将物理移除。 - archive tag
archive/with-builtin-plugins-v0.1.0-preview永久保留含 builtin 子树的最后状态,可随时git checkout取回。
[volume-anomaly v0.6.0] — 2026-05-08 — 显式分维度评分(PR-6)
仅升级 volume-anomaly 插件版本(0.5.0 → 0.6.0);框架版本不变。 升级方式:
deeptrade plugin upgrade <plugin-source>,会自动应用新增的 migration(20260601_002_dimension_scores.sql)。
⚠ Schema 变更:
VATrendCandidate.dimension_scores现为必填字段; 旧 LLM 响应不再可解析(但持久化到va_stage_results.raw_response_json不受影响——升级前的历史数据完整保留可读,仅新写入要求新 schema)。
参考 docs/volume_anomaly_wave2_design.md § 2。
Added (volume-anomaly plugin)
- 新子模型
VADimensionScores:6 个维度(washout/pattern/capital/sector/historical/risk)每个 0–100 整数评分, 其中risk为反向极性(高分 = 高风险),其余为正向极性。 VATrendCandidate.dimension_scores必填(F8):每只候选都必须输出 6 维评分,由 LLM 自洽与launch_score大致一致(F3 / F14 软约束, 不强制公式)。- Prompt 评分尺度锚定:
VA_TREND_SYSTEM加入【dimension_scores 评分尺度】 段(0-30 / 30-60 / 60-80 / 80-100 四档语义),每个判断维度(A–F)末尾 显式标注对应dimension_scores.<name>。 - Few-Shot 示例同步:
prompts_examples.pyA/B 示例补dimension_scores对象,与示例评分尺度一致。 - 拆 6 列持久化(G6):
va_stage_results新增dim_washout/dim_pattern/dim_capital/dim_sector/dim_historical/dim_risk6 个DOUBLE列。runner._write_stage_results写入 对应列;旧行dim_*全部为NULL,stats SQL 自动过滤。 原始 JSON 仍写入raw_response_json作为审计备份。 - stats
--by dimension_scores:新支持子选项,输出 6 个维度评分与ret_t3的 Pearson 相关系数(DuckDBCORR(...)内置聚合)。 - 报告紧凑表达:
write_analyze_report在 imminent_launch / watching 表中新增"W/P/C/S/H/R"列,紧凑展示 6 维分数(如80/75/70/75/60/25)。
Implementation notes
- 拆列而非单 JSON 列的理由(G6):
stats --by dimension_scores要求 6 维与ret_t3做 Pearson,AVG/CORR在拆列模式下纯 SQL 表达;JSON_EXTRACT在 SQL 层昂贵且 DuckDB 跨版本兼容性差。 - 输出 token 预算上调:
DEFAULT_AVG_OUTPUT_TOKENS_PER_CANDIDATE900 → 1100(吸纳 6 维评分 + alpha 字段),plan_batches按新预算切批。 - F13 决策:6 维(W/P/C/S/H/R),不加 liquidity。
Breaking changes
- 旧 LLM 响应(无
dimension_scores)会在 Pydantic 解析阶段抛ValidationError。已落地的批次再 retry 时需更新 prompt(已自动包含)。 - 回滚路径:revert PR-6 不需要 drop 列(
NULL列不影响旧逻辑),但 已经按新 schema 持久化的行将无法被旧版本读取——因此降级时保留raw_response_json而忽略dimension_scores_json。
[volume-anomaly v0.5.0] — 2026-05-08 — RPS / 大盘相对 alpha(PR-5)
仅升级 volume-anomaly 插件版本(0.4.0 → 0.5.0);框架版本不变。 升级方式:
deeptrade plugin upgrade <plugin-source>。本版本不新增 migration。
参考 docs/volume_anomaly_wave2_design.md § 1。
Added (volume-anomaly plugin)
- 沪深 300 alpha 字段(P1-1):候选行新增
alpha_5d_pct/alpha_20d_pct/alpha_60d_pct(个股相对沪深 300 同期累计收益差,单位 %)+baseline_index_coderel_strength_label ∈ {leading, in_line, lagging}(基于 alpha_20d_pct ±5% 分档)。让 LLM 在判断"放量异动"时区分跟随性反弹与抗跌强势。
- 新 tushare 权限
index_daily(optional):250d 拉一次沪深 300 基准 收盘价;G1—与个股 daily 长度对称、cache 友好。 - 维度 D 改名 / 扩写:
VA_TREND_SYSTEM中维度 D 由"板块强度"改为 "板块与市场相对强度",提示 LLM 同时考虑板块层 (sector_strength_*) 与市场层 (alpha_*_pct/rel_strength_label)。 - Few-Shot 示例补充 alpha 引用:
prompts_examples.py的 A/B 示例 各加一条alpha_20d_pct引用,确保 anchoring 与新维度一致。 - G8 显式降级提示:
index_daily权限缺失或 fetch 失败时,runner emit 一条EventLevel.WARNLOG 事件,明确提示用户"alpha 字段降级为 None; 如需启用 alpha,请确认 Tushare 账户已开通 index_daily 权限"。
Behavior change
- analyze 阶段每次 run 多 1 次
index_daily调用(cache 友好;首次冷拉 ~5s),无新数据消费 SLA 影响。 - LLM 输入每候选 +30–40 tokens(5d/20d/60d alpha + label); 在 200K 输入预算下完全可接受。
Implementation notes
- alpha 计算口径:
alpha_n = stock_pct_chg_n − baseline_pct_chg_n(简单收益)。 rel_strength_label仅基于alpha_20d_pct;alpha_20d 缺失时rel_strength_label = None,但baseline_index_code永远输出(元数据)。- F2 决策:行业相对 alpha 暂不做,留给波次 3 视效果再加。
[volume-anomaly v0.4.0] — 2026-05-08 — T+N 自动回测闭环(PR-4)
仅升级 volume-anomaly 插件版本(0.3.0 → 0.4.0);框架版本不变。 升级方式:
deeptrade plugin upgrade <plugin-source>,会自动应用新增的 migration(20260601_001_realized_returns.sql)。
参考 docs/volume_anomaly_wave2_design.md § 3。
Added (volume-anomaly plugin)
- 新表
va_realized_returns:每条 hit 一行(PK=anomaly_date+ts_code), 存 T+1/T+3/T+5/T+10 收盘价、对应收益、5d/10d 窗口最大涨幅与回撤。data_status ∈ {pending, partial, complete}(G5 严格三态:T+1 未到→pending; max_horizon 未到 OR 任一 horizon 数据缺失→partial;max_horizon 已到且全填→complete)。 - 新子命令
evaluate:
从deeptrade volume-anomaly evaluate [--lookback-days N] [--trade-date YYYYMMDD] [--backfill-all] [--force-recompute]va_anomaly_history全集(G3)里取 anomaly_date 在 lookback 内的 hits, 按 trade-day horizon 解析 T+1/T+3/T+5/T+10 实际 trade_date,复用_fetch_daily_history_by_date的 cache 拉取收盘价,UPSERT 进va_realized_returns。complete行默认跳过;--force-recompute强制重算。 - 新子命令
stats:
纯只读 SQL 聚合deeptrade volume-anomaly stats [--from] [--to] [--by prediction|pattern|launch_score_bin]va_stage_results JOIN va_realized_returns,输出每桶的 样本数 / T+3 平均收益 / T+3 胜率 / T+5 最大涨幅均。launch_score_bin默认分箱0-40 / 40-60 / 60-80 / 80-100(G4)。 evaluate写 va_runs / va_events(G10):mode='evaluate'与 screen / analyze / prune 同级,可在history子命令中查看。
Implementation notes
- 所有 horizon 时间换算用
TradeCalendar.next_open跳过周末/节假日。 va_realized_returns.t_close冗余存(G9)——让 stats 在 va_anomaly_history 被异常清空时仍可读取。- 不新增 tushare API 权限(仍只用
daily)。 - 不引入新插件级 config 表(F6);horizon 列表写死在 module-level
EVALUATE_HORIZONS = (1, 3, 5, 10)。
[volume-anomaly v0.3.0] — 2026-05-08 — 波次 1(PR-1 + PR-2 + PR-3)
仅升级 volume-anomaly 插件版本(0.2.0 → 0.3.0);框架版本不变。 升级方式:
deeptrade plugin upgrade <plugin-source>。本版本不新增 migration。
参考 docs/volume_anomaly_wave1_design.md(§ 1–5 共五项 P0 优化)。
Added (volume-anomaly plugin)
PR-1 — Screen 规则
- 上影线过滤(P0-1):T-day 规则后、换手率前新增一道过滤,
upper_shadow_ratio = (high − max(open, close)) / range > upper_shadow_ratio_max的候选直接淘汰,避免"避雷针"型的纯上影 K 线进入候选。诊断字段n_after_upper_shadow暴露在漏斗中;hit 行新增upper_shadow_ratio字段。 - 按流通市值分桶的换手率阈值(P0-2):用
daily_basic.circ_mv把候选按 流通市值(亿元)切 4 档,每档使用独立的[turnover_min, turnover_max]:≤50亿: [5, 15]/50–200亿: [3.5, 12]/200–1000亿: [2.5, 9]/>1000亿: [1.5, 6]。边界值归"较小桶"(E4)。circ_mv缺失时退化到 全局turnover_min/max,并写入data_unavailable。诊断字段turnover_bucket_hits/n_missing_circ_mv/circ_mv_missing_codes暴露在 screen 报告中;hit 行新增circ_mv_yi/turnover_bucket字段。
PR-2 — Analyze 候选行特征
- VCP 三维收敛指标(P0-3):候选行新增
atr_10d_pct/atr_10d_quantile_in_60d(10 日 ATR 在近 60 日 ATR 序列中的分位数)/bbw_20d(20 日 Bollinger 带宽)/bbw_compression_ratio(当前 BBW / 近 60 日 BBW 均值,<1 表示在收敛)。零新数据,复用现有 60d 历史窗口(实际从 250d 切片)。 - 120/250 日阻力位距离(P0-4):
collect_analyze_bundle默认history_lookback由 60 → 250;_build_candidate_row内部切片处理。 候选行新增high_120d/high_250d/low_120d/dist_to_120d_high_pct/dist_to_250d_high_pct/is_above_120d_high/is_above_250d_high/pos_in_120d_range。 E3-A:仅 120d 区间位置,不补low_250d。E7-C:任一窗口数据不足则 对应字段降级为None,不阻塞主流程。 - 首次冷拉延迟:扩展 250d 窗口的首次 fetch 约多 30–45 秒 (Tushare RPS=6 默认下),后续走 cache 零增量。
PR-3 — LLM Prompt 对齐
- Few-Shot 锚定(P0-5):新增
prompts_examples.py::VA_TREND_FEWSHOT, 在VA_TREND_SYSTEM末尾拼接两个示例(VCP 突破 / 高位上影诱多), 引导 LLM 在不同 provider 间保持一致的launch_score尺度。 - 维度 A 提示扩展:在【判断维度】A 段中加入"整理期波动率收敛"指引,
显式提示 LLM 引用
atr_10d_quantile_in_60d/bbw_compression_ratio。 - 字段一致性单测:
test_prompt_consistency.py用正则提取 示例中的"field": "<X>",断言每个 X 都属于_build_candidate_row输出键集合 OR screen hit 行字段集合。 防止后续字段重命名时 prompt 失配。
Default behavior change (heads-up)
- 默认开启 上影线过滤:
ScreenRules.upper_shadow_ratio_max = 0.35。 如需保持旧行为,在screen_rules配置中显式传"upper_shadow_ratio_max": null。 - 默认开启 分桶换手率:
ScreenRules.turnover_buckets = DEFAULT_TURNOVER_BUCKETS。 如需保持旧行为,在screen_rules配置中显式传"turnover_buckets": null, 此时回退到[turnover_min, turnover_max]全局阈值。 - 默认 analyze 历史窗口由 60 → 250 个交易日(E2-A 单 fetch 复用)。 首次冷启动会多消耗 30–45s(RPS=6 默认),后续走 cache 零增量。
Implementation notes
turnover_bucketsJSON 配置中最后一档可用null表示无穷上界,from_dict内转math.inf。as_dict()反向把math.inf序列化为null, 使screen_stats.json仍是合规的标准 JSON(不依赖Infinity扩展)。- 不动 framework;不新增 tushare API 权限(
circ_mv已包含在daily_basic字段中;250d 长窗口仍走daily)。 - VCP 计算
_compute_atr_series用简单平均 TR(与 Wilder 公式相比差异 在 60d 窗口尺度内不显著);BBW 用 4σ / MA20 × 100 表达带宽占均价比例。
[limit-up-board v0.4.0] — 2026-05-07 — 候选股市值/股价过滤 + 持久化设置
仅升级 limit-up-board 插件版本(0.3.2 → 0.4.0);框架版本不变。 升级方式:
deeptrade plugin upgrade <plugin-source>,会自动应用新增的 migration(20260508_005_lub_config.sql)。
Added (limit-up-board plugin)
- Step 1 候选股漏斗增加两条筛选条件:在主板 + 涨停 join 之后、ST/停牌之前
按 流通市值 与 当前股价 上限再筛一层。null 在任一字段 → 过滤(保守语义)。
默认
流通市值 < 100亿+股价 < 15元。bundle.market_summary新增candidate_filter_summary字段(before / after / 阈值),LLM prompt 中可见。 - 新子命令
deeptrade limit-up-board settings:交互式编辑两个上限阈值, 回车保留当前值,写入新表lub_config。 - 新子命令
deeptrade limit-up-board settings show:表格展示当前生效设置, source ∈ {default,persisted}。 - 运行时打印当前配置:
run/sync在 Step 1 之前 emit 一条 LOG 事件 (运行配置: 流通市值 < ...亿、股价 < ...元),dashboard / 日志中可见。
Implementation notes
- 配置存储:插件自有
lub_config表(与lub_runs/lub_events同 Plan A 纯隔离层级),不复用框架级app_config—— 避免_DOT_TO_FIELD白名单 扩张,框架保持轻量。 - 默认值唯一来源:
limit_up_board.config:LubConfig的 dataclass default; SQL / CLI / 文档不重复声明。
[limit-up-board v0.3.0] — 2026-05-07 — Phase A + Phase B 因子补齐
仅升级 limit-up-board 插件版本(0.2.1 → 0.3.0);框架版本不变。 升级方式:
deeptrade plugin upgrade <plugin-source>,会自动应用本版本新增的两条 migration(20260508_001_lub_lhb_tables.sql/20260508_002_lub_cyq_perf.sql)。
参考 docs/limit-up-board-optimization-plan.md。对照 Gemini 给出的"游资思路"因子
清单,分两阶段补齐:A 阶段为派生因子(不增 tushare API),B 阶段接入龙虎榜与筹码。
Added (limit-up-board plugin)
A1 — 派生因子(候选股层,pure compute)
amplitude_pct:T 日振幅 = (high − low) / pre_close × 100。fd_amount_ratio:封单比 = fd_amount / amount × 100,>10% 为强势封板。ma5/ma10/ma20+ma_bull_aligned:基于 prev_daily 的简单移动平均;ma_bull_aligned = (close > ma5 > ma10 > ma20)。历史不足窗口期返回 null。up_count_30d:近 30 个交易日 pct_chg ≥ 9.8 的天数;不足 30 日返回 null。
A2 — 市场情绪三件套(market_summary 层)
limit_step_distribution_prev+limit_step_trend:T 与 T-1 的连板梯队对比, interpretation ∈ {spectrum_lifting,spectrum_collapsing,stable}。yesterday_failure_rate:T-1 全市场炸板率(z / (u + z)),interpretation ∈ {high≥25%,moderate,low≤10%}。yesterday_winners_today:T-1 涨停股在 T 日的连板率与平均涨幅, interpretation ∈ {strong_money_effect,neutral,weak_money_effect}。
B1 — 龙虎榜(top_list / top_inst → required)
- 候选股层字段:
lhb_net_buy_yi(龙虎榜净买入,亿元;正/负均如实给出)、lhb_inst_count(机构席位 unique 数)、lhb_famous_seats(命中游资白名单的 exalter 原文数组)。 FAMOUS_SEATS_HINTS:~15 条主流游资席位子串白名单(拉萨系、宁波系、章盟主、 赵老哥、厦门帮等)。匹配逻辑大小写不敏感;只给 exalter 原文,不暴露白名单标签。- 新表:
lub_top_list(key trade_date+ts_code)、lub_top_inst(key trade_date+ts_code+exalter+side)。 - "未上榜 ≠ 数据缺失":candidate 不在当日 top_list 中时
lhb_*为 null,但data_unavailable中不会出现 top_list/top_inst(接口本身可用)。
B2 — 筹码(cyq_perf → required)
- 候选股层字段:
cyq_winner_pct(获利盘比例 %)、cyq_top10_concentration(100 − (cost_95pct − cost_5pct) / weight_avg × 100,clip [0,100])、cyq_avg_cost_yuan(weight_avg)、cyq_close_to_avg_cost_pct((close − weight_avg) / weight_avg × 100)。 - 新表:
lub_cyq_perf(key trade_date+ts_code)。
Prompt 改动
- R1_SYSTEM【分析维度】新增形态、历史基因、市场情绪三类;evidence 新增"missing_data 字段不得引用"硬约束。
- R2_SYSTEM【判断重点】追加:亏钱效应下自动下调 confidence 一档;梯队拉升期允许 上调 continuation_score;筹码维度三阈值(70/60/-10)+ 龙虎榜维度(未上榜 ≠ 缺失; 负净买入不可作为正面 evidence;不可推断游资身份)。
- R1 不引入 chip / LHB 维度(控制 prompt 噪声,B 阶段仅 R2 使用)。
Changed (BREAKING)
Tushare 权限要求扩张(用户必须确保 tushare 账户已开通以下权限):
top_list/top_inst/cyq_perf由permissions.tushare_apis.optional上移 到required;任一缺失 → run failed。未引入新权限分类——"接口可用但 candidate 未上榜"用 null + 数据层 inner-join 自然表达。
默认值 / 内部行为变更
RunParams.daily_lookback/ CLI--daily-lookback默认值 10 → 30(满足 ma20 + up_count_30d)。collect_round1新增prev_trade_date: str | None入参;3 个 runner 调用点 通过_safe_prev_trade_date(cal, T)计算并传入。- daily 历史窗口的日历缓冲由
lookback + 5改为lookback × 2,确保 30 个交易 日 lookback 在过节窗口下仍能命中 ≥30 行真实交易日。
Migrations
20260508_001_lub_lhb_tables.sql— 创建lub_top_list/lub_top_inst。20260508_002_lub_cyq_perf.sql— 创建lub_cyq_perf。
Tests
-
tests/strategies_builtin/limit_up_board/test_phase_a_factors.py:33 个单元 测试,覆盖 A1/A2 全部派生函数 + 边界(不足窗口期、零分母、空 frame、阈值跳点)。 -
tests/strategies_builtin/limit_up_board/test_phase_b_factors.py:20 个单元 测试,覆盖白名单匹配(大小写、去重、非字符串)、LHB rollup(空帧 / None / 仅 top_list)、cyq 集中度(紧/宽/截断到 0)、close_to_avg_cost_pct 边界。
[v0.7.0] — 2026-05-01 — Stage 概念归插件 + 配置键改名
清理 v0.6 留下的 stage 硬编码技术债。Stage 名字、preset → stage tuning 表全
部移入插件;框架的 LLMClient.complete_json 不再认识 stage,由调用方直接传
入 StageProfile。配置键 deepseek.profile 同步重命名为 app.profile。
Breaking change(项目仍在 dev/iteration 期)。设计原文:DESIGN.md §10.1。
Changed (BREAKING)
框架瘦身 — stage 退出 LLMClient
- 删除
core.llm_client.KNOWN_STAGES/LLMUnknownStageError/_stage_profile()/ 全局_CURRENT_STAGE。 LLMClient.complete_json签名变化:- 删除
stage: str入参;framework 不再写llm_calls.stage列。 - 新增
profile: StageProfile(必填)— 调用方直接传入已解析的调参档。 LLMClient.__init__删除profiles=入参。LLMManager.get_client()不再绑 profile。
- 删除
RecordedTransport改为纯 FIFO:register(response)不再带 stage 标签。- 删除
core.config.DS_STAGES/DeepSeekProfileSet/PROFILES_DEFAULT/ConfigService.get_profile()。 StageProfile升格为公共契约,搬到deeptrade.plugins_api.llm,由from deeptrade.plugins_api import StageProfile公开导出。
配置键改名 — deepseek.profile → app.profile
AppConfig.deepseek_profile→AppConfig.app_profile;_DOT_TO_FIELD同步更新。preset 仍是Literal["fast","balanced","quality"],语义全局, 但键名 vendor-agnostic。- DB 行自动迁移:
config_migrations.migrate_legacy_deepseek_profile_key幂等地把deepseek.profile行改写为app.profile,并删除旧行。 - 环境变量直接断代:
DEEPTRADE_DEEPSEEK_PROFILE不再被识别。ConfigService.get_app_config()启动时若检测到旧 env 而新 env 未设, 抛RuntimeError退出 — 避免静默用错配置(默认会回落到 "balanced", 让用户以为生效但其实并没有)。请改为DEEPTRADE_APP_PROFILE。
DB schema — llm_calls.stage 列删除
- 新 SQL 迁移
20260501_002_drop_llm_calls_stage.sqlALTER TABLE llm_calls DROP COLUMN IF EXISTS stage(DuckDB 1.0+)。 core/migrations/core/20260427_001_init.sql同步去掉stage字段, 让 fresh DB 直接落到 v0.7 期望状态。- 历史 run 的 stage 信息仍可在
~/.deeptrade/reports/<run_id>/llm_calls.jsonl中查阅;v0.7 起新写入的 jsonl 行也不再含stage键。
插件改造(两个内建插件)
- 新增
<plugin>/profiles.py:本地维护 preset → stage tuning 表 +resolve_profile(preset, stage)。 runner.py读取cfg.app_profile(preset 字符串),传给 pipeline 函数; pipeline 内部调resolve_profile()得到StageProfile。volume-anomaly借此改造把语义错误的 stage 名continuation_prediction改回trend_analysis。
Added
deeptrade/plugins_api/llm.py— 公共StageProfile契约(4 字段: thinking / reasoning_effort / temperature / max_output_tokens)。config_migrations.migrate_legacy_deepseek_profile_key+ 4 条单测 (rename / 幂等 / fresh DB no-op / new-already-set 跳过)。tests/core/test_config.py新增两条 env 行为测试(旧 env 报错、新旧并存 以新为准)。
Engineering
- 136 pytest tests passing (无变更)。
- 偿还 v0.6 RV6-4 / RV6 §10.2 已知技术债。
[v0.6.0] — 2026-05-01 — LLM Manager 化(多 Provider)
The LLM client is no longer DeepSeek-specific — it is now a framework-level
service that lets a single plugin call multiple OpenAI-compatible LLMs in
the same run. Breaking change (project remains in dev/iteration phase).
Full rationale: DESIGN.md §0.7 + §10.
Changed (BREAKING)
Configuration model — deepseek.* → llm.*
llm.providers(JSON dict, app_config) —{name: {base_url, model, timeout}}. Multiple providers coexist; each plugin picks by name at call time.llm.<name>.api_key(secret_store) — one secret per provider. Theis_secret_key()predicate (replacing the oldSECRET_KEYSconstant) matchestushare.tokenplus this dynamic prefix.llm.audit_full_payload(bool, app_config) — replacesdeepseek.audit_full_payload.deepseek.profileis kept as the global stage-profile name (rename deferred to v0.7 per §10.1 note).- The four legacy keys
deepseek.base_url/deepseek.model/deepseek.timeout/deepseek.audit_full_payloadare removed fromAppConfig. There is nollm.default— callers must pass a name.
Auto-migration
- On first
apply_core_migrations()after upgrade, legacydeepseek.*rows are migrated intollm.providers["deepseek"]+ the renamed secretllm.deepseek.api_key+llm.audit_full_payload. Idempotent: re-runs on already-migrated DBs are no-ops. Code indeeptrade.core.config_migrations.migrate_legacy_deepseek_keys.
LLM client / new manager
- New
core/llm_manager.py::LLMManager— the only path plugins should use:list_providers(),get_provider_info(name),get_client(name, *, plugin_id, run_id, reports_dir=None). Caches clients per(name, plugin_id, run_id). Documented as not thread-safe. core/deepseek_client.py→core/llm_client.py;DeepSeekClient→LLMClient.OpenAIClientTransportkeeps its name (it really is the OpenAI-compatible transport).LLMNotConfiguredError(new) — raised by manager when a provider is missing or its api_key is unset.
CLI command surface
- Added:
config set-llm(interactive new / edit / delete one provider),config list-llm(show usable providers),config test-llm [name](per-provider connectivity check; tests all when omitted). - Removed:
config set-deepseek,config test. The init-time prompt ("Configure deepseek now?") is now "Configure an LLM provider now?". config showexpandsllm.providersso each provider'sapi_keyslot is its own masked row.
Plugin runtime collapse
volume-anomalyandlimit-up-boardruntimes lose their per-pluginbuild_llm_client(rt); both now declarellms: LLMManagerand callrt.llms.get_client(provider_name, plugin_id=, run_id=, reports_dir=). Provider selection helperpick_llm_provider(rt)ships in each plugin'sruntime.py(prefersdeepseek, falls back to first available); a per-plugindefault_llmconfig key is deferred to v0.7.
Added
LLMProviderConfigPydantic model (per-provider connection record).ConfigService.set_llm_provider(name, *, base_url, model, timeout, api_key=None)anddelete_llm_provider(name)— CRUD helpers used by the CLI.tests/core/test_llm_manager.py— list/info/get_client + cache + missing api_key/provider errors + multi-provider coexistence (11 cases).tests/core/test_config_migrations.py— idempotency + happy path + partial-legacy-state edge case (7 cases).
Engineering
- 136 pytest tests passing (was 130; +11 manager + 7 migration − 12 retired duplicates).
- ruff + mypy clean on touched files.
- DESIGN §0.7 + §7.1 + §7.3 + §10 rewritten; PLAN §11 adds the v0.6 work breakdown.
Known design debts (deferred)
KNOWN_STAGES(strong_target_analysis / continuation_prediction / final_ranking) is still hardcoded in the framework — leaks plugin semantics intocore/llm_client.py. v0.7 will let plugins declare their own stage names + per-stage profile overrides.deepseek.profilekey name retained for backward compat within the current dev cycle; rename tollm.profileplanned for v0.7.- No transport plugin type yet — Anthropic native / Gemini native cannot be
added without code changes. Will land as
type=llm-transportplugins when first user need surfaces.
[v0.5.0] — 2026-04-30 — Plugin CLI dispatch + pure data isolation
Breaking-change reshape per docs/plugin_cli_dispatch_evaluation.md. The
project is in dev/iteration phase; no backward compatibility.
Changed (BREAKING)
Framework command surface — closed
- Top-level CLI is now
init / config / plugin / dataONLY.strategyandchannelcommand groups removed. - Pure pass-through: any unknown first token is looked up as a
plugin_id; if installed + enabled, framework callsPlugin.dispatch(remaining_argv)and is otherwise dumb. deeptrade --helpno longer enumerates plugin subcommands.--helpinside a plugin is the plugin's own responsibility.deeptrade helloremoved. Interactive main menu removed.- Reserved plugin_ids:
init,config,plugin,data.
Plugin contract — minimal
StrategyPluginProtocol removed. New unifiedPluginProtocol:metadata+validate_static(ctx)+dispatch(argv) -> int.ChannelPluginextendsPluginand addspush(ctx, payload).StrategyContext/StrategyParams/StrategyRunner/ TUI dashboard removed. Each plugin owns its own runtime + run lifecycle internally.ChannelContextrenamed toPluginContext(still narrow: db + config + plugin_id).
Data isolation — Plan A (pure)
- Framework owns ONLY:
app_config,secret_store,schema_migrations,plugins,plugin_tables,plugin_schema_migrations,llm_calls,tushare_sync_state,tushare_calls. No business tables. - Tushare-derived shared market tables (
stock_basic,trade_cal,daily,daily_basic,moneyflow) removed from core. Each strategy plugin declares its own prefixed copies (e.g.lub_stock_basic,va_*). tushare_sync_statePK now(plugin_id, api_name, trade_date). Each plugin tracks its own sync state and cache; no cross-plugin sharing.tushare_callsandllm_callsaddplugin_idcolumn.TushareClient.__init__requiresplugin_id. Framework probes use the reservedFRAMEWORK_PLUGIN_ID = "__framework__"sentinel.
Notification API
core/notifier.pyexposes top-levelnotify(db, payload)andnotification_session(db). Re-exported asfrom deeptrade import notify, notification_session. NoopNotifier when no channel enabled.strategy_runs/strategy_eventstables removed. Each plugin defines its own<prefix>_runs/<prefix>_eventsif it wants run history.
Added
- Built-in plugins reshaped to v0.2.0:
limit-up-board: owncli.py+plugin.py+runner.py+runtime.py; new migration with 10lub_*tables.volume-anomaly: same pattern; 5va_*tables; subcommandsscreen/analyze/prune/history/report.stdout-channel: implements newPlugin+ChannelPlugincontracts; owndispatchfortest/log.
- Tests: framework routing tests (
tests/cli/test_routing.py); Plugin Protocol contract tests (tests/plugins_api/test_protocol.py); plugin install + migration isolation tests (tests/core/test_plugin_install.py).
Removed
cli_strategy.py,cli_channel.py,tui/package,core/strategy_runner.py,core/context.py, old pluginstrategy.pyfiles.textualdependency._interactive_main_menu,hellocommand.
v0.1.0 — 2026-04-28
First public release. Baseline implementation of DESIGN.md v0.3.1.
Added
Framework
deeptrade init— DuckDB layout + 11-table core schema migrations (idempotent).deeptrade config show / set / set-tushare / set-deepseek / test— layered config (env > db > default), keyring + plaintext fallback for secrets.deeptrade plugin install / list / info / disable / enable / upgrade / uninstall [--purge]— three-stage lifecycle (install never touches network; validate is connectivity-only; run does the strict checks).deeptrade strategy list / run / history / report <run_id>— Live EVA-themed dashboard (header / progress / events / analysis / footer);--no-dashboardfor non-tty.- Plugin api_version "1":
StrategyPluginProtocol,StrategyContext,StrategyEventenum, PydanticPluginMetadata(YAML).
Core services
Database— single-process, single-writer DuckDB; reentrant write lock; short transactions.SecretStore— keyring-first, plaintext fallback with explicit warning.TushareClient— token-bucket rate limit, tenacity retries, 4 cache classes (static / trade_day_immutable / trade_day_mutable / hot_or_anns),data_completeness(final / intraday) for intraday isolation.DeepSeekClient— JSON-mode + Pydantic double-validate; profile triple (fast/balanced/quality); stage-levelmax_output_tokens(R1/R2 default 32k, final_ranking 8k); never passestools/tool_choice/functions.StrategyRunner— status state machine (running → success / failed / partial_failed / cancelled); KeyboardInterrupt → cancelled; anyVALIDATION_FAILEDevent flips success → partial_failed.setup_logging()— stderr handler + rotating file under~/.deeptrade/logs/.
Built-in strategy: limit-up-board
- Step 0
resolve_trade_date()— most-recent-closed trade day;app.close_afterconfigurable threshold;--allow-intradayopt-in. - Step 1 data assembly — main board filter (Q2: SSE/SZSE only), ST/suspended exclusion, three-tier
sector_strengthfallback (limit_cpt_list→lu_desc_aggregation→industry_fallback), normalized prompt fields (亿/万 + 2dp) while DB keeps raw. - Step 2 R1 —
plan_r1_batches()with input + output token DUAL budget (F5);EvidenceItem.unitmandatory. - Step 4 R2 — single batch by default; auto multi-batch when input exceeds budget.
- Step 4.5
final_ranking— only triggered on multi-batch R2;select_finalists()keeps top + watchlist + boundary avoid samples. - Step 5 reports — 5-file dump under
~/.deeptrade/reports/<run_id>/; banner stack rules (red for partial_failed/failed/cancelled, yellow for INTRADAY MODE, both stack).
Fixed (v0.3 review round 2 → v0.3.1)
- F1
limit_stepwas duplicated in optional table → removed; required-only. - F2
limit_cpt_list✅mismatched optional status → unified tooptional+fallbackeverywhere;sector_strength_sourcelabel propagated to prompts. - F3 Fast profile R2
thinking: truecontradicted "all off" docs → flipped tofalse. - F4
--allow-intradaywould have polluted EOD caches → addeddata_completenesscolumn; daily-mode reader rejects intraday-cached rows; UI/report INTRADAY MODE banner. - F5 Default
max_output_tokens=8192would truncate R1 → moved to per-stage profile (R1/R2 32k, final_ranking 8k); R1 evidence cap 8 → 4; rationale length-capped via prompt. - S1 Migrations are now the sole DDL source;
tablesonly declares names + purge flag. - S2
app.close_afterconfigurable (default 18:00); install never touches tushare. - S3
strategy_runs.statusCHECK constraint removed (DuckDB ALTER limitation); validation moved to Pydantic layer. - S4
row_count=0is a legal outcome (extreme tape day); fallback predicate accepts it. - S5
final_rankingonly ranks finalists; non-finalists keepbatch_local_rank; both surface inround2_predictions.json.
Engineering
- 163 pytest tests passing; ruff + mypy clean.
- 1 real concurrency bug fixed during development:
Database.transaction()+execute()self-deadlock with non-reentrantthreading.Lock→ switched tothreading.RLock. - 1 pandas 2.x compat fix:
pd.read_json(json_str)deprecated → wrap inio.StringIO().
Known design debts (planned for v0.4)
- D1 Replace
configure(ctx) -> dictwith schema-drivenget_param_schema() -> type[BaseModel]; CLI auto-renders questionary forms; non-interactive mode supports--params-file. - D2 Per-API
probesin plugin metadata;validatebecomes two layers (validate_connectivity+validate_required_apis).