Split HIT EA into entry and position manager

This commit is contained in:
Hiroaki86
2026-06-07 22:06:30 +09:00
parent f49769dc1e
commit b7789d4572
10 changed files with 1414 additions and 0 deletions
+472
View File
@@ -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:
// <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 = 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_state0..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 <MyLib/Common/HITRuntimeController.mqh>
#include <MyLib/Signals/HITEntrySignal.mqh>
#include <MyLib/Common/HITExternalProcess.mqh>
#include <MyLib/Signals/HITPythonSignalGateway.mqh>
#include <MyLib/Trading/HITTradeManager.mqh>
+233
View File
@@ -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 <MyLib/Trading/SLTPManager.mqh>
#include <MyLib/Trading/HITPositionLifecycleManager.mqh>
#include <MyLib/Panel/SLTPManagerPanel.mqh>
// 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.");
}
}
@@ -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 <Trade/Trade.mqh>
#include <MyLib/Trading/SLTPManager.mqh>
#include <MyLib/Trading/HITPositionLifecycleManager.mqh>
// 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();
}
@@ -0,0 +1,172 @@
#ifndef HIT_POSITION_LIFECYCLE_MANAGER_MQH
#define HIT_POSITION_LIFECYCLE_MANAGER_MQH
#include <Trade/Trade.mqh>
// 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
+51
View File
@@ -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`.
+51
View File
@@ -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` へ移管する仕様を追加。
@@ -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.
@@ -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として新規定義。
@@ -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`.
@@ -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単体検証用ハーネスとして新規作成。