ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kronos:将LLM预训练范式迁移到K线预测的金融时序模型

Kronos:将LLM预训练范式迁移到K线预测的金融时序模型 简介Kronos 是专为金融K线数据设计的时序基础模型首次把大语言模型的“预训练微调”范式迁移到量化金融领域。核心架构包含两部分一部分是BSQ双粒度分词器把开盘、最高、最低、收盘、成交量等连续数值转换为粗粒度和细粒度两种词元模拟交易者“先看大势、再精算点位”的决策过程另一部分是仅含解码器的自回归Transformer在大规模金融语料上预训练学习跨越不同时间和资产的K线形态与市场规律。整个资源面向具备Python基础的量化研究者、金融数据分析师可用于辅助投研分析、构建量化因子和生成合成市场数据。资源包共92个文件大小约9.01MB以30个Python源代码文件为主覆盖模型预训练、微调、预测、回测和Web界面另有JSON配置文件、CSV数据文件、PNG效果图、Markdown中文说明文档、启动脚本与许可证目录按模型、数据、示例、微调等模块划分便于快速定位和复现实验。已有434人浏览学习下载后可以获得完整可运行的模型实现和部署说明直接体验K线语言建模的完整流程并将预测结果用于多资产分析报告、量化策略因子或策略回测所需的合成数据。1. Kronos把LLM的“预训练微调”搬到K线预测上先看它能解决什么做量化的人大概都经历过这种尴尬用LSTM、GRU甚至普通Transformer去预测下一根K线训练时loss一路下降一到实盘就失灵换一组股票、换一个周期之前的参数全部作废。问题往往不出在模型结构上而出在数据规模和训练范式上——你用几个月甚至几小时的行情数据去训一个模型却指望它学到跨越市场、跨越周期的规律这本身就是不现实的。Kronos这个专为金融K线设计的时序模型思路是直接模仿大语言模型LLM的成功路径先用45个以上全球交易所的120亿条K线数据做预训练让模型先“见过”足够多的价格形态再让你用自己手上的数据做微调。它解决的不是“多一个预测指标”而是把“预训练微调”这套被NLP验证过的建模范式完整迁移到金融时序领域。适合手里有行情数据、想在K线预测上换一个更扎实底座的Python开发者。2. 120亿条K线预训练意味着什么数据形态、token设计与领域假设2.1 Kronos把K线“翻译”成模型能读的token序列大语言模型处理文本时第一步是把文字切成tokenKronos处理K线时同样存在一个“翻译”过程。传统时序模型直接喂数值比如把开盘价、最高价、最低价、收盘价、成交量拼成一个向量这有个老问题不同股票的价格绝对数值差异巨大茅台和农业银行的股价差了几十倍模型必须在训练中自己学会“价格高低不重要价格变化才重要”。Kronos的设计思路是在进入模型之前先对K线做归一化和离散化把连续的OHLCV序列转换成类似token的离散表示或标准化嵌入。常见做法是用对数收益率替换绝对价格因为对数收益率天然消除了价格绝对水平的影响而且跨市场、跨标的有可比性。举个例子一只股价从10元涨到11元另一只从100元涨到110元对数收益率都是0.0953模型看到的模式就是一致的。这一步是整个预训练的数据地基比后面调任何模型参数都关键。2.2 预训练数据量大不是关键数据覆盖维度才是45个以上全球交易所、120亿条K线这两个数字放在一起很容易让人兴奋但做时序模型的人知道数据量再大如果都来自同一个市场、同一个品种周期模型学到的只是单一市场的“方言”换个市场就失效。Kronos的数据覆盖了我认为最有价值的三个维度地域维度45个以上交易所意味着欧美、亚洲、新兴市场都有资产维度股票、指数、ETF、加密货币都可能包含时间周期维度不同交易所的K线周期不同模型必须学会跨周期迁移。这才是120亿条数据的真正含义——不是单纯堆量而是保证训练分布足够宽让模型在预训练阶段就见过足够多样的价格形态。你自己做类似项目时如果手头只有A股日线数据不要指望训练出通用模型但你可以用Kronos的预训练权重作为起点在自己数据上继续训练这比从随机初始化开始训练要稳定得多。2.3 时钟、日历与休市金融时序和LLM处理文本的本质区别文本是离散事件序列句子与句子之间没有日期时间属性但K线序列有严格的时间轴交易时段与休市时段交替每周有周末每年有节假日不同交易所的休市日历还完全不同。这意味着Kronos在预训练时不能像LLM处理文本那样做简单的序列拼接——把周五收盘和周一开盘连在一起当成连续序列模型就会学会一个错误假设每天都是连续交易的。Kronos的设计里需要处理这个时间对齐问题。我看到这个模型在数据组织上的一个关键假设是把K线按时间排序同时通过额外的特征或特殊token标记时间间隔。比如周五15:00收盘到周一9:30开盘间隔了66.5小时而正常K线间隔可能是1小时模型需要知道两个相邻K线之间的真实时间距离否则会把夜间的跳空缺口当成正常的连续价格变化。这一点在你后续微调时也要注意如果你用日线数据但你的标的经常跳空开盘模型预期的时间间隔假设可能和你真实数据不一致。2.4 为什么“将大语言模型的建模范式迁移到金融时序”不是噱头LLM成功的核心其实不是“大”而是三个要素的组合海量数据上的预训练、预测下一个token的自监督任务、以及微调时代化到具体任务的能力。Kronos把这套范式映射到金融时序上对应的是海量K线数据上的预训练、预测下一根K线的自监督任务、以及在你自己的交易标的上的微调。这个迁移之所以成立是因为K线序列和文本序列在底层结构上有相似性——都是有限状态下的序列模式学习价格形态像词汇组合一样存在重复出现的“短语”。当然金融时序比文本更困难的地方在于文本的语法规则是人类制定的相对稳定而市场规律会因为参与者行为改变而漂移。Kronos能做的不是告诉你明天涨还是跌而是提供一个比随机初始化模型更强的特征提取底座让你在微调阶段用更少的数据、更短的时间训练出可用的预测器。3. 拿到Kronos源码后的第一件事模型结构、权重与推理前的准备工作3.1 官方源码的目录结构先分清哪些是模型定义、哪些是训练工具把Kronos的Python源码克隆下来之后不要急着跑demo先花十分钟理清目录结构。我的经验是这类模型的代码库通常分成几个明确的部分模型定义文件包含Transformer主干、输入编码器、输出解码器、数据处理工具负责把K线数据转换成训练样本、训练脚本预训练和微调分开、推理脚本加载权重、输出预测。你本地微调真正需要动的是训练脚本和推理脚本模型定义文件一般只在你想改网络结构时才需要碰。还有一个容易忽略的文件是模型配置通常是JSON或YAML格式里面写了隐藏层维度、注意力头数、层数、dropout比例等超参数。在开始微调之前打开这个配置看一眼确认它和你下载的权重文件一致。很多人在这里翻车换了权重文件但配置还是旧的模型加载时参数名对不上报出一堆莫名其妙的错误。我一般会先把配置里和模型结构相关的字段层数、头数、维度记下来再去看权重文件确保两者对应。3.2 权重文件的加载状态字典、兼容性与版本差异Kronos发布时应该会附带预训练权重文件格式一般是PyTorch的.pt或.pth。加载权重时有几个坑值得提前说。第一个坑是权重文件的键名兼容性——如果Kronos的模型代码后来更新过比如把某个层的命名从encoder.layer0改成了encoder.layers.0你拿旧权重加载新模型会报missing keys。遇到这种情况先看官方有没有升级脚本没有的话就要自己手动映射键名。第二个坑是设备迁移——权重在GPU上保存的你本地如果没有GPU加载的时候要指定map_locationcpu。第三个坑是精度问题——如果预训练用的是FP16混合精度你CPU推理时最好把权重转到FP32否则低精度下的预测结果会有些偏差。我自己的习惯是写一个单独的小脚本来验证权重加载完整性加载完打印所有层的shape和配置文件里比对一遍确保关键维度对得上。这一步花十分钟能省掉后面好几个小时的排查时间。3.3 输入数据的格式约定Kronos到底要什么样的K线序列在推理之前必须搞清楚Kronos的输入格式。从模型设计的角度看它接收的应该是规范的K线序列数据包含时间戳、开盘价、最高价、最低价、收盘价、成交量这些字段。但具体的格式细节——比如时间是字符串还是Unix时间戳、价格是原始值还是已经做过对数转换——每个模型都不一样。我的建议是直接看源码里的数据处理函数尤其是preprocess或encode相关的部分。不要凭感觉猜测。Kronos的处理逻辑大概率在数据入口处做归一化和序列化你需要确保你喂进去的原始数据和预训练时的数据格式一致。举个例子如果预训练时用的K线是向前复权的你推理时用了不复权数据两者在除权除息日的价格模式上会有明显差异模型预测会失真。还有一个细节是序列长度——模型有固定的最大输入长度通常由训练时的窗口大小决定你输入超过这个长度的序列要么截断要么需要分组处理。低于最小长度也不行模型可能无法有效编码短序列的上下文。检查一下你的数据时间跨度是否落在模型支持的范围内。3.4 环境依赖清单Python版本、PyTorch版本与CUDA安装部署Kronos之前先确认本机环境。这类模型的依赖核心是PyTorch模型代码可能在某个PyTorch版本上开发和测试的版本跨度太大容易出兼容性问题。我的建议是用conda建一个独立环境然后按顺序安装依赖先装PyTorch再装模型的其他依赖常见的有pandas、numpy、tqdm这些。不要直接pip install跑requirements.txt因为requirements里可能锁了版本号和你本机已有的包冲突。如果你不用GPU只需要CPU推理安装CPU版PyTorch就够了显存占用为零但推理速度会慢。如果要用GPU加速微调注意CUDA版本和PyTorch版本的对应关系这一步配错的话后续所有GPU相关操作都会失败。如果你的机器是Apple Silicon的Mac也可以用MPS后端跑PyTorch但和CUDA相比某些算子的支持不完整跑Kronos这类Transformer模型可能会出现不兼容的警告。3.5 快速跑通官方demo验证模型能加载、能推理、输出形状正确环境配好、权重加载成功之后第一件要做的事是跑通官方提供的demo或示例脚本。这个demo的目的不是看预测效果好不好而是验证整条链路是通的。我当时做类似模型部署时会先构造一个极小的输入——比如用最近20根K线作为输入序列跑一次推理看模型输出的形状是不是符合预期。预测输出可能是一个向量代表预测的未来收益率分布或K线数值也可能是一组参数。确认输出维度正确之后再逐步增加序列长度验证模型在较长输入下不会报内存错误。跑demo时如果遇到报错先看是模型加载阶段的错误还是前向传播阶段的错误。前者通常是权重和模型结构不匹配后者通常是输入张量的维度或类型不对。把这两类错误分开排查效率会高很多。记住第一次跑通demo追求的不是预测准确而是链路完整——数据能进模型结果能出来。4. 用Kronos跑通一次K线预测最小部署路径与三个必调参数4.1 初始化模型加载预训练权重并进入评估模式当你确认环境无误后写一个最简单的推理脚本来加载Kronos模型。下面这段代码基于PyTorch的常见写法实际调用时要以你下载的源码为准。import torch import kronos from kronos import KronosForPrediction # 以实际源码模块名为准 # 初始化模型结构并加载预训练权重 model KronosForPrediction.from_pretrained( ./kronos_pretrained/kronos_base, # 权重所在目录 config_file./kronos_pretrained/config.json ) # 切换到评估模式关闭dropout和batch norm的随机行为 model.eval() # 如果只有CPU强制把模型放在CPU上并转为FP32 device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device).float() print(f模型已加载运行设备: {device})这段代码的逻辑很直接先通过from_pretrained加载预训练权重这一步会同时读取配置文件来构建模型结构然后调用eval()这在PyTorch里是必须的——不调用的话模型里的dropout层在推理时仍会随机丢弃神经元导致每次预测结果都不一样最后把模型放到可用的计算设备上CPU就转成FP32GPU就保持默认精度。这里最容易被忽略的是model.eval()我见过不少人在推理时漏了这一步结果同一个输入每次跑出的预测都略有差异还以为是模型有问题。4.2 构造输入把原始K线数据处理成模型需要的张量接下来是把你的K线数据转换成模型能接收的张量格式。这里假设你的原始数据是pandas的DataFrame包含open、high、low、close、volume五列。import pandas as pd import numpy as np import torch # 读取本地K线数据Excel或CSV都可以但必须保证列名规范 df pd.read_csv(./data/btc_usdt_1h.csv) print(f原始数据共 {len(df)} 根K线时间范围: {df[timestamp].iloc[0]} ~ {df[timestamp].iloc[-1]}) # 按时间升序排列防止数据乱序影响序列建模 df df.sort_values(timestamp).reset_index(dropTrue) # 计算对数收益率替代绝对价格消除价格水平影响 df[log_return] np.log(df[close] / df[close].shift(1)) df[log_return] df[log_return].fillna(0.0) # 第一行没有前值补0 # 用对数收益率加成交量构造特征矩阵 features df[[log_return, volume]].values.astype(np.float32) print(f特征矩阵形状: {features.shape}) # 取最近64根K线作为输入序列长度要和模型训练时的窗口匹配 seq_len 64 seq features[-seq_len:] # 转换为PyTorch张量并增加batch维度形状变为 (1, seq_len, feature_dim) input_tensor torch.from_numpy(seq).unsqueeze(0).to(device) print(f输入张量形状: {input_tensor.shape})这段代码有两个关键参数值得说明。第一个是seq_len也就是输入序列长度。这个参数不能随便拍脑袋定——它在模型预训练时就已经固定了你输入更长的序列会被截断或分窗输入更短则上下文不足。我一般会先查模型配置文件里的max_seq_len字段然后选用不超过它的、足够描述一个完整交易模式的值比如64或128。第二个是特征构造——我用的是“对数收益率成交量”两列特征你在实践当中可以加入更多特征但前提是预训练权重里对应位置的维度能对齐。如果模型输入维度是固定的你加了特征后维度不匹配模型加载和推理都会报错。4.3 推理预测解读模型输出并转成可操作的风险度量输入张量构造好之后前向传播只需要一行代码但输出的解读才是关键。with torch.no_grad(): output model(input_tensor) # Kronos的输出通常是下一根K线的预测分布参数具体含义看模型设计 pred output[prediction] if isinstance(output, dict) else output print(f模型输出形状: {pred.shape}) # 如果输出是均值和方差可以构造预测区间 if hasattr(pred, mean): pred_mean pred.mean(dim-1).item() print(f下一根K线的预测收益率均值: {pred_mean:.4f}) else: # 如果输出是单个值直接打印 print(f下一根K线的预测值: {pred.item():.4f})torch.no_grad()在推理时是必须的——它告诉PyTorch不需要为这个计算图保存梯度不仅省显存还会让前向传播提速。从模型输出的解读来看Kronos所在的那一类模型通常有两种输出模式一种是直接输出下一根K线的预测值另一种是输出一个概率分布的参数比如均值和方法。如果是后者你得到的预测结果天生带有不确定性度量这在金融场景里非常有用——你不仅可以知道模型预测涨还是跌还能知道模型的置信度有多高。我建议你在使用Kronos时优先关注输出中能反映不确定性的部分不要只盯着预测方向这一个数值。4.4 微调前的三个必调参数学习率、训练轮数与冻结策略当你决定在自己的数据上微调Kronos时有三个参数直接影响最终效果。第一个是学习率。预训练模型已经学到了通用的价格形态微调时如果学习率过大模型会把之前学到的通用特征全部忘掉只记住你提供的少量数据。我一般从1e-5开始如果loss下降太慢再调整到3e-5——这个量级的调整是正常的不建议直接上1e-3。第二个是训练轮数epochs。微调不是越多越好。预训练模型在你的小数据集上训练1到3轮通常就够了训练过久会过拟合——模型在你的历史数据上表现很好但未来数据上预测能力急剧下降。判断方法很简单每一轮结束后在验证集上测一次预测误差当验证误差开始上升时立刻停。第三个是冻结策略。你可以选择冻结模型底层的部分层只微调靠近输出的上层。原因是底层学到的是通用的价格形态特征跨市场通用上层学到的是预训练数据里特定市场的交易风格需要被你自己的数据校正。冻结策略表现成一个布尔参数怎么选看你手里的数据量——数据量越少越应该多冻结底层。4.5 微调训练脚本的骨架数据切分、损失函数与保存检查点from torch.utils.data import DataLoader, TensorDataset # 假设你已经构造好了训练用样本X_train和对应标签y_train X_train torch.from_numpy(train_features).float() y_train torch.from_numpy(train_labels).float() # 封装成PyTorch标准数据集 train_dataset TensorDataset(X_train, y_train) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue) optimizer torch.optim.AdamW(model.parameters(), lr2e-5) loss_fn torch.nn.MSELoss() # 回归任务用MSE如果是概率输出则用NLL model.train() # 切换到训练模式 for epoch in range(3): total_loss 0.0 for batch_X, batch_y in train_loader: batch_X, batch_y batch_X.to(device), batch_y.to(device) optimizer.zero_grad() pred model(batch_X) loss loss_fn(pred, batch_y) loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}/3, loss: {total_loss/len(train_loader):.6f}) # 每个epoch结束后保存一次检查点方便回退到最优版本 torch.save(model.state_dict(), f./checkpoints/kronos_finetuned_epoch{epoch1}.pt)这个脚本是微调的标准骨架。其中batch_size32是一个还算保守的选择如果你的显存不够降到16或8都是可以的。损失函数的选择取决于你的预测目标如果预测的是具体的收益率数值MSE均方误差够用如果模型输出的是分布参数应该用负对数似然损失。每个epoch结束保存检查点是我个人的习惯因为训练过程中可能出现loss先降后升的情况有了检查点你可以回退到表现最好的那个epoch。训练结束之后把微调后的权重和预训练权重都保存好后续回测和实盘验证时可以对比二者效果。5. Kronos避坑实录从数据泄露到归一化处理的五条踩坑记5.1 训练集和测试集混入了同一条K线未来函数引发的虚幻收益现象模型在回测里表现惊人年化收益率高得离谱但一到实盘就完全失灵甚至亏损。排查了很久发现是数据切分出了问题。原因构造训练样本的时候直接按时间排序后截取了一段做训练集另一段做测试集但样本之间存在重叠。比如训练样本用的是第1根到第64根K线预测第65根测试样本却包含了第65根K线作为输入的一部分。模型在训练时已经见过第65根K线的价格信息测试时当然“预测”得异常准确。这是典型的未来函数问题也就是数据泄露。解决按时间严格切分训练集和测试集之间留出间隔。我现在的做法是把数据集按时间切成三段训练集用前70%验证集用中间10%测试集用最后20%并且训练集的结束时间和验证集的开始时间之间留出至少一个序列长度的间隔保证没有样本跨越切分边界。5.2 价格归一化的统一问题训练用复权价推理用原始价现象模型在训练集上loss很低但加载同样的权重去预测新数据时预测结果明显偏倚方向经常判断错误。原因训练时用的K线是前复权价格推理时手里的数据是未复权的原始价格。股票除权除息之后股价会突然跳空前复权数据会把历史价格整体调整未复权数据则保留真实的历史价格。两种数据在同一根K线上的数值不同对数收益率的计算也会有偏差。模型在预训练和微调时学到的价格模式是复权价下的推理时输入未复权价数据分布不一致。解决确保训练和推理用同一复权方式的数据。我个人的习惯是统一用前复权数据因为后复权数据的当前价格和历史价格差距很大模型不好统一处理。另外需要留意交易所停牌复牌造成的长时间价格不变序列某些停牌期长的股票会出现连续好多根K线收盘价完全一样的现象这种极端序列在金融时序里会导致梯度异常处理时可以考虑剔除或标记。5.3 日志收益率出现无穷值除零和数据异常没有预处理现象模型前向传播时报错提示输入张量里存在NaN或inf值监督训练时loss直接变成NaN。原因计算对数收益率时某根K线的收盘价为0数据源出现了异常值或者前一根收盘价为0np.log(0 / prev_close)就会产生-inf。常见的数据源错误还包括成交量缺失或时间为空导致的行错位。模型一旦碰到无穷值反向传播时梯度就会变成NaN整个训练过程直接被污染。解决在预处理阶段显式检查并处理这些异常。我的做法是数据清洗环节里加一步检查遍历所有K线如果发现收盘价小于等于0或出现空值直接丢弃该行对数收益率出现无穷值的行也一并删除或填为0。最稳妥的方式是用df.replace([np.inf, -np.inf], np.nan).dropna()把异常行清除干净再进入后续特征工程。5.4 不同交易所数据拼接顺序混乱跨市场时间轴错位现象想把多个交易所的数据混合起来训练或微调模型加载后训练loss下降很慢预测效果也不好检查发现输入序列的时间顺序是乱的。原因不同交易所的休市时间不同比如美股有中午休市A股有午间休市加密货币则全天交易。如果直接把不同标的的K线按本地时间拼接在一起时间戳会错位模型的注意力机制会把这个混乱的时序当成正常的序列模式去学习结果什么都学不到。解决每个标的单独处理不要混在一个序列里。训练样本应该保证每个样本内部的K线来自同一个标的、同一个周期并且时间严格递增。多标的混合的正确做法是在同一个batch里放不同标的的独立样本通过模型内部的批次维度区分而不是在序列维度上拼接。如果你的模型支持类似LLM的“文档分隔符”也可以用特殊标记区分不同标的的边界。5.5 预训练权重和模型结构版本不匹配加载报错与输出异常现象from_pretrained加载权重时报错报错信息是unexpected key或missing key有些时候不报错但预测结果完全不合理——输出恒为某个固定值。原因官方仓库更新了模型配置或层命名你下载的权重文件是旧版本和当前源码里的模型结构对不上。PyTorch加载权重时按层名严格匹配名字对不上就会报missing或unexpected如果恰好名字匹配但层顺序调整过不会报错但语义就不对了输出自然荒诞。解决检查权重文件附带的版本信息或配置文件确保和源码版本一致。如果源码版本更新了看官方release note里有没有权重迁移说明。最稳妥的折中方案是checkout到和权重文件匹配的git commit再运行。如果实在对不上就自己对权重文件做一个键名映射把旧名字转换为新名字这个需要仔细对比模型构造代码里每一层的名称没有捷径。6. 验证Kronos预测效果的一个技巧按“时间戳”切分而不是按“股票”切分很多人在评估Kronos这类模型的预测能力时习惯把所有标的的数据混合在一起然后随机切分训练集和测试集。这在其他机器学习领域没问题但在金融时序里是错的。原因很简单同一时间段内不同标的价格往往受共同因素影响比如某个宏观新闻导致整个板块同时大涨。如果你的训练集和测试集在同一个时间窗口内模型在训练时已经见过其他标的同一时间段的走势测试时面对这个标的的同类模式等于“看过答案”。正确的评估方式是按时间戳切分用2024年之前的所有数据训练用2024年之后的数据测试物理上保证训练和测试之间没有时间重叠。具体操作时我一般会记录验证集里每一段预测的时间范围确认训练集最后一条数据的时间戳早于验证集最早一条。这个技巧虽然简单但很多人会在实际编码时忘记因为数据加载器shuffle之后时间信息容易丢失。我的习惯是在构造Dataset时让每个样本携带它的结束时间戳切分时严格按时间戳过滤而不是按索引切分。这样做还有一个额外好处你可以单独检查验证集的时间跨度是否覆盖了完整的市场周期——如果测试期恰好只有牛市行情那模型的表现再好看也没有说服力。回测结果需要按年、按季度分别看预测误差确认模型不是只在某个行情阶段有效。我在用Kronos这一类预训练模型时还有一个不成文的习惯微调完之后先在原始预训练模型和新微调模型之间做一个对比基准。用完全相同的验证集数据跑两个模型的预测计算预测误差和相关指标。如果微调后的模型在验证集上的表现还不如预训练模型说明你微调数据的分布与预训练数据严重不匹配或者微调超参数设置不合理需要回头检查数据预处理而不是继续调模型。这个对比过程只需要几十行代码但对判断模型是否真的“变好”非常有价值。关于Kronos的方向我的建议是不要把它当成一个黑匣子也不要指望它给出确定性预测。它真正的价值在于提供了一个见过足够多市场形态的底座你要做的是在这个底座上构建自己的预测逻辑——无论是对接一个简单的趋势跟踪策略还是作为其他信号模型的特征输入。动手之前先把环境、数据、权重这三件事准备好跑通之后再决定要不要微调。这个顺序每次都能少走很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表