English Deutsch
preview
Pythonでの見せかけの回帰

Pythonでの見せかけの回帰

MetaTrader 5統計と分析 | 27 6月 2024, 11:23
127 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

大要

機械学習によるアルゴリズム取引の領域に飛び込む前に、モデルの入力と予測しようとする変数の間に意味のある関係が存在するかどうかを確認することが極めて重要です。本稿では、データセットにこのような関係が存在することを検証するために、モデル残差の単位根検定を用いることの有用性を説明します。

不幸なことに、真正な関係を持たないデータセットを使用してモデルを構築することは可能です。そして、残念なことに、このようなモデルでは誤差指標が驚くほど低く、誤った管理意識や過度に楽観的な見通しを助長する可能性があります。このような欠陥のあるモデルは、一般に 「見せかけの回帰」と呼ばれます。

この記事では、まず見せかけの回帰について直感的に理解することから始めます。その後、見せかけの回帰をシミュレートするために合成時系列データを生成し、その特徴的な効果を観測します。その後、Pythonで作成された機械学習モデルを検証するための洞察力を頼りに、見せかけの回帰を特定する方法を掘り下げていきます。最後に、モデルが検証されたなら、それをONNXにエクスポートし、MQL5で取引戦略を実装します。


はじめに:見せかけの回帰は常に起こる

19世紀半ば、センメルヴェイス・イグナーツはウィーンで開業医をしていました。彼は、自分が勤務していた病院の統計に深い苛立ちを感じていました。

センメルヴェイス・イグナーツ

図1:センメルヴェイス・イグナーツ


問題は、病院で出産した健康な女性の5分の1が、産褥熱で死亡していることでした。センメルヴェイスはその理由を理解しようと決意しました。当時のほとんどの医師は、悪霊を運ぶと信じられていた「悪い空気」がこれらの問題を引き起こしていると考えていました。今日では滑稽に聞こえるかもしれませんが、当時はこれが広く受け入れられていました。しかし、センメルヴェイスは満足しませんでした。時間が経つにつれて、センメルヴェイスはある日、病院の片側にある霊安室で検死をしている医師や医学生が、その間に手を洗うことなく、病院の反対側で分娩するために走っていくのを目撃しました。手指衛生を実践するよう地元病院のスタッフを説得した結果、妊産婦死亡率は20%から1%に低下しました。

残念ながら、センメルヴェイスの発見は気づかれることはありませんでした。自分の発見を他の医師や医療機関に伝えようと努力しても、当時の医学界や「空気が悪い」というその具体的な信念から自分をさらに遠ざけるだけでした。センメルヴェイスは社会から追放され、精神病院で46歳の生涯を閉じました。センメルヴェイスの賢明な言葉を無視した医師たちから、私たちは何を学ぶことができるのでしょうか。

問題は、何の関係もないデータを使用してモデルを構築することが可能だということです。さらに、このモデルは偶然に低い誤差指標を生み出し、存在しない関係を誤って証明する可能性があります。このようなモデルは見せかけの回帰と呼ばれます。

見せかけの回帰とは、存在しない関係を誤って証明するモデルのことです。医師は、「今日は悪霊が多すぎるから、明日はもっと多くの母親が死ぬだろう 」と自分たちに言い聞かせることができたのです。予想通り、翌日にはさらに多くの女性が亡くなりましたが、医師は間違った理由で正しかったのです。機械学習モデルを構築する際、モデルは間違った理由で正しくなることもあります。

入力データと出力データの間に関係があることを明確に認識しているのであれば、心配する必要はありません。しかし、確かでない場合はどうすればいいのでしょうか。あるいは、一度も確認したことがなく、単に関係があるはずだと思い込んでいるだけだとしたらどうでしょう。

最もよく知られている解決策は、モデルの残差に対して特殊な検定をおこなうことです。この検定は単位根検定と呼ばれます。この記事では単位根を定義することはしません。それはまた別の議論です。しかし、私たちの目標を実現するためには、残差に単位根を見つけることができれば、回帰が見せかけであることを知っていれば十分です。

今日検討している単位根による解決策には、物質的な制限が1つだけあります。単位根が存在するにもかかわらず、単位根を見つけることができない場合があるということです。あるいは、存在しない単位根を誤って発見することもあります。これはクラス2エラーです。

残差に単位根があるかどうかを確認するためには、拡張ディッキー–フラー検定やKPSS検定など、多くの検定があります。それぞれの検定には長所と短所があり、異なる条件下で失速します。見せかけの回帰の動作を見るために、独自の時系列データを作成します。互いに関係のない2つの時系列データセットを作成し、その2つの独立したデータセットを使用してモデルを訓練するとどうなるか観測します。


見せかけの回帰のシミュレーション

見せかけの回帰は様々な理由で起こり得ますが、最も一般的な理由は、2つの独立した非定常時系列をモデル化することです。その技術的な定義を紐解いてみましょう。時系列とは、確率変数の観測値を一様に記録したものです。時系列が定常的であるとは、その統計的性質(平均、分散、自己相関構造など)が時間の経過とともに比較的一定であることを意味します。  時系列が非定常であるとは、その統計的特性が時間とともに変動することです。

このディスカッションでは、各ステップにおける真実の根拠を理解するために、自分たちのデータをシミュレーションすることで実践的なアプローチを取ります。このアプローチによって、その効果を直接観測することができます。まず、必要なパッケージをインポートします。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller , kpss
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

次に、2つのデータセットの統計的特性を定義します。1つは入力データ、もう1つは出力データです。どちらのデータセットも、正規分布の乱数を含みます。

size = 1000
mu , sigma = 0 , 1
mu_y , sigma_y = 2 , 4

以下のコードは、ランダムウォークを表す2つの時系列、x_non_stationaryとy_non_stationaryを生成します。ランダム性は正規分布によって導入され、累積和は系列の各値が前の値に依存することを保証し、動的で非定常な動作を作り出します。

x_non_stationaryとy_non_stationaryの両データセットは、numpy.random.normal関数によってランダムに生成されています。したがって、2つのデータセットに関係はありません。

steps = np.random.normal(mu,sigma,size)
steps_y = np.random.normal(mu_y,sigma_y,size)
x_non_stationary =  pd.DataFrame(100 + np.cumsum(steps),index= np.arange(0,1000))
x_non_stationary_lagged = x_non_stationary.shift(1)
x_non_stationary_lagged.dropna(axis=0,inplace=True)
y_non_stationary =   pd.DataFrame(100 + np.cumsum(steps_y),index= np.arange(0,1000))

2つのランダムなデータセットを見てみましょう。

plt.plot(x_non_stationary)

ランダムウォーク x

図2:ランダムウォークx




非定常y時系列をプロットしてみましょう。

plt.plot(y_non_stationary)

ランダムウォークy

図3:ランダムウォークy

ここで、2つの独立した非定常時系列を回帰した結果を観測してみましょう。

ols = sm.OLS(y_non_stationary,x_non_stationary)
lm = ols.fit()
print(lm.summary())

x_y_非定常回帰

図4:  2つの独立した非定常変数を回帰した結果の概要

多くの時系列には、確率的トレンドとして知られるランダムなトレンド成分が存在します。2つの時系列が独立していても、その確率的トレンドが短時間の局所的相関を示すことがあります。残念なことに、このような瞬間的な相関は、時としてモデルを惑わし、2つの独立した時系列間に関係が存在すると誤って結論付けさせてしまうことがあります。このようなケースから生じる見せかけの回帰は、しばしば高い決定係数指標をもたらします。決定係数は、独立変数の分散によって説明される応答変数の分散の割合を測定することを覚えておくことが重要です。したがって、2つの変数が相関する確率的トレンドを持つ場合、決定係数の信頼性が損なわれる可能性があります。見せかけの回帰の問題は、特に時系列データに関連するものであり、慎重に検討する必要があります。

