Repository navigation
Releases
v0.6.1:持仓同步并集与计划执行价格 fail-closed
修复
- 持仓退出筛选池后,日线同步会漏掉持仓,导致收盘 NAV、日亏/回撤检查和 AI 上下文使用旧价。现在
sync_data的同步集合 = 本轮标的 ∪ 全部持仓。 - 待执行计划包含本轮运行池之外的新增标的时,执行上下文缺价会把
limit_price设为 0,绕过单笔金额上限,并得到误导性的“无价格”拒单。现在执行前补齐计划标的的日线和最新价;px<=0明确拒单。 - 计划保存时用定稿收盘价回填买/卖动作基准价(
price_source=last_close);执行缺价时只接受该可信来源兜底。
测试与部署
- 本地 175 项全绿(新增 5 项回归)。
- Pi
git pull到27e6e82,pytest175 项全绿;dashboard active、HTTP 200、版本 0.6.1;engine.enabled=false。 - 实盘数据验证:只点名
600519执行sync,实际补回000858、600036各 5 根日线。 - 历史 NAV 核对:2026-10-21 旧 100282.636 → 100346.636(+64.0,+0.064%),已落
op_lognav:revision,不回写历史。
影响
- 纯纸盘;
engine.enabled保持false。 - 无 schema/config 变更。
v0.6.0 — M1 正确性与回测-实盘口径统一
M1 正确性与回测-实盘口径统一
这一版把「能跑」推进到「口径可信」:回测和实盘用同一套判定、费用、风控与成交假设。engine.enabled 仍保持 false,等 Pi 双端验收通过后再开。
修复的硬问题(14 项审计)
- 持仓元数据:
entry_day/peak_price落库,旧库自动迁移回填;time_stop/trailing_stop在实盘真正生效,重启不丢。 - 回测口径:T 日收盘判定 → T+1 开盘价成交(±0.01 滑点);当日买入 T+1 冻结;
once改为按交易日;一字涨停不买、一字跌停不卖、停牌不成交。 - 风控接入:回测、引擎、条件单、CLI、AI 共用同一组风控参数;峰值与日亏基线落库(
risk_state),重启不被绕过;引擎与条件单共用同一份日内订单预算。 - 计价与未来函数:多持仓按各自最新价计价;引擎日线上下文排除当日未收盘 K 线。
- 实时行情:只认连续竞价 9:30–11:30 / 13:00–15:00;报价日期不是当天或超过 180 秒直接丢弃(午休/节假日旧价不进场)。
- 其他:规则级
enabled:false生效;engine.universe=auto/list语义落地;科创板买入最低 200 股;条件单日上限按成交日统计。 - 除权除息(M1 降级保护):同步时检测历史收盘被回溯调整并写
op_log(data:revision);新增corporate apply人工调整入口。自动调整与分红税细化在 M1.5。
验证
- 新增
tests/test_m1_correctness.py(20 项),含「回测 vs 模拟实盘 tick」成交日/成交价对照。 - 本地
python -m pytest -q:170 passed。 - 路线图与验收清单:
docs/ROADMAP.md。
升级注意
- 首次启动会自动给
positions表加列并回填(幂等)。 - 若同步输出出现
revisions/op_log的data:revision,请人工核对除权除息后用corporate apply调整账本。 engine.enabled本版仍为false。
v0.5.5
v0.5.4 本地模型免令牌 + AI 设计条件单修复
v0.5.4 — 本地模型免令牌 + AI 设计条件单修复
这一版解决三类"API 又坏了":
1. 本地/局域网模型:只填接口地址,不用令牌
原来面板强制要 API Key 才能保存、拉模型,本地推理服务(Ollama、LM Studio、vLLM、
自建网关)根本不校验令牌,等于用不了。
现在:
- 新增预设「本机 Ollama / 本机 LM Studio / 本机 vLLM / 局域网网关」,只填地址即可
- 空 Key 时不发
Authorization头(有些服务收到空 Bearer 反而直接 401) - 切到这类预设会自动清掉之前保存的云端 Key(不把 Key 发到别的机器)
- 云端接口不填 Key 也能先保存,但会明确提示"通常需要 Key,可能 401"
真机验证(Pi → 局域网网关 http://192.168.50.188:8787/v1,无令牌):
拉到 32 个模型,对话 3.3 秒返回。
2. 修:AI 设计条件单一直 500
KeyError: '"conditions"' —— 提示词里的 JSON 示例({"conditions": …})被
str.format 当成了占位符。面板只显示"设计失败",看起来就像 API 挂了。
改用字符串替换后,真机 dry-run 正常产出条件:
002192 升破 49.8 加仓 / 002709 回踩 27.3 低吸 / 000792 跌破 21.95 止损。
3. 修:模型兼容与"空回答"
- 始终思考的模型(GLM 5.3 系列)拒绝
thinking=disabled(HTTP 400 / 1210):
自动降档enabled/low,并把输出预算抬到至少 4096(思考也占预算) - 答案被思考吃光时,直接报"模型只输了思考过程、答案为空(finish_reason=length)",
不再静默返回空串 - 已保存的模型不在
/models列表里时(如已下线的glm-4.5-flash)给出明确提示 secrets.local.json兼容 Windows 记事本的 BOM(原来会被读成"未配置")
验证
- 149 个离线单测全绿,双端(本地 + Pi)一致
- Pi 真机:模型列表、AI 问答、AI 设计条件单(dry-run)全部走通
- 面板浏览器复验:免令牌拉模型 32 个、AI 设计挂单成功
兼容性
无 schema 变更。配置上新增可选预设 ollama / lmstudio / vllm / lan,
已有 glm / deepseek 等配置不受影响。
v0.5.3 样本外检验 + 总开关加固
v0.5.3 — 样本外检验 + 总开关加固
这一版做两件事:给参数网格配一把"过拟合体检"尺子;把总开关的语义钉死。
加:样本外检验(walk-forward)
参数网格按超额收益排序,但样本内排第一的参数,换到没见过的数据上往往就不行了。
engine walkforward 把数据按时间切成两段:前 60% 用来排名,后 40% 用来检验,
并给出"事后最优"作对照——样本内第一名与样本外第一名的差距,就是过拟合的量度。
python -m paper_trading.cli engine walkforward --grid ma_cross --days 250 --split 0.6 --json面板同步加了「样本外检验」按钮:一张表同时显示样本内超额、样本外收益、样本外超额、笔数、夏普。
Pi 真机实测结论(重要,写在 README 里了)
| 网格 | 样本内第一名 | 它在样本外的表现 | 样本外事后最优 |
|---|---|---|---|
ma_cross |
002192 short=10/long=30(-26.92%) | +6.25% | 000792 short=20/long=30(+12.94%) |
rsi_zone |
002192 n=14 lower=30 upper=75(-4.28%) | +1.58% | 002192 n=14 lower=25 upper=70(+14.25%) |
两个网格的共同点:样本内排第一的,在样本外都不是最好的。每个窗口只有 3~7 笔成交,
这个量级下排名基本是噪声,不是 alpha。
结论写得很直白:当前规则集没有证明自己有 alpha,生产配置保持 engine.enabled: false。
这正是这把尺子的价值——它让"回测好看"这件事没法自欺。
修:总开关 engine.enabled 的语义
原来这个开关只在守护进程入口查一次,cron 用 --once 单轮调时绕过了它:
配置写着"不自动交易",实际照常下单。
现在 enabled: false 对所有入口生效(守护进程、cron 单轮、CLI engine run、面板按钮):
只判定不成交,试运行 --dry-run 例外(只看意图)。
顺带修了一个状态语义:开关关着时触发的条件单,之前会被标成**"已拒绝"(永久作废),
现在保持"待触发"**——条件本身没问题,只是现在不该成交,等人开了开关再说。
新增 tests/test_engine_switch.py 锁死这四条。
Pi 部署
- crontab 增加引擎单轮 tick:
* 9-15 * * 1-5 … python -m paper_trading.engine --once --json >> engine.log 2>&1
(enabled=false时只判定不成交;写 crontab 仍用 base64 传输,*会被远端 shell glob 展开) - 129 → 138 个单测全绿,CI 绿
兼容性
无 schema 变更、无配置项变更。engine.enabled 的语义变化是收紧(原来能下的单现在不下了),
这正是这个开关该有的行为。
v0.5.2 回测提速 + JSON 严格化
v0.5.2 — 回测提速 + JSON 严格化
上一版修好了账,这一版让工具好用:参数网格从"几分钟没反应"变成秒级,
面板的网格结果不再静默卡住。
提速:参数网格从几分钟到 23 秒
回测器原来每个交易日都线性扫一遍 K 线找当天收盘(days × bars × 变体数),
参数网格一轮 32 个变体 × 250 天 × 4 只票,代价是几百万次无谓扫描。
改法:
- 每只票建一次
{day: close}价格索引,查询 O(1) - 规则判定需要的"截至某日的收盘序列"用二分切点定位后切片(O(log n + k))
- 基准买入持有同样走索引
效果:Pi 上 32 变体 × 250 日 × 4 票 = 23 秒(原来好几分钟)。
面板默认用 120 日(更快),要 250 日用 CLI。
修:面板参数网格静默卡住
真机上点"参数网格",页面永远停在"搜索中…",控制台报
SyntaxError: Unexpected token 'I', ... "_factor": Infinity。
原因:profit_factor 在"没有亏损单"时是 float('inf'),json.dumps 写成
Infinity —— 这不是合法 JSON,浏览器的 response.json() 直接抛异常,
整块功能连报错都没有。
两处都改了:
- 源头:
profit_factor无亏损时用None(表示"无亏损"),不再是无穷 - 加了一层安全网:
paper_trading/utils/jsonx.py的safe()递归把
NaN / ±Infinity 换成null;面板与 CLI 的所有 JSON 出口都过一遍,
并用allow_nan=False卡死(漏了就直接报错,不静默吐非法 JSON)
新增 tests/test_json_safety.py 锁定。
面板小修
- 撤销条件单后给出明确回执(之前状态栏还留着上一条错误信息)
- 网格结果附注"另有 N 组几乎不触发已剔除"
- 回测的"注意"只提示配置/数据级问题(没有 symbol 的那类),逐笔跳过原因不刷屏
验证
129 个离线单测全绿。Pi 真机验证:面板回测、参数网格、挂条件单、撤销条件单、
非法代码 fail-closed,浏览器端全部走通,控制台无报错。
兼容性
无 schema 变更、无配置项变更。
v0.5.1 回测账目修正 + 分钟线换源
v0.5.1 — 回测账目修正 + 分钟线换源
这一版全是账目与数据源的修正——上一版的功能都在,但两个把数字算错的 bug 必须先修,
否则任何基于回测结论的决策都建立在错误的账上。
修:卖出回款没进现金(严重)
回测器里卖出成交后,成交额没有回到现金。后果是权益曲线凭空缩水:
一次 250 日的真机回测里,策略被算成 -32.26%,而实际只有 -0.86%——
那 3 万块的"亏损"其实是卖出后没入账的钱。
修正后同一段数据:策略 -0.86% / 回撤 1.09% / 基准买入持有 +1.24% / 超额 -2.1%。
结论从"策略巨亏"变成"策略略跑输一直拿着不动"——完全不同的决策含义。
新增 tests/test_backtest_cash.py 用资金守恒这个不变量锁死:
初始现金 - 买入总额(含费) + 卖出净额 == 期末现金,日线路径与日内路径都锁。
修:缺当日日线的票按 0 计价(严重)
多标的回测里,若某只票当天没有日线(常见:不同票的最新日线差一天),
旧实现把它的持仓当 0 元计入市值,权益曲线和基准同时塌一块。
真机实测:3 只票的 30 日基准被算成 -65%,实际约 -9%。
修正:市值沿用该票最近一次有效收盘计价(不当 0);
但当天没数据就不成交(不拿旧价假装成交)。新增 tests/test_backtest_pricing.py 锁定。
换:分钟线数据源
东财 1 分钟接口在部分网络环境(如 Pi)直接断连,实测拉取 0 行——
"看起来成功、其实没数据"是最坏的一种失败。改用两个实测可用的源:
- 新浪 N 分钟 K 线(默认 5 分钟):多日历史(实测 22 个交易日)、真实 OHLC;
成交额用「典型价 × 成交量」估算 - 腾讯分时:今日 1 分钟,带真实累计成交额(
--period 1时用)
刻意不混用不同粒度:同一张表里混着 1 分钟和 5 分钟会让均线类规则前后不可比。
min_bars 数的是库里存的 K 线根数,5 分钟线下 48 根约等于一整天。
修:参数网格把"什么都不做"排第一
熊市里"0 笔成交、收益 0%"的变体会以"超额 +36%"排到网格第一——那不是策略,是没有策略。
现在成交不足 1 笔的变体不参与排名,单独统计在 dropped_no_trade 并在 note 里说明。
修:条件单入库即拒小额
买入条件单在入库前就按手续费地板(min_ticket_cny,默认 5000 元)校验,
不再给"挂得上、永远触发不了"的单子留空间。
真机验证(Pi,真实行情数据)
- 日线回测 250 日:策略 -0.86% vs 基准 +1.24%(超额 -2.1%),资金逐笔对得上
- 参数网格
ma_cross:31 组变体,0 笔的 1 组已剔除;最佳002192 short=10/long=30收益 +5.07%(超额 +3.83%) - 参数网格
rsi_zone:002192 n=14 lower=30 upper=75超额 +27.08%(7 笔) - 日内网格
vwap_dev/minute_ma:用真实 5 分钟线(16 个交易日)跑通,
600036 pct=0.5超额 +11.15%(6 笔) - 条件单全生命周期:挂 → 查 → 试运行检查 → 撤销;非 A 股代码 / 非法条件类型 / 金额越界一律 fail-closed
诚实边界
网格里的超额都是样本内结果,样本只有几笔到几十笔——这是"值得继续验证的线索",
不是结论。样本外复验之前不要写进生产规则集。分钟线只覆盖最近一段,
日内规则的样本天然极小。
验证
125 个离线单测全绿(新增 test_backtest_cash / test_backtest_pricing / test_minute_source)。
兼容性
无 schema 变更、无配置项变更,直接从 v0.5.0 升级即可。
v0.5.0 条件单 + 参数网格 + 日内规则 + 面板引擎区
v0.5.0 — 条件单 + 参数网格 + 日内规则 + 面板引擎区
从"能跑回测"推进到"盘中能自动择时执行"。核心变化:AI 只出条件与策略,盘中执行交给规则引擎——
两者共用同一个 RiskManager + PaperBroker,口径一致。
AI 条件单(P4)
条件单现在不成交,只挂一个可验证的触发条件;盘中引擎每分钟核对实时价,触发才过风控下单。
engine/conditional.py:conditional_orders表 + fail-closed 校验(非 A 股代码 / 非法条件类型 /
金额越界 / 有效期 ≤0 或 >30 天一律不入库)+armed → filled / cancelled / expired / rejected状态机engine/ai_conditions.py:AI 只输出「触发价或涨跌幅 + 有效期 + 理由」,池外代码与越界金额由程序丢弃engine/runner.py:ConditionRunner与规则引擎在同一守护循环里各跑各的,单模块异常不拖垮另一模块- CLI:
cond list / add / cancel / tick / design;面板:「条件单」区可人工挂条件、AI 设计、撤销
不触发就是 0 成本,每条都留痕,可复盘 AI 准不准。
参数网格(P3.5)
engine optimize 批量回测参数变体,按超额收益(对比等权买入持有)排序。
网格:ma_cross / rsi_zone / trailing_stop(日线)与 vwap_dev / minute_ma(日内)。
输出是"值得继续验证的线索",不是结论——样本外复验前不要写进生产规则集。
日内规则(分钟线)
规则类型从 6 种加到 9 种,新增三种依赖当日分钟线的:
| 类型 | 触发 |
|---|---|
vwap_dev |
现价偏离当日均价 ±N%(低于买、高于卖) |
intraday_pullback |
自日内高点回落 N%(可选先涨 M% 以上) |
minute_ma_cross |
分钟均线金叉 / 死叉 |
回测器新增逐分钟重放:窗口只到当前分钟(ts > now 的分钟一律丢弃)、成交价取该分钟收盘、
当日买入的份额当天卖不掉(T+1)。没有分钟数据时回测会在 skipped 里写明"未触发",不假装跑过。
面板
- 「规则引擎」区:规则集一览(开关/间隔/单笔下限/校验结果)、一键回测(策略 vs 基准 + 超额 + ASCII 净值曲线)、
参数网格排序表、最近触发记录 - 「条件单」区:挂条件(代码/方向/触发类型/触发值/金额/有效期)、AI 设计、撤销、状态列表
- CLI 与面板的回测/网格走同一份实现,避免两处口径漂移
修复
size_for_value去掉靠短路求值蒙对的写法- 条件单的 A 股代码校验改用
integration.universe.is_a_share,与选股口径统一
诚实边界
分钟线只覆盖最近约 5 个交易日,日内规则与日内网格的样本天然极小,面板与 CLI 都会显式提示。
手写这批代码时最该记住的一句:回测漂亮不等于能赚——先有度量,再谈优化。
验证
111 个离线单测全绿(新增 test_engine_cond / test_engine_intraday / test_dashboard_engine)。
兼容性
config.yaml新增注释化的engine.rules示例(原为空列表,无行为变化)data.db新增minute_bars/quote_log表,paper_account.db新增conditional_orders/
engine_state/engine_triggers表——均为新增,与 Stock Dashboard 侧无冲突
v0.4.0 全市场自主选股
Paper Trading v0.4.0
全市场自主选股 + 面板手动触发 + 模型拉取开放化。生产已实跑验证(2026-10-16 收盘首日)。
核心:全市场选股漏斗(程序 + AI 共同选)
全市场 5527 只(母项目夜间量化筛选)
↓ 程序选:--pool-from auto(缺省)= screening Top20 → 观察池 → 手填 config 池(逐级降级)
候选 20 只
↓ AI 提名(pass1):写名称或 6 位代码,候选之外随便点,上限 picks_max(默认 5)
↓ 程序校验:母库全市场代码表解析 + A 股前缀 + 过滤指数/ETF + 去重 + 补齐 K 线
本轮决策范围 = 候选 ∪ 提名 → 风控钳制 → 成交
- 手填池降级为兜底:
auto与all都不再叠加手填池(all= screening + watchlist)。 - 提名不放宽风控:只是把股票放进候选范围,100 股倍数/单笔上限/日笔数/T+1/熔断一道不少。
- fail-closed:LLM 坏 JSON、点名不存在的票、指数 ETF → 一律丢弃,退回"只用候选池"。
- 不白花调用:提名放在新鲜守卫之后,没新行情就不调 LLM。
- 可审计:
ai:decide流水记n_picks与提名清单。 - 可关闭:
--no-picks/agent.picks_enabled: false。
面板手动触发
定时队列每行末尾加「执行」按钮:立即跑该行动作(不占队列名额;交易无视今日已决策闸,风控不变;与 cron 共用运行锁防并发)。
模型拉取开放化
GET /api/llm/models去掉口令门禁(只回模型 id,Key 不出本机);失败直接给可执行原因(未配置 / Key 被拒 401 / 超时 / 地址非法)。- Key 保存一次长期有效:重启不丢、换模型不用重填(文案与门禁单测锁定)。
- 坏 JSON 请求体返回 400 + 真实原因(以前静默变空 body → 误导性报错)。
修问答幻觉
上下文里 positions(真持仓)与 recent_closes(池内行情)平级,模型会把池内股票当持仓。现:system 顶部置顶「当前真实持仓」权威段 + 4 条防幻觉硬规则 + context legend 逐 key 说明 + 用户侧附真实持仓切片。决策 prompt 也加了"不得声称买入未持有股票"。
定时机制加固
- 队列消费改 slot 标记制(修 CI 红灯的双时钟错位:mock 时间 vs 落库时间曾让队列重放)。
- 定时闸按日期查全天流水,不再依赖"最近 N 条"窗口(面板高频问答曾把决策挤出窗口致失忆重跑)。
- 单测 hermetic:
tests/conftest.py每例切临时 cwd(本机strategy.local.yaml泄漏进断言的教训)。
工程质量
- 62 → 70 单测(新增 picks/幻觉/模型拉取/JSON 校验/隔离),本地 + Pi 双端全绿。
- 生产实跑(2026-10-16):16:45 分析提名 5 只 → 17:00 计划提名 2 只 → 17:15 成交 2 笔(盐湖股份、融捷股份各 100 股,T+1 冻结、费用正确)→ 17:30/17:45 队列跑完空转不重下单。
升级指引(下游 Stock Dashboard subtree)
git subtree pull --prefix paper_trading git@github-paper:oldfunk/paper-trading.git v0.4.0 --squash注意:
- 面板新增「执行」按钮与定时队列行编辑,vendor 后硬刷页面;
- cron 若用
agent tick,建议去掉--pool-from screening(缺省已是 auto,会自动择优并叠加 AI 提名); integration/universe.py依赖母库stock_snapshot表(全市场代码表)做提名解析,独立运行(无母库)时退化为本地名称表匹配,不报错。
v0.3.0 定时动作队列
Paper Trading v0.3.0
AI 定时队列动作化:时刻 → 动作。v0.2.x 未被下游拉取过,直接拉本版。
新增
- 定时队列:每条 = 时刻 + 动作(
sync同步 /analyze只分析不下单 /plan做计划 /trade实盘交易),如09:00=sync → 13:00=plan → 16:45=trade;每条每天最多跑一次,保序消费。 agent tick:cron 调这个(推荐*/15 16-18),按队列分发;直接agent run= 跑一次 trade。- 面板行编辑器:时刻输入 + 动作下拉 + 增删行 + 保存,今日进度可见(已跑 x/y)。
- CLI:
agent schedule set --entry 09:00=sync --entry 16:45=trade(可重复)。
修正
- 定时消费凭流水 slot 标记(修 CI 红灯的双时钟 bug);定时闸按日期查全天流水,面板高频问答再也不会把决策挤出窗口致同日重跑。
- 默认队列
16:45=trade(保守时间);跳过自带行情日期。
工程质量
- 54 单测(定时 10 个),全量离线可跑,CI 绿;生產 Pi 真机验证(分发/消费/面板/截图)。