用Lag-Llama预测股价

为了解决时间序列泛化挑战,研究人员引入了Lag-Llama,这是一种专为单变量概率时间序列预测设计的基础模型。

用Lag-Llama预测股价

近年来,基础模型彻底改变了机器学习领域,使零样本和少样本泛化成为可能。尽管它们在自然语言处理和计算机视觉方面取得了显著进展,但在时间序列预测中的应用仍然相对受限。

这种限制在金融数据预测中尤为明显,其中固有的噪声和非平稳性带来了重大挑战。

1、引入Lag-Llama

为了解决时间序列泛化挑战,研究人员引入了Lag-Llama,这是一种专为单变量概率时间序列预测设计的基础模型。Lag-Llama采用解码器-only的Transformer架构,通过将滞后值作为协变量来有效捕捉时间序列数据中的时间依赖性。

Lag-Llama架构。来自paper

Lag-Llama学会根据滞后输入特征输出下一时间步值的分布。模型的输入是给定时间步单变量时间序列i的标记x。这里c指的是与时间步t的值一起使用的其他所有协变量,包括*|L|滞后、F日期时间特征和摘要统计量。输入通过M*掩码解码层投影。然后将特征传递到分布头并训练以预测下一时间步的预测分布参数。

标记化过程,来自paper

Lag-Llama以其强大的预训练方法脱颖而出。利用来自各个领域的广泛时间序列数据集,Lag-Llama经过预训练以捕获复杂的时间模式。这种方法赋予Lag-Llama强大的零样本泛化能力。

现在,让我们看看Lag-Llama在基于历史数据预测科技巨头股价方面的表现如何。

2、预测

注意:所有命令都在Google Colab中测试过。将“/content”替换为您根目录。必要时在单元格中使用‘!’运行bash命令。

首先从包含Lag-Llama模型的GitHub存储库克隆并安装所需的包。

git clone https://github.com/time-series-foundation-models/lag-llama/  
cd /content/lag-llama  
pip install -r requirements.txt --quiet

然后从HuggingFace下载预训练模型权重 🤗。

huggingface-cli download time-series-foundation-models/Lag-Llama lag-llama.ckpt --local-dir /content/lag-llama

导入所需的包和可以用于预测的LagLlamaEstimator对象。

from itertools import islice  

import matplotlib.dates as mdates  
import pandas as pd  
import torch  
from gluonts.dataset.pandas import PandasDataset  
from gluonts.dataset.repository.datasets import get_dataset  
from gluonts.evaluation import Evaluator, make_evaluation_predictions  
from lag_llama.gluon.estimator import LagLlamaEstimator  
from matplotlib import pyplot as plt

创建一个Lag-Llama推理函数,可用于不同类型的数据集。该函数返回给定预测范围的预测。预测形状为(num_samples, prediction_length),其中num_samples是从每个时间步的预测概率分布中采样的样本数。


def _get_lag_llama_predictions(dataset, prediction_length, num_samples=100):  
    ckpt = torch.load("lag-llama.ckpt", map_location=torch.device("cuda:0"))  
    estimator_args = ckpt["hyper_parameters"]["model_kwargs"]  

    estimator = LagLlamaEstimator(  
        ckpt_path="lag-llama.ckpt",  
        prediction_length=prediction_length,  

        # pretrained length  
        context_length=32,  

        # estimator arguments  
        input_size=estimator_args["input_size"],  
        n_layer=estimator_args["n_layer"],  
        n_embd_per_head=estimator_args["n_embd_per_head"],  
        n_head=estimator_args["n_head"],  
        scaling=estimator_args["scaling"],  
        time_feat=estimator_args["time_feat"],  
        batch_size=1,  
        num_parallel_samples=100,  
    )  

    lightning_module = estimator.create_lightning_module()  
    transformation = estimator.create_transformation()  
    predictor = estimator.create_predictor(transformation, lightning_module)  

    forecast_it, ts_it = make_evaluation_predictions(  
        dataset=dataset, predictor=predictor, num_samples=num_samples  
    )  
    forecasts = list(forecast_it)  
    tss = list(ts_it)  

    return forecasts, tss

现在安装Yahoo Finance以下载股票价格。

# 使用最新版本  
pip install yfinance==0.2.37

然后下载所列股票符号的收盘价。

import yfinance as yf  

# 根据需要调整tickers、period和frequency  
stock_prices = (  
    yf.Tickers("aapl amd amzn crm goog meta msft nvda tsla")  
    .history(period="max", start="2013-01-01")  
    .Close  
    .resample('1d')  
    .ffill()  
)  
stock_prices

像这样。

平滑价格并计算收益率。

# 根据需要调整平滑窗口  
stock_returns = stock_prices.rolling(5).mean().pct_change().dropna()

创建数据集。

def _get_lag_llama_dataset(dataset):  
    # 避免突变  
    dataset = dataset.copy()  

    # 将数值列转换为`float32`  
    for col in dataset.columns:  
        if dataset[col].dtype != "object" and not pd.api.types.is_string_dtype(  
            dataset[col]  
        ):  
            dataset[col] = dataset[col].astype("float32")  

    # 创建一个`PandasDataset`  
    backtest_dataset = PandasDataset(dict(dataset))  
    return backtest_dataset  

backtest_dataset = _get_lag_llama_dataset(dataset=stock_returns)  
prediction_length = 60  # 预测长度  
num_samples = 1060 # 每个时间步从分布中采样的样本数

现在进行零样本推理。