私たちのモデルでは自由度調整済み決定係数がほぼ1に達しており、これは、モデルがほぼ完璧にフィットしていると認識していることを示しています。このモデルは、応答変数の変動の約90%が予測変数の分散によって説明できると主張します。しかし、この実証は、見せかけの回帰にまつわる落とし穴を思い起こさせる重要な役割を果たします。入力と出力の間に何の関係もないことは分かっています。どちらもランダムであり、共通点は何もありません。

P値は有意に見え、信頼区間には0が目立って存在しないため、課題は残ります。これは一般的には、優れたフィット感の証と解釈されるかもしれませんが、注意が必要です。この場合、モデルは実際には存在しない強い関係を誤って示しています。入力データと出力データは自分たちで作成したもので、関係がないことは分かっています。


予測変数の遅れ

見せかけの回帰の兆候は、予測変数の遅延バージョンを含めると現れます。このような場合、かつては有意であった係数が突然有意でなくなります。これはすぐに観測することになります。この現象は重要な指標となり、誤った結論から私たちを遠ざけ、時系列分析における見せかけの回帰を理解し対処することの重要性を強調しています。

上記と同じ手順を繰り返しますが、今回は入力データの遅延バージョンも含めます。

ols = sm.OLS(y_non_stationary.iloc[0:998,0],x_matrix.loc[0:998,['current_x','lagged_x']])
lm = ols.fit()
print(lm.summary())

X_y非定常回帰

図5:見せかけの回帰からの要約統計

さらに、決定係数の値がダービン・ワトソン統計量を上回っていることにご注意ください。この観測結果は、見せかけの回帰の可能性を示すさらなる警告です。回帰分析で用いられるダービン・ワトソン統計量は、回帰モデルの残差における自己相関の存在を検出するのに役立ちます。自己相関は、時系列または回帰モデルの残差が互いに相関を示すときに現れます。観測が以前の観測に依存している時系列データでは、ダービン・ワトソン検定が特に重要になります。その結果は、自己相関の存在に関する貴重な洞察を提供し、モデルのパフォーマンスの解釈をさらに導いてくれます。


ダービン・ワトソン統計量

  •     範囲:0~4
  •     解釈:値が2に近い場合は、有意な自己相関がないことを示唆。有意に2を下回る値は、正の自己相関(残差は正の相関がある)を示唆。2を有意に上回る値は、負の自己相関(残差は負の相関がある)を示唆。

高い決定係数と低いダービン・ワトソン統計量によって見せかけの回帰の疑いが高まるかもしれませんが、これらの指標だけでは見せかけの回帰の存在が決定的に裏付けられるわけではないことに注意することが重要です。時系列分析の領域では、追加の診断検定と領域知識が、徹底的な評価には不可欠な要素となります。 


単位根検定

見せかけの回帰を識別するための最も信頼できる方法は,残差の分析にあります。モデルの残差が定常性を欠くなら、回帰が本当に見せかけであることが強く示唆されます。しかし、与えられた時系列が定常的かどうかを判断するのは簡単なことではありません。私たちのケースでは,回帰が見せかけであり,したがって残差が非定常であることを知っているので,拡張ディッキー–フラー検定は帰無仮説を棄却できないかもしれません。言い換えれば、回帰が見せかけであるにもかかわらず、データが定常的でないことを証明できない可能性があり、これは見せかけの回帰を識別することの微妙さと難しさを示しています。このことは、時系列分析の複雑さを効果的にナビゲートするために、統計的検定と領域知識を組み合わせたニュアンスのあるアプローチの重要性を強調しています。

次にsklearnを使用して、訓練セットにモデルを当てはめます。

lm = LinearRegression()
lm.fit(x[train_start:train_end],y[train_start:train_end])

次に残差を計算します。

residuals = y[test_start:test_end] - lm.predict(x[test_start:test_end])

残差をプロットしてみましょう。

residuals.plot()

残差プロット

図6:Scikit-Learnで構築した回帰モデルの残差


ここで、残差の定常性を判定するために、拡張ディッキー–フラー検定にかけます。


拡張ディッキーフラー検定

拡張ディッキー–フラー(Augmented Dickey-Fuller:ADF)検定は、時系列の定常性の評価、または非定常性を示す単位根の同定のために設計された極めて重要な統計的手段です。時系列分析の領域では、平均や分散などの統計的属性が時間にわたって不変であることを意味する、定常性が最も重要な役割を担っています。非定常性を示す単位根が時系列に存在すれば、時系列内の観測が確率的またはランダムな性質を持つのではないかという合理的な疑いが生じます。したがってADF検定は、データセットの時間的な振る舞いを精査するための頑健な方法論を提供し、データセット固有の特性や、その後の分析への潜在的な影響についての微妙な理解に寄与します。

  1. 帰無仮説:ADF検定の帰無仮説は、時系列が単位根を持ち、非定常性を示すことを仮定します。
  2. 対立仮説:拡張ディッキー–フラー(ADF)検定の対立仮説は、時系列が単位根を持たず、定常であるというものです。
  3. 識別規則:ADF検定の識別規則は、検定統計量を臨界値と比較します。検定統計量が臨界値より小さければ、帰無仮説(単位根の存在)が棄却され、定常性が示されます。逆に、検定統計量が臨界値より大きい場合は、帰無仮説を棄却する証拠が不十分であり、非定常性が示唆されます。

モデルから生成された残差について拡張ディッキー–フラー(ADF)検定を実施し、その結果得られた知見は、残差内の定常性または非定常性の存在を識別する上で重要な決定要因となります。ADF検定は、回帰結果の検証過程において重要な意味を持ち、分析枠組みの信頼性を確保する上で極めて重要な役割を果たしています。残差の定常性を明らかにすることで、この検定は時系列分析の解釈上の頑健性を高めることに大きく貢献します。

adfuller(residuals)

(-12.753804093890963, 8.423533501802878e-24, 2, 497, {'1%': -3.4435761493506294, '5%': -2.867372960189225, '10%': -2.5698767442886696}, 1366.9343966932422)

この実験から得られたp値、特に提供されたリストの2番目の値である8.423533501802878e-24に主眼を置きます。注目すべきは、このp値がゼロに近づき、妥当な臨界値を大幅に上回っていることです。拡張ディッキー–フラー(ADF)検定の文脈では、ADF統計量が臨界値より小さい場合、帰無仮説の棄却が適切となり、これは定常性の存在を意味します。

他の統計検定と同様に、ADF検定は固有の限界と仮定を伴うことを認識することが不可欠です。ADF検定で帰無仮説を受け入れられない原因となるさまざまな要因が存在し、その結果、基礎となるデータが非定常である場合でも単位根の存在が拒否される可能性があります。これらのニュアンスを理解することは、検査結果を総合的に解釈する上で極めて重要です。

  1. サンプル数が少ない:ADF検定の性能は、サンプルサイズが小さいと影響を受ける可能性があります。このような場合、検定は非定常性を検出するのに十分な検出力を欠いているかもしれません。
  2. 貧弱な遅延注文:ADF検定における遅延の階数(lag order)の選択は極めて重要です。lag orderが正しく指定されないと、不正確な結果につながる可能性があります。少なすぎたり多すぎたりするラグ数は、検定のデータの根本的な構造を捉える能力に影響を与える可能性があります。
  3. 決定論的トレンドの存在:データに検定モデルで説明されていない決定論的トレンド(線形トレンド、2次トレンドなど)が含まれている場合、ADF検定は帰無仮説を棄却できない可能性があります。このような場合、デトレンド処理などの前処理が必要になります。
  4. 不十分な差分:ADF検定で使用された差分の階数がデータを定常化するのに不十分な場合、検定は帰無仮説を棄却できないかもしれません。


KPSS (Kwiatkowski-Phillips-Schmidt-Shin)

