Skip to content

实战教学:构建可解释的变换器模型,精准预测股价波动(四)

作者:老余捞鱼

原创不易,转载请标明出处及原作者。

写在前面的话:欢迎回到我们利用时态融合变换器(TFT)进行逐分钟股价预测系列的最后一期!在本章中,我将详细介绍从数据准备、模型构建到交易策略实施的整个过程,并会通过代码示例分享一些令人振奋的模型训练评估和交易策略实现的实战结果。(封面图为使用 TFT 预测成功交易 $TSLA股票的示意)

阅读本章节的一些前提:

  • 完成本系列第 1-3 章节的学习;
  • 基本掌握了 PyTorch Lightning;
  • 熟悉时间序列指标。

前3章节的链接如下:

本章节结束后,希望您能掌握:

  • 为时间序列预测训练时态融合变换器;
  • 使用 Tensorboard 有效监控培训进度;
  • 用时间序列指标评估模型性能;
  • 利用模型预测实施和回溯测试交易策略。

在第 3 部分中,我们建立了训练数据集。现在,我们将对模型进行训练,并评估其在现实世界中的表现,从而将所有内容整合在一起。

一、完善数据集

在上一部分中,我向大家展示了如何创建一个时间序列数据集(TimeSeriesDataset),该数据集将被我们的模型所使用,下面是该部分的片段:

from pytorch_forecasting import TimeSeriesDataSet

min_prediction_length = max_prediction_length = 20 # 20 minutes
min_encoder_length = max_encoder_length = 240 # 4 hours
training_dataset = TimeSeriesDataSet(
    df_train.reset_index(),
    time_idx="time_idx",
    target="log_return",
    group_ids=["symbol"],
    min_encoder_length=min_encoder_length, 
    max_encoder_length=max_encoder_length,
    min_prediction_length=min_prediction_length,
    max_prediction_length=max_prediction_length,
    time_varying_known_reals=[
         "time_idx", "average_volume"
    ],
    time_varying_known_categoricals=[
        "day_of_the_week", "is_earnings_day", "hour", "minute"
    ],
    time_varying_unknown_reals=[
        "close_rank", "rel_volume", "ATR", "EMA_change", "RSI", "SMA_change", "market_cap", "gap",
        "log_daily_key_level_above_current_price_change", "log_daily_key_level_below_current_price_change",
        "log_hourly_key_level_above_current_price_change", "log_hourly_key_level_below_current_price_change"
    ],
    static_categoricals=[
        "industry"
    ],
    static_reals=[
        "shares_float"
    ],
    add_relative_time_idx=True,
    add_encoder_length=False,
    target_normalizer=None # targets are already normalized
)

初步实验对回报率预测的探索揭示了一个金融预测领域的普遍难题:模型倾向于在各个时间点上预测出接近零的结果。这种行为源于每分钟收益率的统计特性:

  • 收益率(尤其是对数收益率)以零为中心;
  • 近似正态分布;
  • 以一分钟的频率显示极小的波动(通常是百分比的几分之几)。

为了解决这个问题,我转而预测原始价格水平,而不是回报率。以下是我们改进后的数据集配置:

min_prediction_length = 10 # predict 10 minutes into the future
max_prediction_length = 10
min_encoder_length = 120 # 2 hours
max_encoder_length = 120
training_dataset = TimeSeriesDataSet(
    df_train.reset_index(),
    time_idx="time_idx",
    target="close",
    group_ids=["symbol"],
    min_encoder_length=min_encoder_length, 
    max_encoder_length=max_encoder_length,
    min_prediction_length=min_prediction_length,
    max_prediction_length=max_prediction_length,
    time_varying_known_reals=[
         "time_idx", "average_volume"
    ],
    time_varying_known_categoricals=[
        "day_of_the_week", "is_earnings_day", "hour", "minute"
    ],
    time_varying_unknown_reals=[
        "open", "high", "low", "close", "rel_volume", "ATR", "EMA", "RSI", "SMA", "market_cap", "gap",
        "daily_key_level_above_current_price", "daily_key_level_below_current_price", "hourly_key_level_above_current_price", "hourly_key_level_below_current_price"
    ],
    static_categoricals=[
        "industry"
    ],
    static_reals=[
        "shares_float"
    ],
    lags={
        "close": [60, 120, 180], # previous close price
    },
    add_target_scales=True,
    add_relative_time_idx=True,
    add_encoder_length=False,
    target_normalizer=GroupNormalizer(
        groups=["symbol"], transformation="softplus"
    ),
    # scalers=scalers
)

