English Deutsch
preview
データサイエンスとML(第48回):Transformerは取引において重要なのか

データサイエンスとML(第48回):Transformerは取引において重要なのか

MetaTrader 5トレーディング |
24 0
Omega J Msigwa
Omega J Msigwa

内容


Transformerモデルとは

ディープラーニングにおいて、Transformerとはマルチヘッドアテンション機構に基づくニューラルネットワークアーキテクチャです。このアーキテクチャは、Googleの8名の研究者によって執筆された2017年の論文「Attention Is All You Need」で初めて提案されました。この論文では、Bahdanauらが2014年に提案したアテンション機構を発展させた新しいモデルが紹介され、現代AIの基盤技術の一つとして広く認識されています。

Transformerはさまざまな分野で顕著な成果を上げています。自然言語処理(NLP)の分野では、機械翻訳、感情分析、文章要約などで高い性能を示しています。また、画像処理分野にも応用され、画像分類や物体検出などのビジョンタスクでも成功を収めています。さらに、時系列分析への適用も進んでおり、長期依存関係を捉える能力により、株価予測や気象予測などのシーケンシャルデータの予測タスクでも有効性が示されています。

なお、本記事で使用する「Transformer」という用語は、単一の固定モデルではなく、アテンション機構を基盤とする一連のアーキテクチャ群を指します。

Transformerは、再帰構造ではなくアテンション機構を中心に構築されたニューラルネットワークアーキテクチャの総称です。従来の系列モデルとは異なり、系列内の各要素が他のすべての要素に直接注意(Attention)を向けることができるため、長距離依存関係のモデリングと並列計算による効率的な学習が可能になります。

もともとは自然言語処理向けに開発されましたが、その後、画像認識や時系列予測にも応用されるようになりました。特に時系列分野では、将来既知の入力、マルチホライズン予測(Multi-Horizon Forecasting)、データ量の制約といったドメイン固有の課題に対応するため、さまざまな派生アーキテクチャが提案されています。


背景

自然言語処理(NLP)の分野では、RNN (Recurrent Neural Network)やLSTM (Long Short-Term Memory)といった逐次モデルが広く利用されてきました。これらのモデルは機械翻訳や言語モデリングなどのタスクで優れた性能を示してきましたが、系列データを処理する際に構造上の制約を抱えています。

主な制約は以下の通りです。


従来の逐次モデルの構造 

計算時間が長い

従来の逐次モデルでは、現在の時刻の情報を解釈するために、前の時刻までの情報を入力として利用します。図1の例にあるように、隠れ特徴量が段階的に処理される構造では、計算時間は入力長に対して線形にスケールします。そのため、系列が長くなるにつれて非効率性が増大します。

長距離依存関係の学習の難しさ

リカレントニューラルネットワーク(RNN)モデルはその逐次的な性質により、系列内の離れた要素間の依存関係を捉えることが困難です。LSTMやGRUといったアーキテクチャはこの問題を緩和するために導入されましたが、それでも固定サイズの隠れ状態表現に依存しているため、極めて長い系列を効果的に処理する能力には限界があります。

Googleの研究者たちは、再帰構造を完全に排除し、代わりにアテンション機構に基づいて入力および出力要素間のグローバルな依存関係を構築するTransformerモデルを提案しました。このアーキテクチャは、上述の制約を効果的に解決し、モデルのスケーラビリティと効率性を向上させることを目的としています。ただし、時系列など特定のドメインでは、近年の派生モデルにおいて再び再帰構造が導入される場合もあります。


Transformerモデルアーキテクチャ

自己注意機構(Self-Attention Mechanism)

従来のリカレントニューラルネットワーク(RNN)や長短期記憶(LSTM)ニューラルネットワークとは異なり、Transformerは自己注意機構に依存しており、入力系列内の異なる部分の重要度を重み付けしながら予測をおこなうことを可能にします。

並列化

Transformerは学習時の並列化を可能にしますが、一部の派生モデルでは推論時に逐次的処理が残る場合もあります。これにより、RNNのような逐次モデルと比較してより効率的な学習が可能となり、学習時間の短縮にもつながります。

エンコーダ・デコーダ構造

本モデルはエンコーダとデコーダから構成されます。エンコーダは入力系列を処理し文脈情報を抽出し、デコーダは出力系列を生成します。

マルチヘッドアテンション

自己注意機構は複数のヘッドへと拡張されており、入力系列の異なる側面に同時に注目することが可能になります。これにより、複雑な関係性をより適切に捉えることができます。

固定サイズの隠れ状態とは異なり、アテンション機構は系列内の任意の位置から関連情報を動的に選択することが可能です。

位置エンコーディング

Transformerは入力系列の順序を本質的には理解しません。そのため、トークンの位置情報を提供するために位置エンコーディングが入力埋め込みに加えられます。

なお、「Transformer」という用語は常に単一のモデルを指すわけではなく、アーキテクチャは用途によって大きく異なります。例は以下の通りです。

モデル アーキテクチャ 主な用途/ドメイン
オリジナルTransformer エンコーダ・デコーダ 機械翻訳、sequence-to-sequence処理
BERT (Bidirectional Encoder Representations from Transformers) エンコーダのみ テキスト理解(分類、NER、埋め込み)
GPT (Generative Pre-trained Transformer) デコーダのみ テキスト生成、言語モデリング、コード生成
ViT (Vision Transformer) エンコーダのみ 画像分類、視覚表現学習
TFT (Temporal Fusion Transformer)  ハイブリッド(LSTM + アテンション)  時系列予測、財務データ、ビジネスデータ

Transformerがどのように構成されているかに関する詳細情報やその他の内容については、本記事の末尾にある参考文献セクションをご参照ください。

数あるTransformerモデルの中でも、TFTは時系列予測に適したモデルです。本記事では、TFTの概要を説明した後、その実装方法と市場予測への応用について解説します。


TFT (Temporal Fusion Transformer)モデル 

TFTは次のように定義されています。

TFTは、マルチホライズン時系列予測のために設計された、最先端のアテンションベースの深層学習モデルです。

このモデルはGoogleとオックスフォード大学の研究者らによって提案されたもので、記事中では2021年のものとして扱っています。LSTM (Long Short-Term Memory)ベースのエンコーダ・デコーダ構造と自己注意機構を組み合わせることで、高性能かつ解釈可能な予測を実現するよう設計されています。

TFTの理論およびアーキテクチャの詳細については、https://medium.com/dataness-ai/understanding-temporal-fusion-transformer-9a7a4fcde74bをご覧ください。