KPSS(Kwiatkowski-Phillips-Schmidt-Shin)検定は、時系列データの定常性を評価するための拡張ディッキー–フラー(ADF)検定に代わる有効な検定です。両検定は時系列分析では一般的ですが、帰無仮説と対立仮説、またその基礎となるモデルにおいて相違があります。ADF検定とKPSS検定のどちらを選択するかは、調査対象の時系列の特定の特徴量と包括的な研究課題によって決まります。両検定を同時に活用することで、より包括的な定常性分析が可能になり、時系列ダイナミクスのニュアンスに富んだ理解を研究者に提供することができます。

  1. 帰無仮説:KPSS検定の帰無仮説は、時系列がトレンド定常であるというものです。トレンド定常性は、系列が単位根を示すことを意味し、決定論的トレンドの存在を示唆します。
  2. 対立仮説:KPSS検定の対立仮説は、時系列がトレンド定常ではないことであり、確率的トレンドの周りの差分定常または定常であることを示します。
  3. 識別規則:KPSS検定の識別規則は、選択された有意水準(例えば、1%、5%、10%)における臨界値と検定統計量を比較することを含みます。検定統計量が臨界値より大きければ帰無仮説は棄却され、時系列はトレンド定常ではないことが示唆されます。一方、検定統計量が臨界値より小さければ、帰無仮説は棄却できず、トレンドの定常性が示唆されます。

KPSS検定の場合、一般的に採用される閾値は有意水準0.05です。KPSS統計量がこの閾値を下回る場合、データの非定常性が示唆されます。私たちの分析では、KPSS統計量は0.016という値を示し、臨界閾値からの逸脱を確認し、データセットにおける非定常性の傾向を示しました。この結果は、時系列特性を徹底的かつ正確に評価するために、ADF検定やKPSS検定のような複数の診断ツールを検討することの重要性をさらに強調しています。

kpss(residuals)

(0.6709994557854182, 0.016181867655871072, 1, {'10%':0.347, '5%':0.463, '2.5%':0.574, '1%':0.739})

KPSS検定は、特定の状況下では帰無仮説(H0)を誤って棄却する可能性があり、 第一種の過誤につながります。第一種の過誤は、実際には時系列がトレンド定常であるにもかかわらず、検定が時系列がトレンド定常でないと誤って結論づける場合に発生します。

以下は、KPSS検定が帰無仮説を誤って棄却する可能性のある状況です。

  1. 季節的パターン:KPSS検定はトレンドと季節性の両方に敏感です。時系列が強い季節的パターンを示す場合、この検定はそれを非定常トレンドと解釈する可能性があります。このような場合、季節性に対処するために差分を取る必要があるかもしれません。
  2. 構造変化:基礎となるデータ生成過程における突然の有意な変化など、時系列に構造変化がある場合、KPSS検定はこれらを非定常トレンドとして検出することがあります。構造変化は帰無仮説の棄却につながります。
  3. 外れ値:データ中の外れ値の存在は、KPSS検定の性能に影響を与える可能性があります。外れ値はトレンドの逸脱として認識され、トレンドの定常性が否定される可能性があります。外れ値に対する頑健性は、KPSS検定の結果を解釈する際に重要な考慮事項です。
  4. 非線形トレンド:KPSS検定は線形トレンドを仮定します。時系列の基礎となるトレンドが非線形である場合、この検定は誤解を招く結果を生む可能性があります。非線形のトレンドは検定によって十分に捕捉されない可能性があり、定常性の誤った棄却につながります。

KPSS検定の結果を慎重に解釈し、分析対象の時系列の特性を考慮することが極めて重要です。さらに、KPSS検定を拡張ディッキー–フラー(ADF)検定などの他の定常性検定と組み合わせることで、時系列の定常性特性をより包括的に評価することができます。


まとめ

信頼性の基盤を確立したので、合成コントロールデータから離れ、MetaTrader 5端末から直接入手した本物の市場データの分析に重点を移す好機です。この移行を促進するために、私たちはMetaQuotes Language 5(MQL5)スクリプトの開発を提案します。このスクリプトは、取引端末からデータを取得し、書式設定してCSV形式にエクスポートするために特別に設計されます。

スクリプトを開始します。最初のステップではグローバル変数を宣言し、その最初のセットにはテクニカル指標のハンドルが格納されます。これらの変数は、スクリプトの実行を通じて関連する指標を効率的に管理しアクセスする上で極めて重要な役割を果たし、MetaQuotes Language 5(MQL5)プログラムの全体的な一貫性と構成に貢献します。

//---Our handlers for our indicators
int ma_handle;
int rsi_handle;
int cci_handle;
int ao_handle;

そのため、テクニカル指標の読み取り値を収容し、整理するために、綿密に設計されたデータ構造体が必要になります。これらのデータ構造体は、スクリプトの実行を通じて使用されます。 

//---Data structures to store the readings from our indicators
double ma_reading[];
double rsi_reading[];
double cci_reading[];
double ao_reading[];

当然のように、その後ファイル名を作成します。

//---File name
string file_name = "Market Data.csv";

次に、どれだけのデータを取得するかを定義します。

//---Amount of data requested
int size = 3000;

OnStartイベントハンドラの開発を開始します。注文の最初の呼び出しでは、指定したテクニカル指標を初期化します。

//---Setup our technical indicators
ma_handle = iMA(_Symbol,PERIOD_CURRENT,20,0,MODE_EMA,PRICE_CLOSE);
rsi_handle = iRSI(_Symbol,PERIOD_CURRENT,60,PRICE_CLOSE);
cci_handle = iCCI(_Symbol,PERIOD_CURRENT,10,PRICE_CLOSE);
ao_handle = iAO(_Symbol,PERIOD_CURRENT);

スクリプトの実行を進めると、次のタスクとして、指標ハンドルから対応するデータ構造体への値の転送が必要になります。この重要なプロセスには、指標の出力を事前に確立されたデータ構造体に綿密にマッピングすることが含まれます。

//---Set the values as series
CopyBuffer(ma_handle,0,0,size,ma_reading);
ArraySetAsSeries(ma_reading,true);
CopyBuffer(rsi_handle,0,0,size,rsi_reading);
ArraySetAsSeries(rsi_reading,true);
CopyBuffer(cci_handle,0,0,size,cci_reading);
ArraySetAsSeries(cci_reading,true);
CopyBuffer(ao_handle,0,0,size,ao_reading);
ArraySetAsSeries(ao_reading,true);

ファイル書き込みプロセスを開始するために、スクリプト内にファイルハンドラを確立することが重要な前準備となります。

//---Write to file
int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

その後、データセットを系統的に走査し、指定したCSVファイルにデータを書き込む綿密なプロセスを編成するという、スクリプトの極めて重要な段階を展開します。この繰り返しには、各データポイントの詳細な検査と抽出が含まれ、設定されたパラメータに沿った入念な順序に従います。

for(int i=-1;i<=size;i++){
      if(i == -1){
            FileWrite(file_handle,"Open","High","Low","Close","MA 20","RSI 60","CCI 10","AO");
      }
      
        else{
                FileWrite(file_handle,iOpen(_Symbol,PERIOD_CURRENT,i),
                                        iHigh(_Symbol,PERIOD_CURRENT,i),
                                        iLow(_Symbol,PERIOD_CURRENT,i),
                                        iClose(_Symbol,PERIOD_CURRENT,i),
                                        ma_reading[i],
                                        rsi_reading[i],
                                        cci_reading[i],
                                        ao_reading[i]);
      } 
}

スクリプトのセットアップが完了したら、お好みの銘柄でスクリプトを実行してください。その後、スクリプトは以下に例示する形式を反映したCSVファイルを生成し、それによって選択された銘柄に関連する関連データの包括的かつ構造化された表現を提供します。

取引データ

図7:市場データCSVファイル

現在、私たちの焦点は本物の市場データの分析に移っています。私たちの目的は、その後15分以内に予想される価格上昇を予測するように設計された回帰モデルを構築することです。分析追求の極めて重要な基準は、回帰モデルの信憑性の検証です。この信頼性が確立されたなら、検証されたモデルをONNX形式にエクスポートし、エキスパートアドバイザー(EA)の開発に活用する予定です。

この段階に入り、最初のステップでは必要不可欠な依存関係を読み込みます。これらの依存関係の中で特筆すべきは、包括的な統計解析ツール群で有名な'Archパッケージです。Archの統合により、分析作業に活用できる貴重なリソースが豊富に提供され、市場データ分析へのアプローチの深さと洗練度が向上します。