主要的改进包括:

  • 缩短序列长度:编码器长度从 240 条缩短至 120 条,预测时间从 20 分钟缩短为 10 分钟。优点模型占用空间更小,由于预测时间更短,指标性能可能更好。
  • 目标工程:将目标从对数收益率改为原始收盘价;使用带有软加转换的 GroupNormalizer 对每只股票的价格进行归一化处理;新增 60、120 和 180 分钟历史收盘价滞后功能。
  • 功能增强:用原始 OHLC(开盘价、最高价、最低价、收盘价)价格取代之前的 close_rank 功能;保持完整的价格走势信息,同时可能提高模型的可解释性。

这些改进旨在为模型提供更直接的价格信息,同时保持可控的计算量。由于在金融市场中预测未来的难度呈指数级增长,因此缩短预测范围也应能带来更准确的预测。

二、建立训练通道

2.1 数据加载器配置

首先,让我们为训练和验证配置数据加载器:

batch_size = 128
train_dataloader = training_dataset.to_dataloader(train=True, batch_size=batch_size, num_workers=0)
validation = TimeSeriesDataSet.from_dataset(training_dataset, df_val.reset_index(), predict=True, stop_randomization=True)
val_dataloader = validation.to_dataloader(train=False, batch_size=batch_size * 10, num_workers=0)

数据加载器设置包括两个关键配置:

  • 启用训练加载器 (train=True);
  • 验证加载器,只评估每个时间序列的最后一个样本(predict=True)。

这种验证方法大大加快了我们的评估过程,同时还为我们的测试库存提供了有意义的指标。

2.2 Pytorch Lightning Trainer 设置

接下来,使用 PyTorch Lightning 配置我们的训练基础架构:

from lightning.pytorch.callbacks import ModelCheckpoint
import lightning as pl

lr_logger = LearningRateMonitor()  # log the learning rate
logger = TensorBoardLogger("lightning_logs")  # logging results to a tensorboard

checkpoint_callback = ModelCheckpoint(
    filename='stock_forecasting-{epoch:02d}-{val_loss:.2f}',
    save_top_k=3,
    save_last=True,
    monitor="val_loss",
    mode="min",
    every_n_train_steps=200,
)

trainer = pl.Trainer(
    max_epochs=5,
    accelerator="cuda",
    enable_model_summary=True,
    gradient_clip_val=0.1,
    #fast_dev_run=True,  # uncomment to check that the model or dataset has no serious bugs
    callbacks=[lr_logger, checkpoint_callback],
    val_check_interval=100,
    logger=logger,
)

