516 lines
21 KiB
Plaintext
516 lines
21 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| HITEntryEA.mq5 |
|
||
//| Copyright 2026, nanpin-martin.com |
|
||
//| https://www.nanpin-martin.com |
|
||
//+------------------------------------------------------------------+
|
||
#property copyright "Copyright 2026, nanpin-martin.com"
|
||
#property link "https://nanpin-martin.com/"
|
||
#property version "1.11"
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| バッチファイル実行用の設定
|
||
//+------------------------------------------------------------------+
|
||
|
||
// Windowsプロセス起動・終了確認用
|
||
#define CREATE_NO_WINDOW 0x08000000
|
||
#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000
|
||
#define SYNCHRONIZE 0x00100000
|
||
#define WAIT_OBJECT_0 0
|
||
#define WAIT_TIMEOUT 258
|
||
#define STILL_ACTIVE 259
|
||
#define TASKKILL_WAIT_MILLISECONDS 5000
|
||
|
||
struct STARTUPINFO_W
|
||
{
|
||
uint cb;
|
||
string lpReserved;
|
||
string lpDesktop;
|
||
string lpTitle;
|
||
uint dwX;
|
||
uint dwY;
|
||
uint dwXSize;
|
||
uint dwYSize;
|
||
uint dwXCountChars;
|
||
uint dwYCountChars;
|
||
uint dwFillAttribute;
|
||
uint dwFlags;
|
||
ushort wShowWindow;
|
||
ushort cbReserved2;
|
||
long lpReserved2;
|
||
long hStdInput;
|
||
long hStdOutput;
|
||
long hStdError;
|
||
};
|
||
|
||
struct PROCESS_INFORMATION
|
||
{
|
||
long hProcess;
|
||
long hThread;
|
||
uint dwProcessId;
|
||
uint dwThreadId;
|
||
};
|
||
|
||
#import "kernel32.dll"
|
||
int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation);
|
||
int WaitForSingleObject(long hHandle, uint dwMilliseconds);
|
||
int GetExitCodeProcess(long hProcess, uint &lpExitCode);
|
||
int CloseHandle(long hObject);
|
||
long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId);
|
||
#import
|
||
|
||
// バッチファイルのパス
|
||
// Python helper scripts are managed under this MT5 data folder:
|
||
// <TerminalDataPath>\MQL5\python_for_ea
|
||
string python_app_dir = "";
|
||
string get_trend_reply_bat = ""; // H4: trend
|
||
string get_entry_reply_bat = ""; // H1: entry
|
||
string mt5_file_prefix = ""; // Python linkage file namespace
|
||
|
||
// プロセス完了ファイルの設定
|
||
string done_trend_file = "process_done_trend.txt";
|
||
string done_entry_file = "process_done_entry.txt";
|
||
|
||
// Python出力ファイル
|
||
string ohlc_h4_file = "ohlc_H4.csv";
|
||
string ohlc_h1_file = "ohlc_H1.csv";
|
||
string trend_state_file = "trend_state.txt";
|
||
string target_prices_file = "target_prices.txt";
|
||
string target_zones_file = "target_zones.txt";
|
||
|
||
// Python実行中を判定するための管理ファイル
|
||
string running_trend_file = "process_running_trend.txt";
|
||
string running_entry_file = "process_running_entry.txt";
|
||
|
||
//+------------------------------------------------------------------+
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| インプットの設定
|
||
//+------------------------------------------------------------------+
|
||
|
||
enum SplitLotMode
|
||
{
|
||
SPLIT_LOT_TOTAL = 0, // 総ロットをN分割
|
||
SPLIT_LOT_FIXED = 1 // 1注文あたり固定ロット
|
||
};
|
||
|
||
//--- 入力パラメータ
|
||
input double lot_size = 0.01; // ロット数
|
||
input double spread_limit = 60; // 許容スプレッド(point)
|
||
input int input_entry_magic_number = 10001; // 発注EAのマジックナンバー
|
||
input ulong input_slippage_points = 10; // 許容スリッページ(point)
|
||
input int initial_order = 0; // 互換性維持用。起動時H4/H1再構築は常時実行
|
||
input int input_position_limit = 10; // 自EAの未約定注文+ポジション上限
|
||
input bool use_split_entry_zone = true; // H1予測ゾーンで分割エントリーする
|
||
input int split_entry_count = 3; // 分割エントリー本数(1..10)
|
||
input SplitLotMode split_lot_mode = SPLIT_LOT_FIXED; // 分割ロット配分モード
|
||
input double split_total_lot_size = 0.09; // 総量分割モードの合計ロット
|
||
input double split_fixed_lot_size = 0.01; // 固定ロットモードの1注文ロット
|
||
input bool cancel_old_split_pending_on_new_zone = true; // 新ゾーン読込時に旧分割pendingを取消
|
||
input int input_cancel_retry_cooldown_seconds = 60; // pending取消失敗時のticket単位再試行間隔
|
||
input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認
|
||
input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可
|
||
input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加
|
||
input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間
|
||
input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度
|
||
input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point)
|
||
input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ
|
||
input int input_entry_max_candidate_age_minutes = 120; // H1候補価格を新規発注に使う最大経過分数
|
||
input double input_tp_multiplier = 1.50; // TP distance multiplier (1.0=no expansion)
|
||
input int input_min_tp_points = 0; // Minimum TP distance in points (0=disabled)
|
||
input int input_max_tp_points = 0; // Maximum expanded TP distance in points (0=disabled)
|
||
input bool input_range_breakout_guard_enabled = true; // レンジ上抜け/下抜け時に逆張りLimitを退避
|
||
input ENUM_TIMEFRAMES input_range_breakout_guard_timeframe = PERIOD_M15; // レンジブレイク監視足
|
||
input int input_range_breakout_lookback_bars = 20; // レンジ高値安値の参照本数
|
||
input double input_range_breakout_max_width_multiplier = 6.0; // 平均レンジに対する最大レンジ幅
|
||
input double input_range_breakout_buffer_multiplier = 0.20; // ブレイク確定バッファ倍率
|
||
input double input_range_breakout_near_multiplier = 0.50; // ブレイク警戒の境界接近幅倍率
|
||
input double input_range_breakout_body_multiplier = 1.50; // 勢い判定の平均実体倍率
|
||
input int input_range_breakout_cooldown_bars = 4; // 逆張りLimit停止を継続する足数
|
||
|
||
// Existing shared HIT include files read these runtime globals.
|
||
int magic_number = 10001;
|
||
ulong slippage = 10;
|
||
|
||
//--- 主要な定数・設定
|
||
#define POSITION_LIMIT 48
|
||
#define ENTRY_H1_LIMIT 2 // H1本数(=2時間)
|
||
#define HISTORY_BARS 72
|
||
#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す
|
||
#define M15_CONFIRM_BARS 30
|
||
#define M15_MIN_ENTRY_ZONE_POINTS 10
|
||
#define M15_MIN_BODY_RATIO 0.25
|
||
#define M15_REJECTION_WICK_RATIO 0.35
|
||
#define ENTRY_RETRY_SECONDS 60
|
||
#define ENTRY_RETRY_LIMIT 10
|
||
// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定)
|
||
#define TARGET_SIZE 13
|
||
#define TARGET_ZONE_SCHEMA_VERSION 2
|
||
#define DEFAULT_TARGET_PRICE 0.0
|
||
#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒)
|
||
|
||
#define MARKET_LOW_VOL_RANGE 0
|
||
#define MARKET_HIGH_VOL_RANGE 1
|
||
#define MARKET_LOW_VOL_UP 2
|
||
#define MARKET_HIGH_VOL_UP 3
|
||
#define MARKET_LOW_VOL_DOWN 4
|
||
#define MARKET_HIGH_VOL_DOWN 5
|
||
#define MARKET_TECHNICAL_ERROR_STOP 6
|
||
|
||
#define RANGE_BREAKOUT_NONE 0
|
||
#define RANGE_BREAKOUT_UP 1
|
||
#define RANGE_BREAKOUT_DOWN -1
|
||
|
||
//+------------------------------------------------------------------+
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| EAの状態をまとめる構造体
|
||
//+------------------------------------------------------------------+
|
||
|
||
struct EAState
|
||
{
|
||
int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数
|
||
int res_chk; // GPTの回答が正しく取得できたか
|
||
double en_price[5]; // エントリー基準価格
|
||
double tp_price[5]; // 利益確定価格
|
||
double sl_price[5]; // ロスカット基準価格
|
||
int zone_res_chk; // 予測ゾーンが正しく取得できたか
|
||
string zone_candidate_id; // H1確定足由来の候補ID
|
||
double zone_low[5]; // 分割エントリー用ゾーン下限
|
||
double zone_high[5]; // 分割エントリー用ゾーン上限
|
||
double zone_tp[5]; // 分割エントリー用TP
|
||
double zone_sl[5]; // 分割エントリー用SL
|
||
bool load_trend_flg; // ★変更:トレンドを更新するタイミングか
|
||
bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか
|
||
int chk_cnt; // エントリー判定の試行回数
|
||
datetime last_trend_update; // ★追加:前回トレンドを更新した時刻
|
||
datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻
|
||
datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻
|
||
datetime target_candidate_at; // H1候補価格の元になった確定足時刻
|
||
datetime last_chk;
|
||
};
|
||
|
||
EAState g_ea; // EA全体の状態をここで管理
|
||
|
||
// Startup refresh flags for H4 trend and H1 entry candidates.
|
||
// Keep them separate to prevent repeated startup launches.
|
||
bool g_init_trend_pending = false;
|
||
bool g_init_entry_pending = false;
|
||
|
||
// H4/H1のバー更新状態
|
||
int g_pre_bars_H4 = 0;
|
||
int g_pre_bars_H1 = 0;
|
||
int g_pre_bars_M15 = 0;
|
||
bool g_bars_H1_check = false;
|
||
bool g_bars_M15_check = false;
|
||
|
||
// pending取消失敗時の過剰な取引サーバー要求を防止する。
|
||
ulong g_cancel_retry_tickets[];
|
||
datetime g_cancel_retry_at[];
|
||
|
||
// Range-breakout guard state for adverse reversal Limit orders.
|
||
int g_range_breakout_guard_direction = RANGE_BREAKOUT_NONE;
|
||
datetime g_range_breakout_guard_until = 0;
|
||
|
||
// ティックごとの価格情報
|
||
struct TickContext
|
||
{
|
||
double ask;
|
||
double bid;
|
||
double spread;
|
||
double spread_points;
|
||
int digits;
|
||
};
|
||
|
||
// 外部Pythonプロセスの状態
|
||
struct ExternalProcessState
|
||
{
|
||
long handle;
|
||
uint process_id;
|
||
bool active;
|
||
bool exit_code_ready;
|
||
uint exit_code;
|
||
datetime started_at;
|
||
};
|
||
|
||
ExternalProcessState g_trend_process;
|
||
ExternalProcessState g_entry_process;
|
||
|
||
//+------------------------------------------------------------------+
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Python連携パスをMT5データフォルダから組み立てる関数
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief MT5データフォルダ配下のMQL5ルートを返します。
|
||
*/
|
||
string MQL5RootPath()
|
||
{
|
||
return TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5";
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief EAと同じMQL5ツリー内にあるPython補助アプリのルートを返します。
|
||
*/
|
||
string PythonAppDir()
|
||
{
|
||
return MQL5RootPath() + "\\python_for_ea";
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief Python補助アプリ配下のbatファイル絶対パスを返します。
|
||
*/
|
||
string PythonBatchPath(const string filename)
|
||
{
|
||
return PythonAppDir() + "\\bat\\" + filename;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief ファイル名に使える安全なトークン文字か判定します。
|
||
*/
|
||
bool IsSafeFileTokenChar(const int ch)
|
||
{
|
||
return ((ch >= 48 && ch <= 57) || // 0-9
|
||
(ch >= 65 && ch <= 90) || // A-Z
|
||
(ch >= 97 && ch <= 122) || // a-z
|
||
ch == 95 || ch == 45); // _ or -
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief シンボル名などをファイル名向けの安全なトークンへ変換します。
|
||
*/
|
||
string SanitizeFileToken(const string value)
|
||
{
|
||
string result = "";
|
||
int length = StringLen(value);
|
||
for(int i = 0; i < length; i++)
|
||
{
|
||
int ch = StringGetCharacter(value, i);
|
||
if(IsSafeFileTokenChar(ch))
|
||
result += StringSubstr(value, i, 1);
|
||
else
|
||
result += "_";
|
||
}
|
||
|
||
if(result == "")
|
||
return "UNKNOWN";
|
||
|
||
return result;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief 同一Terminal内の複数EA/複数シンボルで連携ファイルが衝突しない接頭辞を返します。
|
||
*/
|
||
string BuildMT5FilePrefix()
|
||
{
|
||
return "HIT_" + SanitizeFileToken(_Symbol) + "_" + IntegerToString(magic_number);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief Python連携ファイル名へEA固有の接頭辞を付けます。
|
||
*/
|
||
string PrefixedMT5FileName(const string base_name)
|
||
{
|
||
if(mt5_file_prefix == "")
|
||
return base_name;
|
||
|
||
return mt5_file_prefix + "_" + base_name;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief Python連携で使用するMT5 Files配下のファイル名を初期化します。
|
||
*/
|
||
void ConfigurePythonGatewayFileNames()
|
||
{
|
||
mt5_file_prefix = BuildMT5FilePrefix();
|
||
|
||
ohlc_h4_file = PrefixedMT5FileName("ohlc_H4.csv");
|
||
ohlc_h1_file = PrefixedMT5FileName("ohlc_H1.csv");
|
||
done_trend_file = PrefixedMT5FileName("process_done_trend.txt");
|
||
done_entry_file = PrefixedMT5FileName("process_done_entry.txt");
|
||
trend_state_file = PrefixedMT5FileName("trend_state.txt");
|
||
target_prices_file = PrefixedMT5FileName("target_prices.txt");
|
||
target_zones_file = PrefixedMT5FileName("target_zones.txt");
|
||
running_trend_file = PrefixedMT5FileName("process_running_trend.txt");
|
||
running_entry_file = PrefixedMT5FileName("process_running_entry.txt");
|
||
|
||
Print("Python file prefix: ", mt5_file_prefix);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief Python連携で使用するbatパスを初期化します。
|
||
*/
|
||
void ConfigurePythonGatewayPaths()
|
||
{
|
||
ConfigurePythonGatewayFileNames();
|
||
|
||
python_app_dir = PythonAppDir();
|
||
get_trend_reply_bat = PythonBatchPath("get_trend_reply.bat");
|
||
get_entry_reply_bat = PythonBatchPath("get_entry_reply.bat");
|
||
|
||
Print("Python app dir: ", python_app_dir);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| |
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief The split entry/manager design requires ticket-based hedging positions.
|
||
*/
|
||
bool IsHedgingAccount()
|
||
{
|
||
return (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| 起動時の処理
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief EA起動時の初期化処理を行います。
|
||
*
|
||
* Python連携用のdone/runningファイル状態を確認し、起動直後は安全側として
|
||
* H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。
|
||
* H4結果を再読込するまでは技術エラー停止値を維持し、古い連携結果での発注を防ぎます。
|
||
*/
|
||
int OnInit()
|
||
// プロセス完了ファイルと実行中ファイルの状態を初期化
|
||
{
|
||
magic_number = input_entry_magic_number;
|
||
slippage = input_slippage_points;
|
||
|
||
if(!IsHedgingAccount())
|
||
{
|
||
Print("HITEntryEA requires a retail hedging account. account_margin_mode=",
|
||
AccountInfoInteger(ACCOUNT_MARGIN_MODE));
|
||
return INIT_FAILED;
|
||
}
|
||
|
||
ConfigurePythonGatewayPaths();
|
||
|
||
PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend");
|
||
PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry");
|
||
|
||
// Always rebuild startup state before allowing new orders.
|
||
g_ea.trend_state = MARKET_TECHNICAL_ERROR_STOP;
|
||
g_ea.res_chk = 0;
|
||
g_ea.zone_res_chk = 0;
|
||
g_ea.zone_candidate_id = "0";
|
||
g_init_trend_pending = true;
|
||
g_init_entry_pending = true;
|
||
ArrayResize(g_cancel_retry_tickets, 0);
|
||
ArrayResize(g_cancel_retry_at, 0);
|
||
|
||
return INIT_SUCCEEDED;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| 終了時の処理
|
||
//+------------------------------------------------------------------+
|
||
void OnDeinit(const int reason)
|
||
{
|
||
ResetExternalProcessState(g_trend_process);
|
||
ResetExternalProcessState(g_entry_process);
|
||
|
||
// Clear chart status text left by Comment() when the EA is removed.
|
||
Comment("");
|
||
ChartRedraw(0);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| ティック毎の処理
|
||
//+------------------------------------------------------------------+
|
||
/**
|
||
* @brief ティック受信ごとにEA全体の処理を制御します。
|
||
*
|
||
* 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、
|
||
* H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。
|
||
* 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。
|
||
*/
|
||
void OnTick()
|
||
{
|
||
TickContext ctx;
|
||
if(!GetTickContext(ctx))
|
||
return;
|
||
|
||
// Expired pending orders are managed by the entry EA before any Python wait state.
|
||
ManageExpiredEntryOrders();
|
||
|
||
// Delete and pause adverse limit orders before breakout spikes can fill them.
|
||
ManageRangeBreakoutLimitGuard(ctx);
|
||
|
||
// 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。
|
||
RecoverTimedOutPythonProcesses();
|
||
|
||
// H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。
|
||
ProcessTrendUpdate(g_ea);
|
||
|
||
// トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。
|
||
if(!IsTrendResultReady())
|
||
return;
|
||
|
||
RefreshTrendState(g_ea);
|
||
|
||
// H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。
|
||
ProcessEntryUpdate(g_ea);
|
||
|
||
// Read completed H1 results before spread gating so stale split orders and state updates are not delayed.
|
||
bool entry_result_ready = IsEntryResultReady();
|
||
if(entry_result_ready)
|
||
RefreshTargetPrices(g_ea);
|
||
|
||
// M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。
|
||
ProcessM15EntryTimingUpdate();
|
||
|
||
UpdateStatusComment(ctx);
|
||
|
||
// ここから下は新規注文に関する処理。
|
||
if(!IsSpreadAllowed(ctx))
|
||
return;
|
||
|
||
if(!entry_result_ready)
|
||
return;
|
||
|
||
ProcessEntryDecisionIfNeeded(g_ea, ctx);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Entry order lifecycle
|
||
//+------------------------------------------------------------------+
|
||
void ManageExpiredEntryOrders()
|
||
{
|
||
CleanupPendingOrderCancelAttempts();
|
||
|
||
if(OrdersTotal() > 0)
|
||
CancelExpiredOrders();
|
||
}
|
||
|
||
// Function implementations are kept in project headers so the EA entry points stay compact.
|
||
#include <MyLib/Common/HITRuntimeController.mqh>
|
||
#include <MyLib/Signals/HITEntrySignal.mqh>
|
||
#include <MyLib/Common/HITExternalProcess.mqh>
|
||
#include <MyLib/Signals/HITPythonSignalGateway.mqh>
|
||
#include <MyLib/Trading/HITTradeManager.mqh>
|