import pandas as pd
import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from arch.unitroot import PhillipsPerron , ADF , KPSS
import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import DoubleTensorType

次にMQL5スクリプトで作成したcsvを読み込みます。

csv = pd.read_csv("/enter/your/path/here")

そして目標を準備します。終値の伸びです。

csv["Target"] = csv["Close"] / csv["Close"].shift(-15)
csv.dropna(axis=0,inplace=True)

データフレームを調べてみましょう。

データフレーム

図8:Pandasで市場データのCSVファイルを読み込む

そこから、訓練とテストの分割を準備します。

train = np.arange(0,20000)
test = np.arange(20020,89984)


ここでstatsmodelsを使用して重回帰をフィットします。

ols = sm.OLS(csv.loc[:,"Target"],csv.loc[:,["Open","High","Low","Close","MA 20","RSI 60","CCI 10","AO"]])
lm = ols.fit()
print(lm.summary())

市場データOLS

図9:価格の伸びを予測するためにすべての変数を回帰した結果

まとめて、モデル結果の解釈を掘り下げてみましょう。始値特徴に関連付けられた係数の信頼区間は0を含んでおり、統計的有意性の欠如を示しています。従って、この特徴をモデルから除外するという決定は、その寄与が有意でない可能性があることによります。さらに精査すると、相対力指数(RSI)と商品チャネル指数(CCI)はともに0に近い係数を示し、それぞれの信頼区間もこの値に近いことがわかります。これを踏まえて、これらの特徴の有用性への限界的な貢献は限られている可能性があると仮定し、慎重にこれらの機能を削除することを選択します。

私たちのモデルは際立って高い決定係数値を誇っており、分散のかなりの割合が含まれる特徴によって説明されることを示唆していますが、同時にダービン・ワトソン(DW)統計量を調べると低い値であることがわかります。DW統計量が低いということは、残留相関の可能性を示しており、モデルの妥当性を損なう可能性があるためです。その結果、私たちは残差の綿密な分析、特にその定常性に注目することを提唱します。この追加の精査層は、データ内の根本的なパターンを捉えるモデルの堅牢性と信頼性を確保するために不可欠です。

重要だと思われる特徴を保存するためのデータ構造体を作ってみましょう。

predictors = ["High","Low","Close","MA 20","AO"]

残差を記録しましょう。

residuals = csv.loc[test[0]:test[-1],"Target"] - lm.predict(csv.loc[test[0]:test[-1],predictors])

モデル評価過程の重要なステップである残差プロットの作成に進みましょう。 

plt.plot(residuals)

市場データ残差

図10:市場データの予測から残差をプロットする

残差を分析することは、私たちのモデルがデータにどの程度フィットしているかを理解する上で極めて重要なステップです。残差は、モデルに系統誤差があるかどうか、あるいは一貫した誤りがあるかどうかを明らかにすることができます。予測に一貫したばらつきがあるか、過去の誤差に依存していないかなど、モデルが特定の基本的なルールに従っているかどうかを確認するのに役立つため、これは重要です。

残差を見るときに注意しなければならないのは「単位根」と呼ばれるものです。これは基本的に、残差が時間の経過とともに消えない変化のパターンを示すことを意味します。誤差にしつこい傾向があるようなものです。残差の単位根を見つけることは大きな問題です。各予測が他の予測から独立しているというような、モデルに関する基本的な仮定のいくつかを台無しにするからです。

単位根を処理することは重要です。なぜなら、もし処理しなければ、モデルがどれだけ優れているかの推定が台無しになり、そこから導き出される結論を信頼することが難しくなるからです。

残留を調査するときは、単に外側を確認するだけではありません。モデルが精査に耐えうることを確認し、単位根のような問題を修正することで、予測の信頼性を高めています。

次のように考えることが助けになるかもしれません。一見簡単そうに見える猫の分類作業を想像してみてほしいです。私たちの猫に対する理解が不変の真理を象徴するような理想的なシナリオでは、それぞれの分類は完璧であり、そのような理想的なモデルから誤差をプロットすれば、y=定数で表される静止した直線が得られるでしょう。この線は、猫はどの時点でも常に猫であり、どの時点でも猫と呼べば誤差は0になるという真理の不変性を象徴しています。

しかし、分類器の熟練度が低下すると、誤分類が発生し、残差がこの完全な定常性から逸脱してしまいます。この偏差は分類器の真理からの乖離に対応し、残差の揺らぎとして現れます。この非定常性は、犬が猫と誤判定される日や、その逆の日に発生します。これは、分類器が猫の特徴を不確実に理解していることを反映しています。

統計的厳密性をより深く掘り下げるために、残差の単位根と時系列分析の非定常性との関連を考えてみましょう。残差に単位根が存在することは、分類器の知識不足に類似しており、非定常性を意味し、残差のボラティリティを増加させます。知識不足が深まれば深まるほど、その変動は顕著になり、犬を猫と誤認したり、その逆もまた然りです。

これらの概念を理解することは重要です。より良い予測をするためにモデルを微調整するのに役立つからです。よって、一見うまくいっているように見えても、残差の動きを再確認する価値はあります。これに対して万能の検定はないため、複数の方法を使い、残差の全体的な傾向を見るのが最善です。


フィリップス–ペロン検定

フィリップス–ペロン検定は、計量経済学や時系列分析において、時系列データセットにおける単位根の存在を評価するために用いられる統計検定です。単位根は、時系列変数が確率的トレンドを持ち、非定常であることを意味します。定常性は多くの統計分析において重要な仮定であり、非定常な時系列データは見せかけの回帰の結果を導く可能性があります。

フィリップス–ペロン検定はディッキー–フラー検定のバリエーションで、1988年に、Peter C.B.フィリップスとピエール・ペロンによって提案されました。ディッキー–フラー検定と同様に、フィリップス–ペロン検定は、時系列変数の経時的な振る舞いを調べることによって単位根の存在を検出するように設計されています。

フィリップス–ペロン検定の基本的な考え方は、差分化された時系列変数をそのラグ値で回帰することです。そして検定統計量を用いて、遅延変数の係数がゼロから有意に異なるかどうかを評価します。係数がゼロから有意に異なれば、単位根の存在は否定され、時系列が定常的であることが示唆されます。

フィリップス–ペロン検定の特筆すべき特徴の1つは、データ内のある種の系列相関と不均一分散性が許容されることです。これは、元のディッキー–フラー検定では考慮されません。これにより、フィリップス–ペロン検定は、現実の時系列データに存在する可能性のある特定の仮定違反に対して頑健になります。

  1. 帰無仮説:フィリップス–ペロン検定の帰無仮説は、時系列変数に単位根が含まれ、非定常であることを意味します。
  2. 対立仮説:対立仮説は、時系列変数が単位根を含まず、定常であることを示します。
  3. 識別規則:計算された検定統計量が臨界値より小さければ、単位根が存在するという帰無仮説を棄却し、時系列が定常であるという証拠が示唆されます。  計算された検定統計量が臨界値より大きい場合、帰無仮説を棄却できず、時系列が定常であると結論づける証拠が不十分であることが示されます。

フィリップス–ペロン検定を実行するためにarchライブラリを使用します。

pp = PhillipsPerron(residuals)

要約した結果を得ることができます。フィリップス–ペロン検定の要約統計量

pp.summary()

Phillips-Perron Test
(Z-tau)

Test Statistic -73.916

P-value 0.000

Lags 62

Trend: Constant
Critical Values: -3.43 (1%), -2.86 (5%), -2.57 (10%)

Null Hypothesis: The process contains a unit root.
Alternative Hypothesis: The process is weakly stationary.

結果を一緒に解釈しましょう。検定統計量は-73.916です。この値は、推定された係数が仮説の値1(単位根の存在を示す)から何標準偏差離れているかを表します。この場合、非常に大きな負の検定統計量は、単位根の存在に対する強い証拠を示唆し、時系列の定常性を支持します。

