From b7789d45729f4e3f92fdca8d6832e185e99756fa Mon Sep 17 00:00:00 2001 From: Hiroaki86 Date: Sun, 7 Jun 2026 22:06:30 +0900 Subject: [PATCH] Split HIT EA into entry and position manager --- Experts/MyProject/HITEntryEA.mq5 | 472 ++++++++++++++++++ Experts/MyProject/HITPositionManagerEA.mq5 | 233 +++++++++ .../HITPositionManagerTestHarness.mq5 | 243 +++++++++ .../Trading/HITPositionLifecycleManager.mqh | 172 +++++++ docs/MyProject/HITEntryEA_spec_en.md | 51 ++ docs/MyProject/HITEntryEA_spec_ja.md | 51 ++ .../MyProject/HITPositionManagerEA_spec_en.md | 51 ++ .../MyProject/HITPositionManagerEA_spec_ja.md | 51 ++ .../HITPositionManagerTestHarness_spec_en.md | 45 ++ .../HITPositionManagerTestHarness_spec_ja.md | 45 ++ 10 files changed, 1414 insertions(+) create mode 100644 Experts/MyProject/HITEntryEA.mq5 create mode 100644 Experts/MyProject/HITPositionManagerEA.mq5 create mode 100644 Experts/MyProject/HITPositionManagerTestHarness.mq5 create mode 100644 Include/MyLib/Trading/HITPositionLifecycleManager.mqh create mode 100644 docs/MyProject/HITEntryEA_spec_en.md create mode 100644 docs/MyProject/HITEntryEA_spec_ja.md create mode 100644 docs/MyProject/HITPositionManagerEA_spec_en.md create mode 100644 docs/MyProject/HITPositionManagerEA_spec_ja.md create mode 100644 docs/MyProject/HITPositionManagerTestHarness_spec_en.md create mode 100644 docs/MyProject/HITPositionManagerTestHarness_spec_ja.md diff --git a/Experts/MyProject/HITEntryEA.mq5 b/Experts/MyProject/HITEntryEA.mq5 new file mode 100644 index 0000000..0bad810 --- /dev/null +++ b/Experts/MyProject/HITEntryEA.mq5 @@ -0,0 +1,472 @@ +//+------------------------------------------------------------------+ +//| 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.10" + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// 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: +// \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 = false; // H1予測ゾーンで分割エントリーする +input int split_entry_count = 3; // 分割エントリー本数(1..10) +input SplitLotMode split_lot_mode = SPLIT_LOT_TOTAL; // 分割ロット配分モード +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候補価格を新規発注に使う最大経過分数 + +// 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 CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#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 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 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[]; + +// ティックごとの価格情報 +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 EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、起動直後は安全側として + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4結果を再読込するまでは技術エラー停止値を維持し、古い連携結果での発注を防ぎます。 + */ +int OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + magic_number = input_entry_magic_number; + slippage = input_slippage_points; + + 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(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + 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 +#include +#include +#include +#include diff --git a/Experts/MyProject/HITPositionManagerEA.mq5 b/Experts/MyProject/HITPositionManagerEA.mq5 new file mode 100644 index 0000000..386ac69 --- /dev/null +++ b/Experts/MyProject/HITPositionManagerEA.mq5 @@ -0,0 +1,233 @@ +//+------------------------------------------------------------------+ +//| HITPositionManagerEA.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.00" + +#include +#include +#include + +// This EA never opens new positions. It manages hedging-account tickets created by HITEntryEA. +input int input_managed_magic_number = 10001; // 管理対象のマジックナンバー +input ulong input_slippage_points = 10; // 決済/SL変更の許容偏差(point) +input int input_position_close_after_h1_bars = 12; // 保有時間決済(H1本数、0で無効) +input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 +input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 +input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips +input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips +input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 +input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips +input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 +input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips +input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips +input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips +input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips +input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 +input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 +input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 +input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 + +CSLTPManager g_sltp_manager; +CSLTPManagerPanel g_sltp_panel; +SLTPManagerPanelSettings g_sltp_settings; +CHITPositionLifecycleManager g_position_lifecycle; + +bool g_sltp_settings_valid = false; +bool g_sltp_panel_created = false; + +int EffectiveCloseAfterSeconds() + { + int close_bars = input_position_close_after_h1_bars; + if(close_bars < 0) + close_bars = 0; + if(close_bars > 240) + close_bars = 240; + + const int h1_seconds = PeriodSeconds(PERIOD_H1); + if(close_bars <= 0 || h1_seconds <= 0) + return 0; + + return close_bars * h1_seconds; + } + +bool IsHedgingAccount() + { + return (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); + } + +string SLTPBoolText(const bool value) + { + return value ? "ON" : "OFF"; + } + +void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) + { + settings.manager_enabled = input_sltp_manager_enabled; + + settings.use_breakeven = input_sltp_use_breakeven; + settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; + settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; + + settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; + settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; + settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; + + settings.use_active_trailing = input_sltp_use_active_trailing; + settings.active_breakeven_pips = input_sltp_active_breakeven_pips; + settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; + settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; + settings.active_step_move_pips = input_sltp_active_step_move_pips; + + settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; + settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; + settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; + + settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; + } + +bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, + const bool print_summary) + { + g_sltp_settings_valid = false; + + g_sltp_manager.SetMagicNumber((ulong)input_managed_magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(input_slippage_points); + g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, + settings.breakeven_trigger_pips, + settings.breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, + settings.elapsed_breakeven_hours, + settings.elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, + settings.active_breakeven_pips, + settings.active_stop_loss_offset_pips, + settings.active_step_trigger_pips, + settings.active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, + settings.tp_progress_trigger_percent, + settings.tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); + + if(!g_sltp_manager.ValidateSettings()) + { + Print("HIT position manager SLTP settings rejected. Manager is disabled until valid settings are applied."); + return false; + } + + g_sltp_settings = settings; + g_sltp_settings_valid = true; + + if(print_summary) + { + Print("HIT position manager SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), + " BE=", SLTPBoolText(settings.use_breakeven), + " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), + " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), + " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), + " HighVol=", SLTPBoolText(settings.use_high_volatility_limit), + " magic=", input_managed_magic_number); + } + + return true; + } + +bool InitializeSLTPManager() + { + SLTPManagerPanelSettings initial_settings; + LoadSLTPInputSettings(initial_settings); + + if(!ApplySLTPSettings(initial_settings, true)) + return false; + + g_sltp_panel.Init(g_sltp_settings); + if(input_sltp_show_panel) + { + if(!g_sltp_panel.CreatePanel()) + { + Print("HIT position manager SLTP panel is unavailable. EA continues with input-based settings."); + return true; + } + + g_sltp_panel.SetInitialValues(); + g_sltp_panel_created = true; + } + + return true; + } + +bool InitializePositionLifecycleManager() + { + return g_position_lifecycle.Init(_Symbol, + (ulong)input_managed_magic_number, + input_slippage_points, + EffectiveCloseAfterSeconds()); + } + +void ManageTargetPositions() + { + g_position_lifecycle.SetCloseAfterSeconds(EffectiveCloseAfterSeconds()); + g_position_lifecycle.CloseExpiredPositions(); + + if(!g_sltp_settings_valid) + return; + if(!g_sltp_settings.manager_enabled) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +int OnInit() + { + if(!IsHedgingAccount()) + { + Print("HITPositionManagerEA requires a retail hedging account. account_margin_mode=", + AccountInfoInteger(ACCOUNT_MARGIN_MODE)); + return INIT_FAILED; + } + + if(!InitializePositionLifecycleManager()) + return INIT_FAILED; + + if(!InitializeSLTPManager()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +void OnDeinit(const int reason) + { + if(g_sltp_panel_created) + g_sltp_panel.Destroy(reason); + + Comment(""); + ChartRedraw(0); + } + +void OnTick() + { + ManageTargetPositions(); + } + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + if(!g_sltp_panel_created) + return; + + SLTPManagerPanelSettings candidate_settings = g_sltp_settings; + if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) + { + if(!ApplySLTPSettings(candidate_settings, true)) + Print("HIT position manager panel apply failed. Please review panel values."); + } + } diff --git a/Experts/MyProject/HITPositionManagerTestHarness.mq5 b/Experts/MyProject/HITPositionManagerTestHarness.mq5 new file mode 100644 index 0000000..2d03796 --- /dev/null +++ b/Experts/MyProject/HITPositionManagerTestHarness.mq5 @@ -0,0 +1,243 @@ +//+------------------------------------------------------------------+ +//| HITPositionManagerTestHarness.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.00" + +#include +#include +#include + +// Strategy Tester harness only. It seeds positions, then calls the production managers. +input int input_managed_magic_number = 10001; // 管理対象magic +input int input_control_magic_number = 20002; // 非対象確認用magic +input ulong input_slippage_points = 10; // 許容偏差(point) +input double input_test_lot_size = 0.01; // 検証用ロット +input bool input_seed_managed_buy = true; // 管理対象BUYを作成 +input bool input_seed_managed_sell = true; // 管理対象SELLを作成 +input bool input_seed_control_position = true; // magic違いポジションを作成 +input int input_position_close_after_h1_bars = 12; // 保有時間決済(H1本数、0で無効) +input bool input_sltp_manager_enabled = true; // SLTP管理を有効化 +input bool input_sltp_use_breakeven = true; // 通常BEを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; +input double input_sltp_breakeven_buffer_pips = 3.0; +input bool input_sltp_use_elapsed_breakeven = false; +input double input_sltp_elapsed_breakeven_hours = 4.0; +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; +input bool input_sltp_use_active_trailing = false; +input double input_sltp_active_breakeven_pips = 30.0; +input double input_sltp_active_stop_loss_offset_pips = 5.0; +input double input_sltp_active_step_trigger_pips = 10.0; +input double input_sltp_active_step_move_pips = 5.0; +input bool input_sltp_use_tp_progress_stop = false; +input double input_sltp_tp_progress_trigger_percent = 70.0; +input double input_sltp_tp_progress_sl_lock_percent = 30.0; +input bool input_sltp_use_high_volatility_limit = false; + +CTrade g_test_trade; +CSLTPManager g_sltp_manager; +CHITPositionLifecycleManager g_position_lifecycle; + +bool g_seed_done = false; +bool g_sltp_settings_valid = false; + +int EffectiveCloseAfterSeconds() + { + int close_bars = input_position_close_after_h1_bars; + if(close_bars < 0) + close_bars = 0; + if(close_bars > 240) + close_bars = 240; + + const int h1_seconds = PeriodSeconds(PERIOD_H1); + if(close_bars <= 0 || h1_seconds <= 0) + return 0; + + return close_bars * h1_seconds; + } + +bool IsHedgingAccount() + { + return (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); + } + +double NormalizedVolume(const double volume) + { + const double min_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + const double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + const double step_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + if(min_volume <= 0.0 || max_volume <= 0.0 || step_volume <= 0.0) + return volume; + + double normalized = volume; + if(normalized < min_volume) + normalized = min_volume; + if(normalized > max_volume) + normalized = max_volume; + + const double steps = MathFloor((normalized - min_volume) / step_volume + 0.5); + normalized = min_volume + steps * step_volume; + + int volume_digits = 0; + double step = step_volume; + while(step < 1.0 && volume_digits < 8) + { + step *= 10.0; + volume_digits++; + } + + return NormalizeDouble(normalized, volume_digits); + } + +bool ConfigureManagers() + { + if(!g_position_lifecycle.Init(_Symbol, + (ulong)input_managed_magic_number, + input_slippage_points, + EffectiveCloseAfterSeconds())) + return false; + + g_sltp_manager.SetMagicNumber((ulong)input_managed_magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(input_slippage_points); + g_sltp_manager.SetBreakevenSettings(input_sltp_use_breakeven, + input_sltp_breakeven_trigger_pips, + input_sltp_breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(input_sltp_use_elapsed_breakeven, + input_sltp_elapsed_breakeven_hours, + input_sltp_elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(input_sltp_use_active_trailing, + input_sltp_active_breakeven_pips, + input_sltp_active_stop_loss_offset_pips, + input_sltp_active_step_trigger_pips, + input_sltp_active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(input_sltp_use_tp_progress_stop, + input_sltp_tp_progress_trigger_percent, + input_sltp_tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(input_sltp_use_high_volatility_limit); + + g_sltp_settings_valid = g_sltp_manager.ValidateSettings(); + return g_sltp_settings_valid; + } + +bool SeedBuyPosition(const ulong magic, + const string comment) + { + MqlTick tick; + if(!SymbolInfoTick(_Symbol, tick)) + { + Print("Harness tick read failed: err=", GetLastError()); + return false; + } + + const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + const double sl = NormalizeDouble(tick.bid - 5000.0 * point, digits); + const double tp = NormalizeDouble(tick.bid + 10000.0 * point, digits); + + g_test_trade.SetExpertMagicNumber(magic); + ResetLastError(); + if(!g_test_trade.Buy(NormalizedVolume(input_test_lot_size), _Symbol, 0.0, sl, tp, comment)) + { + Print("Harness BUY seed failed: magic=", magic, + " retcode=", g_test_trade.ResultRetcode(), + " err=", GetLastError()); + return false; + } + + Print("Harness BUY seed succeeded: magic=", magic, + " ticket=", g_test_trade.ResultOrder(), + " comment=", comment); + return true; + } + +bool SeedSellPosition(const ulong magic, + const string comment) + { + MqlTick tick; + if(!SymbolInfoTick(_Symbol, tick)) + { + Print("Harness tick read failed: err=", GetLastError()); + return false; + } + + const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + const double sl = NormalizeDouble(tick.ask + 5000.0 * point, digits); + const double tp = NormalizeDouble(tick.ask - 10000.0 * point, digits); + + g_test_trade.SetExpertMagicNumber(magic); + ResetLastError(); + if(!g_test_trade.Sell(NormalizedVolume(input_test_lot_size), _Symbol, 0.0, sl, tp, comment)) + { + Print("Harness SELL seed failed: magic=", magic, + " retcode=", g_test_trade.ResultRetcode(), + " err=", GetLastError()); + return false; + } + + Print("Harness SELL seed succeeded: magic=", magic, + " ticket=", g_test_trade.ResultOrder(), + " comment=", comment); + return true; + } + +void SeedTestPositions() + { + if(g_seed_done) + return; + + g_test_trade.SetDeviationInPoints((int)input_slippage_points); + g_test_trade.SetTypeFillingBySymbol(_Symbol); + + if(input_seed_managed_buy) + SeedBuyPosition((ulong)input_managed_magic_number, "HIT PM TEST managed BUY"); + if(input_seed_managed_sell) + SeedSellPosition((ulong)input_managed_magic_number, "HIT PM TEST managed SELL"); + if(input_seed_control_position) + SeedBuyPosition((ulong)input_control_magic_number, "HIT PM TEST control magic"); + + g_seed_done = true; + } + +void ManageTargetPositions() + { + g_position_lifecycle.SetCloseAfterSeconds(EffectiveCloseAfterSeconds()); + g_position_lifecycle.CloseExpiredPositions(); + + if(!input_sltp_manager_enabled || !g_sltp_settings_valid) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +int OnInit() + { + if(!MQLInfoInteger(MQL_TESTER)) + { + Print("HITPositionManagerTestHarness is for Strategy Tester only."); + return INIT_FAILED; + } + + if(!IsHedgingAccount()) + { + Print("HITPositionManagerTestHarness requires a retail hedging account. account_margin_mode=", + AccountInfoInteger(ACCOUNT_MARGIN_MODE)); + return INIT_FAILED; + } + + if(!ConfigureManagers()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +void OnTick() + { + SeedTestPositions(); + ManageTargetPositions(); + } diff --git a/Include/MyLib/Trading/HITPositionLifecycleManager.mqh b/Include/MyLib/Trading/HITPositionLifecycleManager.mqh new file mode 100644 index 0000000..f617d8b --- /dev/null +++ b/Include/MyLib/Trading/HITPositionLifecycleManager.mqh @@ -0,0 +1,172 @@ +#ifndef HIT_POSITION_LIFECYCLE_MANAGER_MQH +#define HIT_POSITION_LIFECYCLE_MANAGER_MQH + +#include + +// Ticket-based position lifecycle manager for hedging accounts. +class CHITPositionLifecycleManager +{ +private: + string m_symbol; + ulong m_magic; + ulong m_deviation_points; + int m_close_after_seconds; + CTrade m_trade; + +public: + CHITPositionLifecycleManager() + { + m_symbol = _Symbol; + m_magic = 0; + m_deviation_points = 10; + m_close_after_seconds = 0; + } + + bool Init(const string symbol, + const ulong magic, + const ulong deviation_points, + const int close_after_seconds) + { + m_symbol = symbol; + m_magic = magic; + m_deviation_points = deviation_points; + m_close_after_seconds = close_after_seconds; + + if(m_symbol == "") + { + Print("HITPositionLifecycleManager init failed: symbol is empty."); + return false; + } + + ResetLastError(); + if(!SymbolSelect(m_symbol, true)) + { + Print("HITPositionLifecycleManager init failed: symbol=", m_symbol, + " err=", GetLastError()); + return false; + } + + m_trade.SetExpertMagicNumber(m_magic); + m_trade.SetDeviationInPoints((int)m_deviation_points); + m_trade.SetTypeFillingBySymbol(m_symbol); + + return true; + } + + void SetCloseAfterSeconds(const int close_after_seconds) + { + m_close_after_seconds = close_after_seconds; + } + + int CountTargetPositions() + { + int count = 0; + + for(int i = PositionsTotal() - 1; i >= 0; --i) + { + const ulong ticket = PositionGetTicket(i); + if(ticket == 0) + continue; + if(!PositionSelectByTicket(ticket)) + continue; + if(!IsTargetSelectedPosition()) + continue; + + count++; + } + + return count; + } + + bool CloseExpiredPositions() + { + if(m_close_after_seconds <= 0) + return false; + + bool result = false; + const datetime current_time = TimeCurrent(); + + for(int i = PositionsTotal() - 1; i >= 0; --i) + { + const ulong ticket = PositionGetTicket(i); + if(ticket == 0) + continue; + + ResetLastError(); + if(!PositionSelectByTicket(ticket)) + { + Print("HITPositionLifecycleManager select failed: ticket=", ticket, + " err=", GetLastError()); + continue; + } + + if(!IsTargetSelectedPosition()) + continue; + + const datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); + if(entry_time <= 0) + continue; + + if(current_time - entry_time < m_close_after_seconds) + continue; + + if(CloseSelectedPosition(ticket, "time limit")) + result = true; + } + + return result; + } + +private: + bool IsTargetSelectedPosition() + { + if(PositionGetString(POSITION_SYMBOL) != m_symbol) + return false; + if((ulong)PositionGetInteger(POSITION_MAGIC) != m_magic) + return false; + + return true; + } + + string PositionTypeText(const ENUM_POSITION_TYPE type) + { + if(type == POSITION_TYPE_BUY) + return "BUY"; + if(type == POSITION_TYPE_SELL) + return "SELL"; + + return "UNKNOWN"; + } + + bool CloseSelectedPosition(const ulong ticket, + const string reason) + { + const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + const double volume = PositionGetDouble(POSITION_VOLUME); + + ResetLastError(); + if(!m_trade.PositionClose(ticket, m_deviation_points)) + { + Print("HITPositionLifecycleManager close failed: ticket=", ticket, + " symbol=", m_symbol, + " magic=", m_magic, + " type=", PositionTypeText(type), + " volume=", DoubleToString(volume, 2), + " reason=", reason, + " retcode=", m_trade.ResultRetcode(), + " err=", GetLastError()); + return false; + } + + Print("HITPositionLifecycleManager closed position: ticket=", ticket, + " symbol=", m_symbol, + " magic=", m_magic, + " type=", PositionTypeText(type), + " volume=", DoubleToString(volume, 2), + " reason=", reason, + " retcode=", m_trade.ResultRetcode()); + return true; + } +}; + +#endif diff --git a/docs/MyProject/HITEntryEA_spec_en.md b/docs/MyProject/HITEntryEA_spec_en.md new file mode 100644 index 0000000..a5becbc --- /dev/null +++ b/docs/MyProject/HITEntryEA_spec_en.md @@ -0,0 +1,51 @@ +# HITEntryEA Specification + +## 1. Overview + +`HITEntryEA.mq5` is the entry-only Expert Advisor. It runs the H4/H1 Python signal workflow, applies the M15 confirmation rules, and submits pending orders. It does not manage filled positions. Stop management, trailing, and time-based position exits are delegated to `HITPositionManagerEA.mq5`. + +The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number` / `input_managed_magic_number`. The operating assumption is a retail hedging account. + +## 2. Indicators + +- No standard or custom indicators are used. +- Confirmed H4/H1 OHLC data is exported to Python. The EA reads `trend_state.txt`, `target_prices.txt`, and `target_zones.txt`. +- M15 confirmation uses `CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates)`. + +## 3. Parameters + +- `lot_size = 0.01`: Standard order volume. +- `spread_limit = 60`: Maximum allowed spread in points. +- `input_entry_magic_number = 10001`: Magic number assigned to pending orders. +- `input_slippage_points = 10`: Allowed order deviation in points. +- `input_position_limit = 10`: Limit for same-symbol/same-magic pending orders plus positions. +- `use_split_entry_zone = false`: Enables split entries from the H1 prediction zone. +- `split_entry_count = 3`: Number of split entry orders. +- `split_lot_mode = SPLIT_LOT_TOTAL`: Total-volume split or fixed-volume split. +- `input_cancel_retry_cooldown_seconds = 60`: Ticket-level retry cooldown after pending cancellation failures. +- `use_m15_entry_filter = true`: Enables M15 closed-bar confirmation. +- `input_entry_max_candidate_age_minutes = 120`: Maximum age for using an H1 candidate for new execution. + +## 4. Entry And Exit Conditions + +- New pending orders are sent only after spread, H4 state, H1 candidate, M15 timing, price consistency, broker distance, volume, and same-symbol/same-magic limit checks pass. +- Pending orders receive `input_entry_magic_number`. +- The entry EA cancels expired pending orders, pending orders that conflict with the refreshed H4 state, and stale split-entry pending orders. +- Filled-position SL updates, trailing, and time-based exits are not performed by this EA. + +## 5. Risk Management + +- The order limit counts only pending orders and positions matching `_Symbol` and `input_entry_magic_number`. +- Python process markers, process IDs, done files, and timeouts are monitored to prevent duplicate launches and stale-result reuse. +- `OrderSend` return values and `MqlTradeResult.retcode` are checked and logged. +- Because the design assumes hedging accounts, per-ticket position management is handled by `HITPositionManagerEA.mq5`. + +## 6. Unit Test Scope + +- In the Strategy Tester, this EA is tested for pending-order creation, expired pending cancellation, and H4-state mismatch cancellation. +- Position management behavior is outside this EA's unit-test scope. +- Python linkage filenames include `_Symbol` and `input_entry_magic_number` to preserve reproducibility. + +## 7. Changelog + +- 2026-06-07: Split entry responsibilities from `HIT-EA_refactor_ver6.mq5`; moved SLTP panel and filled-position management responsibilities to `HITPositionManagerEA.mq5`. diff --git a/docs/MyProject/HITEntryEA_spec_ja.md b/docs/MyProject/HITEntryEA_spec_ja.md new file mode 100644 index 0000000..6b20852 --- /dev/null +++ b/docs/MyProject/HITEntryEA_spec_ja.md @@ -0,0 +1,51 @@ +# HITEntryEA 仕様書(日本語) + +## 1. 概要 + +`HITEntryEA.mq5` は、H4/H1の外部Python判定とM15確認を使って新規pending注文までを実行する発注専用EAです。約定後ポジションのSL移動、トレーリング、時間決済は行わず、同じマジックナンバーを監視する `HITPositionManagerEA.mq5` に委譲します。 + +2つのEAは `_Symbol` と `input_entry_magic_number` / `input_managed_magic_number` の一致で連携します。運用口座はhedging口座を前提とします。 + +## 2. 使用インジケータ + +- 標準・カスタムインジケータは使用しません。 +- H4/H1の確定足OHLCをPythonへ渡し、`trend_state.txt`, `target_prices.txt`, `target_zones.txt` を読み込みます。 +- M15確認は `CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates)` を使用します。 + +## 3. パラメータ設定 + +- `lot_size = 0.01`: 通常注文ロット。 +- `spread_limit = 60`: 許容スプレッド(point)。 +- `input_entry_magic_number = 10001`: 発注EAがpending注文へ付与するマジックナンバー。 +- `input_slippage_points = 10`: 注文送信時の許容偏差。 +- `input_position_limit = 10`: 同一symbol/magicのpending注文 + ポジション上限。 +- `use_split_entry_zone = false`: H1予測ゾーンによる分割エントリーを有効化するか。 +- `split_entry_count = 3`: 分割本数。 +- `split_lot_mode = SPLIT_LOT_TOTAL`: 総量分割または固定ロット分割。 +- `input_cancel_retry_cooldown_seconds = 60`: pending取消失敗後のticket単位再試行間隔。 +- `use_m15_entry_filter = true`: M15確定足確認を使うか。 +- `input_entry_max_candidate_age_minutes = 120`: H1候補を新規発注に使える最大経過分数。 + +## 4. エントリー/エグジット条件 + +- 新規注文はスプレッド、H4状態、H1候補、M15確認、価格整合、ブローカー距離、ロット制約、同一symbol/magicの上限を満たす場合のみ送信します。 +- pending注文には `input_entry_magic_number` を付与します。 +- H4状態と矛盾するpending注文、期限切れpending注文、古い分割pending注文は発注EAが取消します。 +- 約定後ポジションのSL変更、時間決済、トレーリングは発注EAでは行いません。 + +## 5. リスク管理 + +- 発注上限は口座全体ではなく `_Symbol` と `input_entry_magic_number` が一致するpending注文 + ポジションで判定します。 +- Python連携は起動中/完了ファイルとプロセスIDを監視し、二重起動と古い結果の再利用を抑止します。 +- `OrderSend` の戻り値と `MqlTradeResult.retcode` を確認し、失敗時はエラーまたはretcodeをログに出します。 +- hedging口座前提のため、約定後の複数ticket管理は `HITPositionManagerEA.mq5` が担当します。 + +## 6. 単体テスト + +- Strategy Testerでは発注EA単体で、pending注文の作成、期限切れ取消、H4状態不一致取消を確認します。 +- 管理EA機能はこのテスト対象に含めません。 +- Python連携ファイル名は `_Symbol` と `input_entry_magic_number` を含むprefixで分離し、再現性を保ちます。 + +## 7. 変更履歴 + +- 2026-06-07: `HIT-EA_refactor_ver6.mq5` から発注責務を分離し、SLTPパネルとポジション管理を `HITPositionManagerEA.mq5` へ移管する仕様を追加。 diff --git a/docs/MyProject/HITPositionManagerEA_spec_en.md b/docs/MyProject/HITPositionManagerEA_spec_en.md new file mode 100644 index 0000000..7ad9b80 --- /dev/null +++ b/docs/MyProject/HITPositionManagerEA_spec_en.md @@ -0,0 +1,51 @@ +# HITPositionManagerEA Specification + +## 1. Overview + +`HITPositionManagerEA.mq5` is the filled-position manager for positions created by `HITEntryEA.mq5`. It never creates new orders or pending orders. It is a retail hedging account EA and fails initialization on non-hedging accounts. + +The managed set is limited to positions whose `POSITION_SYMBOL` equals `_Symbol` and whose `POSITION_MAGIC` equals `input_managed_magic_number`. Multiple tickets, split entries, and simultaneous buy/sell positions are managed independently. + +## 2. Indicators + +- No standard or custom indicators are used. +- SLTP management uses the current tick, each position's open price, SL, TP, open time, and symbol stop/freeze levels. +- High-volatility stop tightening uses M1/M3/M5/M10/M15 open prices and the current market price. + +## 3. Parameters + +- `input_managed_magic_number = 10001`: Magic number of positions to manage. It should match the entry EA's `input_entry_magic_number`. +- `input_slippage_points = 10`: Allowed deviation for closes and SL modifications. +- `input_position_close_after_h1_bars = 12`: Time-based close threshold in H1 bars. `0` disables it. +- `input_sltp_manager_enabled = false`: Master switch for SLTP management. +- `input_sltp_show_panel = true`: Shows the SLTP control panel. +- `input_sltp_use_breakeven`: Standard breakeven. +- `input_sltp_use_elapsed_breakeven`: Elapsed-time breakeven. +- `input_sltp_use_active_trailing`: Active trailing. +- `input_sltp_use_tp_progress_stop`: TP-progress stop. +- `input_sltp_use_high_volatility_limit`: High-volatility stop tightening. + +## 4. Entry And Exit Conditions + +- There are no entry conditions because this EA does not create orders. +- On every tick, the EA first evaluates ticket-based time exits and then applies SLTP management. +- Time exits close target tickets when `POSITION_TIME` is older than `input_position_close_after_h1_bars * PeriodSeconds(PERIOD_H1)` by using `CTrade::PositionClose(ticket)`. +- SLTP management applies only candidates that improve the current SL in the profit-protection direction, preserving the current TP with `CTrade::PositionModify(ticket, new_sl, current_tp)`. + +## 5. Risk Management + +- All position processing uses `PositionGetTicket(i)` followed by `PositionSelectByTicket(ticket)`. +- Only positions matching `_Symbol` and `input_managed_magic_number` are touched. +- Different magic numbers, different symbols, and manual-position equivalents are ignored. +- Logs include ticket, symbol, magic, position type, trade retcode, and `GetLastError()`. +- Standard breakeven and active trailing cannot be enabled together; settings validation rejects that conflict. + +## 6. Unit Test Scope + +- The production manager EA contains no test-only order creation. +- In the Strategy Tester, `HITPositionManagerTestHarness.mq5` seeds test BUY/SELL positions and a different-magic control position, then calls the same production manager classes. +- Test coverage targets same-symbol/same-magic multi-ticket handling, simultaneous buy/sell positions, magic mismatch exclusion, time exits, and SL updates. + +## 7. Changelog + +- 2026-06-07: Split position-management responsibilities from `HIT-EA_refactor_ver6.mq5` and defined a hedging-account, ticket-based manager EA. diff --git a/docs/MyProject/HITPositionManagerEA_spec_ja.md b/docs/MyProject/HITPositionManagerEA_spec_ja.md new file mode 100644 index 0000000..f847809 --- /dev/null +++ b/docs/MyProject/HITPositionManagerEA_spec_ja.md @@ -0,0 +1,51 @@ +# HITPositionManagerEA 仕様書(日本語) + +## 1. 概要 + +`HITPositionManagerEA.mq5` は、`HITEntryEA.mq5` が作成した約定済みポジションを管理する専用EAです。新規注文やpending注文は作成しません。hedging口座専用とし、起動時に `ACCOUNT_MARGIN_MODE_RETAIL_HEDGING` でない場合は `INIT_FAILED` とします。 + +管理対象は `_Symbol` と `input_managed_magic_number` が一致するポジションのみです。複数ポジション、両建て、分割エントリーをticket単位で独立管理します。 + +## 2. 使用インジケータ + +- 標準・カスタムインジケータは使用しません。 +- SLTP管理は現在tick、ポジションの建値/SL/TP/保有時間、シンボルのstop level / freeze levelを使用します。 +- 急変時SL引き締めは M1/M3/M5/M10/M15 の始値と現在価格を使用します。 + +## 3. パラメータ設定 + +- `input_managed_magic_number = 10001`: 管理対象ポジションのマジックナンバー。発注EAの `input_entry_magic_number` と一致させます。 +- `input_slippage_points = 10`: 決済とSL変更の許容偏差。 +- `input_position_close_after_h1_bars = 12`: 保有時間決済のH1本数。0で無効。 +- `input_sltp_manager_enabled = false`: SLTP管理全体のON/OFF。 +- `input_sltp_show_panel = true`: SLTP操作パネルを表示するか。 +- `input_sltp_use_breakeven`: 通常ブレークイーブン。 +- `input_sltp_use_elapsed_breakeven`: 保有時間ベースのブレークイーブン。 +- `input_sltp_use_active_trailing`: アクティブトレーリング。 +- `input_sltp_use_tp_progress_stop`: TP進捗率SL。 +- `input_sltp_use_high_volatility_limit`: 急変時SL引き締め。 + +## 4. エントリー/エグジット条件 + +- 新規エントリー条件はありません。このEAは注文作成を行いません。 +- `OnTick()` では、まずticket単位で保有時間決済を評価し、その後SLTP管理を実行します。 +- 時間決済は `POSITION_TIME` から `input_position_close_after_h1_bars * PeriodSeconds(PERIOD_H1)` 以上経過した対象ticketを `CTrade::PositionClose(ticket)` で閉じます。 +- SLTP管理は、対象ticketの現在SLより利益保護方向へ改善する候補だけを `CTrade::PositionModify(ticket, new_sl, current_tp)` で反映します。 + +## 5. リスク管理 + +- すべてのポジション選択は `PositionGetTicket(i)` と `PositionSelectByTicket(ticket)` を使います。 +- `POSITION_SYMBOL == _Symbol` かつ `POSITION_MAGIC == input_managed_magic_number` のみ処理します。 +- magic違い、symbol違い、手動ポジション相当には触れません。 +- ログにはticket、symbol、magic、position type、retcode、`GetLastError()` を含めます。 +- 通常ブレークイーブンとアクティブトレーリングの同時ONは設定検証で拒否します。 + +## 6. 単体テスト + +- 管理EA本体は本番安全性のため新規注文を持ちません。 +- Strategy Testerでは `HITPositionManagerTestHarness.mq5` を使い、検証用のBUY/SELLおよびmagic違いポジションを作成してから同じ管理クラスを呼び出します。 +- テスト観点は、同一symbol/magicの複数ticket管理、BUY/SELL両建て、magic違い非対象、時間決済、SL更新です。 + +## 7. 変更履歴 + +- 2026-06-07: `HIT-EA_refactor_ver6.mq5` からポジション管理責務を分離し、hedging口座専用のticket単位管理EAとして新規定義。 diff --git a/docs/MyProject/HITPositionManagerTestHarness_spec_en.md b/docs/MyProject/HITPositionManagerTestHarness_spec_en.md new file mode 100644 index 0000000..ab9cfa6 --- /dev/null +++ b/docs/MyProject/HITPositionManagerTestHarness_spec_en.md @@ -0,0 +1,45 @@ +# HITPositionManagerTestHarness Specification + +## 1. Overview + +`HITPositionManagerTestHarness.mq5` is a Strategy Tester-only EA for unit-testing the management logic used by `HITPositionManagerEA.mq5`. It is not intended for live trading. It fails initialization outside `MQL_TESTER` and on non-hedging accounts. + +The harness seeds test positions, then calls the same production `CSLTPManager` and `CHITPositionLifecycleManager` classes used by the manager EA. + +## 2. Indicators + +- No standard or custom indicators are used. +- The management logic uses the current tick, selected position data, and symbol trading constraints. + +## 3. Parameters + +- `input_managed_magic_number = 10001`: Magic number for managed test positions. +- `input_control_magic_number = 20002`: Magic number for a different-magic control position. +- `input_slippage_points = 10`: Allowed deviation for seed orders, closes, and SL modifications. +- `input_test_lot_size = 0.01`: Test position volume. +- `input_seed_managed_buy = true`: Seeds a managed BUY position. +- `input_seed_managed_sell = true`: Seeds a managed SELL position. +- `input_seed_control_position = true`: Seeds a different-magic control position. +- `input_position_close_after_h1_bars = 12`: Time-exit threshold in H1 bars. +- `input_sltp_*`: Same SLTP settings as the production manager EA. + +## 4. Entry And Exit Conditions + +- On the first tick, the harness creates the configured managed BUY, managed SELL, and different-magic BUY positions. +- After seeding, it runs the same management sequence as production: time exits, SLTP management, and high-volatility stop tightening. +- The different-magic control position is expected to remain unmanaged. + +## 5. Risk Management + +- This EA is tester-only and does not run in live mode. +- Test volume is normalized to `SYMBOL_VOLUME_MIN/MAX/STEP`. +- The target filter is the same as production: `_Symbol + input_managed_magic_number`. + +## 6. Unit Test Scope + +- The harness keeps test-only order creation out of the production manager EA. +- It verifies buy/sell coexistence, multiple-ticket handling, magic mismatch exclusion, time exits, and SL updates. + +## 7. Changelog + +- 2026-06-07: Created as the Strategy Tester unit-test harness for `HITPositionManagerEA.mq5`. diff --git a/docs/MyProject/HITPositionManagerTestHarness_spec_ja.md b/docs/MyProject/HITPositionManagerTestHarness_spec_ja.md new file mode 100644 index 0000000..52f0854 --- /dev/null +++ b/docs/MyProject/HITPositionManagerTestHarness_spec_ja.md @@ -0,0 +1,45 @@ +# HITPositionManagerTestHarness 仕様書(日本語) + +## 1. 概要 + +`HITPositionManagerTestHarness.mq5` は、`HITPositionManagerEA.mq5` の管理ロジックをStrategy Testerで単体検証するためのテスト専用EAです。本番運用には使用しません。`MQL_TESTER` 以外では `INIT_FAILED` とし、hedging口座以外でも起動しません。 + +テスター内で検証用ポジションを作成した後、本番管理EAと同じ `CSLTPManager` と `CHITPositionLifecycleManager` を呼び出します。 + +## 2. 使用インジケータ + +- 標準・カスタムインジケータは使用しません。 +- 管理ロジックは現在tick、ポジション情報、シンボル制約を使用します。 + +## 3. パラメータ設定 + +- `input_managed_magic_number = 10001`: 管理対象として作成する検証用ポジションのmagic。 +- `input_control_magic_number = 20002`: magic違い非対象確認用ポジションのmagic。 +- `input_slippage_points = 10`: 検証用注文、決済、SL変更の許容偏差。 +- `input_test_lot_size = 0.01`: 検証用ロット。 +- `input_seed_managed_buy = true`: 管理対象BUYを作成するか。 +- `input_seed_managed_sell = true`: 管理対象SELLを作成するか。 +- `input_seed_control_position = true`: magic違いポジションを作成するか。 +- `input_position_close_after_h1_bars = 12`: 保有時間決済のH1本数。 +- `input_sltp_*`: 本番管理EAと同じSLTP管理設定。 + +## 4. エントリー/エグジット条件 + +- 初回tickで、設定に従って管理対象BUY、管理対象SELL、magic違いBUYを作成します。 +- 作成後は本番管理ロジックと同じ順序で、保有時間決済、SLTP管理、急変時SL引き締めを実行します。 +- magic違いポジションは管理対象外として残ることを確認します。 + +## 5. リスク管理 + +- 本EAはテスター専用であり、ライブ環境では起動しません。 +- 検証用ポジションは `_Symbol` 上に作成し、ロットは `SYMBOL_VOLUME_MIN/MAX/STEP` に合わせて正規化します。 +- 管理対象判定は本番と同じ `_Symbol + input_managed_magic_number` です。 + +## 6. 単体テスト + +- 管理EA本体に新規注文コードを入れないためのテストハーネスです。 +- BUY/SELL両建て、複数ticket、magic違い除外、時間決済、SL更新の再現性を確認します。 + +## 7. 変更履歴 + +- 2026-06-07: `HITPositionManagerEA.mq5` のStrategy Tester単体検証用ハーネスとして新規作成。