ChatGPTでMT5用複数チャート同期インジケーターを作ってみた
複数チャートのシンボルを即同期させる仕組みを作りたい
ChatGPTを使って、MT5向けのチャート同期インジケーターを作ることにしました。事前のイメージとしては、
- 複数のチャートを開いている状態
- 気配値表示からのドラッグ&ドロップで、適当なチャート1つのシンボルを変更
- 同じインジケーターをインストールしているチャート全てが、その新たなシンボルに切り替わる
といったことを実現できるインジケーターです。
裁量トレードで複数の時間足を同時表示しているのですが、銘柄を切り替える時に、毎回全部1個1個変更するのは、正直かなり面倒くさいわけです。
できれば1回の操作で全チャートが同期するツールが欲しいなと思って調べてみた所、有料でそれっぽいインジケーターは見つかりましたが、無料では意外に見つからず。
というわけで、ChatGPTを活用しつつ、自前で作ってみることにしました。ChatGPTと一体どんなやり取りをしてプログラミングしていくのか、プログラミング初心者の方向けにその過程を公開します。
1周目:必要要件を聞き出す
エンジニア向けのプロンプトを参考にしつつ、最初はアバウトな感じでChatGPTに投げかけてみます。
あなたは、MT5及びMQL5に精通したプロのITエンジニアです。
以下の制約条件をもとに、最高のMQL5プログラムを作ってください。
##制約条件
・複数のチャートが開いている状態
・ある一つのチャートのシンボルをシンボルAに変更した際、同時に他の全てのチャートがシンボルAに変わる
回答を出力する前に、前提条件が不足していないかチェックしてください。
不足している情報があれば、どんな情報が必要なのか箇条書きで書き出してください。
ChatGPTからの回答は以下の通り。
この要件を満たすMQL5プログラムを作成するためには、以下の前提条件を確認する必要があります:
1. どのようなトリガーでチャートのシンボル変更を検知するのか?
- チャートのシンボルが変更されたことを検知する具体的な方法
- 例えば、ある特定のイベント、ボタンのクリック、または定期的なチェックなど
2. シンボルAは固定のシンボルなのか、それともユーザーが任意に変更できるシンボルなのか?
- ユーザーが任意にシンボルAを設定できる場合、その設定方法
3. プログラムを適用するチャートの数や特定のチャートの識別方法
- 例えば、全てのチャートなのか、特定の名前やIDを持つチャートのみなのか
4. エラーハンドリングや例外処理の要件
- 例えば、シンボルの変更が失敗した場合の処理
5. パフォーマンスに関する要件
- 複数のチャートのシンボル変更に伴うパフォーマンスへの影響や最適化の必要性
以下が不足している情報の箇条書きです:
- シンボル変更を検知する具体的なトリガー
- シンボルAの設定方法(固定かユーザー任意か)
- 対象となるチャートの識別方法
- エラーハンドリングや例外処理の要件
- パフォーマンスに関する具体的な要件
上記の情報を提供いただければ、より具体的かつ精度の高いMQL5プログラムを作成することができます。
事前に仕様を深く考えていなかったせいか、細かいツッコミがたくさん来てしまいました。
2周目:叩き台になるコードを作成してもらう
続いて指定された順にそのまま返答してみます。
1. 特定のイベント
2. 気配値表示からのドラッグアンドドロップでユーザーが任意に変更
3. 同じインジケーターをインストールしており、変更許可されている全てのチャート
4. ログにエラーを出力
5. 特になし
そして返って来たコードが以下の通り。
//+------------------------------------------------------------------+
//| SyncSymbols.mq5 |
//| Copyright 2024, User |
//| www.user.com |
//+------------------------------------------------------------------+
#property strict
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
EventSetTimer(1); // 1秒ごとにチェック
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
}
//+------------------------------------------------------------------+
//| Expert timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
static string lastSymbol = "";
string currentSymbol = Symbol();
if (lastSymbol != currentSymbol)
{
lastSymbol = currentSymbol;
Print("Symbol changed to: ", currentSymbol);
SynchronizeCharts(currentSymbol);
}
}
//+------------------------------------------------------------------+
//| Function to synchronize all charts to the new symbol |
//+------------------------------------------------------------------+
void SynchronizeCharts(string newSymbol)
{
long chartID = ChartFirst();
while (chartID >= 0)
{
if (ChartIndicatorExist(chartID,0,"SyncSymbols") >= 0
{
if (ChartSymbol(chartID) != newSymbol)
{
if (!ChartSetSymbolPeriod(chartID, newSymbol, ChartPeriod(chartID)))
{
Print("Failed to set symbol for chart ID: ", chartID, " Error: ", GetLastError());
}
}
}
chartID = ChartNext(chartID);
}
}
//+------------------------------------------------------------------+
とりあえずそれっぽいコードを出してきましたが、このコードが実際に機能するかどうかはまだ分かりません。これを叩き台にして細かい点を詰めていきます。
3周目~:疑問点を質問⇒修正⇒動作確認を繰り返す
動かしてみる前に、今の段階で疑問に思うこと、変更したいことを洗い出してみます。
- OnTimer毎に毎回lastSymbol = “"と宣言しているため、(lastSymbol != currentSymbol)の部分は常に真にならないか?
- ChartIndicatorExist関数なんてMQL5に存在したっけ?コード内にも記述はない模様。
- OnTimerとかOnTickで処理を回すのは重くなるので、できればイベント処理なんかでドラッグ&ドロップで切り替えた瞬間を検知してトライする形にしたい。
これらの疑問をChatGPTにぶつけたいわけですが、一度に全部投げかけると変な方向へ行ってしまう可能性もあるので、1個1個順番に質問を投げて疑問をつぶしていくことにします。
(追記)後から気付きましたが、そもそも"property indicator_chart_window"の指定もOnCaluculateもないので、このままだとインジケーターでなくEAとして動作します。
1. lastSymbol != currentSymbolは常に真ではないか、問題
まずは最初のlastSymbolの疑問からです。以下のように、ChatGPTに質問してみました。
###############
void OnTimer()
{
static string lastSymbol = “";
string currentSymbol = Symbol();
if (lastSymbol != currentSymbol)
{
lastSymbol = currentSymbol;
Print(“Symbol changed to: “, currentSymbol);
SynchronizeCharts(currentSymbol);
}
}
###############
上記のコードについて質問です。"static string lastSymbol = “";"となっているため、"lastSymbol != currentSymbol"は常に真となりませんか?
以下、ChatGPTからの回答。
質問ありがとうございます。この部分のコードでは、
staticキーワードを使用しているため、lastSymbol変数は関数が呼び出されるたびに初期化されず、プログラムの実行中に一度だけ初期化されます。したがって、最初の呼び出しではlastSymbolが空文字列であるため、currentSymbolとは異なり、条件が真になります。最初の比較以降、
lastSymbolは最後に取得されたcurrentSymbolの値を保持するため、シンボルが変更されていない限り、条件は偽になります。これをもう少し明確にするために、以下に修正版のコードと解説を示します:
void OnTimer() { static string lastSymbol = ""; // 初回のみ初期化される string currentSymbol = Symbol(); if (lastSymbol != currentSymbol) { lastSymbol = currentSymbol; // lastSymbolをcurrentSymbolに更新 Print("Symbol changed to: ", currentSymbol); SynchronizeCharts(currentSymbol); // シンボル変更処理を呼び出し } }解説
1. staticキーワードの使用
staticキーワードを使用することで、lastSymbolは関数が呼び出されるたびに初期化されるのではなく、プログラムの実行中に一度だけ初期化され、その後の呼び出しでは前回の値を保持します。2. 最初の比較
- 最初の呼び出しでは、
lastSymbolは空文字列なので、currentSymbolと異なるため、条件が真になります。このとき、lastSymbolはcurrentSymbolに更新されます。3. 次回以降の呼び出し
- 次回以降の呼び出しでは、
lastSymbolは前回のcurrentSymbolを保持しているため、シンボルが変更されていない限り、条件は偽になります。シンボルが変更された場合のみ条件が真になり、再度lastSymbolが更新されます。このようにして、シンボルが変更されたときにのみ処理が実行されるようになります。
この最初の疑問は自分の理解の方がおかしくて、やさしく間違いを指摘されました。static変数は最初の値を保持するため、ChatGPTの方が正しかったわけです。こんな風に自分の方が間違っている時は、初心者が分かりやすいコードにかみ砕いて修正してくれたりします。
2. ChartIndicatorExist関数なんて無くない?問題
続いて2番目の疑問。存在のあやしい関数について、ズバリそのまま尋ねてみます。
ChartIndicatorExistって何ですか?
以下、ChatGPT回答。
申し訳ありません。
ChartIndicatorExistは存在しない関数です。インジケーターの存在を確認するために、別の方法を使用する必要があります。代わりに、カスタムインジケーターの名前を使って存在を確認する方法を示します。以下は修正版のコードです。
//+------------------------------------------------------------------+ //| SyncSymbols.mq5 | //| Copyright 2024, User | //| www.user.com | //+------------------------------------------------------------------+ #property strict input string IndicatorName = "YourIndicatorName"; // 対象となるインジケーターの名前 //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); // 1秒ごとにチェック return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert timer function | //+------------------------------------------------------------------+ void OnTimer() { static string lastSymbol = ""; string currentSymbol = Symbol(); if (lastSymbol != currentSymbol) { lastSymbol = currentSymbol; Print("Symbol changed to: ", currentSymbol); SynchronizeCharts(currentSymbol); } } //+------------------------------------------------------------------+ //| Function to synchronize all charts to the new symbol | //+------------------------------------------------------------------+ void SynchronizeCharts(string newSymbol) { long chartID = ChartFirst(); while (chartID >= 0) { if (IsIndicatorInstalled(chartID, IndicatorName)) { if (ChartSymbol(chartID) != newSymbol) { if (!ChartSetSymbolPeriod(chartID, newSymbol, ChartPeriod(chartID))) { Print("Failed to set symbol for chart ID: ", chartID, " Error: ", GetLastError()); } } } chartID = ChartNext(chartID); } } //+------------------------------------------------------------------+ //| Function to check if the indicator is installed on the chart | //+------------------------------------------------------------------+ bool IsIndicatorInstalled(long chartID, string indicatorName) { int totalIndicators = ChartIndicatorsTotal(chartID, 0); for (int i = 0; i < totalIndicators; i++) { if (ChartIndicatorName(chartID, 0, i) == indicatorName) { return true; } } return false; } //+------------------------------------------------------------------+解説
- IndicatorName入力パラメータ
- 対象となるインジケーターの名前を入力パラメータとして指定します。
- IsIndicatorInstalled関数
- 指定されたチャートに特定のインジケーターがインストールされているかを確認するための関数です。
ChartIndicatorsTotal関数を使ってチャート上のインジケーターの数を取得し、ChartIndicatorName関数を使って各インジケーターの名前をチェックします。- SynchronizeCharts関数
- 全てのチャートをループし、
IsIndicatorInstalled関数を使って対象のインジケーターがインストールされているかを確認します。- インジケーターがインストールされているチャートのみシンボルを変更します。
この修正により、指定されたインジケーターがインストールされているチャートのみが対象となり、他の全てのチャートが新しいシンボルに変更されるようになります。
今度は自分の方が正しく、素直に謝られて修正してきました。このように、実は存在しない関数やプロパティをさも実在するかのようにシレっと書いてきたりすることは、結構多いです。その間違い探しをするのも地味に勉強になったりします。
このコードに関して突っ込むと、インジケーター名の入力部分は、全部同じインジケーター名で統一できるはずなので少し冗長な感じです。次の3つ目の疑問と合わせて一緒に修正してもらうことにします。
3. OnTimer()、OnTick()を使わないでできるか?問題
続いて3番目のOnTimerを使わないでほしい問題です。次のようにChatGPTに投げかけてみました。
次の条件を追加します。
- 対象となるインジケーターは、同じSyncSymbols.mq5のみです。
- OnTimerやOnTick関数を使わないでください。
- 手動でシンボルを変更した際、処理が起動する形にしてください。
以下、ChatGPTからの回答を引用したい所ですが、意外に難儀して長いラリーを続けることになったため割愛します。
結論から言うと、OnTimerを使わずイベント処理するタイプは、動作が不安定で思ったように機能せず。チャートのシンボル変更を検知するためには、どうやらOnChartEventで直接検知することは難しいことが判明。逆にOnTimerを使った処理は、素直に安定動作しました。
しかしサクッと同期させるためだけに、毎回OnTimerでチェックし続けるのは無駄な処理が発生するため、やりたくありません。そこでインジケーター形式はやめて、OnStart関数を使ったスクリプト形式に変更することにしました。
MT5複数チャートシンボル同期スクリプト
最終結果としてできたスクリプトがこれ。
//+------------------------------------------------------------------+
//| SyncSymbols.mq5 |
//| Copyright 2024, User |
//| www.user.com |
//+------------------------------------------------------------------+
#property strict
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
string newSymbol = Symbol();
Print("Applying symbol synchronization to: ", newSymbol);
SynchronizeCharts(newSymbol);
}
//+------------------------------------------------------------------+
//| Function to synchronize all charts to the new symbol |
//+------------------------------------------------------------------+
void SynchronizeCharts(string newSymbol)
{
long chartID = ChartFirst();
while (chartID >= 0)
{
if (ChartSymbol(chartID) != newSymbol)
{
if (!ChartSetSymbolPeriod(chartID, newSymbol, ChartPeriod(chartID)))
{
Print("Failed to set symbol for chart ID: ", chartID, " Error: ", GetLastError());
}
}
chartID = ChartNext(chartID);
}
}
//+------------------------------------------------------------------+
- どれか一つのチャートのシンボルを気配値からのドラッグ&ドロップなどで手動変更
- 変更したチャートに本スクリプト(SyncSymbols.mq5)をドラッグ&ドロップ
- 開いている他のチャート全てがそのシンボルに切り替え
といった感じで動作します。スクリプトに変えたことでインジケーター有無の判別もなくなったので、コードがよりシンプルになりました。2回のドラッグ&ドロップが発生し、若干手間はかかりますが、良い感じのスクリプトが出来上がりました。
【追記】MT5複数チャートシンボル同期インジケーター(ボタン起動型)
毎度2回のドラッグ&ドロップも地味に面倒になってきたため、ボタンをクリックするとシンボル同期処理するインジケーター版の『SyncSymbols』を作ってみました。デフォルトでは右下コーナーに小さく表示されます。ボタンの表示位置、大きさは自由に変更できます。
//+------------------------------------------------------------------+
//| SyncSymbols.mq5 |
//| Copyright 2024, User |
//| www.user.com |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
#property script_show_inputs
#include <ChartObjects\ChartObjectsTxtControls.mqh>
input int ButtonX = 40;
input int ButtonY = 40;
input int ButtonWidth = 30;
input int ButtonHeight = 20;
input ENUM_BASE_CORNER ButtonCorner = CORNER_RIGHT_LOWER;
CChartObjectButton myButton;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// ボタンを作成
if (!myButton.Create(0, "syncButton", 0, ButtonX, ButtonY, ButtonX + ButtonWidth, ButtonY + ButtonHeight))
{
Print("Failed to create button. Error: ", GetLastError());
return(INIT_FAILED);
}
myButton.Description("Sync Symbols");
myButton.SetString(OBJPROP_TEXT, "Sync");
myButton.SetInteger(OBJPROP_COLOR, clrBlack);;
myButton.SetInteger(OBJPROP_FONTSIZE, 8);
myButton.SetInteger(OBJPROP_STATE, false);
myButton.SetInteger(OBJPROP_CORNER, ButtonCorner);
myButton.SetInteger(OBJPROP_XSIZE, ButtonWidth);
myButton.SetInteger(OBJPROP_YSIZE, ButtonHeight);
ChartRedraw();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// ボタンを削除
ObjectDelete(0, "syncButton");
}
//+------------------------------------------------------------------+
//| Expert event handler function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if (id == CHARTEVENT_OBJECT_CLICK && sparam == "syncButton")
{
string currentSymbol = Symbol();
Print("Syncing symbols to: ", currentSymbol);
SynchronizeCharts(currentSymbol);
myButton.SetInteger(OBJPROP_STATE, false);
}
}
//+------------------------------------------------------------------+
//| Function to synchronize all charts to the new symbol |
//+------------------------------------------------------------------+
void SynchronizeCharts(string newSymbol)
{
long chartID = ChartFirst();
while (chartID >= 0)
{
if (ChartSymbol(chartID) != newSymbol)
{
if (!ChartSetSymbolPeriod(chartID, newSymbol, ChartPeriod(chartID)))
{
Print("Failed to set symbol for chart ID: ", chartID, " Error: ", GetLastError());
}
}
chartID = ChartNext(chartID);
}
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
//---
//--- return value of prev_calculated for next call
return(rates_total);
}
//+------------------------------------------------------------------+
ChatGPTを使って作ってみた感想
ChatGPTには適度に間違いがあり、そのツッコミを考えることが良い感じの学習効果につながっていると感じます。またこちらの理解が間違っている時は、やさしく冗長に指摘してくれます。お互いにミスを指摘しながら一緒に作り上げていく感じが面白いです。
これが人間同士の教え合いだと、お互いのプライドが邪魔して人間関係のトラブルに発展する可能性もありますが、対話型AIならそうした問題が起きないのが良いですね。お互いが容赦なくミスを指摘し合える関係って、学びに最も理想的な関係ではないでしょうか?
プログラミングの場合、最終的に思った通りの動作をするかどうかで完全に白黒がつくので、ChatGPTを使った学習がより効果を発揮するのかもしれません。今後もChatGPTをフル活用してトレードプログラミングを学んでいきたいと思います。





ディスカッション
コメント一覧
まだ、コメントがありません