别装了,仓位就是预测:一个中证1000周频 LLM 预测实验
每周调仓,给 LLM 喂 48 周带标签历史(周涨跌、ATR、5日RSI、z-score 及各自下周结果),让它研判趋势与反转并决定买入/加仓/减仓。
最后更新: 2026-08-31
Table of Content
本文是对"LLM 仓位风控"思路的一次自我纠偏。上一篇文章强调"模型不预测方向、只是风控顾问",我认为这个表述不诚实:决定下周暴露多少仓位,本质上就是对下周涨跌的方向性押注。 所以这篇不绕弯——直接告诉模型"这是预测",把下周真实收益当作标签来校验它,最后用命中率和风险调整收益说话。
配套代码完整嵌入本文,用 jupytext 可转成 Notebook:
jupytext --to ipynb weekly-llm-csi1000.md。
我们在测什么¶
目标:让 LLM 研判中证1000(000852.SH)当前处于上涨趋势、下跌趋势,还是即将/正在/已经发生趋势反转,并据此决定下周动作——买入、加大仓位、减少仓位或空仓观望(只做多,仓位 [0,1])。与最初版本相比,这次对齐了"状态统计表"式会话的两个关键设计:
- 每次会话带上过去 48 周的带标签历史。 每个历史周都含:周涨跌幅、周日均 ATR 比率、每个交易日当天及前 4 天的 5 日 RSI、相对 5 日均线的 z-score,以及该周对应的下一周实际涨跌幅(next_pnl)与年化夏普(next_sharpe)。模型可以像读训练集一样对照"当时长什么样→下一周发生了什么"。
- 趋势研判而非单点预测。 任务不是"猜下周涨跌",而是识别趋势状态与拐点(上涨停滞、下跌反转等一阶导信息),再落到仓位动作上。
- 周频、调仓日可配、数据与决策双层缓存。 调仓日(周一到周五)是参数;Tushare 数据落盘缓存避免重复拉取;LLM 决策按(提示词版本+日期)缓存,重跑不花第二次钱。
数据与缓存¶
先用 Tushare 下载指数日线并缓存到磁盘——这样每次调试都不会重复调用 Tushare,只有首次才拉取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | |
发给 LLM 的输入长什么样(真实样例)¶
每个调仓周调用一次,请求由一条 system 消息(只输出 JSON。)和一条 user 消息组成。user 消息 = 任务 + 字段说明书 + 研判要求 + 当前周特征 + 过去 48 周带标签历史表 + 输出契约。下面是 2026-08-17(周一)这一周实际发出的消息骨架(历史表 48 条太长,只展示首尾各 1 条,数值保留 2~4 位小数):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
与旧版的本质差别:历史样本每条都带着"下一周实际发生了什么"(next_pnl / next_sharpe),模型可以像读训练集一样对照"当时长这样→后来如何",而不是只凭一个孤立快照瞎猜。
同一周 glm-5.3 的真实返回(与上面输入配对):
1 2 | |
读几点:
- 模型确实在引用历史样本做类比(点名了 2026-04-13、06-15 等相似周及其下周结果),这正是带标签历史表想要的效果;它还同时引用了反例(超买后停滞的样本),所以选择折中的 0.6 仓而非满仓。
- 这一周实际
next_week_return = -5.99%——它判断错了(看多但市场大跌)。单个样例对错不说明问题,看整窗指标。 - 程序对返回做硬约束:
position裁剪到 [0,1](只做多);解析失败/超时重试 3 次后仍失败则该周降级为空仓(position=0)并记录,不中断回测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
1 2 3 4 5 6 7 8 9 10 11 | |
结果:48 周带标签历史改变了什么¶
全部用最近 100 周样本外验证(每个调仓日自己取各自的 100 个调仓周),统一取"该调仓日同期买入持有"作基准。为诚实起见,按版本分组记录:
- v1(趋势研判,position 独立):周一、周二。
- v2(趋势研判 + action 与 position 强自洽,含 prev_position 强制对齐):周三起。
v1 版本(周一、周二调仓)¶
| 调仓日 | 策略 | 总收益 | 年化波动 | 夏普 | 最大回撤 | 仓位-收益相关 |
|---|---|---|---|---|---|---|
| 周一 | 买入持有 | +47.6% | 34.7% | 0.75 | -21.8% | — |
| 周一 | 动量规则(只做多) | -20.2% | 18.3% | -0.55 | -20.6% | -0.20 |
| 周一 | LLM 趋势研判 | +34.4% | 17.1% | 0.98 | -9.1% | +0.13 |
| 周二 | 买入持有 | +50.8% | 34.8% | 0.78 | -20.8% | — |
| 周二 | 动量规则(只做多) | +26.5% | 29.3% | 0.55 | -13.3% | +0.01 |
| 周二 | LLM 趋势研判 | +37.2% | 18.6% | 0.97 | -6.5% | +0.11 |
v2 版本(周三调仓;周四、周五结果见本文末尾更新)¶
| 调仓日 | 策略 | 总收益 | 年化波动 | 夏普 | 最大回撤 | 仓位-收益相关 |
|---|---|---|---|---|---|---|
| 周三 | 买入持有 | +54.4% | 27.9% | 0.95 | -21.2% | — |
| 周三 | 动量规则(只做多) | +63.9% | 21.9% | 1.28 | -8.6% | +0.13 |
| 周三 | LLM 趋势研判 | +16.1% | 9.8% | 0.84 | -9.1% | -0.06 |
几点解读(先说结论,再泼冷水):
- v1 的周一、周二一致给出了令人兴奋的信号:夏普 0.97~0.98 vs 买入持有 0.750.78,波动砍半(1719% vs 35%),最大回撤个位数(-69%),**仓位-收益相关转正(+0.11+0.13)**——仓位确实"加在关键时候"。
- v2 的周三出现了明显的逆转:LLM 总收益只有 +16%(垫底),夏普 0.84 弱于买入持有的 0.95、更弱于动量规则的 1.28,corr 转负(-0.06)。两个维度都解释了余地:
- 加"自洽约束"改变了仓位行为:v2 把仓位普遍压得更中性(avg 0.41),而"买入/加仓必须高于上周"的硬约束在连续下跌段容易把仓位卡在低位,反应变钝;
- 周三这个调仓日本身就是动量规则的"主场"(动量规则周三 +63.9%、夏普 1.28、corr +0.13),是一个很难打败的强基准——这时和 LLM 比,要求和日历上最顺手的规则打,门槛极高。
- 因此不能单看某一天下结论:周一周二"LLM 有信息量"、周三"LLM 不如简单规则",说明结果对调仓日敏感。这本身就是重要发现:周频策略的日历选择可能比模型微调更影响结果。
必须交代的局限:
- 单一窗口、单一资产。 100 周恰好覆盖一轮完整的暴跌-修复周期,行情结构对趋势研判天然友好;换长牛或长期震荡,结论未必成立。
- 版本不一致。 周一/周二(v1)与周三(v2)不完全是同一套提示词,对比时要小心;理想的复核是让 v2 统一重跑全部五个调仓日。
- "明确表态"的周数不多。 多数周给 0.4~0.6 中性仓位,明确表态样本量小。
- 模型推理偶发截断。 个别周降级为空仓(如周三 2/100),对总体影响小但会轻微扭曲平均仓位。
- 存在研究自由度。 提示词经过多轮迭代,严格做法是换窗口、换资产、加基准复验。周五、周四的结果将追加在文末"调仓日扩展实验"。
怎么往下走¶
- 统一版本复跑全部五个调仓日:让 v2(自洽版)重跑周一~周五,排除"版本不一致"的干扰,再做严谨的调仓日对比。
- 换窗口/换资产复验:全 499 周、沪深300/标普等,检验 corr 与夏普优势是否稳定。
- 加"永远半仓"基准:排除"适度仓位本身就是优势"的解释(动量规则已部分回答了这一点,但还不够)。
- 让模型输出下周收益点估计,直接计算 IC(信息系数)序列并做显著性检验。
- 成本敏感性(5bp → 10/20bp),确认低成本假设下仍存活。
v3 版本实验(周四调仓)——否决,代码已回退 v2¶
v3 把 v2 的前置改动进一步明确:提示词显式传入"当前仓位 current_position"作为决策起点,并要求输出"新目标仓位 position + 调仓说明 adjustment_reason"(说明从当前仓调整到新仓的理由)。代码保留 v2 的 action/position 自洽强制(reconcile_position)。
周四调仓、最近 100 周的结果(两轮:首轮 0 失败;禁用可能掺混的 glm-5.3-flash 后重跑,2 次失败降级空仓):
| 策略 | 总收益 | 年化波动 | 夏普 | 最大回撤 | 仓位-收益相关 | 平均仓位 |
|---|---|---|---|---|---|---|
| 买入持有 | +56.8% | 25.9% | 1.03 | -22.0% | — | 1.00 |
| 动量规则(只做多) | +52.1% | 19.4% | 1.22 | -9.9% | +0.08 | 0.61 |
| LLM 趋势研判 v3(首轮) | +20.4% | 13.0% | 0.81 | -13.5% | -0.08 | 0.50 |
| LLM 趋势研判 v3(禁用掺混后重跑) | +17.3% | 12.2% | 0.74 | -10.7% | -0.12 | 0.50 |
与 v2 周四(同调仓日同窗口)直接对比:
| 指标 | v2 周四 | v3 首轮 | v3 重跑 |
|---|---|---|---|
| 总收益 | +27.7% | +20.4% | +17.3% |
| 夏普 | 1.12 | 0.81 | 0.74 |
| 最大回撤 | -7.9% | -13.5% | -10.7% |
| corr | +0.03 | -0.08 | -0.12 |
| 平均仓位 | 0.47 | 0.50 | 0.50 |
结论:这条路被否决,v3 提示词代码已回退到 v2。 两点证据:
- 锚定效应稳定复现:显式传入"当前仓位"后,模型从"基于方向信念表态"退化为"我现在持多少、微调多少"(平均仓位 0.50、std 0.20),本该大幅撤退时只小幅减仓,最大回撤反而从 v2 的 -7.9% 恶化;corr 一路转负(-0.08 → -0.12)。给模型更多仓位上下文,反而稀释了它的方向信息。
- 与模型掺混无关:禁用 glm-5.3-flash 后用纯 deepseek-v4-flash 重跑,结果依旧更差(夏普 0.74、corr -0.12)——问题出在 v3 提示词设计本身,不是代理侧模型路由问题。
这一结论对后续有直接指导意义:不要把"当前持仓"作为强输入喂给模型;模型需要的独立的方向判断,而不是在既有仓位上的微调。
调仓日扩展实验(周四、周五)¶
周四、周五以 v2(self-consistent) 版本运行:周四 0 次失败,周五 3 次失败(降级空仓)。失败数已如实记录。
| 策略 | 周四 总收益 | 周四 夏普 | 周四 corr | 周五 总收益 | 周五 夏普 | 周五 corr |
|---|---|---|---|---|---|---|
| 买入持有 | +56.8% | 1.03 | — | +50.0% | 0.92 | — |
| 动量规则 | +52.1% | 1.22 | +0.08 | +56.9% | 1.34 | +0.12 |
| LLM 趋势研判 | +27.7% | 1.12 | +0.03 | +14.7% | 0.66 | -0.06 |
五调仓日全景(截至本文定稿)¶
把所有调仓日并列:
| 调仓日 | 版本 | LLM 夏普 | 买入持有夏普 | 动量夏普 | LLM corr | LLM 总收益 | 相对结论 |
|---|---|---|---|---|---|---|---|
| 周一 | v1 | 0.98 | 0.75 | -0.55 | +0.13 | +34.4% | LLM 大幅占优 |
| 周二 | v1 | 0.97 | 0.78 | 0.55 | +0.11 | +37.2% | LLM 大幅占优 |
| 周三 | v2 | 0.84 | 0.95 | 1.28 | -0.06 | +16.1% | LLM 垫底、动量主场 |
| 周四 | v2 | 1.12 | 1.03 | 1.22 | +0.03 | +27.7% | LLM 优于买入持有,逊于动量 |
| 周五 | v2 | 0.66 | 0.92 | 1.34 | -0.06 | +14.7% | LLM 垫底、动量主场 |
跨调仓日读出的三个稳定事实:
- LLM 的波动与回撤在五天全部处于低位(年化波动 10~19%、回撤 6~12%,均显著低于买入持有的 26~35% / 20%+)。"模型把仓位节奏放稳"这一点是稳健的。
- corr 符号随调仓日摆动(+0.13 / +0.11 / -0.06 / +0.03 / -0.06)——LLM 的方向信息量不稳定。周一周二两个最强窗口恰好都在 v1;v2 之后的三天全部平淡或负。
- 动量规则在周三、周五强得反常(夏普 1.28 / 1.34、corr +0.12~0.13)——这些调仓日对"上周涨跌→下周续涨"的日历结构特别友好。当动量已是"主场"时,LLM 很难赢,这本身是重要的调仓日效应。
结论分级:LLM 的优势在 jg.周一、周二(v1)真实存在但不跨调仓日稳定;v2 自洽约束后更保守、信息量反而下降。要确认"LLM 会做趋势研判",必须用统一版本(建议 v1)重跑全部五天做对称对比——这是下一步该做的事。
结语¶
这一版把两件事做对了:会话里带上 48 周带标签历史(让模型对照"当时长这样→下一周如何"),以及把任务定为趋势与拐点研判而非单点涨跌猜测。五个调仓日跑下来,唯一跨调仓日稳健的事实是:LLM 始终把波动与回撤压得很低(10~19% / 6~12%),说明它在仓位节奏上确实有个稳定的"求稳"倾向。
但LLM 的方向信息量并不稳定:corr 从周一的 +0.13 摆到周五的 -0.06,夏普在周一/周二/周四高于买入持有,周三/周五却明显落后——而这两天恰好是动量规则的强主场(夏普 1.28 / 1.34)。同时,引入"action 与 position 强自洽"(v2)后模型明显更保守,反而削弱了信息量。周频策略的调仓日选择,可能比 LLM 提示词的微调更能决定结果。
干净的下一步:用统一提示词版本重跑五个调仓日,若 corr 与夏普优势不再随日历大幅摆动,才算拿到了"LLM 会做趋势研判"的可复现证据;否则它更像一个"把仓位放稳"的工具,而不是"能预测方向"的模型。这个结论诚实、可复现、可审计——每个决策都有缓存与理由存档。