训练设置包括几个重要组成部分:

  • 日志配置: 集成 TensorBoard,实现训练指标的可视化 – 学习率监控(对未来的学习率调度实验非常有用);
  • 模型检查点:每 200 个训练步骤保存模型快照 – 根据验证损失保留前 3 个模型 – 保留最新模型作为备份;
  • 训练参数:5 个训练历元 – 梯度剪切为 0.1,以防止梯度爆炸 – 每 100 个训练步验证 – GPU 加速(CPU 训练时更改为 “cpu”,Mac M1/M2 时更改为 “mps”) – GPU 加速(CPU 训练时更改为 “cpu”,Mac M1/M2 时更改为 “mps。

提示:取消 fast_dev_run=True 以执行快速调试运行,在全面训练前验证您的设置。

2.3 初始化时空融合转换器

现在,我们将用超参数来配置 TFT 模型:

from pytorch_forecasting.metrics import QuantileLoss

tft = TemporalFusionTransformer.from_dataset(
    training_dataset,
    learning_rate=0.02,
    hidden_size=128,
    lstm_layers=2,
    attention_head_size=4,
    dropout=0.1,
    hidden_continuous_size=128,
    loss=QuantileLoss([0.3, 0.5, 0.7]), # 3 quantiles for uncertainty estimation
    log_interval=10,  # uncomment for learning rate finder and otherwise, e.g. to 10 for logging every 10 batches
    reduce_on_plateau_patience=100,
    optimizer="ranger"
)
print(f"Number of parameters in network: {tft.size()/1e3:.1f}k")
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs

Number of parameters in network: 3168.2k

我们的模型配置产生了约 320 万个参数,在模型容量和计算效率之间取得了平衡。让我们来看看每个关键超参数及其影响:

  • Learning Rate (0.02) — 模型收敛的关键参数。 我建议在第 2 个历元后执行学习率调度程序,可考虑使用 ReduceLROnPlateau 或 cosine annealing。
  • Hidden Size (128) — 隐藏大小 (128) – 控制模型学习模式的能力。 512 以上的值可能会导致消费级 GPU 的内存问题,目前的设置在性能与内存使用之间取得了良好的平衡。
  • LSTM Layers (2) — 根据经验,该任务的最佳层数,额外层数的收益递减。有助于捕捉短期和中期依赖关系。
  • Attention Heads (4) — 通过实验发现是最佳的,可让模型同时关注不同的时间模式。 更多的注意头并不能显著提高性能。

附加参数:

  • Dropout (0.1):防止过度拟合;
  • Hidden Continuous Size (128):匹配平衡架构的隐藏大小;
  • 优化器:使用 Ranger(RAdam + LookAhead);
  • Patience (100):如果使用高原调度程序,学习率降低前的步骤。

该模型的 320 万个参数表明,该网络规模适中,可以在大多数 GPU 上高效地进行训练,同时保持足够的容量来捕捉复杂的市场模式。

2.4 The Loss

我们的模型使用 Quantile Loss(量子损失法)和 three regression heads (α = 0.3、0.5、0.7)来生成预测区间。每个预测值 ŷ 和实际值 y 的损失函数定义为:

这一  loss function 创建了三个不同的预测目标:

  • Lower Bound (α = 0.3) – 目标是在 70% 的情况下低于实际价格 – 对低估的惩罚比对高估的惩罚更重 – 形成置信度下限。
  • Central Prediction (α = 0.5) – 等同于平均绝对误差 (MAE) – 提供我们的最佳点估计值 – 对高估和低估进行平衡惩罚。
  • Upper Bound (α = 0.7) – 目标是在 70% 的情况下高于实际价格 – 对高估的惩罚比对低估的惩罚更重 – 形成置信度上边界。

final loss 是所有量化值和时间步长的平均值,创建的模型可为每个预测价格提供点预测和置信区间。这种不确定性估计对交易应用特别有价值,因为它有助于量化预测置信度和潜在的价格范围。

三、模型训练

现在准备开始训练过程:

trainer.fit(
    tft,
    train_dataloaders=train_dataloader,
    val_dataloaders=val_dataloader,
)

这将打印可训练和冻结的模型参数,就像这样:

LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]

   | Name                               | Type                            | Params | Mode 
------------------------------------------------------------------------------------------------
0  | loss                               | QuantileLoss                    | 0      | train
1  | logging_metrics                    | ModuleList                      | 0      | train
2  | input_embeddings                   | MultiEmbedding                  | 1.0 K  | train
3  | prescalers                         | ModuleDict                      | 6.1 K  | train
4  | static_variable_selection          | VariableSelectionNetwork        | 201 K  | train
5  | encoder_variable_selection         | VariableSelectionNetwork        | 1.5 M  | train
6  | decoder_variable_selection         | VariableSelectionNetwork        | 410 K  | train
7  | static_context_variable_selection  | GatedResidualNetwork            | 66.3 K | train
8  | static_context_initial_hidden_lstm | GatedResidualNetwork            | 66.3 K | train
9  | static_context_initial_cell_lstm   | GatedResidualNetwork            | 66.3 K | train
10 | static_context_enrichment          | GatedResidualNetwork            | 66.3 K | train
11 | lstm_encoder                       | LSTM                            | 264 K  | train
12 | lstm_decoder                       | LSTM                            | 264 K  | train
13 | post_lstm_gate_encoder             | GatedLinearUnit                 | 33.0 K | train
14 | post_lstm_add_norm_encoder         | AddNorm                         | 256    | train
15 | static_enrichment                  | GatedResidualNetwork            | 82.7 K | train
16 | multihead_attn                     | InterpretableMultiHeadAttention | 41.2 K | train
17 | post_attn_gate_norm                | GateAddNorm                     | 33.3 K | train
18 | pos_wise_ff                        | GatedResidualNetwork            | 66.3 K | train
19 | pre_output_gate_norm               | GateAddNorm                     | 33.3 K | train
20 | output_layer                       | Linear                          | 387    | train
------------------------------------------------------------------------------------------------
3.2 M     Trainable params
0         Non-trainable params
3.2 M     Total params
12.673    Total estimated model params size (MB)
526       Modules in train mode
0         Modules in eval mode

