Skip to content

Releases

v0.6.1:持仓同步并集与计划执行价格 fail-closed

Choose a tag to compare

@oldfunk oldfunk released this 22 Oct 02:10

修复

  • 持仓退出筛选池后,日线同步会漏掉持仓,导致收盘 NAV、日亏/回撤检查和 AI 上下文使用旧价。现在 sync_data 的同步集合 = 本轮标的 ∪ 全部持仓。
  • 待执行计划包含本轮运行池之外的新增标的时,执行上下文缺价会把 limit_price 设为 0,绕过单笔金额上限,并得到误导性的“无价格”拒单。现在执行前补齐计划标的的日线和最新价;px<=0 明确拒单。
  • 计划保存时用定稿收盘价回填买/卖动作基准价(price_source=last_close);执行缺价时只接受该可信来源兜底。

测试与部署

  • 本地 175 项全绿(新增 5 项回归)。
  • Pi git pull 到 27e6e82,pytest 175 项全绿;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_log nav:revision,不回写历史。

影响

  • 纯纸盘;engine.enabled 保持 false。
  • 无 schema/config 变更。

v0.6.0 — M1 正确性与回测-实盘口径统一

Choose a tag to compare

@oldfunk oldfunk released this 21 Oct 07:49

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

Choose a tag to compare

@oldfunk oldfunk released this 20 Oct 07:48

修正:只改 Key 或接口地址时,不再把已保存的模型清空(此前会变成“未指定模型”,看起来像 API 失效);模型不在厂商列表时只如实提示,不再建议更换。

新增回归测试,150 项全部通过。

v0.5.4 本地模型免令牌 + AI 设计条件单修复

Choose a tag to compare

@oldfunk oldfunk released this 20 Oct 04:58

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 样本外检验 + 总开关加固

Choose a tag to compare

@oldfunk oldfunk released this 19 Oct 12:37

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 严格化

Choose a tag to compare

@oldfunk oldfunk released this 19 Oct 12:20

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 回测账目修正 + 分钟线换源

Choose a tag to compare

@oldfunk oldfunk released this 19 Oct 08:55

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 条件单 + 参数网格 + 日内规则 + 面板引擎区

Choose a tag to compare

@oldfunk oldfunk released this 19 Oct 08:00

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 全市场自主选股

Choose a tag to compare

@oldfunk oldfunk released this 16 Oct 11:16

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

注意:

  1. 面板新增「执行」按钮与定时队列行编辑,vendor 后硬刷页面;
  2. cron 若用 agent tick,建议去掉 --pool-from screening(缺省已是 auto,会自动择优并叠加 AI 提名);
  3. integration/universe.py 依赖母库 stock_snapshot 表(全市场代码表)做提名解析,独立运行(无母库)时退化为本地名称表匹配,不报错。

v0.3.0 定时动作队列

Choose a tag to compare

@oldfunk oldfunk released this 30 Sep 09:15

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 真机验证(分发/消费/面板/截图)。