
【ソースコード付き】EAのマジックナンバー重複問題とその解決策
いつも記事をお読みいただき、ありがとうございます。
今回は、MT4のEA(エキスパート・アドバイザー)の開発・運用でよく遭遇しうる「マジックナンバーの重複」問題について、Win32 APIを活用した解決策を詳しく解説します。
⏬この記事は、以下のような方に特に参考になるかと思います:
✅複数のEAを同時運用している、もしくは検討している方
✅MT4でのEA開発に携わっているプログラマーの方
✅Win32 APIを使ったMQL4プログラミングに興味がある方
✅より安全なEA運用を目指している方
一見些細な問題に思えるかもしれませんが、複数のEAを運用する際には重要な検討ポイントとなります。
ぜひ最後までお付き合いください。
⚠️ 免責事項と利用上の注意
本記事で提供するソースコードは、MITライセンスの下で提供されています。
以下にライセンス条項の原文を示します:
The MIT Licence
Copyright (c) 2024, HANDY SYSTEMS
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
引き続き、日本語参考訳を示します:
MIT ライセンス
Copyright (c) 2024, HANDY SYSTEMS
以下に定める条件に従い、本ソフトウェアおよび関連文書のファイル(以下「ソフトウェア」)の複製を取得するすべての人に対し、ソフトウェアを無制限に扱うことを無償で許可します。
これには、ソフトウェアの複製を使用、複写、変更、結合、掲載、頒布、サブライセンス、および/または販売する権利、およびソフトウェアを提供する相手に同じことを許可する権利も無制限に含まれます。
上記の著作権表示および本許諾表示を、ソフトウェアのすべての複製または重要な部分に記載するものとします。
ソフトウェアは「現状のまま」で、明示であるか暗黙であるかを問わず、何らの保証もなく提供されます。
ここでいう保証とは、商品性、特定の目的への適合性、および権利非侵害についての保証も含みますが、それに限定されるものではありません。
作者または著作権者は、契約行為、不法行為、またはそれ以外であろうと、ソフトウェアに起因または関連し、あるいはソフトウェアの使用またはその他の扱いによって生じる一切の請求、損害、その他の義務について何らの責任も負わないものとします。
上記の通り、「著作権表示」と「MIT ライセンスの全文掲載(原文への URL でも可)」をクリアしていれば利用条件を満たしたことになります。
また、ウェブページ内に「著作権表示」と「MIT ライセンスの全文」を掲載し、その URL をソースコードに掲載するという方法でも問題ありません。
また、本実装には以下のようなリスクが伴います:
Win32 APIの使用によるリスク
MT4の動作環境やWindowsバージョンの違いによる予期せぬ動作
APIの仕様変更による影響
システムリソースへのアクセスに関するセキュリティリスク
ファイルシステムへのアクセス権限の問題
EAの運用に関するリスク
重複チェックの失敗による意図しない取引の発生
システムクラッシュ時のファイルハンドル開放の問題
MT4の異常終了時のリソース解放の問題
複数のMT4ターミナルインスタンス間での競合
システムリソースに関するリスク
一時ファイルの残留の可能性
メモリリークの可能性
ファイルハンドルの上限への到達
これらのリスクを理解した上で、自己責任での利用をお願いいたします。
本コードの使用によって生じたいかなる損害についても、作者は一切の責任を負いません。
✅はじめに
MT4での自動売買において、一つのMT4ターミナル内で複数のEAを同時に動作させて運用することは一般的な手法です。
しかし、その際に見落としがちなのが、マジックナンバーの重複問題です。
一見些細に思えるこの問題が、予期せぬ重大なトラブルの原因となることがあります。
本記事では、この問題に対する効果的な解決策を提案します。
従来の手法の限界を理解し、より確実な方法でマジックナンバーの重複をチェックする方法について解説していきます。
✅マジックナンバー重複がもたらす問題
マジックナンバーの重複は、予想以上に深刻な問題を引き起こす可能性があります。
例えば、複数の移動平均線を使用したトレード戦略を実装したEAを、異なる時間足で運用しているケースを考えてみましょう。
同じマジックナンバーを持つEAが存在すると、一方のEAが出した注文を他方のEAが自分の注文と誤認識してしまう危険性があります。
その結果、意図しないタイミングでの決済や、ストップロス・利確位置の予期せぬ変更が発生する可能性があります。
特に注意が必要なのは、バックテスト時には気づきにくい問題だという点です。単体でのバックテストでは問題なく動作するEAでも、実運用時に他のEAと組み合わせた際に初めて問題が顕在化することがあります。
✅従来の解決策とその限界
これまで、この問題に対する一般的な解決策として、グローバル変数を使用する方法がよく採用されてきました。
例えば、以下のようなコードで実装されることが多いでしょう:
if (GlobalVariableGet(StringFormat("%d", MagicNumber), value))
{
Alert("Magic number is duplicated");
return INIT_FAILED;
}
GlobalVariableSet(StringFormat("%d", MagicNumber), 1);
しかし、この方法には重大な技術的制約があります。
最も大きな問題は、MT4のOnDeinit関数に2.5秒という厳格なタイムアウト制限が存在することです。
この制限により、EAの終了時にグローバル変数を確実に削除することができません。
Deinit #
グローバル変数が初期化解除されプログラム(エキスパートアドバイザーまたはカスタム指標)がアンロードされる前に、クライアント端末はプログラムに Deinit イベントを送信します。Deinit はまた、クライアント端末の閉鎖時、チャートの閉鎖時、セキュリティ及び/または時間軸の変更直前、プログラムの再コンパイル成功時、入力パラメータの変更時、及び口座変更時に生成されます。
初期化解除の理由は OnDeinit() 関数に渡されたパラメータによって取得出来ます。OnDeinit() 関数の実行は 2.5 秒に制限されています。この間に完了しない場合、この関数は強制的に終了されます。Deinit イベントはスクリプトでは生成されません。
さらに、MT4のグローバル変数は4週間という長期間保持されるという性質があります。
グローバル変数は最後のアクセスから4週間はクライアントターミナルに保存されています。
グローバル変数へのアクセスは、変数の変更だけでなく読み取りもアクセスとして判断します。
そのため、一度設定されたグローバル変数が必要以上に長く残り続け、誤検出の原因となってしまいます。
✅技術解説:Win32 APIを活用した実装
Win32 APIとは
Win32 APIは、Windowsオペレーティングシステムの基本的なシステムサービスにアクセスするためのインターフェースです。
MQL4では通常アクセスできない低レベルのシステム機能を利用することができ、今回はこれを活用してより確実なマジックナンバーの重複チェックを実現します。
✅MQL4からWin32 APIを利用するための準備
MQL4では、#importディレクティブを使用することで、Windowsのシステムライブラリ(DLL)の関数を直接呼び出すことができます。
#import "kernel32.dll"
int CreateFileW(string lpFileName, int dwDesiredAccess, int dwShareMode,
int lpSecurityAttributes, int dwCreationDisposition,
int dwFlagsAndAttributes, int hTemplateFile);
bool CloseHandle(int hObject);
int GetTempPathW(int nBufferLength, string& lpBuffer);
int GetCurrentProcessId();
#import
#import "ntdll.dll"
int RtlGetLastWin32Error();
#import
ここでは2つのシステムライブラリから関数をインポートしています:
kernel32.dll:
Windowsの基本的なシステム機能を提供するライブラリ
ファイル操作やプロセス管理などの基本機能を含む
ntdll.dll:
Windowsの低レベルなシステム機能を提供するライブラリ
RtlGetLastWin32Error関数は、最後に発生したWin32 APIのエラーコードを取得するために使用
MT4環境では標準のGetLastError関数がMQL4のエラーを返すため、Win32 APIのエラーを取得するにはRtlGetLastWin32Errorが必要
これらの#import文により、通常のMQL4関数では実現できない、より高度なシステム制御が可能になります。
注意点:
DLLのインポートはMT4の設定で許可されている必要があります
システムの核となる機能にアクセスするため、慎重な利用が求められます
Windows環境に依存するため、異なるバージョンのWindowsでは動作が異なる可能性があります
この設定は、MT4のメニュー[ツール] → [オプション] → [エキスパートアドバイザー]から行うことができ、「DLLの使用を許可する」にチェックを入れる必要があります。
ただし、この設定はシステムのセキュリティに影響を与える可能性があるため、信頼できるソースのEAを使用する場合にのみ有効にすることをお勧めします。
✅重要なWin32 API関数
本実装で使用する主なWin32 API関数は以下の通りです:
#import "kernel32.dll"
int CreateFileW(string lpFileName, int dwDesiredAccess, int dwShareMode,
int lpSecurityAttributes, int dwCreationDisposition,
int dwFlagsAndAttributes, int hTemplateFile);
bool CloseHandle(int hObject);
int GetTempPathW(int nBufferLength, string& lpBuffer);
int GetCurrentProcessId();
#import
CreateFileW関数の詳細
CreateFileW関数は、ファイルを作成したり開くための非常に強力な関数です。以下のパラメータが特に重要です:
1. アクセス権(dwDesiredAccess)
#define GENERIC_WRITE 0x40000000
#define GENERIC_READ 0x80000000
これらのフラグにより、ファイルへの読み書き権限を制御します。
2. 共有モード(dwShareMode)
#define FILE_SHARE_DELETE 0x00000004
他のプロセスからのファイルアクセス方法を制御します。
3. 作成方法(dwCreationDisposition)
#define CREATE_NEW 1
#define OPEN_EXISTING 3
ファイルが既に存在する場合の動作を制御します。
4. ファイル属性(dwFlagsAndAttributes)
#define FILE_ATTRIBUTE_TEMPORARY 0x00000100
#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000
これらは本実装の核となる重要なフラグです:
FILE_ATTRIBUTE_TEMPORARY:一時ファイルとして最適化します
FILE_FLAG_DELETE_ON_CLOSE:プロセス終了時に自動的に削除します
✅一時ファイルによる排他制御の仕組み
本実装では、以下の方法で排他制御を実現しています:
1. ファイル名の生成
string GetLockFileName()
{
return tempPath + StringFormat("MT4_Magic_%d_%s_%d.lock",
MagicNumber,
Symbol(),
GetCurrentProcessId()
);
}
2. 一時ファイルの作成
lockFileHandle = CreateFileW(
fileName,
(int)GENERIC_WRITE,
0,
0,
CREATE_NEW,
(int)(FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE),
0
);
この方法には以下の利点があります:
プロセスIDによって一時ファイルの完全な一意性が確保されます
OSによって確実に一時ファイルが削除されます
システムリソースが効率的に利用できます
✅実装の処理解説
1. 初期化処理
OnInit関数では、以下の処理を行います:
TEMPパスの取得
マジックナンバーの重複チェック
重複が見つかった場合のユーザーへの確認画面の表示
ロックファイルの作成
2. 定期的なチェック
OnTick関数では、指定された間隔で以下のチェックを実行します:
他のEAによる同一マジックナンバーの使用確認
重複が見つかった場合の警告画面の表示
3. 終了処理
OnDeinit関数では、以下のクリーンアップを実施:
ロックファイルハンドルのクローズ
エラー発生時のログ出力
✅運用上の注意点
システムの初期設定について
マジックナンバー重複チェッカーを導入する際は、まず適切な初期設定が重要です。
マジックナンバーの選定は、他のEAとの重複を避けるため、一定のルールに基づいて慎重に行う必要があります。
例えば、EA開発者やストラテジーごとに番号の範囲を決めておくなどの工夫が有効です。
チェック間隔の設定も、取引スタイルに合わせて適切に調整しましょう。
高頻度取引を行う場合は、より短いインターバルでのチェックが推奨されますが、システムへの負荷とのバランスを考慮する必要があります。
スキャルピングなどの短期取引では60秒程度、中長期の取引では300秒以上の間隔設定が妥当であると考えられます。
システム異常時の対応方法
1. MT4クラッシュ時の挙動
MT4が予期せず終了した場合でも、Win32 APIのFILE_FLAG_DELETE_ON_CLOSEフラグによって、一時ファイルは自動的に削除されます。
これはOSレベルで保証される動作であり、次回MT4起動時に問題が引き継がれることはありません。
2. ファイルアクセスエラーへの対処
ファイルアクセスに関するエラーが発生した場合は、まずエラーコードを確認してください。
代表的なエラーコードとその対処方法は以下の通りです:
エラー32(共有違反):他のプロセスがファイルを使用中
エラー87(無効なパラメータ):APIの呼び出しパラメータに問題
エラー80(ファイルが存在する):マジックナンバーの重複の可能性あり
これらのエラーが継続する場合は、一度MT4を再起動することで解決することが多いです。
3. 重複検出時のベストプラクティス
重複が検出された場合は、まず以下の確認を行うことをお勧めします:
同一通貨ペアで他のEAが動作していないか
MT4のグローバル変数に古い情報が残っていないか
他のMT4ターミナルインスタンスのEAで同じマジックナンバーを使用していないか
✅システムパフォーマンスの最適化
本システムは、できるだけ軽量に動作するよう設計されていますが、長期運用時には以下の点に注意が必要です:
メモリ使用量の管理:
定期的なチェックによるメモリ使用量は通常わずかですが、大量のEAを同時運用する場合は、チェック間隔を適切に調整することでシステムへの負荷を抑えることができます。
ログ出力の最適化:
デバッグ情報の出力は、問題解決に役立つ一方で、長期運用時にログファイルが肥大化する原因となります。
本番環境では必要最小限のログ出力に留めることをお勧めします。
リソースの監視:
長期運用時は、Windowsのタスクマネージャーでメモリ使用量やファイルハンドル数を定期的に確認することをお勧めします。
異常が見られた場合は、早めに対処することで安定した運用が可能です。
💡まとめ
Win32 APIを活用することで、より確実なマジックナンバーの重複チェックを実現できるようになりました。
本実装の特長は以下の通りです:
高い信頼性
OSレベルでの排他制御
プロセスIDによる確実なMT4の識別
自動的なリソースの解放
運用の柔軟性
ユーザーによる継続/中止の選択が可能
定期的な監視機能
詳細なエラー情報の提供
システムリソースの効率的な利用
一時ファイルの最適な管理
自動的なクリーンアップ
最小限のリソース消費
📄ソースコード
本実装のソースコードを以下に記述します。
ご自身の環境に合わせて適切にカスタマイズしてご利用ください。
//+------------------------------------------------------------------+
//| MagicNumberDuplicateChecker.mq4 |
//| Copyright (c) 2024, HANDY SYSTEMS |
//+------------------------------------------------------------------+
/*
The MIT License
Copyright (c) 2024, HANDY SYSTEMS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#property copyright "Copyright (c) 2024, HANDY SYSTEMS"
#property version "1.0"
#property description "Magic Number Duplicate Checker"
#property strict
//+------------------------------------------------------------------+
//| Debug mode |
//+------------------------------------------------------------------+
#define DEBUG_MODE false
//+------------------------------------------------------------------+
//| Win32 API functions |
//+------------------------------------------------------------------+
#import "kernel32.dll"
int CreateFileW(string lpFileName, int dwDesiredAccess, int dwShareMode,
int lpSecurityAttributes, int dwCreationDisposition,
int dwFlagsAndAttributes, int hTemplateFile);
bool CloseHandle(int hObject);
int GetTempPathW(int nBufferLength, string& lpBuffer);
int GetCurrentProcessId();
#import
#import "ntdll.dll"
int RtlGetLastWin32Error();
#import
//+------------------------------------------------------------------+
//| Win32 API constants |
//+------------------------------------------------------------------+
#define GENERIC_WRITE 0x40000000
#define GENERIC_READ 0x80000000
#define FILE_SHARE_DELETE 0x00000004
#define CREATE_NEW 1
#define CREATE_ALWAYS 2
#define OPEN_EXISTING 3
#define FILE_ATTRIBUTE_TEMPORARY 0x00000100
#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000
#define INVALID_HANDLE_VALUE -1
#define ERROR_FILE_EXISTS 80
//+------------------------------------------------------------------+
//| Message constants |
//+------------------------------------------------------------------+
#define MSG_DUPLICATE_WARNING "WARNING: Magic number %d is already in use.\n\n" + \
"There may already be an EA running with the\n" + \
"same magic number for the same currency pair.\n\n" + \
"Do you want to continue running it?"
#define MSG_SYSTEM_ERROR "System error occurred: %d"
#define MSG_TEMP_PATH_ERROR "Failed to get temporary path"
//+------------------------------------------------------------------+
//| Input parameters |
//+------------------------------------------------------------------+
input int MagicNumber = 12345; // Magic Number: Unique identifier for EA
input bool CheckDuplicate = true; // Check Duplicate: Enable/disable duplicate checking
input int CheckInterval = 300; // Check Interval: Time between checks (seconds)
//+------------------------------------------------------------------+
//| Global variables |
//+------------------------------------------------------------------+
datetime lastCheckTime = 0; // Last check timestamp
int lockFileHandle = INVALID_HANDLE_VALUE; // Lock file handle
string tempPath = ""; // Temporary file path
//+------------------------------------------------------------------+
//| Custom logging function |
//+------------------------------------------------------------------+
void LogDebug(const string message)
{
if (DEBUG_MODE) Print("[DEBUG] ", message);
}
//+------------------------------------------------------------------+
//| Get Windows temporary path |
//+------------------------------------------------------------------+
bool GetTempPath()
{
string buffer = " "; // 50 chars buffer
int result = GetTempPathW(StringLen(buffer), buffer);
if (result > 0)
{
tempPath = StringSubstr(buffer, 0, result);
LogDebug("Temp path: " + tempPath);
return true;
}
Print(MSG_TEMP_PATH_ERROR);
return false;
}
//+------------------------------------------------------------------+
//| Generate lock file name |
//+------------------------------------------------------------------+
string GetLockFileName()
{
string fileName = StringFormat("MT4_Magic_%d_%s_%d.lock",
MagicNumber,
Symbol(),
GetCurrentProcessId()
);
LogDebug("Lock file name: " + fileName);
return tempPath + fileName;
}
//+------------------------------------------------------------------+
//| Check for magic number duplication |
//+------------------------------------------------------------------+
bool CheckDuplicateMagicNumber()
{
string fileName = GetLockFileName();
int fileHandle = CreateFileW(
fileName,
(int)GENERIC_READ,
0,
0,
OPEN_EXISTING,
0,
0
);
if (fileHandle != INVALID_HANDLE_VALUE)
{
if (fileHandle != lockFileHandle)
{
CloseHandle(fileHandle);
return true; // Duplicate found
}
CloseHandle(fileHandle);
}
return false; // No duplicate
}
//+------------------------------------------------------------------+
//| Register magic number |
//+------------------------------------------------------------------+
bool RegisterMagicNumber()
{
string fileName = GetLockFileName();
LogDebug("Attempting to register magic number: " + IntegerToString(MagicNumber));
lockFileHandle = CreateFileW(
fileName,
(int)GENERIC_WRITE,
0,
0,
CREATE_NEW,
(int)(FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE),
0
);
if (lockFileHandle == INVALID_HANDLE_VALUE)
{
int error = RtlGetLastWin32Error();
if (error == ERROR_FILE_EXISTS)
{
// Show warning and ask user whether to continue
string message = StringFormat(MSG_DUPLICATE_WARNING, MagicNumber);
int response = MessageBox(
message,
"Magic Number Duplicate Warning",
MB_ICONWARNING | MB_OKCANCEL
);
if (response == IDCANCEL)
{
LogDebug("User canceled operation");
return false;
}
LogDebug("User chose to continue despite duplicate");
return true; // User accepted the risk
}
else
{
// Handle other system errors
string errorMsg = StringFormat(MSG_SYSTEM_ERROR, error);
MessageBox(
errorMsg,
"System Error",
MB_ICONERROR | MB_OK
);
Print(errorMsg);
return false;
}
}
LogDebug("Successfully registered magic number");
return true;
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Get temporary path
if (!GetTempPath())
{
return INIT_FAILED;
}
// Perform duplicate checking if enabled
if (CheckDuplicate)
{
if (!RegisterMagicNumber())
{
return INIT_FAILED;
}
}
lastCheckTime = TimeCurrent();
LogDebug("Initialization completed successfully");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if (lockFileHandle != INVALID_HANDLE_VALUE)
{
LogDebug("Closing lock file handle");
if (!CloseHandle(lockFileHandle))
{
int error = RtlGetLastWin32Error();
Print("Failed to close handle: ", error);
}
lockFileHandle = INVALID_HANDLE_VALUE;
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if (!CheckDuplicate) return;
// Perform periodic checks
if (TimeCurrent() - lastCheckTime >= CheckInterval)
{
if (CheckDuplicateMagicNumber())
{
Print("Warning: Duplicate magic number detected during runtime check");
Alert("Warning: Duplicate magic number detected\nMagic Number: ", MagicNumber);
}
lastCheckTime = TimeCurrent();
}
}
//+------------------------------------------------------------------+
最後までお読みいただき、ありがとうございました。
スキ・コメント・フォローなどいただけけましたら嬉しいです。
✅作者ホームページ
#MT4 #MQL #プログラミング #コーディング #ソースコード #オープンソース #Win32 #FX自動売買 #FX #EA #EA開発 #EA運用 #マジックナンバー #MagicNumber