3.1 模型架构概述

让我们来看看其核心组成部分有哪些:

  • 用于特征处理的输入嵌入和预分级器;
  • 用于静态、编码器和解码器输入的可变选择网络;
  • 具有注意力机制的 LSTM 编码器-解码器架构。

其总参数3.2M,最大的组件包括:

  • 编码器变量选择(~1.5M 个参数)
  • 解码器变量选择(~410K 个参数)
  • LSTM 编码器/解码器(各有 264K 个参数)

重要提示: 验证所有模块是否在最右边一列显示 “train mode(训练模式)”。在训练期间,任何模块处于 “eval mode(评估模式)”都可能表明存在配置问题。

3.2 用 TensorBoard 监控训练进度

1. 实时可视化训练:如果尚未安装,请安装 TensorBoard:

pip install tensorboard

2. 启动 TensorBoard:

tensorboard --logdir=lightning_logs

3.在浏览器中打开 http://localhost:6006,访问 TensorBoard 用户界面。

监测的关键指标:

  • Training loss(培训损失)
  • Validation loss(验证损失)
  • MAPE (平均绝对百分比误差)
  • Learning rate(学习率)
  • Quantile predictions (0.3, 0.5, 0.7)

通过 TensorBoard 界面,我们可以:

  • 实时跟踪模型收敛;
  • 比较不同的训练运行;
  • 及早发现潜在问题;
  • 导出指标用于报告。

提示:考虑使用不同的超参数设置多个 TensorBoard 运行,以并行比较其性能。

观察 TensorBoard 中的训练曲线,我们发现在 2000 步左右就出现了早期收敛。总结如下:

  • 训练和验证损失均达到高峰;
  • 快速趋平表明我们已经达到了局部最佳值;
  • 这种收敛模式在金融时间序列预测中非常典型。

平台初期反应的现象可能说明了几个问题:

  • 模型已经掌握了数据中可预测的模式;
  • 分钟级价格走势的可预测性可能存在上限;
  • 我们可能会受益于学习率调整。

潜在的改进措施:

  • 执行学习率调度程序,在达到峰值后降低学习率;
  • 尝试使用不同的优化器,如 AdaBelief 或带有热身功能的 Adam;
  • 考虑增加更多的功能或以不同的方式设计现有功能;
  • 调整量化损失参数,关注不同的预测区间。

四、模型评估


4.1 加载验证

可以通过两种方式加载性能最佳的模型:

# Option 1: Load best model from training
model_path = checkpoint_callback.best_model_path

# Option 2: Load specific checkpoint
model_path = "path_to_checkpoint.ckpt"

# Load the model
best_tft = TemporalFusionTransformer.load_from_checkpoint(model_path)

我们可以在验证集和训练集的最后一个样本上调用该模型,并计算 MAPE(平均绝对误差百分比):

from pytorch_forecasting import MAPE

test_dataset = TimeSeriesDataSet.from_dataset(training_dataset, df_test.reset_index(), predict=True, stop_randomization=True)
test_dataloader = test_dataset.to_dataloader(train=False, batch_size=batch_size, num_workers=0)
predictions = best_tft.predict(test_dataloader, return_y=True, trainer_kwargs=dict(accelerator="cuda"))
print(f"test set MAPE: {MAPE()(predictions.output, predictions.y)}")
print(f"validation set MAPE: {MAPE()(best_tft.predict(val_dataloader, return_y=True).output, best_tft.predict(val_dataloader, return_y=True).y)}")
test set MAPE: 0.02121799625456333
validation set MAPE: 0.01347196102142334

结果输出了验证  MAPE: 1.34%和测试 MAPE: 2.12%。验证集和测试集之间的性能差距(约 0.8%)可能是由于:

  • 训练数据的时间距离;
  • 测试期内市场制度的潜在变化;
  • 验证样本量有限(10 个实例)。

4.2 如何评估模型

