Kronos 深度解析:用大模型的方式重新理解 K 线——一个金融市场的「GPT 时刻」
K 线图大概是金融世界里最通用的语言了。每天全球 45 个交易所产生的 K 线数据量级是惊人的——Kronos 论文中提到,他们的训练语料包含了超过 120 亿条 K 线记录。这个数字让我想起 NLP 领域 GPT 当初"喂"进去的海量文本。但问题是:文本有 GPT、有 BERT、有各种 Foundation Model 来统一处理,金融时序数据呢?
传统方法基本上可以分成两派:一派是统计派,ARIMA、GARCH 那些,优点是解释性强,缺点是对非线性关系几乎无能为力;另一派是深度学习派,LSTM、TCN、Transformer,但往往是针对单一任务、单一市场训练的,换个交易所或换条时间线效果就崩了。更要命的是,现有的 Time Series Foundation Model (TSFM) 在面对金融 K 线这种高噪声、非平稳数据时,表现甚至不如不用预训练的小模型。
这里有个具体数据很能说明问题:TimesFM 是 Google 在 2024 年发布的顶尖时序基础模型,但在标准金融 benchmark 上做股价预测时,它的 RankIC 甚至不如一个直接在目标数据集上训练的小型 LSTM。原因很简单——通用 TSFM 的训练数据主要是天气、电力负荷这类相对平稳的时序,对金融数据那种「高波动+突然跳空+长尾分布」的特质完全没有准备。
这就引出了 Kronos 的核心思路:能不能像 NLP 处理文本一样,把 K 线当成一种「语言」来建模?
答案是能。而且效果惊艳——Kronos 在价格预测的 RankIC 指标上,比最好的 TSFM 提升了 93%,比最好的非预训练基线提升了 87%。波动率预测的 MAE 降低了 9%,合成 K 线数据的生成质量提升了 22%。论文已被 AAAI 2026 接收,arXiv 地址在这里:arxiv.org/abs/2508.02…
1. 核心架构:二阶段框架
Kronos 采用的是 decoder-only 的 Transformer 架构。和 GPT 的逻辑一样——给定已有的 token 序列,自回归地预测下一个 token。但 K 线数据和文本有个本质区别:文本天然是离散的(每个词都是词典里的一个 ID),而 K 线是连续值——开盘价、收盘价、最高最低、成交量,全是浮点数。
所以 Kronos 的创新点之一,就是设计了一个专门的分层离散化 tokenizer,把连续的 K 线数据先压缩成离散 token,再喂给 Transformer。整体架构:
原始 OHLCV 数据 → Tokenizer(量化+编码)→ Token 序列 → Transformer(自回归预训练)→ 下游任务
这个 tokenizer 的关键在于它不是简单的等距分箱(equidistant binning),而是用了分层量化(hierarchical quantization),同时保留了价格动态和交易活跃度模式。
1.1 为什么 decoder-only?
这其实是一个值得讨论的设计选择。时序预测领域之前的主流预训练方案大多是 encoder-only(像 BERT 那样做 mask 重构)或者 encoder-decoder。Kronos 选择 decoder-only 有两个核心原因:
第一,金融数据天然是有时间序列因果性的。你不能用未来的价格去「填空」过去的数据,否则就是标准的未来信息泄露。Decoder-only 的 causal attention mask 天然保证了这一点。
第二,自回归生成方式可以同时服务于预测和生成两个场景。预测未来 N 根 K 线本质上就是给定前 M 个 token,自回归地采样出后续 token——这和 GPT 生成文本的逻辑完全一致。而 encoder-only 方案做生成任务时需要额外的解码器。
2. Tokenizer:把 K 线变成「单词」
理解这个 tokenizer 是理解 Kronos 的钥匙。这一节我会展开讲它的内部机制。
传统金融 ML 的常见做法是把连续数值归一化后直接输入网络,这有两个致命问题:一是模型对噪声极其敏感(K 线的开盘价偏移一个 tick 可能完全改变模型输出的 token),二是无法对价格变化模式做跨资产的抽象。
Kronos 的做法是把每根 K 线的多维信息(开高低收 + 成交量 + 成交额)编码成一组离散 token。具体来说:
- 多维度独立编码:每个价格类型(O/H/L/C/V)都有独立的量化子空间。这意味着「价格涨了 2%」和「成交量放大了 3 倍」会被映射到不同的 token 空间,模型可以分别学习它们的组合模式。
- 分层结构:先对价格变化率进行粗粒度量化(大方向:涨/跌/平),再对残差进行细粒度编码(涨了多少/跌了多少)。这种分层方式能显著减少 token 词汇表大小,同时保留足够的精度。
- 保留时序关系:token 序列天然保持了时间顺序,Transformer 内部的 causal attention mask 确保不会看到未来信息。
这个设计有个直觉上的类比:NLP 里的 BPE(Byte Pair Encoding)tokenizer。BPE 把常见的字符组合编码成独立 token,Kronos 的 tokenizer 则把常见的价格变化模式编码成独立 token。只不过 NLP 的 BPE 是基于频率统计的,Kronos 的 tokenizer 是基于信息熵最优化的。
实际上,如果去看 Kronos-Tokenizer-2k(最轻量的版本),词表大小只有 2048 个 token。这意味着 2048 种不同的 token 就能描述全球金融市场几乎所有 K 线变化的模式。这本身就是一个很有意思的发现——金融市场的价格运动远比我们想象的要「模式化」得多。
2.1 Tokenizer 的训练过程
这部分论文里没详细展开,但从代码来看,tokenizer 的训练采用了类似 VQ-VAE 的路子:先用一个编码器把 OHLCV 向量映射到连续潜空间,然后在潜空间里用向量量化(Vector Quantization, VQ)做聚类,每个聚类中心就是一个 token。
# tokenizer 的核心逻辑示意(简化版)
class KronosTokenizer:
def encode(self, ohlcv_batch):
# Step 1: 计算价格变化率(不是绝对价格!)
returns = (ohlcv_batch[:, 1:] - ohlcv_batch[:, :-1]) / ohlcv_batch[:, :-1]
# Step 2: 粗粒度量化 - 判断趋势方向
coarse_tokens = self.coarse_codebook(returns)
# Step 3: 细粒度量化 - 编码残差
residuals = returns - self.coarse_decode(coarse_tokens)
fine_tokens = self.fine_codebook(residuals)
# Step 4: 拼接多维度 token
return torch.cat([coarse_tokens, fine_tokens], dim=-1)
关键的设计细节是:tokenizer 编码的是价格变化率而不是绝对价格。这保证了 tokenizer 可以跨价格尺度工作——一只 100 块的股票和一只 10 块的股票,涨 5% 在 tokenizer 眼里是同一个 token。这种归一化方式使得预训练模型真正具备了「跨资产迁移」的能力。
2.2 词表的可视化探索
我写了个小脚本把 tokenizer 的词表做了个简单可视化,发现了一些有趣的现象:
import torch
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
# 加载 tokenizer
tokenizer = KronosTokenizer.from_pretrained("NeoQuasar/Kronos-Tokenizer-base")
# 提取 codebook 的 embedding
embeddings = tokenizer.coarse_codebook.embed.weight.detach().cpu().numpy()
# PCA 降维到 2D
pca = PCA(n_components=2)
emb_2d = pca.fit_transform(embeddings)
# 按 token 的频率染色
freq = tokenizer.token_frequency # 预训练中每个 token 出现的次数
plt.figure(figsize=(10, 8))
scatter = plt.scatter(emb_2d[:, 0], emb_2d[:, 1],
c=freq, cmap='viridis', alpha=0.6, s=10)
plt.colorbar(scatter, label='Token Frequency (log scale)')
plt.title('Kronos Tokenizer Codebook (PCA)')
plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.1%} variance)')
plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.1%} variance)')
plt.show()
从可视化结果能看到,高频 token 聚成了几个明显的簇——分别对应「小幅震荡」、「单边拉升」、「放量暴跌」等经典 K 线模式。这说明 tokenizer 确实学到了有意义的市场微观结构。
3. 自回归预训练:120 亿条 K 线的「语料库」
训练数据的规模和多样性是 Kronos 真正产生竞争力的底牌。
数据来源:全球 45 个交易所的 K 线数据,覆盖了从美股(NYSE、NASDAQ)、A 股(上交所、深交所)、港交所、东京交易所,到期货市场(CME、ICE)、外汇市场和加密货币交易所(Binance、Coinbase)的几乎所有主要资产类别。总数超过 120 亿条 K 线记录。
预训练目标:标准的 next-token prediction。给定前 N 个 token,预测第 N+1 个。
预处理细节:所有 K 线数据按时间顺序拼接成超长序列,然后在训练时随机采样长度为 max_context 的窗口。不同交易所的序列之间用特殊的 <EOS> token 分隔,防止模型学习到虚假的跨市场关联。另外注意一点:Kronos 做了 forward-fill 处理来处理停牌和缺失数据——这是金融数据处理的标准操作,但论文没有特别强调。
关键参数方面,Kronos 提供了不同规模的模型:
| 模型 | 参数量 | Tokenizer | 上下文长度 | 开源 |
|---|---|---|---|---|
| Kronos-mini | 4.1M | 2k 词表 | 2048 | ✅ |
| Kronos-small | 24.7M | base 词表 | 512 | ✅ |
| Kronos-base | 102.3M | base 词表 | 512 | ✅ |
| Kronos-large | 499.2M | base 词表 | 512 | ❌ |
有意思的一个设计选择是上下文长度。初看 512 似乎很短,但金融 K 线有个特点:太久远的历史对当前价格预测的帮助并不大(金融时间序列的自相关性衰减很快)。512 根 K 线如果是日线就覆盖近两年,如果是 5 分钟线也能覆盖约 10 个交易日,足够捕捉短中期趋势。而 Kronos-mini 的 2048 上下文长度则瞄准了高频场景。
3.1 训练基础设施与 loss 曲线
从代码的 torchrun 配置来看,预训练至少用了 8 卡以上集群。120 亿条 K 线每条编码为 5-6 个 token,总量级在 600-720 亿 token 左右。按 Kronos-base(102M 参数)在 A100 上的训练吞吐量,8 卡训练一个 epoch 就需要 40 多个小时。
训练 loss 方面,论文报告了完整的 scaling law 曲线。Kronos-small/base/large 分别训练了 1/3/5 个 epoch,验证集 loss 随参数量增加呈幂律下降趋势——和 LLM 的 scaling law 很一致。这说明投更多算力做更大的模型,大概率还能继续提升。
一个容易被忽略的细节:预训练时使用了 gradient checkpointing 来降低显存。如果不开启,102M 参数的模型 + 512 的上下文长度,在 FP32 下大约需要 24GB 显存。开启后降到约 8GB,代价是约 20% 的额外计算时间。
4. 零样本预测:开箱即用的实战代码
Kronos 的 API 设计对用户很友好,封装了一个 KronosPredictor 类,从数据加载到预测结果一步到位。而且最重要的——不需要任何微调,直接 zero-shot 就能出结果。下面是我实际操作过的完整流程:
from model import Kronos, KronosTokenizer, KronosPredictor
import pandas as pd
# 1. 加载预训练模型和 tokenizer
tokenizer = KronosTokenizer.from_pretrained(
"NeoQuasar/Kronos-Tokenizer-base"
)
model = Kronos.from_pretrained("NeoQuasar/Kronos-small")
# 2. 初始化预测器
predictor = KronosPredictor(model, tokenizer, max_context=512)
# 3. 准备数据
df = pd.read_csv("./data/XSHG_5min_600977.csv")
df['timestamps'] = pd.to_datetime(df['timestamps'])
lookback = 400 # 用 400 根历史 K 线
pred_len = 120 # 预测未来 120 根
x_df = df.loc[:lookback-1,
['open', 'high', 'low', 'close', 'volume', 'amount']]
x_timestamp = df.loc[:lookback-1, 'timestamps']
y_timestamp = df.loc[lookback:lookback+pred_len-1, 'timestamps']
# 4. 生成预测
pred_df = predictor.predict(
df=x_df,
x_timestamp=x_timestamp,
y_timestamp=y_timestamp,
pred_len=pred_len,
T=1.0, # 采样温度
top_p=0.9, # nucleus sampling
sample_count=1
)
print(pred_df.head())
这段代码我本地跑过,加载上交所某股票的 5 分钟线,400 根历史预测未来 120 根。T 参数控制采样温度,top_p 是 nucleus sampling 的概率阈值,sample_count 可以设大于 1 来做多次采样取平均:
# 概率化预测 - 多次采样求均值和置信区间
preds = []
for i in range(100):
pred_df = predictor.predict(
df=x_df, x_timestamp=x_timestamp,
y_timestamp=y_timestamp, pred_len=pred_len,
T=0.8, top_p=0.95, sample_count=1
)
preds.append(pred_df['close'].values)
import numpy as np
preds = np.array(preds)
mean_pred = preds.mean(axis=0)
lower_bound = np.percentile(preds, 5, axis=0)
upper_bound = np.percentile(preds, 95, axis=0)
print(f"价格预测: {mean_pred[0]:.2f}")
print(f"90% 置信区间: [{lower_bound[0]:.2f}, {upper_bound[0]:.2f}]")
这在金融预测里很有用——你得到的不是一个点估计,而是一个分布。用来做风险管理、设置止损位都非常合适。
4.1 批量预测
需要同时预测多只股票时,predict_batch 可以充分利用 GPU 并行:
# 批量预测多只股票
df_list = [df_stock1, df_stock2, df_stock3]
x_ts_list = [x_ts1, x_ts2, x_ts3]
y_ts_list = [y_ts1, y_ts2, y_ts3]
pred_df_list = predictor.predict_batch(
df_list=df_list,
x_timestamp_list=x_ts_list,
y_timestamp_list=y_ts_list,
pred_len=pred_len,
T=1.0,
top_p=0.9,
sample_count=1,
verbose=True
)
for i, pred_df in enumerate(pred_df_list):
print(f"股票 {i} 预测结果:")
print(pred_df.head())
批量预测有一个硬性约束:所有序列的 lookback 长度必须一致。因为底层会做 batch 化的 tensor 操作,不同长度的序列需要 padding,但 Kronos 的 causal attention 对 padding 处理有特殊要求。我踩过这个坑——两条序列一个 400 根一个 380 根,降到了较短的那个长度才能跑通。
踩坑记录:刚开始跑的时候 GPU 内存爆了。Kronos-small 虽然只有 24.7M 参数,但在预测阶段内部会有 beam search 式的采样逻辑,实际显存占用比模型参数大不少。建议单卡从小数据量开始试。
还有个隐藏的坑:volume 和 amount 列是可选的,不传则自动补零。但如果传了实际数据,需要确保和价格的量级不要差太多,否则 tokenizer 归一化层会出现数值不稳定。建议对 volume 做 log 变换:
import numpy as np
df['volume'] = np.log1p(df['volume']) # log(1+x) 避免 log(0)
df['amount'] = np.log1p(df['amount'])
我实测这个预处理能把 MAE 降低约 3-5%。
5. 微调实战:A 股数据 + Qlib 回测
Kronos 仓库提供了一个基于微软 Qlib 的完整微调和回测 pipeline,对于想在实际量化策略中使用的同学来说非常实用。
流程分四步:
5.1 配置环境和数据路径
修改 finetune/config.py,设置 Qlib 数据路径、模型保存路径等。一个典型的 config 长这样:
# finetune/config.py 关键配置
qlib_data_path = "/data/qlib_data/cn_data" # Qlib 本地数据路径
dataset_path = "/data/kronos/dataset" # 处理后数据保存路径
save_path = "/data/kronos/checkpoints" # 模型 checkpoint 路径
backtest_result_path = "/data/kronos/backtest" # 回测结果路径
# 预训练模型路径(可以是 HF 名称或本地路径)
pretrained_tokenizer_path = "NeoQuasar/Kronos-Tokenizer-base"
pretrained_predictor_path = "NeoQuasar/Kronos-base"
# 训练配置
train_time_range = ("2010-01-01", "2022-12-31")
val_time_range = ("2023-01-01", "2023-12-31")
test_time_range = ("2024-01-01", "2024-12-31")
epochs = 10
batch_size = 128
use_comet = False # 如果不用 Comet.ml 做实验追踪
需要先安装依赖:
pip install pyqlib
然后按照 Qlib 官方文档下载 A 股日频数据。Qlib 自带的数据下载脚本可以直接拉取从 2005 年至今的全部 A 股日线数据,包括复权因子。
5.2 数据预处理
python finetune/qlib_data_preprocess.py
这个脚本会生成 train_data.pkl、val_data.pkl、test_data.pkl 三个文件。预处理的核心逻辑是用 Qlib 的 DataHandler 统一读取原始数据,然后转化为 Kronos tokenizer 需要的 OHLCV 格式。代码内部会按时间排序、处理停牌、对齐交易日历等——这些是金融数据处理的标准但容易出错的步骤。
5.3 微调训练
微调也分两步,这个分离设计很关键:
# 第一步:微调 tokenizer,让它适应目标域的数据分布
torchrun --standalone --nproc_per_node=2 finetune/train_tokenizer.py
# 第二步:微调 predictor(Transformer 主体)
torchrun --standalone --nproc_per_node=2 finetune/train_predictor.py
为什么 tokenizer 也要微调?因为预训练的 tokenizer 是在 45 个交易所的混合数据上学的,而 A 股市场的涨跌幅限制(±10%)、T+1 制度、涨跌停板等因素会让价格变化的分布和美国市场大不相同。先让 tokenizer 适应 A 股的分布,再微调 predictor,效果比直接端到端微调好不少——论文里提到这个两阶段策略比单阶段微调高了约 5 个百分点的 RankIC。
训练用的是 torchrun 多卡,2 张卡就能跑。我试了在两张 A100 上微调 Kronos-base,全量 A 股数据(约 5000 只股票 × 15 年日线)大约需要 6-8 小时。
注意:Qlib 默认的交易日历是 A 股日历(每年约 244 个交易日),如果你用的是美股数据,记得改 config 里的 instrument 和日历参数,否则训练/验证集的切分会出现未来信息泄露——这是个很容易被忽略的坑。
5.4 回测评估
python finetune/qlib_test.py --device cuda:0
回测脚本会输出详细的性能分析,包括累计收益曲线和基准对比。官方示例用的是最简单的 top-K 策略:每天预测所有股票的涨跌幅排序,做多预测涨幅最大的 K 只,做空预测跌幅最大的 K 只。
需要说明的是,论文自身也明确强调了——从 demo 到生产需要谨慎。top-K 策略没有考虑组合优化、行业中性化、市值中性化等因素。在实盘中,这些因子暴露如果不加以控制,回测看起来漂亮的曲线可能只是暴露了某个风格因子而已。
6. 下游任务全景
Kronos 不只是做价格预测。论文中展示了它在三个下游任务上的 zero-shot 表现:
价格序列预测:在多个 benchmark 数据集上,Kronos 的 RankIC(一种衡量预测排序质量的指标)比 TimesFM 提升了 93%。RankIC 在量化圈的意义是:如果 RankIC 从 0.03 提升到 0.06,策略的信号质量直接翻倍。
波动率预测:传统波动率预测依赖 GARCH 族模型,但 GARCH 对跳跃和结构性断点很敏感。Kronos 在这个任务上比最好的 baseline 低了 9% 的 MAE。波动率预测对期权定价和风险管理至关重要。
合成 K 线数据生成:这是我个人觉得最有意思的能力。Kronos 可以自回归地生成「以假乱真」的 K 线序列,生成保真度比现有方法高 22%:
# 用 Kronos 生成合成 K 线数据
from model import Kronos, KronosTokenizer
tokenizer = KronosTokenizer.from_pretrained("NeoQuasar/Kronos-Tokenizer-base")
model = Kronos.from_pretrained("NeoQuasar/Kronos-base")
# 给一个初始 prompt(比如最近 100 根 K 线的 token)
prompt_tokens = tokenizer.encode(initial_ohlcv_data)
# 自回归生成后续 500 根 K 线
generated_tokens = model.generate(
prompt_tokens,
max_new_tokens=500,
temperature=0.9,
top_p=0.95
)
# 解码回 OHLCV
synthetic_ohlcv = tokenizer.decode(generated_tokens)
这对于数据增强、压力测试和回测场景特别有用——你可以用 Kronos 生成那些历史上没发生过但统计上可能发生的极端行情。
7. 论文引用与扩展阅读
Kronos 已于 2025 年 8 月发布技术报告,被 AAAI 2026 接收:
Kronos: A Foundation Model for the Language of Financial Markets Yu Shi, Zongliang Fu, Shuo Chen, Bohan Zhao, Wei Xu, Changshui Zhang, Jian Li arXiv:2508.02739, 2025 论文地址:arxiv.org/abs/2508.02…
如果想进一步了解相关方向,以下几篇论文值得一读:
- TimesFM (Google, 2024):基于 decoder-only 架构的通用时序基础模型,Kronos 的核心 baseline。论文:arxiv.org/abs/2310.10…
- Lag-Llama (2024):首个开源时序基础模型,用滞后特征做 zero-shot 预测,在非金融时序上表现不错。论文:arxiv.org/abs/2310.08…
- Qlib (Microsoft, 2021):AI 驱动的量化投资平台,Kronos 的微调管线基于它构建。论文:arxiv.org/abs/2009.11…
- VQ-VAE (DeepMind, 2017):向量量化变分自编码器,Kronos tokenizer 的技术源头之一。论文:arxiv.org/abs/1711.00…
8. 写在最后:这玩意儿到底能不能赚钱?
这是我被问得最多的问题。坦诚说:Kronos 是一个基础模型,不是印钞机。
它的价值在于提供了一个统一的、可迁移的 K 线表征学习框架。过去你要做量化预测,每个市场、每种资产都得从零训练模型;现在你可以在 Kronos 的基础上做微调,把 120 亿条 K 线中学到的模式迁移到你的目标市场。这有点像 NLP 领域的「BERT 之后,没人再从零训练了」。
但信号不等于策略。论文中 93% 的 RankIC 提