検定統計量のp値は0.000です。p値は帰無仮説に対する証拠の尺度です。p値が0.000というのは、帰無仮説が真であるという仮定のもとでは、観測された検定統計量がきわめてありえないことを意味します。実際的には、この極めて小さなp値は、単位根の存在に対する強力な証拠となります。

非常に低いp値(0.000)を考えると、通常の有意水準(例えば0.05)では帰無仮説を棄却するのが普通です。p値が選択した有意水準を下回ることから、時系列が定常的である可能性が高いことが示唆されます。要約すると、提供された結果に基づき、単位根の帰無仮説を棄却する強力な証拠があり、時系列が定常である可能性が高いことを示しています。ただし、私たちは検定に基づいて決定を下すことはできません。さまざまな検定から中心傾向の尺度を観察したいのです。

拡張ディッキー–フラー検定

archライブラリを使用して拡張ディッキー–フラー検定をおこないます。

adf = ADF(residuals)

要約統計を取得することができます。拡張ディッキー–フラー検定による要約統計量

adf.summary()
Augmented Dickey-Fuller Results

Test Statistic -31.300
P-value 0.000
Lags 60

Trend: Constant
Critical Values: -3.43 (1%), -2.86 (5%), -2.57 (10%)

Null Hypothesis: The process contains a unit root.
Alternative Hypothesis: The process is weakly stationary.


では、結果を解釈してみましょう。検定統計量は-31.300です。フィリップス–ペロン検定と同様に、ADF検定統計量は時系列における単位根の存在を評価するために使用されます。この場合、非常に大きな負の値は、単位根の帰無仮説に対する強い証拠を示しており、時系列が定常であるという考えを裏付けています。

関連するp値は0.000です。フィリップス–ペロン検定と同様、p値が0.000というのは、帰無仮説(単位根の存在)が真であるという仮定の下では、観測された検定統計量が極めてあり得ないことを意味します。p値が非常に小さいことは、帰無仮説に対する強力な証拠となります。

低いp値(0.000)を考えると、通常の有意水準では帰無仮説を棄却するのが普通です。拡張ディッキー–フラー検定から得られた証拠は、時系列が定常的である可能性が高いという結論を支持しています。

フィリップス–ペロン検定と拡張ディッキー–フラー検定の両方が、単位根の存在に対する強力な証拠を提供しており、時系列が定常的である可能性が高いことを示しています。これら2つの検定は、どちらも時系列データの定常性を評価するために設計されているため、結果の類似性は予想通りです。これらの選択は、多くの場合、データの特定の特性とテストの仮定に依存します。あなたの場合、どちらのテストも時系列が定常的であることを示唆しています。


モデルをONNX形式にエクスポートする

ONNX(Open Neural Network Exchange)は、機械学習モデルを表現するためのオープンで相互運用可能な形式です。共同コミュニティによって開発されたONNXは、様々なフレームワークやツール間でモデルのシームレスな交換を可能にし、機械学習のエコシステムにおける相互運用性を促進します。これによって、学習済みモデルを表現し、転送するための標準化された方法が提供され、開発者は容易に異なるプラットフォーム間でモデルを展開し、多様なアプリケーションに統合できるようになります。ONNXは、幅広い機械学習モデルとフレームワークをサポートし、モデル開発と導入ワークフローの柔軟性と効率性を促進します。

このコードでは、ONNXファイルを生成する際に使用する「double_input」という変数の初期型を定義しています。指定された型はDoubleTensorTypeであり、入力データがdouble精度であることを示します。入力テンソルの形状は、予測に使用される特徴('csv.loc[:, predictors].shape[1]'を使用して取得)に対応するDataFrame(おそらくcsvという名前)の列数によって決定されます。形状のNoneは、最初の次元のサイズ(インスタンスまたはサンプルの数を表すと思われる)が、この段階では固定されていないことを示しています。

initial_type_double = [('double_input', DoubleTensorType([None, csv.loc[:,predictors].shape[1]]))]

このコードは、学習した線形回帰モデル「lm」をONNX表現に変換するためにconvert_sklearn関数を利用しています。先のコードスニペットで定義されたinitial_type_double変数は、入力データの型としてdouble精度を指定します。さらに、target_opsetパラメータは12に設定され、ONNX演算子セットの望ましいバージョンを示します。結果として得られるonnx_model_doubleは、提供された線形回帰モデルのONNXモデル表現となり、 ONNX形式をサポートする他のフレームワークとの展開や相互運用に適しています。

onnx_model_double = convert_sklearn(lm, initial_types=initial_type_double, target_opset=12)

このコードは、線形回帰モデルのONNXモデル表現を保存するためのファイル名前(EURUSD_ONNX)を指定しています。前述のconvert_sklearn関数を使用して変換された結果のONNXモデルは、このファイル名で保存され、将来使用または展開する際に簡単に識別してアクセスできるようになります。

onnx_model_filename = "EURUSD_ONNX"

このコードは、以前に定義されたファイル名(EURUSD_ONNX)と接尾辞(_Double.onnx)を組み合わせて、新しいファイル名(EURUSD_ONNX_Double.onnx)を作成しています。その後、onnx.save_model関数を使用して、ONNXモデル(onnx_model_double)を作成したファイル名でファイルに保存します。このプロセスは、double精度の線形回帰モデルを表すONNXモデルが保存され、指定されたファイル名を使用して簡単に参照できることを保証します。

onnx_filename=onnx_model_filename+"_Double.onnx"
onnx.save_model(onnx_model_double, onnx_filename)


統合された多用途のMetaEditorを活用して、MetaQuotes Language 5 (MQL5)でONNXファイルのEAを構築する

MetaQuotes Language 5 (MQL5)でONNXファイルのEAを開発するには、統合された多機能なMetaEditorの機能を活用する必要があります。EAはMQL5で書かれたスクリプトで、MetaTrader 5プラットフォームでの自動取引を可能にします。この文脈において、EAはONNXファイルとのインターフェイスを持ち、機械学習モデルを取引戦略にシームレスに統合することを容易にし、それによって予測分析に基づく意思決定プロセスを強化します。MetaEditorは、EAのコーディング、テスト、最適化のための包括的な環境を提供し、MetaTrader 5フレームワーク内での効率的な展開と実行を保証します。

まず、MetaTrader 5 (MT5)で取引操作を処理するための標準ライブラリであるMQL5のTradeライブラリを組み込みます。Tradeライブラリは、ポジションのオープンやクローズ、注文の管理、取引関連のイベントの処理など、さまざまな取引活動の実行を容易にする定義済みの関数や構造を提供します。このライブラリをEAに含めることで、MQL5コード内で取引ロジックと操作を合理的かつ効率的に実装できます。

//Our trade class helps us open trades
#include <Trade\Trade.mqh>
CTrade Trade;

MQL5のこのコードスニペットでは、EAを活用し、予測分析のためにONNXモデルを組み込んでいます。#resourceディレクティブは、ONNXモデルファイル「EURUSD_ONNX_Double.onnx」をONNXModelというバイト配列としてEAのリソースに埋め込むために使用されます。これにより、EA内での機械学習モデルへのアクセスと活用が容易になります。

変数ONNXHandleはINVALID_HANDLEとして初期化され、EAの実行中にONNXモデルが読み込まれると、そのハンドルまたは識別子を格納するために使用されることを示します。

さらに、PredictedMoveは-1に初期化され、ONNXモデルに基づく予測された動きや結果がまだ決定されていないことを示唆します。この変数は、EAが実行中にONNXモデルを通じて関連データを処理すると、予測値で更新される可能性が高いです。予測ロジックとさらなる処理の詳細は、EAコードの後続セクションによります。

//Loading our ONNX model
#resource "ONNX\\EURUSD_ONNX_Double.onnx" as uchar EURUSD_ONNX_MODEL[]
long     ONNXHandle=INVALID_HANDLE;

EA用のMQL5コードのこのセクションでは、移動平均用のma_handleとma_reading[]、そしてAwesome Oscillator用のao_handleとao_reading[]の2つの変数セットが宣言されています。