还记得 TFT 的最大优势之一是其可解释性吗?下面我们就来看看它是如何发挥作用的。我们可以直观地看到每个时间步在注意力中的重要性,以及静态和动态变量的重要性:

raw_predictions = best_tft.predict(val_dataloader, mode="raw", return_x=True)
interpretation = best_tft.interpret_output(raw_predictions.output, reduction="sum")
best_tft.plot_interpretation(interpretation)

静态变量的重要性图示:

动态编码器变量(未知数)的重要性图示:

动态解码器变量(已知)的重要性图示:

特征理解:

  • 大多数工程特征显示出对预测的贡献。
  • 值得注意的例外情况:is_earnings_day 由于很少出现,其重要性较低。
  • 建议针对特定事件(如收益日)建立专门模型的可能性。

改进建议:

  • 考虑针对不同的市场条件采用不同的模式。
  • 增加验证集的大小,以获得更可靠的指标。
  • 调查罕见但重要事件的特征工程。
  • 监测培训期和测试期之间的概念偏移。

五、预测可视化

为了直观地显示模型的预测结果,我们首先要重新配置测试数据集,使其包括所有timesteps,而不仅仅是最后一个timesteps:

test_dataset = TimeSeriesDataSet.from_dataset(training_dataset, df_test.reset_index(), predict=False, stop_randomization=True)
test_dataloader = test_dataset.to_dataloader(train=False, batch_size=batch_size, num_workers=0)

5.1 利用预测区间创建可视化

下面的函数可以通过蜡烛图和不确定性带直观地显示我们的预测:

for i in range(5):
    symbol = "TSLA"
    first_test_time_idx = test_dataset.filter(lambda x: x.symbol == symbol)[0][0]["encoder_time_idx_start"].item()
    first_test_time_idx += np.random.randint(0, df_test.time_idx.max() - first_test_time_idx)
    date = df_test[df_test["time_idx"] == first_test_time_idx]["date"].values[0]
    first_predict_point_time_idx = first_test_time_idx + min_encoder_length
    prediction = best_tft.predict(
        test_dataset.filter(lambda x: (x.symbol == symbol) & (x.time_idx_first_prediction == first_predict_point_time_idx)),
        mode="raw",
        return_x=True,
    )
    candles = df_test[(df_test["symbol"] == symbol) & (df_test["time_idx"] >= first_test_time_idx) & (df_test["time_idx"] < first_predict_point_time_idx+max_prediction_length)]
    fig, ax = plt.subplots(figsize=(12, 6))
    ax.set_title(f"{symbol} stock price prediction on {date}")
    prediction_quantile_30 = prediction.output.prediction[:, :, 0].cpu().numpy().squeeze(0)
    prediction_quantile_50 = prediction.output.prediction[:, :, 1].cpu().numpy().squeeze(0)
    prediction_quantile_70 = prediction.output.prediction[:, :, 2].cpu().numpy().squeeze(0)
    adjust_predictions = prediction_quantile_50[0] - candles.iloc[-max_prediction_length]["open"]
    prediction_quantile_50 -= adjust_predictions
    prediction_quantile_30 -= adjust_predictions
    prediction_quantile_70 -= adjust_predictions
    quantile_30 = np.concatenate([np.array([np.nan]*min_encoder_length), prediction_quantile_30])
    quantile_50 = np.concatenate([np.array([np.nan]*min_encoder_length), prediction_quantile_50])
    quantile_70 = np.concatenate([np.array([np.nan]*min_encoder_length), prediction_quantile_70])
    addplots = [
        mpf.make_addplot(quantile_50, type="line", color="orange", width=2, ax=ax),  # Middle line bold
    ]
    
    mpf.plot(
        candles,
        ax=ax,
        type="candle",
        addplot=addplots,
    )
    
    ax.fill_between(
        range(len(quantile_50)),  # x values
        quantile_30,  # bottom quantile (30)
        quantile_70,  # top quantile (70)
        color="orange", alpha=0.3  # fill color and transparency
    )
    plt.show()


5.2 理解可视化

图表显示了如下信息需要大家关注:

  • 编码器期间的历史蜡烛图。
  • 橙线显示预测中值(0.5 量级)。
  • 橙色阴影区域显示预测区间(0.3 至 0.7 量级)。

重要的模型限制:我们所采用的预测调整是针对一个根本问题的临时变通方法,即模型倾向于预测与最后已知价格脱节的第一个时间步值。

