diff --git a/Experts/MyProject/HITEntryEA.mq5 b/Experts/MyProject/HITEntryEA.mq5 index 963e0ef..ad0bc04 100644 --- a/Experts/MyProject/HITEntryEA.mq5 +++ b/Experts/MyProject/HITEntryEA.mq5 @@ -134,7 +134,6 @@ 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 @@ -375,6 +374,17 @@ void ConfigurePythonGatewayPaths() Print("Python app dir: ", python_app_dir); } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief The split entry/manager design requires ticket-based hedging positions. + */ +bool IsHedgingAccount() + { + return (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); + } + //+------------------------------------------------------------------+ //| 起動時の処理 //+------------------------------------------------------------------+ @@ -391,6 +401,13 @@ int OnInit() magic_number = input_entry_magic_number; slippage = input_slippage_points; + if(!IsHedgingAccount()) + { + Print("HITEntryEA requires a retail hedging account. account_margin_mode=", + AccountInfoInteger(ACCOUNT_MARGIN_MODE)); + return INIT_FAILED; + } + ConfigurePythonGatewayPaths(); PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); @@ -459,6 +476,11 @@ void OnTick() // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 ProcessEntryUpdate(g_ea); + // Read completed H1 results before spread gating so stale split orders and state updates are not delayed. + bool entry_result_ready = IsEntryResultReady(); + if(entry_result_ready) + RefreshTargetPrices(g_ea); + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 ProcessM15EntryTimingUpdate(); @@ -468,10 +490,9 @@ void OnTick() if(!IsSpreadAllowed(ctx)) return; - if(!IsEntryResultReady()) + if(!entry_result_ready) return; - RefreshTargetPrices(g_ea); ProcessEntryDecisionIfNeeded(g_ea, ctx); } diff --git a/Experts/MyProject/HITPositionManagerEA.mq5 b/Experts/MyProject/HITPositionManagerEA.mq5 index e0a4b83..afe3f37 100644 --- a/Experts/MyProject/HITPositionManagerEA.mq5 +++ b/Experts/MyProject/HITPositionManagerEA.mq5 @@ -94,8 +94,6 @@ void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) 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); @@ -111,13 +109,13 @@ bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, 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); + 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()) + if(settings.manager_enabled && !g_sltp_manager.ValidateSettings()) { - Print("HIT position manager SLTP settings rejected. Manager is disabled until valid settings are applied."); + Print("HIT position manager SLTP settings rejected. Previous valid settings remain active."); return false; } diff --git a/Include/MyLib/Common/HITExternalProcess.mqh b/Include/MyLib/Common/HITExternalProcess.mqh index 285a477..277b2de 100644 --- a/Include/MyLib/Common/HITExternalProcess.mqh +++ b/Include/MyLib/Common/HITExternalProcess.mqh @@ -512,14 +512,46 @@ bool IsProcessResultReady(const string done_file, const string running_file, con if(!FileIsExist(result_file)) { - Print("[", label, "] done exists but result file is missing: ", result_file); - return false; + Print("[", label, "] done exists but result file is missing: ", result_file); + DeleteDoneFile(done_file); + DeleteRunningFile(running_file); + ResetExternalProcessState(process); + MarkProcessRetryPending(label); + return false; } DeleteRunningFile(running_file); return true; } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief Mark a missing-result Python run for immediate retry on the next tick. + */ +void MarkProcessRetryPending(const string label) + { + if(label == "trend") + { + g_ea.trend_state = MARKET_TECHNICAL_ERROR_STOP; + g_ea.load_trend_flg = false; + g_init_trend_pending = true; + return; + } + + if(label == "entry") + { + g_ea.res_chk = 0; + g_ea.zone_res_chk = 0; + g_ea.load_target_flg = false; + g_bars_H1_check = false; + g_bars_M15_check = false; + g_ea.chk_cnt = 0; + g_init_entry_pending = true; + } + } + //+------------------------------------------------------------------+ //| Python開始可能状態を返す関数 //+------------------------------------------------------------------+ diff --git a/Include/MyLib/Common/HITRuntimeController.mqh b/Include/MyLib/Common/HITRuntimeController.mqh index cd5b3d0..1a9fd6f 100644 --- a/Include/MyLib/Common/HITRuntimeController.mqh +++ b/Include/MyLib/Common/HITRuntimeController.mqh @@ -25,26 +25,6 @@ bool GetTickContext(TickContext &ctx) return true; } -//+------------------------------------------------------------------+ -//| 期限切れ注文・期限切れポジションを処理する関数 -//+------------------------------------------------------------------+ -/** - * @brief 期限切れの未約定注文と保有ポジションを処理します。 - * - * 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。 - * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 - */ -void ManageExpiredTrades() - { - CleanupPendingOrderCancelAttempts(); - - if(OrdersTotal() > 0) - CancelExpiredOrders(); - - if(PositionsTotal() > 0) - CloseExpiredPositions(); - } - //+------------------------------------------------------------------+ //| H4更新検知とトレンド判定Python起動を処理する関数 //+------------------------------------------------------------------+ diff --git a/Include/MyLib/Trading/HITTradeManager.mqh b/Include/MyLib/Trading/HITTradeManager.mqh index c64c9bf..92a96c1 100644 --- a/Include/MyLib/Trading/HITTradeManager.mqh +++ b/Include/MyLib/Trading/HITTradeManager.mqh @@ -1068,86 +1068,4 @@ bool CancelExpiredOrders() } return result; } - -//+------------------------------------------------------------------+ -//| 時間が経過したポジションをクローズする関数 -//+------------------------------------------------------------------+ -/** - * @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。 - * - * @return 1件以上クローズに成功した場合はtrue。 - * - * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 - */ -bool CloseExpiredPositions() - { - bool result = false; - -// ポジションを逆順でループ - for(int i = PositionsTotal() - 1; i >= 0; i--) - { - string position_symbol = PositionGetSymbol(i); - if(position_symbol!="") - { - // Magic Number とシンボルでフィルタリング - if(position_symbol != _Symbol) - continue; - if(PositionGetInteger(POSITION_MAGIC) == magic_number) - { - // ポジションのエントリー時刻を取得 - datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); - - int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1); - if(entry_time <= 0 || expiration_seconds <= 0) - continue; - - if(TimeCurrent() - entry_time >= expiration_seconds) - { - MqlTradeRequest request = {}; - MqlTradeResult trade_result = {}; - - // ポジションタイプに応じてリクエストを設定 - int position_type = (int)PositionGetInteger(POSITION_TYPE); - request.action = TRADE_ACTION_DEAL; - request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 - request.symbol = position_symbol; // シンボル - request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ - request.price = (position_type == POSITION_TYPE_BUY) - ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ - : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ - request.deviation = slippage; - request.type = (position_type == POSITION_TYPE_BUY) - ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ - : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ - request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー - request.magic = magic_number; - - // 注文送信 - if(!OrderSend(request, trade_result)) - { - Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); - continue; - } - - // 結果の確認 - if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) - { - Print("Position closed successfully due to a time limit. Ticket: ", request.position); - result = true; // 少なくとも1つ成功した場合 - } - else - { - Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); - } - } - } - } - else - { - Print("Failed to select position at index ", i, ". Error: ", GetLastError()); - } - } - return result; - } - #endif diff --git a/Include/MyLib/Trading/SLTPManager.mqh b/Include/MyLib/Trading/SLTPManager.mqh index 3b0b30e..14d15b2 100644 --- a/Include/MyLib/Trading/SLTPManager.mqh +++ b/Include/MyLib/Trading/SLTPManager.mqh @@ -335,12 +335,14 @@ public: continue; double candidate_sl = 0.0; + const double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN); const double evaluation_price = (type == POSITION_TYPE_BUY) ? tick.bid : tick.ask; if (!CalcHighVolatilitySL(type, - evaluation_price, - open_m1, - open_m3, - open_m5, + evaluation_price, + position_open_price, + open_m1, + open_m3, + open_m5, open_m10, open_m15, pip_size, @@ -590,6 +592,7 @@ private: /// @return いずれかの時間足でしきい値を超え、SL候補を出力した場合は true。 bool CalcHighVolatilitySL(const ENUM_POSITION_TYPE type, const double current_price, + const double position_open_price, const double open_m1, const double open_m3, const double open_m5, @@ -612,13 +615,36 @@ private: if (!has_candidate) return false; + if (!IsProfitProtectingStopLoss(type, candidate_sl, position_open_price)) + return false; + sl_value = NormalizeDouble(candidate_sl, digits); return true; } + /// @brief 急変時SL候補が建値より不利にならないことを確認する。 + bool IsProfitProtectingStopLoss(const ENUM_POSITION_TYPE type, + const double candidate_sl, + const double open_price) + { + if (open_price <= 0.0) + return false; + + const double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT); + const double tolerance = point * 0.1; + + if (type == POSITION_TYPE_BUY) + return (candidate_sl >= open_price - tolerance); + + if (type == POSITION_TYPE_SELL) + return (candidate_sl <= open_price + tolerance); + + return false; + } + /// @brief 1本の時間足始値から急変幅を評価し、有効なSL候補なら最良候補へ反映する。 void EvaluateHighVolatilityOpen(const ENUM_POSITION_TYPE type, - const double current_price, + const double current_price, const double open_price, const double limit_pips, const double rate_ratio, diff --git a/docs/MyProject/HITEntryEA_spec_en.md b/docs/MyProject/HITEntryEA_spec_en.md index c8aef8c..1210e14 100644 --- a/docs/MyProject/HITEntryEA_spec_en.md +++ b/docs/MyProject/HITEntryEA_spec_en.md @@ -4,7 +4,7 @@ `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. +The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number` / `input_managed_magic_number`. The operating assumption is a retail hedging account, and initialization fails on non-hedging accounts. ## 2. Indicators @@ -45,12 +45,14 @@ The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number` - 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. - When the range-breakout guard is enabled, upward breakout warning/confirmation cancels existing T4 Sell Limit orders and blocks new T4 entries until cooldown expiry. Downward breakout warning/confirmation cancels existing T2 Buy Limit orders and blocks new T2 entries. +- H1 Python results, candidate timestamps, and stale split-pending cleanup are processed before the spread gate; excessive spread only blocks new order submission. - 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. +- If a done file exists but the expected result file is missing, the EA clears the done/running state and marks the same workflow for retry on the next tick. - If the API TP direction contradicts the Buy/Sell price rules, the EA does not repair it; the existing price-consistency checks skip the order. - The range-breakout guard only touches pending orders matching `_Symbol` and `input_entry_magic_number`; upward breakouts cancel T4 Sell Limit only, and downward breakouts cancel T2 Buy Limit only. - `OrderSend` return values and `MqlTradeResult.retcode` are checked and logged. @@ -64,6 +66,7 @@ The two EAs coordinate through matching `_Symbol` and `input_entry_magic_number` ## 7. Changelog +- 2026-06-14: Added initialization failure on non-hedging accounts. Moved H1 result loading before spread gating, added retry recovery for done-without-result Python states, and removed the unused filled-position time-exit path from the entry EA side. - 2026-06-13: Connected the range-breakout guard from `HIT-EA_refactor_ver6.mq5` to the entry EA, including T4 Sell Limit retreat on upward breakouts, T2 Buy Limit retreat on downward breakouts, and cooldown-based new-entry blocking. - 2026-06-13: Added `input_tp_multiplier`, `input_min_tp_points`, and `input_max_tp_points` to expand API-derived TP distances for both standard orders and per-slot split entries. - 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 index 355dcf6..128d343 100644 --- a/docs/MyProject/HITEntryEA_spec_ja.md +++ b/docs/MyProject/HITEntryEA_spec_ja.md @@ -4,7 +4,7 @@ `HITEntryEA.mq5` は、H4/H1の外部Python判定とM15確認を使って新規pending注文までを実行する発注専用EAです。約定後ポジションのSL移動、トレーリング、時間決済は行わず、同じマジックナンバーを監視する `HITPositionManagerEA.mq5` に委譲します。 -2つのEAは `_Symbol` と `input_entry_magic_number` / `input_managed_magic_number` の一致で連携します。運用口座はhedging口座を前提とします。 +2つのEAは `_Symbol` と `input_entry_magic_number` / `input_managed_magic_number` の一致で連携します。運用口座はhedging口座を前提とし、非hedging口座では初期化に失敗します。 ## 2. 使用インジケータ @@ -45,12 +45,14 @@ - pending注文には `input_entry_magic_number` を付与します。 - H4状態と矛盾するpending注文、期限切れpending注文、古い分割pending注文は発注EAが取消します。 - レンジブレイクガードがONの場合、M15監視足で上方向ブレイク警戒/確定を検出すると既存T4 Sell Limitを取消し、T4新規発注をクールダウン終了まで停止します。下方向ブレイクでは既存T2 Buy Limitを取消し、T2新規発注を停止します。 +- H1 Python結果の読み込み、候補時刻更新、古い分割pending取消はスプレッド判定より前に実行し、スプレッド超過は新規発注だけを停止します。 - 約定後ポジションのSL変更、時間決済、トレーリングは発注EAでは行いません。 ## 5. リスク管理 - 発注上限は口座全体ではなく `_Symbol` と `input_entry_magic_number` が一致するpending注文 + ポジションで判定します。 - Python連携は起動中/完了ファイルとプロセスIDを監視し、二重起動と古い結果の再利用を抑止します。 +- doneファイルが存在しても結果ファイルが欠落している場合は、done/running状態を破棄し、次tickで同じ処理を再実行できる状態へ戻します。 - API由来TPの方向がBuy/Sell条件と逆の場合、EAは方向を補正せず既存の価格整合チェックで発注を見送ります。 - レンジブレイクガードは `_Symbol` と `input_entry_magic_number` が一致するpending注文だけを対象にし、上方向ではT4 Sell Limit、下方向ではT2 Buy Limitのみを取消します。 - `OrderSend` の戻り値と `MqlTradeResult.retcode` を確認し、失敗時はエラーまたはretcodeをログに出します。 @@ -64,6 +66,7 @@ ## 7. 変更履歴 +- 2026-06-14: 非hedging口座での初期化停止を追加。H1結果読み込みをスプレッド判定前へ移動し、doneのみ残るPython不整合時の再実行復旧を追加。発注EA側に残っていた未使用の約定後時間決済経路を削除。 - 2026-06-13: `HIT-EA_refactor_ver6.mq5` と同等のレンジブレイクガードを発注EAへ接続。レンジ上抜け時のT4 Sell Limit退避、レンジ下抜け時のT2 Buy Limit退避、およびクールダウン中の新規発注停止を反映。 - 2026-06-13: API由来TPをエントリー価格からの距離ベースで拡大する `input_tp_multiplier`, `input_min_tp_points`, `input_max_tp_points` を追加し、通常注文と分割slotごとの最終TP計算に反映。 - 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 index fb220a5..975ff5b 100644 --- a/docs/MyProject/HITPositionManagerEA_spec_en.md +++ b/docs/MyProject/HITPositionManagerEA_spec_en.md @@ -41,6 +41,7 @@ The managed set is limited to positions whose `POSITION_SYMBOL` equals `_Symbol` - 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)`. +- High-volatility stop tightening only accepts candidates that are at or beyond breakeven: at or above entry for BUY positions and at or below entry for SELL positions. ## 5. Risk Management @@ -49,6 +50,7 @@ The managed set is limited to positions whose `POSITION_SYMBOL` equals `_Symbol` - 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. +- When `input_sltp_manager_enabled=false`, contradictory detailed SLTP settings do not stop the EA; time exits continue to run. Detailed settings are validated when SLTP management is enabled. ## 6. Unit Test Scope @@ -58,5 +60,6 @@ The managed set is limited to positions whose `POSITION_SYMBOL` equals `_Symbol` ## 7. Changelog +- 2026-06-14: Allowed the EA to continue time-exit management when SLTP master is off even if detailed SLTP settings conflict. Added breakeven protection to high-volatility stop tightening so loss-side SL candidates are discarded. - 2026-06-13: Synchronized SLTP input defaults with `HIT-EA_refactor_ver6.mq5` and documented each default value. - 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 index ca56291..7409490 100644 --- a/docs/MyProject/HITPositionManagerEA_spec_ja.md +++ b/docs/MyProject/HITPositionManagerEA_spec_ja.md @@ -41,6 +41,7 @@ - `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)` で反映します。 +- 急変時SL引き締めは、BUYでは建値以上、SELLでは建値以下となる候補だけを採用し、損失側へのSL候補は破棄します。 ## 5. リスク管理 @@ -49,6 +50,7 @@ - magic違い、symbol違い、手動ポジション相当には触れません。 - ログにはticket、symbol、magic、position type、retcode、`GetLastError()` を含めます。 - 通常ブレークイーブンとアクティブトレーリングの同時ONは設定検証で拒否します。 +- `input_sltp_manager_enabled=false` の場合、SLTP詳細設定に矛盾があってもEA全体は停止せず、時間決済を継続します。SLTP管理をONにする時点で詳細設定を検証します。 ## 6. 単体テスト @@ -58,5 +60,6 @@ ## 7. 変更履歴 +- 2026-06-14: SLTPマスターOFF時は詳細設定の矛盾でEA全体を停止しないよう変更。急変時SL引き締めに建値保護条件を追加し、損失側SL候補を採用しないようにした。 - 2026-06-13: SLTP関連inputの既定値を `HIT-EA_refactor_ver6.mq5` と同じ初期状態に同期し、仕様書へ各既定値を明記。 - 2026-06-07: `HIT-EA_refactor_ver6.mq5` からポジション管理責務を分離し、hedging口座専用のticket単位管理EAとして新規定義。