変数ma_handleは、移動平均指標の参照または識別子として機能し、EAがこの特定のテクニカル分析ツールと対話し、情報を取得できるようにします。でk列ma_reading[]は移動平均の計算値を格納するためのもので、EAが意思決定のために過去の値にアクセスし、分析できるようにします。

同様に、変数ao_handleはAwesome Oscillator指標の識別子を表し、配列ao_reading[]は対応する計算値を格納するように指定されます。 

//Handles for our technical indicators and dynamic arrays to store their readings
int ma_handle;
double ma_reading[];
int ao_handle;
double ao_reading[];

//Inputs
int input sl_width = 150; //How wide should the stoploss be?
int input  positions = 1; //How many positions should the we open?
int input lot_multiple = 1; //How many times greater than minimum lot should each position be?

//Symbol variables
double min_volume;
double bid,ask;

//We'll use this time stamp to keep track of the number of candles passing
static datetime time_stamp;

//Our model's forecast will be stored here
vector model_forecast(1);


OnInit関数

OnInit()関数は、MQL5のEAの重要な部分であり、EAがMetaTrader 5のチャートにアタッチされたときに自動的に実行される初期化関数として機能します。この関数では通常、EAのセットアップや準備に関するさまざまなタスクが実行されます。これから検証する残りのコードは、OnInit()ハンドラの中にネストされています。

EAのOnInit()関数内のこの条件文は、取引銘柄がEURUSDで、チャートの時間枠がM1(1分間隔)に設定されているかどうかを確認します。条件が満たされない場合、EAはPrint()関数を使用してコンソールにメッセージを表示し、モデルが1分足時間枠のEURUSD通貨ペアで特別に動作する必要があることを示します。その後、「return(INIT_FAILED)」文がEAの初期化プロセスを終了するために使用され、指定された条件が満たされない場合は初期化に失敗したことを示します。

int OnInit(){
    //Validating trading conditions
   if(_Symbol!="EURUSD" || _Period!=PERIOD_M1)
     {
      Print("Model must work with EURUSD on the M1 timeframe");
      return(INIT_FAILED);
     }

MQL5のEA内のこのコードセグメントは、静的バッファからONNXモデルを作成する役割を担っています。この目的のために関数OnnxCreateFromBufferが利用され、ONNXModelバッファに格納されたONNXモデルデータをパラメータとして受け取り、デフォルト設定を使用してモデルを作成します。

実行すると、ONNXHandle変数に、作成されたONNXモデルに関連するハンドルまたは識別子が代入されます。その後、条件文がONNXHandleが有効なハンドルであるか(INVALID_HANDLEと等しくないか)を確認します。ハンドルが有効でない場合、EAはPrint()関数を使用してコンソールにエラーメッセージを表示し、(GetLastError()を介して)発生したエラーに関する情報を提供し、INIT_FAILEDを返すことによって初期化の失敗を通知します。

このコードセクションは、EAを機能的なONNXモデルで初期化し、提供されたバッファからモデルが正常に作成されるようにするために重要です。このプロセスに不備があった場合は、速やかに報告されます。

ONNXHandle=OnnxCreateFromBuffer(ONNXModel,ONNX_DEFAULT);

if(ONNXHandle==INVALID_HANDLE)
{
   Print("OnnxCreateFromBuffer error ",GetLastError());
   return(INIT_FAILED);
}

このコードでは、2つの要素を持つinput_shapeという定数配列を宣言しています。この配列はlong型で、整数を保持します。配列の要素は、機械学習モデルやアルゴリズムの入力データの形状を表します。この場合、配列input_shapeは値{1, 5}で初期化され、入力データが1行5列であることを示します。

//Defining the model's input shape
const long input_shape[] = {1,5};

このコードブロックはMQL5のEA内にあり、関数OnnxSetInputShapeを使用してONNXモデルの入力形状の設定が成功したかどうかを確認します。入力形状はinput_shape配列で指定され、これは入力データの予想される次元を示します。

if文は、OnnxSetInputShapeという条件の否定(設定が成功しなければ真を返す)が成り立つかどうかを評価します。この条件がTRUEの場合、EAはPrint()関数を使用してエラーメッセージをコンソールに表示し、 GetLastError()で取得したエラーの詳細を伝えます。その後、この関数は初期化に失敗したことを示すINIT_FAILEDを返します。

if(!OnnxSetInputShape(ONNXHandle,ONNX_DEFAULT,input_shape)) 
{
    Print("OnnxSetInputShape error ",GetLastError());
    return(INIT_FAILED); 
}

このコード行は、output_shapeという2つの要素を持つ定数配列を宣言しています。先のinput_shapeの宣言と同様、この配列はlong型で整数を保持します。この場合、値{1, 1}がoutput_shapeに代入され、機械学習モデルからの出力データの予想される形状が、1つの要素を持つ1次元配列であることを示します。

出力形状の指定は、機械学習モデルによって生成された結果を適切に扱い、解釈するために極めて重要です。 

//Defining the model's output shape
const long output_shape[] = {1,1};

このコードブロックはMQL5のEA内にあり、関数OnnxSetOutputShapeを使用してONNXモデルの出力形状の設定が成功したかどうかを確認します。出力形状はoutput_shape配列で指定され、これは出力データの予想寸法を示します。

if文は、条件OnnxSetOutputShapeの否定(設定が成功しなければ真を返す)が成り立つかどうかを評価します。この条件がTRUEの場合、EAはPrint()関数を使用してエラーメッセージをコンソールに表示し、 GetLastError()で取得したエラーの詳細を伝えます。その後、この関数は初期化に失敗したことを示すINIT_FAILEDを返します。

入力形状の設定と同様に、出力形状の設定も、機械学習モデルの出力の期待される形式と、EAにおける下流の処理要件を整合させるために不可欠です。 

if(!OnnxSetOutputShape(ONNXHandle,0,output_shape))
{
    Print("OnnxSetOutputShape error ",GetLastError());
    return(INIT_FAILED);
}

MQL5のEAのOnInit()関数のこの部分では、2つのテクニカル指標が初期化されています。

  1. ma_handleには、_Symbolで指定された銘柄の1分足チャート(PERIOD_M1)の終値(PRICE_CLOSE)に基づいて計算された20期間の指数移動平均(EMA)のハンドルまたは識別子が割り当てられます。
  2. ao_handleには、同じ銘柄の1分足チャートで計算されたAwesome Oscillator (AO)指標のハンドルが割り当てられます。
  3. min_volumeは、ブローカーが許容する最小の契約サイズです。
//Setting up our technical indicators
ma_handle = iMA(_Symbol,PERIOD_M1,20,0,MODE_EMA,PRICE_CLOSE);
ao_handle = iAO(_Symbol,PERIOD_M1);
min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
return(INIT_SUCCEEDED);
}


OnDeinit関数

このMQL5 EAのOnDeinit関数は、EAの非初期化プロセスのハンドラとして機能します。EAが削除されるか、取引プラットフォームが閉じられると自動的に実行されます。

この関数内では、条件文がONNXHandle変数が有効なハンドルを保持しているか(INVALID_HANDLEと等しくないか)を確認します。この条件が真であれば、EAのライフタイム中にONNXモデルが初期化されたことを意味します。このような場合、ONNXモデルに関連付けられたリソースを解放するためにOnnxRelease関数が呼び出され、その後ONNXHandleがINVALID_HANDLEに設定されます。

この非初期化ルーチンは、リソースの適切な解放を保証し、メモリリークを防ぎ、EAのライフサイクルの全体的な効率ときれいさに貢献します。これは、EAの実行中に取得したリソースを管理および解放し、取引システムの堅牢性と信頼性を向上させるための責任あるコーディングプラクティスを反映しています。