虽然这种调整使我们的可视化效果更具可解释性,但它掩盖了一个需要解决的训练问题。这里有一些潜在的解决方案可供探索:

  • 训练改进:增加连续性损失项,以惩罚编码器和解码器之间的巨大差距 – 增加损失函数中第一步预测误差的权重 – 在解码器中将最后已知价格作为特殊功能包括在内
  • 结构改进:增加上一个编码器时间步的剩余连接。

六、回测基于量值的交易策略


6.1 生成完整的测试集预测

首先需要对整个测试集进行预测:

# Generate predictions (note: this is computationally intensive)
predictions = best_tft.predict(
    test_dataloader,
    mode="quantiles",
    return_index=True,
    trainer_kwargs=dict(accelerator="cuda")
)
# Create DataFrame with predictions
predictions_df = pd.DataFrame({
    'symbol': predictions.index['symbol'],     # the symbol column from index_df
    'time_idx': predictions.index['time_idx'],   # the time index column from index_df
    'predictions': list(predictions.output.cpu().numpy())   # store each [10, 3] array as a list in the DataFrame
})

# Adjust predictions to match current price levels
def adjust_predictions(row):
    if type(row["predictions"]) == float:
        return row
    adjust_scale = row["predictions"][0][1] - row["open"]
    row["predictions"] = row["predictions"] - adjust_scale

    return row

# Process predictions
df_index = df_test.index
predictions_df = df_test.merge(predictions_df, on=['symbol', 'time_idx'], how='left').set_index(df_index)
predictions_df = predictions_df.apply(adjust_predictions, axis=1)
predictions_df = predictions_df[~predictions_df["predictions"].isna()]

# Calculate predicted and actual changes
predictions_df["change_prediction_q_50"] = predictions_df["predictions"].apply(lambda x: x[-1][1] - x[0][1])
predictions_df["change_prediction_q_30"] = predictions_df["predictions"].apply(lambda x: x[-1][0] - x[0][0])
predictions_df["change_prediction_q_70"] = predictions_df["predictions"].apply(lambda x: x[-1][2] - x[0][2])
predictions_df["change_actual"] = predictions_df["close"].shift(-10) - predictions_df["open"]
predictions_df["direction_matches"] = predictions_df.apply(lambda x: np.sign(x["change_prediction_q_50"]) == np.sign(x["change_actual"]), axis=1)
direction_accuracy = predictions_df["direction_matches"].mean()
print(f"Directional accuracy: {direction_accuracy}")
Directional accuracy:(方向精度): 0.45370907583920433

45.37% 的方向准确率表明,仅仅使用简单的方向预测是不够的。我们将利用模型的量化预测来创建一个更复杂的策略。

6.2 交易策略设计

我们的策略是通过量化预测来利用模型的不确定性估计,其买入条件如下:

  • 多头入场:如果过去 5 个预测分钟内任何 0.3 分位数 > 当前价格 – 止盈:最高 0.5 分位数价格 – 止损:最低 0.3 分位数价格;
  • 做空入场:如果过去 5 分钟预测中的任何 0.7 分位数 < 当前价格 – 止盈:最低 0.5 分位数价格 – 止损: 最高 0.7 分位数价格。

退出条件为:

  • Hit take profit
  • Hit stop loss
  • Time-based exit after 10 minutes(10 分钟后定时退出)

我不打算在这里提供回溯测试代码,因为它实在太长了。但根据我之前创建的 predictions_df,这很容易实现,其中每一行都是一个条形图,有一列是预测的未来 10 分钟的数据。

6.3 回溯测试结果

Portfolio Statistics:
- Initial Capital: $10,000
- Total PnL: $1,547.89 (15.48% return)
- Average PnL per Stock: $154.79
- Maximum Drawdown: $223.97
- Sharpe Ratio: 0.11
- Win Rate: 35.81%
- Total Trades: 4,160

Individual Stock Performance:
1. PLUG: $493.77
2. SOFI: $287.35
3. FFIE: $211.26
4. NVDA: $199.52
5. NIO: $167.89
6. TSLA: $55.23
7. AMD: $49.51
8. F: $40.00
9. PLTR: $35.09
10. AAPL: $8.29