PyTorch-forecastingフレームワークを使用してこのモデルを実装しましょう。

まず、このプロジェクトで使用するすべての依存関係(本記事末尾に添付されているzipファイル内のrequirements.txtファイルに含まれています)をPython仮想環境にインストールします。

pip install -r requirements.txt


TFTモデル用のデータ準備

まず、MetaTrader 5ターミナルからデータをインポートします。

import MetaTrader5 as mt5

# Get rates from the MetaTrader5 app

if not mt5.initialize():
    print("initialize() failed, error code =", mt5.last_error())
    quit()

symbol = "EURUSD"
rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_M15, 0, 10000)
rates_df = pd.DataFrame(rates)

rates_df["time"] = pd.to_datetime(rates_df["time"], unit="s")
data = pd.concat([rates_df, get_features(rates_df)], axis=1)

特徴量エンジニアリング

受け取った特徴量すべてを使う必要はありません。不要と思われる特徴量は削除します。

rates_df.drop(columns=[
            "spread",
            "real_volume"
        ], inplace=True)

この操作後の残りの特徴量も十分ではないので、DataFrameに新しい特徴量を追加します。

以下は、さまざまな特徴量(特徴量エンジニアリングによる新しい変数)を導入するのに役立つシンプルなクラスです。

features.py

  1. 日付と時刻に基づく特徴量
    features.py
    import pandas as pd
    from ta.trend import sma_indicator, ema_indicator, macd_diff, macd_signal
    from ta.momentum import stochrsi_k, stochrsi_d, rsi
    from ta.volatility import bollinger_hband, bollinger_lband
    
    class FeatureEngineer:
        
        # date/time features
        
        @staticmethod
        def hour(date_series: pd.Series) -> pd.Series:
            return date_series.dt.hour
        
        @staticmethod
        def dayofweek(date_series: pd.Series) -> pd.Series:
            return date_series.dt.dayofweek
        
        @staticmethod
        def dayofmonth(date_series: pd.Series) -> pd.Series:
            return date_series.dt.day
        
        @staticmethod
        def month(date_series: pd.Series) -> pd.Series:
            return date_series.dt.month
  2. トレンドフォロー指標
        # trend following indicators
        
        @staticmethod
        def sma(price: pd.Series, window: int=20) -> pd.Series:
            return sma_indicator(price, window)
    
        @staticmethod
        def ema(price: pd.Series, window: int=20) -> pd.Series:
            return ema_indicator(price, window)
    
        @staticmethod
        def macd_diff(price: pd.Series, window_slow: int=26, window_fast: int=12, window_signal: int=9) -> pd.Series:
            return macd_diff(price, window_slow=window_slow, window_fast=window_fast, window_sign=window_signal)
    
        @staticmethod
        def macd_signal(price: pd.Series, window_slow: int=26, window_fast: int=12, window_signal: int=9) -> pd.Series:
            return macd_signal(price, window_slow=window_slow, window_fast=window_fast, window_sign=window_signal
  3. モメンタム指標
        # momentum indicators
        
        @staticmethod
        def rsi(price: pd.Series, window: int=14) -> pd.Series:
            return rsi(price, window)
        
        @staticmethod
        def stochrsi_k(price: pd.Series, window: int=14, smooth1: int=3, smooth2: int=3) -> pd.Series:
            return stochrsi_k(price, window=window, smooth1=smooth1, smooth2=smooth2)
        
        @staticmethod
        def stochrsi_d(price: pd.Series, window: int=14, smooth1: int=3, smooth2: int=3) -> pd.Series:
            return stochrsi_d(price, window=window, smooth1=smooth1, smooth2=
  4. ボラティリティ指標
        # volatility indicators
        
        @staticmethod
        def bollinger_hband(price: pd.Series, window: int=20, window_dev: int=2) -> pd.Series:
            return bollinger_hband(price, window=window, window_dev=window_dev)
    
        @staticmethod
        def bollinger_lband(price: pd.Series, window: int=20, window_dev: int=2) -> pd.Series:
            return bollinger_lband(price, window=window, window_dev=window_dev)

このクラスのメソッドは、同じクラス内のget_allという単一の静的関数内で呼び出されます。

    @staticmethod
    def get_all(data: pd.DataFrame) -> pd.DataFrame:
        
        return pd.DataFrame({
            "hour": FeatureEngineer.hour(data["time"]),
            "dayofweek": FeatureEngineer.dayofweek(data["time"]),
            "dayofmonth": FeatureEngineer.dayofmonth(data["time"]),
            "month": FeatureEngineer.month(data["time"]),
            "sma_20": FeatureEngineer.sma(data["close"]),
            "ema_20": FeatureEngineer.ema(data["close"]),
            "macd_diff": FeatureEngineer.macd_diff(data["close"]),
            "macd_signal": FeatureEngineer.macd_signal(data["close"]),
            "rsi": FeatureEngineer.rsi(data["close"]),
            "stochrsi_k": FeatureEngineer.stochrsi_k(data["close"]),
            "stochrsi_d": FeatureEngineer.stochrsi_d(data["close"]),
            "bollinger_hband": FeatureEngineer.bollinger_hband(data["close"]),
            "bollinger_lband": FeatureEngineer.bollinger_lband(data["close"]),
        })

このクラスを使用して、特徴量で構成された新しいPandas-DataFrameを取得します。

new_features = features.FeatureEngineer.get_all(rates_df)

以下が実行結果です。

(.env) C:\Users\Omega Joctan\OneDrive\mql5 articles\Data Science and ML\Part 48\TFT>python train.py
      hour  dayofweek  dayofmonth  month    sma_20    ema_20  macd_diff  macd_signal        rsi  stochrsi_k  stochrsi_d  bollinger_hband  bollinger_lband
0       20          3          21      8       NaN       NaN        NaN          NaN        NaN         NaN         NaN              NaN              NaN
1       20          3          21      8       NaN       NaN        NaN          NaN        NaN         NaN         NaN              NaN              NaN
2       20          3          21      8       NaN       NaN        NaN          NaN        NaN         NaN         NaN              NaN              NaN
3       20          3          21      8       NaN       NaN        NaN          NaN        NaN         NaN         NaN              NaN              NaN
4       21          3          21      8       NaN       NaN        NaN          NaN        NaN         NaN         NaN              NaN              NaN
...    ...        ...         ...    ...       ...       ...        ...          ...        ...         ...         ...              ...              ...
9985    20          4          16      1  1.160491  1.160265  -0.000107    -0.000404  41.591063    0.303937    0.316652         1.162598         1.158383
9986    20          4          16      1  1.160384  1.160213  -0.000068    -0.000421  43.685146    0.499779    0.367431         1.162418         1.158349
9987    20          4          16      1  1.160269  1.160151  -0.000047    -0.000433  42.436779    0.698607    0.500775         1.162214         1.158323
9988    21          4          16      1  1.160154  1.160067  -0.000046    -0.000444  40.194753    0.774743    0.657710         1.162051         1.158257
9989    21          4          16      1  1.160052  1.160010  -0.000026    -0.000450  42.452830    0.687333    0.720228         1.161864         1.158240

[9990 rows x 13 columns]

その後、両方のDataFrameを結合し、最終的なモデル学習に使用するより大きなDataFrameを作成します。

data = pd.concat([rates_df, new_features], axis=1) # concatenate dataframes

以下が実行結果です。

                    time     open     high      low    close  tick_volume  ...  macd_signal        rsi  stochrsi_k  stochrsi_d  bollinger_hband  bollinger_lband
0    2025-08-21 20:00:00  1.16112  1.16171  1.16112  1.16170          642  ...          NaN        NaN         NaN         NaN              NaN              NaN
1    2025-08-21 20:15:00  1.16170  1.16173  1.16126  1.16136          557  ...          NaN        NaN         NaN         NaN              NaN              NaN
2    2025-08-21 20:30:00  1.16136  1.16167  1.16129  1.16158          414  ...          NaN        NaN         NaN         NaN              NaN              NaN
3    2025-08-21 20:45:00  1.16158  1.16187  1.16149  1.16151          513  ...          NaN        NaN         NaN         NaN              NaN              NaN
4    2025-08-21 21:00:00  1.16151  1.16152  1.16103  1.16106          473  ...          NaN        NaN         NaN         NaN              NaN              NaN
...                  ...      ...      ...      ...      ...          ...  ...          ...        ...         ...         ...              ...              ...
9985 2026-01-16 20:15:00  1.15911  1.15954  1.15905  1.15951          407  ...    -0.000404  41.591063    0.303937    0.316652         1.162598         1.158383
9986 2026-01-16 20:30:00  1.15951  1.15973  1.15936  1.15972          289  ...    -0.000421  43.685146    0.499779    0.367431         1.162418         1.158349
9987 2026-01-16 20:45:00  1.15972  1.15994  1.15955  1.15956          447  ...    -0.000433  42.436779    0.698607    0.500775         1.162214         1.158323
9988 2026-01-16 21:00:00  1.15956  1.15967  1.15921  1.15927          457  ...    -0.000444  40.194753    0.774743    0.657710         1.162051         1.158257
9989 2026-01-16 21:15:00  1.15927  1.15958  1.15915  1.15947          290  ...    -0.000450  42.452830    0.687333    0.720228         1.161864         1.158240

[9990 rows x 19 columns]

目的変数の作成

典型的な教師あり機械学習では、モデルが他の特徴量(説明変数)を用いて予測すべき目的変数が必要になります。

今回のような時系列問題では、リターンをターゲット変数として使用します。

data["returns"] = data["close"].pct_change()

時系列データセットオブジェクトの作成

PyTorch-forecastingでは、モデル用のデータを、TimeSeriesDatasetと呼ばれるオブジェクトに格納する必要があります。

そのためには、TimeSeriesDatasetに渡す前に、DataFrame内にtime_idxという列を用意する必要があります。

data["time_idx"] = data.index
data.drop(columns=["time"], inplace=True)

これは、データ内の時間インデックスを表す整数型の列です。この列はサンプルの系列を決定するために使用されます。観測値に欠損がない場合、時間インデックスは各サンプルごとに+1ずつ増加する必要があります。各系列の最初のtime_idxは必ずしも0である必要はなく、任意の値を取ることができます。

time_idx列をDataFrameに追加した後は、元の時間(datetime)を含む列は削除する必要があります(TFTはdatetime変数を直接使用しないためです)。

data.drop(columns=["time"], inplace=True)
max_prediction_length = 6
max_encoder_length = 24
training_cutoff = data["time_idx"].max() - max_prediction_length

training = TimeSeriesDataSet(
    data[lambda x: x.time_idx <= training_cutoff],
    time_idx="time_idx",
    target="returns",
    group_ids=["symbol"],
    min_encoder_length=max_encoder_length // 2,  # keep encoder length long (as it is in the validation set)
    max_encoder_length=max_encoder_length,
    min_prediction_length=1,
    max_prediction_length=max_prediction_length,
    static_categoricals=["symbol"],
    # time_varying_known_categoricals=[],
    
    time_varying_known_reals=[
                            "hour",
                            "dayofweek",
                            "dayofmonth",
                            "month",
                            "time_idx", 
                            "stochrsi_k",
                            "stochrsi_d",
                            "rsi",
                            "macd_diff",
                            ],
    
    time_varying_unknown_categoricals=[],
    time_varying_unknown_reals=[
        "open",
        "high",
        "low",
        "close",
        "tick_volume",
        "ema_20",
        "sma_20",
        "bollinger_hband",
        "bollinger_lband"
    ],
    
    target_normalizer=GroupNormalizer(
        groups=["symbol"], transformation="softplus"
    ),  # use softplus and normalize by group
    
    add_target_scales=True,
    add_encoder_length=True,
)

このオブジェクトは多くの変数を受け取ります。以下の表はそれらのいくつかを示しています。(詳細はこちら)。 

変数 詳細
max_encoder_length モデルが参照できる過去の期間
max_prediction_length モデルが予測しなければならない未来の期間
time_varying_known_reals 時間とともに変化し、かつ将来の値が既知である連続変数のリスト 

今回のケースでは、DataFrameに含まれる定常指標および時間特徴量を選択しています。 
time_varying_unknown_reals 将来の値が未知であり、時間とともに変化する連続変数のリスト。実際の目的変数もここに含めます。

このため、open、high、low、closeなどの特徴量を選択しています。これらは将来の値が不確実な特徴量です。
group_ids データ内の時系列インスタンスを識別するための列名リスト。group_idsはtime_idxと組み合わせて1つのサンプルを定義します。単一の時系列しかない場合は、一定の値を持つ列名を指定します。

Pandas DataFrameにはシンボルが1つしか収集されていないため、割り当てるグループは現在のシンボルのみとなります。
data["symbol"] = "EURUSD"
グループは、異なる通貨ペア(銘柄)や時間足など、複数の時系列データを表すこともあります。

最後に、学習用オブジェクトを模倣して検証データを作成します。

#  (predict=True) which means to predict the last max_prediction_length points in time for each series

validation = TimeSeriesDataSet.from_dataset(
    training, data, predict=True, stop_randomization=True
)

最後に、学習データと検証データの両方に対して、 PyTorchデータローダーを作成します(データローダーは、モデルにデータを供給するのに適しています)。

batch_size = 128  # set this between 32 to 128
train_dataloader = training.to_dataloader(
    train=True, batch_size=batch_size, num_workers=0
)
val_dataloader = validation.to_dataloader(
    train=False, batch_size=batch_size * 10, num_workers=0
)


TFTの学習

PyTorch Lightningを使用してモデルをトレーニングします。以下はトレーニングタスク用のLightningトレーナーです。

pl.seed_everything(42) # random seed for reproducibility

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

# configure network and trainer
early_stop_callback = EarlyStopping(
    monitor="val_loss", min_delta=1e-4, patience=10, verbose=False, mode="min"
)

trainer = pl.Trainer(
    max_epochs=50,
    accelerator="cpu",
    enable_model_summary=True,
    gradient_clip_val=0.1,
    limit_train_batches=50,  # comment in for training, running validation every 30 batches
    # fast_dev_run=True,  # comment in to check that networkor dataset has no serious bugs
    callbacks=[lr_logger, early_stop_callback],
    logger=logger,
)

モデルにとって最適な学習率を事前に知る方法はないため、適切な学習率を探索することにします。

まず、最適な学習率を見つけるために使用するTFTモデルのインスタンスを作成します。

tft = TemporalFusionTransformer.from_dataset(
    training,
    # not meaningful for finding the learning rate but otherwise very important
    learning_rate=0.03,
    hidden_size=8,  # most important hyperparameter apart from learning rate
    # number of attention heads. Set to up to 4 for large datasets
    attention_head_size=2,
    dropout=0.1,  # between 0.1 and 0.3 are good values
    hidden_continuous_size=8,  # set to <= hidden_size
    loss=metrics.QuantileLoss(),
    optimizer="ranger",
    # reduce learning rate if no improvement in validation loss after x epochs
    # reduce_on_plateau_patience=1000,
)

最適な学習率を見つけます。

res = Tuner(trainer).lr_find(
    tft,
    train_dataloaders=train_dataloader,
    val_dataloaders=val_dataloader,
    max_lr=10.0,
    min_lr=1e-6,
)

optimal_lr = res.suggestion()

print(f"suggested learning rate: {optimal_lr}")
fig = res.plot(show=False, suggest=True)

plots_path = os.path.join(outputs_dir, "Plots")
os.makedirs(plots_path, exist_ok=True)

fig.savefig(os.path.join(plots_path, "lr_finder.png"))

以下が実行結果です。

Finding best initial lr:  91%|█████████████████████████████████████████████████████████████████████████████████████████████▋         | 91/100 [00:41<00:04,  2.20it/s]
LR finder stopped early after 91 steps due to diverging loss.
Restoring states from the checkpoint path at C:\Users\Omega Joctan\OneDrive\mql5 articles\Data Science and ML\Part 48\TFT\.lr_find_6df0be87-4347-4325-98f1-3a2b5a244c46.ckpt
Restored all states from the checkpoint at C:\Users\Omega Joctan\OneDrive\mql5 articles\Data Science and ML\Part 48\TFT\.lr_find_6df0be87-4347-4325-98f1-3a2b5a244c46.ckpt
Learning rate set to 1.3182567385564071e-05
suggested learning rate: 1.3182567385564071e-05

最適な学習率が得られたら、新しいモデルインスタンスを作成し、その学習率の値を用いて学習させます。

tft = TemporalFusionTransformer.from_dataset(
    training,
    learning_rate=optimal_lr,
    hidden_size=16,
    attention_head_size=2,
    dropout=0.1,
    hidden_continuous_size=8,
    loss=metrics.QuantileLoss(),
    log_interval=10,  # uncomment for learning rate finder and otherwise, e.g., to 10 for logging every 10 batches
    optimizer="ranger",
    reduce_on_plateau_patience=4,
)

print(f"Number of parameters in network: {tft.size() / 1e3:.1f}k")

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

tft_predictions = tft.predict(val_dataloader, return_y=True)
print("TFT MAE: ", metrics.MAE()(tft_predictions.output, tft_predictions.y))

以下が実行結果です。

Epoch 4: 100%|██████████████████████████████████████████| 50/50 [01:04<00:00,  0.77it/s, v_num=1, train_loss_step=0.000846, val_loss=0.0013, train_loss_epoch=0.00092]`Trainer.fit` stopped: `max_epochs=5` reached.                                                                                                                          
Epoch 4: 100%|██████████████████████████████████████████| 50/50 [01:05<00:00,  0.77it/s, v_num=1, train_loss_step=0.000846, val_loss=0.0013, train_loss_epoch=0.00092]
💡 Tip: For seamless cloud uploads and versioning, try installing [litmodels](https://pypi.org/project/litmodels/) to enable LitModelCheckpoint, which syncs automatically with the Lightning model registry.
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
TFT MAPE:  tensor(1.2644)+

モデルによる予測値と実際の値を比較して可視化することで、モデルの予測値が真の値(元の値)と比べてどの程度の精度なのかを把握してみましょう。

best_model_path = trainer.checkpoint_callback.best_model_path
best_tft = TemporalFusionTransformer.load_from_checkpoint(best_model_path)

# raw predictions are a dictionary from which all kinds of information, including quantiles, can be extracted
raw_predictions = best_tft.predict(
    val_dataloader, mode="raw", return_x=True, trainer_kwargs=dict(accelerator="cpu")
)

n = raw_predictions.output.prediction.shape[0]
print(f"Plotting {n} predictions...")

for idx in range(n):
    fig = best_tft.plot_prediction(
        raw_predictions.x,
        raw_predictions.output,
        idx=idx,
        add_loss_to_title=True
    )
    
    fig.savefig(os.path.join(plots_path, f"tft_prediction_{idx}.png"))
    plt.close(fig=fig)

以下が実行結果です。

予測値は元の値にかなり近い結果となっているようです。

ただし、MAE(平均絶対誤差)という指標は単体では意味を持たず、比較対象があって初めて評価できます。そのため、他のモデルと比較する必要があります。そこで、ベースラインモデルを用いて確認します。

ベースラインモデル

ベースラインモデルとは、直前の既知のターゲット値をそのまま予測値として使用するシンプルなモデルです。これは、モデルが最低限上回るべき基準となる指標を提供します。

baseline_predictions = Baseline().predict(val_dataloader, return_y=True)
print("Baseline model MAE: ",metrics.MAE()(baseline_predictions.output, baseline_predictions.y))

上記のスクリプトを再度実行した結果、TFTモデルはベースラインモデルの約2倍の精度を達成しました。

TFT MAE:  tensor(0.0002)
Baseline model MAE:  tensor(0.0004)


TFTモデルの最適パラメータ探索

Transformerベースのモデルはニューラルネットワークの一種であり(内部にLSTMも含まれています)、他のニューラルネットワークと同様にハイパーパラメータに強く依存します。

そのため、問題に応じて最適なハイパーパラメータを見つけることが重要です。

ドキュメントによると、

PyTorch Forecastingではoptunaを用いたハイパーパラメータチューニングが標準でサポートされており、optimize_hyperparameters 関数を使ってTFTの最適化をおこなうことができます。

例は以下の通りです。

import pickle

from pytorch_forecasting.models.temporal_fusion_transformer.tuning import optimize_hyperparameters

# create study
study = optimize_hyperparameters(
    train_dataloader,
    val_dataloader,
    model_path="optuna_test",
    n_trials=200,
    max_epochs=50,
    gradient_clip_val_range=(0.01, 1.0),
    hidden_size_range=(8, 128),
    hidden_continuous_size_range=(8, 128),
    attention_head_size_range=(1, 4),
    learning_rate_range=(0.001, 0.1),
    dropout_range=(0.1, 0.3),
    trainer_kwargs=dict(limit_train_batches=30),
    reduce_on_plateau_patience=4,
    use_learning_rate_finder=False,  # use Optuna to find ideal learning rate or use in-built learning rate finder
)

# save study results - also we can resume tuning at a later point in time
with open("test_study.pkl", "wb") as fout:
    pickle.dump(study, fout)

# show best hyperparameters
print(study.best_trial.params)

scikit-learnやKerasといった他の機械学習用Pythonフレームワークとは異なり、PyTorchのモジュールではある程度手動でコードを書く必要があり、その結果として多くの処理を個別に実装しなければならないことがよくあります。これは、コーディング作業を煩雑にしたり、バグの原因になったりすることがあります。

そこで、作業をより簡単にするために、必要なコードをすべて一つのクラスにまとめてラップすることにします。

model.py

class TFTModel:
    def __init__(self, training: TimeSeriesDataSet, 
                train_dataloader: DataLoader, 
                val_dataloader: DataLoader,
                parameters: dict,
                loss: metrics=metrics.QuantileLoss(),
                trainer_max_epochs = 10):
        
        """
        Initialize the Temporal Fusion Transformer model with training and validation data.
        Args:
            training (TimeSeriesDataSet): The training dataset loader containing time series data
                for model training.
            parameters (dict): A dictionary containing hyperparameters for the model configuration:
                - learning_rate (float, optional): Learning rate for the optimizer. Default is 0.03.
                - hidden_size (int, optional): Size of hidden layers. Most important hyperparameter apart
                  from learning rate. Default is 8.
                - attention_head_size (int, optional): Number of attention heads. Set to up to 4 for
                  large datasets. Default is 2.
                - dropout (float, optional): Dropout rate for regularization. Values between 0.1 and 0.3
                  are recommended. Default is 0.1.
                - hidden_continuous_size (int, optional): Size of continuous hidden layers. Should be set
                  to <= hidden_size. Default is 8.
            loss (metrics): Loss function to be used for model training, e.g., QuantileLoss.
        Attributes:
            model (TemporalFusionTransformer): The initialized Temporal Fusion Transformer model with
                a given loss function and Ranger optimizer.
            trainer: PyTorch Lightning trainer instance configured for model training.
        """

        # configure network and trainer
        pl.seed_everything(42)

        self.train_dataloader = train_dataloader
        self.val_dataloader = val_dataloader
        self.training = training
        self.loss = loss
        
        self.model = self._create_model(parameters=parameters)
        self.trainer = self._create_trainer(max_epochs=trainer_max_epochs)

    def _create_model(self, parameters: dict) -> TemporalFusionTransformer:

        return TemporalFusionTransformer.from_dataset(
            self.training,
            # not meaningful for finding the learning rate but otherwise very important
            learning_rate=parameters.get("learning_rate", 0.03),
            hidden_size=parameters.get("hidden_size", 8),  # most important hyperparameter apart from learning rate
            # number of attention heads. Set to up to 4 for large datasets
            attention_head_size=parameters.get("attention_head_size", 2),
            dropout=parameters.get("dropout", 0.1),  # between 0.1 and 0.3 are good values
            hidden_continuous_size=parameters.get("hidden_continuous_size", 8),  # set to <= hidden_size
            loss=self.loss,
            optimizer="ranger",
            # reduce learning rate if no improvement in validation loss after x epochs
            # reduce_on_plateau_patience=1000,
        )
        
    def _create_trainer(self, max_epochs: int=50, grad_clip_val=0.1, limit_train_batches: int=50) -> pl.Trainer:
        
        lr_logger = LearningRateMonitor()  # log the learning rate
        logger = TensorBoardLogger("lightning_logs")  # logging results to a tensorboard

        # configure network and trainer
        early_stop_callback = EarlyStopping(
            monitor="val_loss", min_delta=1e-4, patience=10, verbose=False, mode="min"
        )

        return pl.Trainer(
            max_epochs=max_epochs,
            accelerator="cpu",
            enable_model_summary=True,
            gradient_clip_val=grad_clip_val,
            limit_train_batches=limit_train_batches,  # comment in for training, running validation every 30 batches
            # fast_dev_run=True,  # comment in to check that networkor dataset has no serious bugs
            callbacks=[lr_logger, early_stop_callback],
            logger=logger,
        )


    def find_optimal_lr(self, plot_output_dir: str,
                        max_lr: float=10.0,
                        min_lr: float=1e-6,
                        show_plot: bool=False,
                        save_plot: bool=True) -> float:
        
        """find an optimal learning rate"""
        
        res = Tuner(self.trainer).lr_find(
            self.model,
            train_dataloaders=self.train_dataloader,
            val_dataloaders=self.val_dataloader,
            max_lr=max_lr,
            min_lr=min_lr,
        )

        optimal_lr = res.suggestion()
        
        # ---- optional, saving the plot ---- 
        
        fig = res.plot(show=show_plot, suggest=True)
        
        if save_plot:
            try:
                fig.savefig(os.path.join(plot_output_dir, "lr_finder.png"))
            except Exception as e:
                print("Error saving learning rate finder plot: ", e)
        
        return optimal_lr
    
    def load_best_model(self) -> bool:
        
        """Load the best model checkpoint after training."""
        
        model = None
        
        try:
            best_model_path = self.trainer.checkpoint_callback.best_model_path
            model = TemporalFusionTransformer.load_from_checkpoint(best_model_path)
        except Exception as e:
            print("Error loading best model checkpoint: ", e)
            return False
        
        self.model = model    
        return True
    
    def fit(self):            
        self.trainer.fit(
            self.model,
            train_dataloaders=self.train_dataloader,
            val_dataloaders=self.val_dataloader,
        )
    
    def predict(self, x: TimeSeriesDataSet,  return_x: Optional[bool]=False, mode: Optional[str]="prediction", return_y: bool=True):
        
        try:
            tft_predictions = self.model.predict(x, mode=mode, return_x=return_x, return_y=return_y)
        except Exception as e:
            print(f"Failed to predict: {e}")
            return None
        
        return tft_predictions

    @staticmethod
    def find_optimal_parameters(train_dataloader: TimeSeriesDataSet, 
                                val_dataloader: TimeSeriesDataSet,
                                max_epochs: int=50,
                                n_trials: int=100,
                                use_learning_rate_finder: bool=False,
                                model_path: str="optuna_test",
                                best_params_path: str="best_params.pkl",
                                timeout: int=300) -> dict:
        
        """
        Find optimal hyperparameters for a Temporal Fusion Transformer model using Optuna. Best parameters are saved for potential later usage
        Args:
            train_dataloader (TimeSeriesDataSet): Training dataset loader containing time series data.
            val_dataloader (TimeSeriesDataSet): Validation dataset loader for evaluating model performance.
            max_epochs (int, optional): Maximum number of training epochs per trial. Defaults to 50.
            n_trials (int, optional): Number of optimization trials to run. Defaults to 100.
            use_learning_rate_finder (bool, optional): Whether to use built-in learning rate finder 
                instead of Optuna-based learning rate optimization. Defaults to False.
            model_path (str, optional): Directory path to save model checkpoints during optimization. 
                Defaults to "optuna_test".
            best_params_path (str, optional): File path to save the best hyperparameters. 
                Defaults to "best_params.pkl".
            timeout (int, optional): Maximum time in seconds to run the optimization study. 
                Defaults to 300.
        Returns:
            dict: Dictionary containing the best hyperparameters found during optimization.
        """
        
        # create study
        study = optimize_hyperparameters(
            train_dataloader,
            val_dataloader,
            model_path=model_path,
            n_trials=n_trials,
            max_epochs=max_epochs,
            gradient_clip_val_range=(0.01, 1.0),
            hidden_size_range=(8, 128),
            hidden_continuous_size_range=(8, 128),
            attention_head_size_range=(1, 4),
            learning_rate_range=(0.001, 0.1),
            dropout_range=(0.1, 0.3),
            trainer_kwargs=dict(limit_train_batches=30),
            reduce_on_plateau_patience=4,
            use_learning_rate_finder=use_learning_rate_finder,  # use Optuna to find ideal learning rate or use in-built learning rate finder
            timeout=timeout,  # stop study after given seconds
        )

        # save study results - also we can resume tuning at a later point in time
        
        best_params = study.best_trial.params
        try:
            with open(best_params_path, "wb") as fout:
                pickle.dump(best_params, fout)
                print("Best parameters saved to: ", best_params_path)
        except Exception as e:
            print("Error saving best parameters: ", e)

        # return best hyperparameters
        return best_params

    def plot_raw_predictions(self, raw_predictions, plots_path: str, show=False):

        n = raw_predictions.output.prediction.shape[0]
        print(f"Plotting {n} predictions...")

        for idx in range(n):
            fig = self.model.plot_prediction(
                raw_predictions.x,
                raw_predictions.output,
                idx=idx,
                add_loss_to_title=True
            )
            
            if show:
                plt.show(fig=fig)
                
            fig.savefig(os.path.join(plots_path, f"tft_prediction_{idx}.png"))
            plt.close(fig=fig)
    

クラスを用いることで、再利用可能なトレーナーの管理、モデルの学習、最適パラメータの探索などをより整理された形で扱うことができます。

たとえば、TFTModelクラス内にfind_optimal_parametersをstaticメソッドとして定義することで、クラスのインスタンスを生成する前にこの関数を実行し、最適なパラメータを取得することが可能になります。その後、その結果をクラスインスタンスに再度反映させることができます。

例は以下の通りです。

best_params = model.TFTModel.find_optimal_parameters(train_dataloader=train_dataloader,
                                            val_dataloader=val_dataloader,
                                            timeout=optuna_timeout,
                                            best_params_path=best_params_path
                                            )
        
    print("Best hyperparameters found: ", best_params)
    
    tft_model = model.TFTModel(
        training=training,
        train_dataloader=train_dataloader,
        val_dataloader=val_dataloader,
        parameters=best_params,
        trainer_max_epochs=max_training_epochs
    )

 

TFTモデルを自動売買ロボットに組み込む

実用的な自動売買ロボットを構築するためには、推論時においても市場の最新データを用いてモデルの予測値を取得する必要があります。

TFTでは、推論時にも学習時と同じ特徴量セット(目的変数を含む)が必要になるため、データ収集と特徴量エンジニアリングを一貫しておこなうためのグローバル関数を用意する必要があります。

bot.py

def prepare_data(rates_df: pd.DataFrame) -> pd.DataFrame:
    
    rates_df["time"] = pd.to_datetime(rates_df["time"], unit="s") # convert time in seconds to datetime
    
    features_df = features.FeatureEngineer.get_all(rates_df)
    data = pd.concat([rates_df, features_df], axis=1) # concatenate dataframes
    
    # making the target variable
    
    data["returns"] = data["close"].pct_change()
    data["symbol"] = "EURUSD" # assigning symbol name as a group
    
    # drop NANs if any
    
    data.dropna(inplace=True)
    
    # assigning a time index
    
    data = data.reset_index(drop=True)
    data["time_idx"] = data.index
    
    # let's keep track of unused features
    
    unused_features = ["time", "spread", "real_volume"] 
    return data.drop(columns=unused_features)

上記の関数は、MetaTrader 5から取得した生のDataFrameを入力として受け取り、新しい特徴量を生成し、目的変数、グループ情報、時間インデックスなどを含む必要なすべての特徴量を整形したうえで、最終的にモデル学習に必要なデータセットを返します。

また、モデルをトレーニングするための関数も必要です。この関数は、学習済みのTFTModelインスタンスをグローバル変数として保持する役割を持つように設計します。

bot.py

def train_model(start_bar: int=100,
                num_bars: int=1000,
                symbol: int = "EURUSD",
                timeframe: int=mt5.TIMEFRAME_M15,
                max_prediction_length: int = 6,
                max_encoder_length: int = 24,
                load_best_parameters = False):
    
    # we extract training data from MetaTrader 5
    
    try:
        rates = mt5.copy_rates_from_pos(symbol, timeframe, start_bar, num_bars)
    except Exception as e:
        print("Error retrieving data from MetaTrader 5: ", e)
        return
    
    data = prepare_data(rates_df=pd.DataFrame(rates))
    
    # ------------ preparing training data and data loaders ------------
    
    training_cutoff = data["time_idx"].max() - max_prediction_length

    training = TimeSeriesDataSet(
        data[lambda x: x.time_idx <= training_cutoff],
        time_idx="time_idx",
        target="returns",
        group_ids=["symbol"],
        min_encoder_length=max_encoder_length // 2,  # keep encoder length long (as it is in the validation set)
        max_encoder_length=max_encoder_length,
        min_prediction_length=1,
        max_prediction_length=max_prediction_length,
        static_categoricals=["symbol"],
        # time_varying_known_categoricals=[],
        
        time_varying_known_reals=[
                                "hour",
                                "dayofweek",
                                "dayofmonth",
                                "month",
                                "time_idx", 
                                "stochrsi_k",
                                "stochrsi_d",
                                "rsi",
                                "macd_diff",
                                ],
        
        time_varying_unknown_categoricals=[],
        time_varying_unknown_reals=[
            "open",
            "high",
            "low",
            "close",
            "tick_volume",
            "ema_20",
            "sma_20",
            "bollinger_hband",
            "bollinger_lband"
        ],
        
        target_normalizer=GroupNormalizer(
            groups=["symbol"], transformation="softplus"
        ),  # use softplus and normalize by group
        
        add_relative_time_idx=True,
        add_target_scales=True,
        add_encoder_length=True,
    )

    # create validation set (predict=True) which means to predict the last max_prediction_length points in time
    # for each series
    validation = TimeSeriesDataSet.from_dataset(
        training, data, predict=True, stop_randomization=True
    )

    # create dataloaders for model
    batch_size = 128  # set this between 32 to 128
    train_dataloader = training.to_dataloader(
        train=True, batch_size=batch_size, num_workers=4, persistent_workers=True
    )
    val_dataloader = validation.to_dataloader(
        train=False, batch_size=batch_size * 10, num_workers=4, persistent_workers=True
    )

    best_params_path = os.path.join(outputs_dir, "best_params.pkl")
    
    if load_best_parameters:
        try:
            with open(best_params_path, "rb") as fin:
                best_params = pickle.load(fin)
        except Exception as e:
            print("Error loading best parameters: ", e)
            print("Finding optimal parameters instead...")
            
            best_params = model.TFTModel.find_optimal_parameters(train_dataloader=train_dataloader,
                                                    val_dataloader=val_dataloader,
                                                    timeout=optuna_timeout,
                                                    best_params_path=best_params_path,
                                                    )
    else:
        best_params = model.TFTModel.find_optimal_parameters(train_dataloader=train_dataloader,
                                                    val_dataloader=val_dataloader,
                                                    timeout=optuna_timeout,
                                                    best_params_path=best_params_path
                                                    )
        
    print("Best hyperparameters found: ", best_params)
    
    global trained_model
    trained_model = model.TFTModel(
        training=training,
        train_dataloader=train_dataloader,
        val_dataloader=val_dataloader,
        parameters=best_params,
        trainer_max_epochs=max_training_epochs
    )
    
    trained_model.load_best_model()
    trained_model.fit()

すべての取引操作は、trading_functionという名前の関数内で実行されます。

    def trading_function():
        
        global trained_model
        if trained_model is None:
            
            train_model(symbol=symbol, timeframe=timeframe, max_encoder_length=lookback_window,
                                        max_prediction_length=lookahead_window, load_best_parameters=True) # get a trained model instance
            return
        
        # ---------- get data for model's inference -------
        
        rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 100)
        rates_df = pd.DataFrame(rates)
        
        if rates_df.empty:
            return
        
        data = prepare_data(rates_df=rates_df)
        
        predicted_returns = trained_model.predict(x=data, return_x=False, return_y=False)
        print(f"predicted returns: {np.array(predicted_returns)}")
        
        
        next_return = np.array(predicted_returns).ravel()[-1]
        print(f"next_return: {next_return:.2f}")
        
        # ------------- some trading strategy ----------------
        
        tick_info = mt5.symbol_info_tick(symbol)
        if tick_info is None:
            print("Failed to get tick information. Error = ",mt5.last_error())
            return
        
        symbol_info = mt5.symbol_info(symbol)
        if symbol_info is None:
            print(f"Failed to get information for {symbol}")
            return 
        
        lotsize = symbol_info.volume_min
        
        if next_return > 0:
            if not pos_exists(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_BUY):
                m_trade.buy(volume=lotsize, symbol=symbol, price=tick_info.ask)
                close_by_type(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_SELL) # close a different type 
        else:
            if not pos_exists(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_SELL):
                m_trade.sell(volume=lotsize, symbol=symbol, price=tick_info.bid)
                close_by_type(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_BUY) # close a different type

まずこの関数内では、trained_modelというグローバル変数に有効なモデルが存在するかどうかを確認いたします。もし存在しない場合は、train_model関数を用いてモデルを初回学習させます。

この取引戦略は非常にシンプルで、時系列の最終予測リターンが正であればロングシグナルとみなし、買い注文を実行いたします。一方で負であればショートシグナルとみなし、売り注文を実行します。また、シグナルと反対方向のポジションは close_by_type関数を用いてすべて決済します。

最後に、売買シグナルの確認頻度や取引アクションの実行間隔をスケジューリングし、さらに市場データに適応するためのモデル再学習のタイミングと頻度も設定します。

bot.py

timeframe = mt5.TIMEFRAME_M15
symbol = "EURUSD"
magic_number = 20012026
slippage = 100

lookback_window = 24
lookahead_window = 6

if __name__ == "__main__":

    mt5_exe_path = r"C:\Program Files\MetaTrader 5 IC Markets Global\terminal64.exe"
    
    if not mt5.initialize(mt5_exe_path):
        print("initialize() failed, error code =", mt5.last_error())
        quit()

    m_trade = CTrade(magic_number=magic_number, filling_type_symbol=symbol, deviation_points=slippage, mt5_instance=mt5)


    def trading_function():
        
        global trained_model
        if trained_model is None:
            
            train_model(symbol=symbol, timeframe=timeframe, max_encoder_length=lookback_window, max_prediction_length=lookahead_window, load_best_parameters=True) # get a trained model instance
            return
        
        # ---------- get data for model's inference -------
        
        rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, 100)
        rates_df = pd.DataFrame(rates)
        
        if rates_df.empty:
            return
        
        data = prepare_data(rates_df=rates_df)
        
        predicted_returns = trained_model.predict(x=data, return_x=False, return_y=False)
        print(f"predicted returns: {np.array(predicted_returns)}")
        
        
        next_return = np.array(predicted_returns).ravel()[-1]
        print(f"next_return: {next_return:.2f}")
        
        # ------------- some trading strategy ----------------
        
        tick_info = mt5.symbol_info_tick(symbol)
        if tick_info is None:
            print("Failed to get tick information. Error = ",mt5.last_error())
            return
        
        symbol_info = mt5.symbol_info(symbol)
        if symbol_info is None:
            print(f"Failed to get information for {symbol}")
            return 
        
        lotsize = symbol_info.volume_min
        
        if next_return > 0:
            if not pos_exists(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_BUY):
                m_trade.buy(volume=lotsize, symbol=symbol, price=tick_info.ask)
                close_by_type(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_SELL) # close a different type 
        else:
            if not pos_exists(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_SELL):
                m_trade.sell(volume=lotsize, symbol=symbol, price=tick_info.bid)
                close_by_type(symbol=symbol, magic=magic_number, type=mt5.POSITION_TYPE_BUY) # close a different type
                

    schedule.every(15).minutes.do(trading_function) # check for signals after 15 minutes (according to the timeframe)
    schedule.every(lookback_window*15).minutes.do(train_model, 
                                                max_encoder_length=lookback_window, 
                                                max_prediction_length=lookahead_window)
    
    while True:
        schedule.run_pending()
        time.sleep(1)



最終的な考察

TFTは、マルチホライズン予測を可能にするため、複数時間足での確認などに有用です。また、複数のグループデータにも対応しており、異なる金融商品や時間足などのデータを扱うことで、モデルが異なるドメインにまたがる有用なパターンを学習できるようになっています。さらに、このモデルには解釈性を高めるためのさまざまな機能が備わっており、例えば予測値と実測値のプロットや特徴量重要度の可視化などが可能であり、モデルの意思決定を理解する助けとなります。 

総じて、TFTは時系列データの予測において、十分に有望なモデルだと言えます。

しかしながら、LSTMを基盤とした複雑なアーキテクチャを持つため計算コストが高く、大規模データで試す場合にはGPUがほぼ必須となります。CPU環境では学習に非常に長い時間がかかります。  また、十分なデータ量がないと汎化性能が低下しやすく、その結果としてノイズを学習してしまう可能性もあります。その対策として、モデルが出力する分位点予測を活用することが一般的です。

他分野と比較しても、Transformerが金融領域でどの程度有効かについてはまだ十分に解明されていません。金融市場は非常に複雑で予測が困難なため、本記事はTFTをこの分野で応用する可能性を示すとともに、今後のさらなる研究の出発点となるものです。

この記事のディスカッションセクションで、ご意見やご感想をぜひお聞かせください。


添付ファイルの表

ファイル名 説明と使用法
train.py 本記事で使用するコードの中心となる実験用スクリプトです。TFTモデルの学習および評価のプロセスを実演します。
features.py 特徴量エンジニアリングを担当するクラスを含むモジュールです。OHLCデータからテクニカル指標などの追加特徴量を生成します。
model.py TFTモデルの実装をまとめたTFTModelクラスを含みます。
bot.py TFTを用いて売買判断をおこなう最終的な自動売買ロボットです。
error_description.py MetaTrader 5のエラーコードを人間が理解しやすいエラーメッセージへ変換する関数を含みます。
Trade/Trade.py MQL5/Include/Tradeに相当するディレクトリです。このパスには標準の取引クラスライブラリに類似したPythonモジュールが含まれています。
requirements.txt 本プロジェクトで使用するPython依存ライブラリおよびそのバージョン情報を記載しています。 

このプロジェクトで使用されているPythonのバージョンは3.11.1です。


参照文献

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18885

添付されたファイル |
Attachments.zip (17.9 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
プライスアクション分析ツールキットの開発(第57回):MQL5による市場状態分類モジュールの開発 プライスアクション分析ツールキットの開発(第57回):MQL5による市場状態分類モジュールの開発
確定済み価格データを用いて価格挙動を解釈するMQL5向けの市場状態分類モジュールを開発および解説します。ボラティリティの収縮および拡大、ならびに構造的一貫性を分析することにより、市場環境をコンプレッション、トランジション、エクスパンション、トレンドとして分類し、プライスアクション分析のための明確な文脈把握の枠組みを提供します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5取引ツール(第13回):グラフパネルと統計パネルを備えたCCanvasベースの価格ダッシュボードの実装 MQL5取引ツール(第13回):グラフパネルと統計パネルを備えたCCanvasベースの価格ダッシュボードの実装
MQL5においてCCanvasクラスを使用してインタラクティブなパネルを構築し、最近の価格グラフや口座統計を可視化する「CCanvasベースの価格ダッシュボード」を開発します。本システムは、背景画像、フォグ効果、グラデーション塗りつぶしにも対応しています。さらに、ドラッグ&リサイズ機能をマウスイベント処理で実装し、テーマ切り替え(ダーク/ライトモード)による動的な色変更、最小化/最大化コントロールも備え、チャート領域を効率的に管理できる設計となっています。