void OnDeinit(const int reason)
  {
      //OnDeinit we should freeup resources we don't require
      //We'll begin by releasing the ONNX models then removing the Expert Advisor
      if(ONNXHandle!=INVALID_HANDLE)
     {
      OnnxRelease(ONNXHandle);
      ONNXHandle=INVALID_HANDLE;
     }
   //Lastly remove the Expert Advisor
   ExpertRemove();
  }


OnTick関数

OnTick関数は、このMQL5 EAの重要なコンポーネントであり、各入力ティックで実行されるコードを表します。この関数では以下をおこないます。

移動平均(MA)とオーサムオシレーター(AO)の履歴データは、CopyBuffer関数を使用して取得されます。最新の10個の値が配列(ma_readingとao_reading)にコピーされ、ArraySetAsSeriesが適用され、配列がシリーズのように整理されます。

現在のタイムスタンプ(current_time)は、1分足チャート(PERIOD_M1)と指定された銘柄(_Symbol)についてiTime関数を使用して取得されます。

条件文は、現在のタイムスタンプが保存されているタイムスタンプ(time_stamp)と異なるかどうかを確認します。新しいティックを示す差異があれば、ModelForecast関数が呼び出されます。

このコード構造により、EAは各ティックで直近の指標値を取得・整理することができ、ModelForecast関数を通じて機械学習モデルの予測を定期的に評価することが容易になります。したがって、OnTick関数は、最新の市況とモデル予測に基づくリアルタイムの意思決定の基盤を確立し、EAのダイナミックで適応的な性質に貢献します。

void OnTick()
{
   //Update the arrays storing the technical indicator values to their current values
   //Then set the arrays as series so that the current reading is at the top and the oldest reading is last
   CopyBuffer(ma_handle,0,0,10,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ao_handle,0,0,10,ao_reading);
   ArraySetAsSeries(ao_reading,true);
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
//Update time marker
   datetime current_time = iTime(_Symbol,PERIOD_M1,0);
   //Periodically make forecasts if we have no open positions
   if(time_stamp != current_time){
      //No open positions
      if(PositionsTotal() == 0){
               ModelForecast();
      }  
      //We have open positions to manage
      else if(PositionsTotal() > 0){
               ManageTrade();
      }
   }
  }


モデル推論

このMQL5 EA内のModelForecast関数は、現在の市場状況に基づいて予測をおこなうための機械学習モデルを実行するように設計されています。以下はコードの詳細です。

関数内で2つのベクトルが宣言されています。

  1. model_forecast:機械学習モデルの出力を格納するためのベクトルで、予想や予測を表します。
  2. model_input:モデルに必要な入力特徴量を含むベクトル。これらの特徴量には、現在のローソク足の高値、安値、終値、移動平均の値(ma_reading[0])、Awesome Oscillatorの値(ao_reading[0])が含まれます。

OnnxRun関数は、ONNXモデル(ONNXHandle)を使用して推論を実行するために呼び出されます。ONNX_NO_CONVERSIONフラグは、推論プロセス中にデータ型変換が適用されないことを示します。入力特徴量(model_input)が提供され、結果の予測はmodel_forecastベクトルに格納されます。

条件文は、推論プロセスが成功したかどうかを確認します。成功していない場合は、Print()関数を使用してエラーメッセージがコンソールに出力され、GetLastError()で取得したエラーの詳細が表示されます。

推論が成功すれば、model_forecast vectorに格納された予測がコンソールに出力されます。

この関数は、現在の市場環境に基づくモデル予測を得るための重要なステップをカプセル化し、EA内で動的かつ適応的な取引戦略を育成します。エラー処理メカニズムを組み込むことで、推論プロセス中の潜在的な問題に対する洞察を提供し、システムのロバスト性を高めます。完了すると、この関数はNextMove()を呼び出します。

//This function provides these utilities:
//          1) Inferencing using our ONNX model 
//          2) Calling the next function responsible for intepreting model forecasts and other subroutines
void ModelForecast(void){
      //These are the inputs for our ONNX model:
      //          1)High 
      //          2)Low
      //          3)Close
      //          4)20 PERIOD MA MODE: EMA  APPLIED_PRICE:PRICE CLOSE 
      //          5)Awesome Oscilator
      vector model_input{iHigh(_Symbol,PERIOD_M1,0),iLow(_Symbol,PERIOD_M1,0),iClose(_Symbol,PERIOD_M1,0),ma_reading[0],ao_reading[0]};
      //Inferencing with our model
      if(!OnnxRun(ONNXHandle,ONNX_NO_CONVERSION,model_input,model_forecast)){
            Print("Error performing inference: ",GetLastError());
      }
      //Pring model forecast to the terminal
      else{
               Print(model_forecast);
                NextMove();
      }
}


モデルの解釈

モデルを解釈するためには、2つやることがあります。

  1. モデル出力の解釈:この関数は、予測モデルの出力を解釈するためにInterpretForecast関数を使用します。InterpretForecast関数は、最初は引数1で、次に引数-1で、2回呼び出されます。これらの呼び出しの目的は、モデルの出力が特定の方向を示しているかどうかを確認することです。プラス予想なら1、マイナス予想なら-1です。
  2. 解釈に基づいて行動する:モデル出力の解釈に応じて、この関数は特定のアクションを取ります。モデルがプラスの結果(1)を予測した場合、CheckOrder関数を引数1で呼び出し、戻ります。モデルがマイナスの結果(-1)を予測した場合、CheckOrder関数を-1の引数で呼び出します。

要約すると、NextMove関数は予測モデルの出力を処理し、それを特定の値(1または-1)に基づいて解釈し、解釈された値でCheckOrder関数を呼び出して対応するアクションを取るように設計されています。

//This function provides these utilities:
//          1) Getting the model output intepreted
//          2) Acting on the intepretations
void NextMove(){
      if(InterpretForecast(1)){
            CheckOrder(1);
            return;
      }     
      else if(InterpretForecast(-1)){
            CheckOrder(-1);
      }
}


InterpretForecast関数は、指定された方向に基づいて予測モデルの出力を解釈する目的を果たします。この関数は、引数に方向(1または-1)を取ります。解釈は、モデルが1より大きい数値を予測したか、1より小さい数値を予測したかによって異なります。

コードの内訳はこうです。

directionが1に等しい場合、この関数は配列model_forecastの最初の要素(model_forecast[0])が1より大きいかどうかを確認します。1より大きい場合、この関数はtrueを返し、モデルが現在の価格よりも大きな成長を予測していることを示します。

directionが-1の場合、この関数は配列model_forecastの最初の要素(model_forecast[0])が1より小さいかどうかを確認します。1より小さい場合、この関数はtrueを返し、モデルが現在の価格よりも低い成長を予測していることを示します。

方向が1でも-1でもない場合、この関数はfalseを返します。これはデフォルトのケースであり、指定された方向が認識されず、関数が意味のある解釈を提供できないことを示します。

要約すると、InterpretForecast関数は指定された方向に基づいてモデルの予測値を確認し、条件が満たされればtrueを返し、そうでなければfalseを返します。

//This function provides these utilities:
//          1) Check whether the model forecasted a reading greater than or less than 1.
bool InterpretForecast(int direction){
      //1 means check if the model is forecasting growth greater than the current price
      if(direction == 1){
            return(model_forecast[0] > 1);
      }
      //-1 means check if the model is forecasting growth less than the current price
      if(direction == -1){
            return(model_forecast[0] < 1);
      }
      //Otherwise return false.
      return false;
}


注文の実行

次のコードはCheckOrder関数を定義しています。この関数は、指定された注文方向に基づいて取引ポジションを開始する責任を負います。

ポジションの検証:最初の条件文(PositionsTotal() == 0)は、ポートフォリオにアクティブな取引がないときにのみ新規ポジションが建てられることを保証する、前提条件確認の役割を果たします。

買い注文の執行:order_directionパラメータが1(買い注文を示す)に等しい場合、この関数はforループを採用し、希望するポジション数(ポジション)を繰り返し実行します。このループの中で、Trade.PositionOpen 関数が呼び出され、買いポジションが初期化されます。銘柄(_Symbol)、注文タイプ(ORDER_TYPE_BUY)、数量(min_volume * lot_multiple)、執行価格(ask)などの関連するパラメータが、この関数の引数として提供されます。