如上面的回测结果得出如下分析结果:

  • 战略回报率15.48%;
  • 买入并持有基准为7%;
  • 超额收益为 8.48%;
  • 胜率较低(35.81%),但总体回报率较高,表明仓位大小有效;
  • 不同股票的表现差异显著,增长型股票(PLUG、SOFI)表现最佳;
  • 其局限性为假定公开价格完全执行,且不包括交易费用。

6.4 交易可视化

下图为AAPL的亏损交易情况如图:

F美元的亏损交易情况如图:

在 $FFIE 交易中盈利的交易情况如图:

在 $TSLA 交易中盈利的交易情况如图:


七、项目总结


7.1 项目摘要

本系列探讨了如何利用时态融合转换器开发先进的算法交易系统,进行分钟级股价预测。通过将现代深度学习技术与传统交易理念相结合,我们创建了一种策略,其性能优于标准基准,同时还能提供可解释的预测。

7.2 主要成果

Data Engineering(数据工程)– 创建全面的分钟级股票价格数据集 – 开发丰富的功能集,包括: – 技术指标(ATR、EMA、RSI、SMA) – 市场动态(波动率、成交量概况) – 事件标记(收益日期) – 价格水平(支撑位/阻力位)。

Model Architecture(模型架构) – 成功实施了带有量化预测的 TFT – 通过多个预测头实现了有效的不确定性估计 – 利用模型的可解释性进行特征重要性分析 – 优化了金融时间序列的超参数。

Trading System(交易系统) – 利用基于量值的预测开发了一种新颖的策略 – 相对于 7% 的买入并持有基准,实现了 15.48% 的回报率 – 尽管方向准确率低于 50%,但仍产生了盈利交易 – 在多只股票上展示了有效性。

7.3 重要见解

模型行为:45% 的方向准确率凸显了点预测的局限性 – 事实证明,不确定性估计比方向预测更有价值 – 特征重要性与金融领域知识相一致。

交易表现:尽管胜率较低(35.81%),但策略仍能盈利 – 不同股票的表现差异显著 – 基于量值的进入/退出规则的有效性。

技术挑战:早期训练收敛表明模型容量有限 – 编码器和解码器之间的价格水平不连续性 – 分钟级预测的计算要求。

7.4 未来发展方向

增强架构:在编码器和解码器之间增加连续性约束 – 探索更大的模型容量。

训练优化:利用学习率调度延长训练时间 – 实施自适应损失函数。

7.5 观点总结

本项目充分展现了现代深度学习技术与量化交易策略融合所蕴含的巨大潜力。尽管初步实施已取得令人鼓舞的成效,但模型架构与交易策略仍有广阔的优化余地。此外,量化方法的成功实践揭示了一个重要趋势:在金融应用中,重视不确定性估计而非单一的点预测可能更具实际价值。

  • 股票价格预测的挑战:金融时间序列预测的不确定性,特别是在分钟级别的股票价格预测中,模型需要能够处理收益率的统计特性和极小的波动。
  • 模型训练与评估:大家可以看到使用PyTorch Lightning进行模型训练的便利性,我通过TensorBoard实时监控训练指标,以及如何评估模型性能的重要性。
  • 特征工程的重要性:通过调整特征和目标变量,改进了模型的预测能力,并强调了特征选择和预处理在模型性能中的作用。
  • 模型的可解释性:文章通过分析特征重要性和模型的注意力机制,展示了TFT模型的可解释性,帮助理解模型预测的依据。
  • 交易策略的实践应用:通过回测,我给大家展示了如何将模型预测应用于交易策略,并分析了策略的回报和风险,如最大回撤和胜率。
  • 模型和策略的改进方向:讨论模型训练和交易策略可能的改进措施,包括学习率调整、特定市场条件的模型、以及更大规模的验证集。
  • 项目的整体评估:总结项目的成就和挑战,并提出了未来可能的研究方向,如模型架构的增强和训练优化。

八、感谢

我想借此机会感谢所有从始至终关注这个系列的人。当我开始分享这个项目时,我从未想到会得到如此热烈的回应和支持。关注者、评论和留言的数量着实让人惭愧。

感谢您阅读到最后,希望本文能给您带来新的收获。祝您投资顺利!如果对文中的内容有任何疑问,请给我留言,必复。


本文内容仅限技术探讨和学习,不构成任何投资建议

Published inAI&Invest专栏

Be First to Comment

发表回复