forecasts, tss = _get_lag_llama_predictions(  
    backtest_dataset, prediction_length, num_samples  
)

现在绘制模型在此数据集上的预测。

plt.figure(figsize=(20, 15))  
date_formater = mdates.DateFormatter("%b, %d")  
plt.rcParams.update({"font.size": 15})  

# 遍历系列,绘制预测样本  
for idx, (forecast, ts) in islice(enumerate(zip(forecasts, tss)), 9):  
    ax = plt.subplot(3, 3, idx + 1)  

    plt.plot(  
        ts[(-4 * prediction_length):].to_timestamp(),  
        label="target",  
    )  
    forecast.plot(color="g")  
    plt.xticks(rotation=60)  
    ax.xaxis.set_major_formatter(date_formater)  
    ax.set_title(forecast.item_id)  

plt.gcf().tight_layout()  
plt.legend()  
plt.show()

期望得到这个。

目标股票收益率(蓝色)与预测值(绿色)

或者,以累计收益或盈亏(pnl)的形式。

目标股票pnl(蓝色)与预测值(绿色)

但等等,还有更多。

3、回测

预测很好,但如果我们将它们用于交易会怎样?让我们使用滚动窗口方法对模型进行回测,每季度重置投资组合权重。然后启动下一个季度回报的预测模型。如果预测的累计回报大于零,我们就买入资产;如果小于零,我们就卖出。最终权重按绝对值重新缩放至统一。佣金和做空费用也考虑在内。

结果表明,在交易中采用零样本预测意义不大。

零样本交易

虽然模型可以帮助避免一些回撤,但在上升趋势期间仍会蒙受损失。

权重大于零,按绝对值重新缩放至统一。

但这还不是全部。让我们尝试通过微调来增强模型。

4、微调

使用少量数据特定更改对Lag-Llama基础模型进行微调。

ckpt = torch.load("lag-llama.ckpt", map_location="cuda")  
estimator_args = ckpt["hyper_parameters"]["model_kwargs"]  

estimator = LagLlamaEstimator(  
    ckpt_path="lag-llama.ckpt",  
    prediction_length=prediction_length,  
    context_length=32,  

    # 根据需要调整  

    # distr_output="neg_bin",  
    # scaling="mean",  
    nonnegative_pred_samples=True,  
    aug_prob=0,  
    lr=5e-4,  

    # estimator args  
    input_size=estimator_args["input_size"],  
    n_layer=estimator_args["n_layer"],  
    n_embd_per_head=estimator_args["n_embd_per_head"],  
    n_head=estimator_args["n_head"],  
    time_feat=estimator_args["time_feat"],  

    # rope_scaling={  
    #     "type": "linear",  
    #     "factor": max(  
    #         1.0, (context_length + prediction_length) / estimator_args["context_length"]  
    #     ),  
    # },  

    batch_size=64,  
    num_parallel_samples=num_samples,  
    trainer_kwargs={  
        "max_epochs": 50,  
    },  # <- lightning trainer arguments  
)

训练预测器。

# 例如,使用前5年的数据  
# 然后在其余数据集上进行测试  
train_dataset = _get_lag_llama_dataset(stock_returns.iloc[:252*5])  
predictor = estimator.train(  
    train_dataset,  
    cache_data=True,  
    shuffle_buffer_length=1000,  
)

更新带有微调预测器的预测函数。

def _get_lag_llama_predictions(  
    dataset, prediction_length, num_samples=100, predictor=None,  
):  
    ckpt = torch.load(  
        "lag-llama.ckpt", map_location=torch.device("cuda:0")  
    )  
    estimator_args = ckpt["hyper_parameters"]["model_kwargs"]  

    estimator = LagLlamaEstimator(  
        ckpt_path="lag-llama.ckpt",  
        prediction_length=prediction_length,  

        # pretrained length  
        context_length=32,  

        # estimator args  
        input_size=estimator_args["input_size"],  
        n_layer=estimator_args["n_layer"],  
        n_embd_per_head=estimator_args["n_embd_per_head"],  
        n_head=estimator_args["n_head"],  
        scaling=estimator_args["scaling"],  
        time_feat=estimator_args["time_feat"],  
        batch_size=1,  
        num_parallel_samples=100,  
    )  

    lightning_module = estimator.create_lightning_module()  
    transformation = estimator.create_transformation()  
    predictor = (  
        predictor  
        if predictor is not None  
        else estimator.create_predictor(transformation, lightning_module)  
    )  

    forecast_it, ts_it = make_evaluation_predictions(  
        dataset=dataset, predictor=predictor, num_samples=num_samples  
    )  
    forecasts = list(forecast_it)  
    tss = list(ts_it)  

    return forecasts, tss

现在,绘制微调模型在测试数据集上的预测,例如在初始5年数据上训练后:stock_returns.iloc[252*5:]

目标股票收益率(蓝色)与预测值(绿色)
目标股票pnl(蓝色)与预测值(绿色)

看起来好多了。让我们让回测再次变得伟大。

微调后的交易

事实证明,通过简单的测试发现,Lag-Lama模型在股票价格预测的零样本方法中表现不佳。然而,即使对模型进行轻微微调也能改善收益结构并帮助平滑PnL。

这仅仅是开始;还有无数优化和迭代在前方。这包括寻找最佳参数、考虑各种波动率结构周期、数据清理和去噪、微调交易策略本身、计算交叉验证等。


原文链接:Stock Price Forecasting with Lag-Llama Transformers

汇智网翻译整理,转载请标明出处