売り注文の執行:逆に、order_directionパラメータが-1(売り注文を示す)であり、現在アクティブなポジションがない場合(PositionsTotal() == 0が再評価される)、この関数は同様の反復プロセスを通じて売りポジションを建てることに進みます。Trade.PositionOpen関数は、売りポジション用に調整されたパラメータで再び使用されます。

要約すると、CheckOrder機能は、既存のポジションがないことを考慮し、指定された注文方向を遵守して、規律ある方法で取引ポジションを建てることを保証します。このコードは、アルゴリズム取引戦略の文脈の中で取引ロジックをカプセル化しています。

//This function is responsible for opening positions
void CheckOrder(int order_direction){
      //Only open new positions if we have no open positions
     if(PositionsTotal() == 0){
            //Buy
            if(order_direction == 1){
                  //Iterate over the desired number of positions
                  for(int i = 0; i < positions; i++){
                           Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,0,0,"Volatitlity Doctor AI");
                  }
            }
            //Sell
            else if(order_direction == -1 && PositionsTotal() == 0){
                  //Iterate over the desired number of positions
                  for(int i = 0; i < positions; i++){
                        Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume  * lot_multiple ,bid,0,0,"Volatitlity Doctor AI");
            }
         }
   }
}


取引管理

ManageTrade関数は、中央取引管理モジュールとして機能し、ストップロスおよびテイクプロフィットレベルの調整責任をCheckStop関数に委任します。

CheckStop関数は、すべてのポジションを反復処理し、銘柄、チケット、ポジションタイプ、現在のストップロス、ポジション始値などの関連情報を抽出します。ポジションの銘柄が、アクティブに取引されている銘柄(_Symbol)と一致していることを確認します。各有効なポジションについて、コードはBid価格とAsk価格、ストップロスの幅(sl_width)、ポイント値(_Point)のような事前に定義されたパラメータに基づいて、新しいストップロスとテイクプロフィットのレベルを計算します。

そして、買いポジションと売りポジションを区別します。買いポジションの場合、Ask価格に基づいて新しいストップロスとテイクプロフィットのレベルを計算し、新しいレベルがより有利な場合にのみ調整します。同様に、売りポジションの場合、計算はbit値に基づいておこなわれ、新しい水準がより有利な場合は調整がおこなわれます。

NormalizeDoubleを使用すると、計算されたレベルが指定された桁数(_Digits)に適合するようになります。Trade.PositionModify関数は、必要な場合のみ、更新されたストップロスとテイクプロフィットのレベルで既存の取引を修正するために使用されます。

//This function handles our trade management
void ManageTrade(){
   CheckStop();
}

//This funciton will update our S/L & T/P 
void CheckStop(){
      //First we iterate over the total number of open positions                      
      for(int i = PositionsTotal() -1; i >= 0; i--){
            //Then we fetch the name of the symbol of the open position
            string symbol = PositionGetSymbol(i);
            //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading
                  if(_Symbol == symbol){
                           //Now we get information about the position
                           ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket
                           double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price
                           long type = PositionGetInteger(POSITION_TYPE); //Position Type
                           double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value
                           //If the position is a buy
                           if(type == POSITION_TYPE_BUY){
                                  //The new stop loss value is just the ask price minus the stop we calculated above
                                  double new_stop_loss = NormalizeDouble(ask - ((sl_width * _Point) / 2) ,_Digits);
                                  //The new take profit is just the ask price plus the stop we calculated above
                                  double new_take_profit = NormalizeDouble(ask + (sl_width * _Point),_Digits);
                                  //If our current stop loss is less than our calculated stop loss 
                                  //Or if our current stop loss is 0 then we will modify the stop loss and take profit
                                 if((current_stop_loss < new_stop_loss) || (current_stop_loss == 0)){
                                       Trade.PositionModify(ticket,new_stop_loss,new_take_profit);
                                 }  
                           }
                            //If the position is a sell
                           else if(type == POSITION_TYPE_SELL){
                                  //The new stop loss value is just the ask price minus the stop we calculated above
                                  double new_stop_loss = NormalizeDouble(bid + ((sl_width * _Point)/2),_Digits);
                                  //The new take profit is just the ask price plus the stop we calculated above
                                  double new_take_profit = NormalizeDouble(bid - (sl_width * _Point),_Digits);
                                 //If our current stop loss is greater than our calculated stop loss 
                                 //Or if our current stop loss is 0 then we will modify the stop loss and take profit 
                                 if((current_stop_loss > new_stop_loss) || (current_stop_loss == 0)){
                                       Trade.PositionModify(ticket,new_stop_loss,new_take_profit);
                                 }
                           }  
                  }  
            }
}


EAのメニューにこれらのパラメータが追加されます。


EA

図11:EA


また、各取引が開始されるたびに、ストップ・ロスと利益確定レベルが自動的に設定されるはずです。

作動中のEA

図12:作動中のEA


結論

結論として、時系列データをモデル化する際、見せかけの回帰は重要な課題となり、しばしば誤解を招く信頼性の低い結果につながります。研究者や実務家は、特に非定常時系列データを扱う場合、回帰結果を解釈する際に注意を払わなければなりません。見せかけの回帰のリスクを軽減するためには、単位根検定、 共積分(cointegration)分析、定常変数の利用など、適切な統計的手法を採用することが重要ですさらに、誤差補正モデルなどの高度な時系列手法を採用することで、回帰分析の頑健性を高め、より正確で意味のある経済解釈に貢献することができます。結局のところ、潜在的な見せかけの関係に直面しながらも、信頼できる有効な回帰結果を得ようとする研究者にとって、基礎となるデータ特性の微妙な理解と統計的手法の厳密な適用が不可欠です。

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

MQL5における修正グリッドヘッジEA(第4部):シンプルなグリッド戦略の最適化(I) MQL5における修正グリッドヘッジEA(第4部):シンプルなグリッド戦略の最適化(I)
この第4部では、以前に開発したシンプルヘッジとシンプルグリッドエキスパートアドバイザー(EA)を再考します。最適な戦略の使用を目指し、数学的分析と総当り攻撃アプローチを通じてシンプルグリッドEAを改良することに焦点を移します。戦略の数学的最適化について深く掘り下げ、後の回でコーディングに基づく最適化を探求するための舞台を整えます。
MQL5入門(第7回):MQL5でEAを構築し、AI生成コードを活用するための初心者ガイド MQL5入門(第7回):MQL5でEAを構築し、AI生成コードを活用するための初心者ガイド
この記事は、MQL5でエキスパートアドバイザー(EA)を構築するための包括的な、究極の初心者ガイドです。擬似コードを使用してEAを構築し、AIが生成したコードのパワーを活用する方法をステップごとに学びましょう。アルゴリズム取引が初めての方にも、スキルアップを目指す方にも、このガイドは効果的なEAを作成するための明確な道筋を提供します。
Candlestick Trend Constraintモデルの構築(第3回):使用中のトレンド変化の検出 Candlestick Trend Constraintモデルの構築(第3回):使用中のトレンド変化の検出
この記事では、経済ニュースの発表、投資家の行動、さまざまな要因が市場のトレンド反転にどのような影響を与えるかを探ります。ビデオによる説明もあり、MQL5のコードをプログラムに組み込むことで、トレンドの反転を検出し、警告を発し、市場の状況に応じて適切な行動を取ることができます。これは、本連載の過去の記事に基づいています。
多通貨エキスパートアドバイザーの開発(第2回):取引戦略の仮想ポジションへの移行 多通貨エキスパートアドバイザーの開発(第2回):取引戦略の仮想ポジションへの移行
複数の戦略を並行して動作させる多通貨エキスパートアドバイザー(EA)の開発を続けましょう。マーケットポジションを建てることに関連するすべての作業を、戦略レベルから、戦略を管理するEAのレベルに移してみましょう。戦略自体は、マーケットポジションを持つことなく、仮想の取引のみをおこないます。