From 4de3d24dcd3876d939db15eff3a4d6d66937ab0b Mon Sep 17 00:00:00 2001 From: Hiroaki86 Date: Sat, 13 Jun 2026 11:27:10 +0900 Subject: [PATCH] Add range breakout guard for reverse limit orders --- Experts/MyProject/HIT-EA_refactor_ver6.mq5 | 19 ++ Include/MyLib/Signals/HITEntrySignal.mqh | 13 + Include/MyLib/Trading/HITTradeManager.mqh | 334 +++++++++++++++++++++ docs/MyProject/HIT-EA_spec_en.md | 25 +- docs/MyProject/HIT-EA_spec_ja.md | 25 +- 5 files changed, 414 insertions(+), 2 deletions(-) diff --git a/Experts/MyProject/HIT-EA_refactor_ver6.mq5 b/Experts/MyProject/HIT-EA_refactor_ver6.mq5 index 3528d32..f308c27 100644 --- a/Experts/MyProject/HIT-EA_refactor_ver6.mq5 +++ b/Experts/MyProject/HIT-EA_refactor_ver6.mq5 @@ -117,6 +117,14 @@ input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ input int input_entry_max_candidate_age_minutes = 120; // H1候補価格を新規発注に使う最大経過分数 +input bool input_range_breakout_guard_enabled = true; // レンジ上抜け/下抜け時に逆張りLimitを退避 +input ENUM_TIMEFRAMES input_range_breakout_guard_timeframe = PERIOD_M15; // レンジブレイク監視足 +input int input_range_breakout_lookback_bars = 20; // レンジ高値安値の参照本数 +input double input_range_breakout_max_width_multiplier = 6.0; // 平均レンジに対する最大レンジ幅 +input double input_range_breakout_buffer_multiplier = 0.20; // ブレイク確定バッファ倍率 +input double input_range_breakout_near_multiplier = 0.50; // ブレイク警戒の境界接近幅倍率 +input double input_range_breakout_body_multiplier = 1.50; // 勢い判定の平均実体倍率 +input int input_range_breakout_cooldown_bars = 4; // 逆張りLimit停止を継続する足数 input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 @@ -162,6 +170,10 @@ ulong slippage = 10; // スリッページ #define MARKET_HIGH_VOL_DOWN 5 #define MARKET_TECHNICAL_ERROR_STOP 6 +#define RANGE_BREAKOUT_NONE 0 +#define RANGE_BREAKOUT_UP 1 +#define RANGE_BREAKOUT_DOWN -1 + //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ @@ -209,6 +221,10 @@ bool g_bars_M15_check = false; ulong g_cancel_retry_tickets[]; datetime g_cancel_retry_at[]; +// レンジブレイク時に逆張りLimitを一時停止する状態。 +int g_range_breakout_guard_direction = RANGE_BREAKOUT_NONE; +datetime g_range_breakout_guard_until = 0; + // ティックごとの価格情報 struct TickContext { @@ -450,6 +466,9 @@ void OnTick() // Existing-position protection stays active even while Python results are pending. ManageSLTPPositions(); + // Delete and pause adverse limit orders before breakout spikes can fill them. + ManageRangeBreakoutLimitGuard(ctx); + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 RecoverTimedOutPythonProcesses(); diff --git a/Include/MyLib/Signals/HITEntrySignal.mqh b/Include/MyLib/Signals/HITEntrySignal.mqh index 8d11550..021de45 100644 --- a/Include/MyLib/Signals/HITEntrySignal.mqh +++ b/Include/MyLib/Signals/HITEntrySignal.mqh @@ -453,6 +453,13 @@ int TrySendSplitEntryOrders(const int orderType, EAState &state, TickContext &ct { string entry_type = EntryTypeName(orderType); double cur_price = CurrentPriceForOrderType(orderType, ctx); + string guard_reason = ""; + if(IsLimitOrderBlockedByRangeBreakoutGuard(orderType, guard_reason)) + { + Print("[Split Skip] ", entry_type, " blocked by range breakout guard. ", guard_reason); + return 0; + } + double zone_low = state.zone_low[orderType]; double zone_high = state.zone_high[orderType]; double tp = state.zone_tp[orderType]; @@ -551,6 +558,12 @@ bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) { string entry_type = EntryTypeName(orderType); double cur_price = CurrentPriceForOrderType(orderType, ctx); + string guard_reason = ""; + if(IsLimitOrderBlockedByRangeBreakoutGuard(orderType, guard_reason)) + { + Print("[Skip] ", entry_type, " blocked by range breakout guard. ", guard_reason); + return false; + } double en = state.en_price[orderType]; double tp = state.tp_price[orderType]; diff --git a/Include/MyLib/Trading/HITTradeManager.mqh b/Include/MyLib/Trading/HITTradeManager.mqh index 95cca47..c64c9bf 100644 --- a/Include/MyLib/Trading/HITTradeManager.mqh +++ b/Include/MyLib/Trading/HITTradeManager.mqh @@ -599,6 +599,340 @@ bool CancelPendingOrdersNotAllowedByTrend(const int trend_state) return result; } +//+------------------------------------------------------------------+ +//| レンジブレイクガード用の入力値を安全な範囲へ丸める関数群 +//+------------------------------------------------------------------+ +int EffectiveRangeBreakoutLookbackBars() + { + int bars = input_range_breakout_lookback_bars; + if(bars < 5) + bars = 5; + if(bars > 200) + bars = 200; + + return bars; + } + +double EffectiveRangeBreakoutMaxWidthMultiplier() + { + double multiplier = input_range_breakout_max_width_multiplier; + if(multiplier < 0.0) + multiplier = 0.0; + if(multiplier > 100.0) + multiplier = 100.0; + + return multiplier; + } + +double EffectiveRangeBreakoutBufferMultiplier() + { + double multiplier = input_range_breakout_buffer_multiplier; + if(multiplier <= 0.0) + multiplier = 0.20; + if(multiplier > 10.0) + multiplier = 10.0; + + return multiplier; + } + +double EffectiveRangeBreakoutNearMultiplier() + { + double multiplier = input_range_breakout_near_multiplier; + if(multiplier <= 0.0) + multiplier = 0.50; + if(multiplier > 20.0) + multiplier = 20.0; + + return multiplier; + } + +double EffectiveRangeBreakoutBodyMultiplier() + { + double multiplier = input_range_breakout_body_multiplier; + if(multiplier <= 0.0) + multiplier = 1.50; + if(multiplier > 20.0) + multiplier = 20.0; + + return multiplier; + } + +int EffectiveRangeBreakoutCooldownBars() + { + int bars = input_range_breakout_cooldown_bars; + if(bars < 1) + bars = 1; + if(bars > 96) + bars = 96; + + return bars; + } + +//+------------------------------------------------------------------+ +//| レンジブレイク方向の表示名 +//+------------------------------------------------------------------+ +string RangeBreakoutDirectionName(const int direction) + { + if(direction == RANGE_BREAKOUT_UP) + return "UP"; + if(direction == RANGE_BREAKOUT_DOWN) + return "DOWN"; + + return "NONE"; + } + +//+------------------------------------------------------------------+ +//| 価格を現在シンボルの桁数で表示する関数 +//+------------------------------------------------------------------+ +string RangeBreakoutPriceText(const double price) + { + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + return DoubleToString(price, digits); + } + +//+------------------------------------------------------------------+ +//| 期限切れのレンジブレイクガード状態を解除する関数 +//+------------------------------------------------------------------+ +void ExpireRangeBreakoutLimitGuard() + { + if(g_range_breakout_guard_direction == RANGE_BREAKOUT_NONE) + return; + if(g_range_breakout_guard_until <= 0) + return; + if(TimeCurrent() < g_range_breakout_guard_until) + return; + + Print("[Range Breakout Guard] cooldown expired. direction=", + RangeBreakoutDirectionName(g_range_breakout_guard_direction)); + g_range_breakout_guard_direction = RANGE_BREAKOUT_NONE; + g_range_breakout_guard_until = 0; + } + +//+------------------------------------------------------------------+ +//| レンジブレイク状態を有効化する関数 +//+------------------------------------------------------------------+ +void ActivateRangeBreakoutLimitGuard(const int direction, const string reason) + { + if(direction == RANGE_BREAKOUT_NONE) + return; + + int timeframe_seconds = PeriodSeconds(input_range_breakout_guard_timeframe); + if(timeframe_seconds <= 0) + timeframe_seconds = PeriodSeconds(_Period); + if(timeframe_seconds <= 0) + timeframe_seconds = 60; + + datetime new_until = TimeCurrent() + timeframe_seconds * EffectiveRangeBreakoutCooldownBars(); + if(g_range_breakout_guard_direction == direction && + g_range_breakout_guard_until > TimeCurrent()) + return; + + g_range_breakout_guard_direction = direction; + g_range_breakout_guard_until = new_until; + + Print("[Range Breakout Guard] activated. direction=", RangeBreakoutDirectionName(direction), + " until=", TimeToString(g_range_breakout_guard_until, TIME_DATE | TIME_SECONDS), + " reason=", reason); + } + +//+------------------------------------------------------------------+ +//| レンジブレイクを分析し、逆張りLimitを止める方向を返す関数 +//+------------------------------------------------------------------+ +bool AnalyzeRangeBreakoutLimitGuard(TickContext &ctx, int &direction, string &reason) + { + direction = RANGE_BREAKOUT_NONE; + reason = ""; + + if(!input_range_breakout_guard_enabled) + return false; + + int lookback = EffectiveRangeBreakoutLookbackBars(); + MqlRates rates[]; + int copied = CopyRates(_Symbol, input_range_breakout_guard_timeframe, 1, lookback + 1, rates); + if(copied < lookback + 1) + return false; + + int last_index = copied - 1; + int history_count = copied - 1; + if(history_count < lookback || last_index <= 0) + return false; + + bool initialized = false; + double range_high = 0.0; + double range_low = 0.0; + double total_range = 0.0; + double total_body = 0.0; + + // Build the reference range from closed bars before the latest closed bar. + for(int i = 0; i < history_count; i++) + { + if(!initialized) + { + range_high = rates[i].high; + range_low = rates[i].low; + initialized = true; + } + else + { + if(rates[i].high > range_high) + range_high = rates[i].high; + if(rates[i].low < range_low) + range_low = rates[i].low; + } + + total_range += rates[i].high - rates[i].low; + total_body += MathAbs(rates[i].close - rates[i].open); + } + + if(!initialized) + return false; + + double avg_range = total_range / history_count; + double avg_body = total_body / history_count; + if(avg_range <= 0.0) + avg_range = Point(); + if(avg_body <= 0.0) + avg_body = Point(); + + double range_width = range_high - range_low; + if(range_width <= 0.0) + return false; + + double max_width_multiplier = EffectiveRangeBreakoutMaxWidthMultiplier(); + if(max_width_multiplier > 0.0 && range_width > avg_range * max_width_multiplier) + return false; + + MqlRates last_bar = rates[last_index]; + double buffer = MathMax(avg_range * EffectiveRangeBreakoutBufferMultiplier(), ctx.spread); + double near_buffer = MathMax(avg_range * EffectiveRangeBreakoutNearMultiplier(), ctx.spread); + double body_threshold = avg_body * EffectiveRangeBreakoutBodyMultiplier(); + double last_body = MathAbs(last_bar.close - last_bar.open); + + bool last_bullish = (last_bar.close > last_bar.open); + bool last_bearish = (last_bar.close < last_bar.open); + bool last_large_body = (last_body >= body_threshold); + + bool confirmed_up = (last_bar.close > range_high + buffer); + bool confirmed_down = (last_bar.close < range_low - buffer); + bool warning_up = (ctx.bid >= range_high - near_buffer && + (confirmed_up || ctx.bid > range_high + buffer || + (last_bullish && (last_large_body || last_bar.close > range_high)))); + bool warning_down = (ctx.ask <= range_low + near_buffer && + (confirmed_down || ctx.ask < range_low - buffer || + (last_bearish && (last_large_body || last_bar.close < range_low)))); + + if(confirmed_up || warning_up) + { + direction = RANGE_BREAKOUT_UP; + reason = "upward " + string(confirmed_up ? "confirmed" : "warning") + + " tf=" + EnumToString(input_range_breakout_guard_timeframe) + + " range=[" + RangeBreakoutPriceText(range_low) + "," + + RangeBreakoutPriceText(range_high) + "] bid=" + + RangeBreakoutPriceText(ctx.bid) + " last_close=" + + RangeBreakoutPriceText(last_bar.close); + return true; + } + + if(confirmed_down || warning_down) + { + direction = RANGE_BREAKOUT_DOWN; + reason = "downward " + string(confirmed_down ? "confirmed" : "warning") + + " tf=" + EnumToString(input_range_breakout_guard_timeframe) + + " range=[" + RangeBreakoutPriceText(range_low) + "," + + RangeBreakoutPriceText(range_high) + "] ask=" + + RangeBreakoutPriceText(ctx.ask) + " last_close=" + + RangeBreakoutPriceText(last_bar.close); + return true; + } + + return false; + } + +//+------------------------------------------------------------------+ +//| レンジブレイク方向と逆向きのLimit pendingを取消する関数 +//+------------------------------------------------------------------+ +bool CancelRangeBreakoutOppositeLimitOrders(const int direction, const string reason) + { + if(direction == RANGE_BREAKOUT_NONE) + return false; + + bool result = false; + CleanupPendingOrderCancelAttempts(); + + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(ticket == 0 || !OrderSelect(ticket)) + continue; + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + ENUM_ORDER_TYPE order_type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); + bool should_cancel = ((direction == RANGE_BREAKOUT_UP && order_type == ORDER_TYPE_SELL_LIMIT) || + (direction == RANGE_BREAKOUT_DOWN && order_type == ORDER_TYPE_BUY_LIMIT)); + if(!should_cancel) + continue; + + if(TryCancelPendingOrder(ticket, "range breakout guard: " + reason)) + result = true; + } + + return result; + } + +//+------------------------------------------------------------------+ +//| レンジブレイクガードを監視し、逆方向Limitを退避する関数 +//+------------------------------------------------------------------+ +void ManageRangeBreakoutLimitGuard(TickContext &ctx) + { + if(!input_range_breakout_guard_enabled) + return; + + ExpireRangeBreakoutLimitGuard(); + + int direction = RANGE_BREAKOUT_NONE; + string reason = ""; + if(AnalyzeRangeBreakoutLimitGuard(ctx, direction, reason)) + ActivateRangeBreakoutLimitGuard(direction, reason); + + if(g_range_breakout_guard_direction == RANGE_BREAKOUT_NONE || + g_range_breakout_guard_until <= TimeCurrent()) + return; + + string active_reason = "active " + + RangeBreakoutDirectionName(g_range_breakout_guard_direction) + + " cooldown until " + + TimeToString(g_range_breakout_guard_until, TIME_DATE | TIME_SECONDS); + CancelRangeBreakoutOppositeLimitOrders(g_range_breakout_guard_direction, active_reason); + } + +//+------------------------------------------------------------------+ +//| 新規Limit注文がレンジブレイクガードで停止中か判定する関数 +//+------------------------------------------------------------------+ +bool IsLimitOrderBlockedByRangeBreakoutGuard(const int orderType, string &reason) + { + reason = ""; + if(!input_range_breakout_guard_enabled) + return false; + + ExpireRangeBreakoutLimitGuard(); + + if(g_range_breakout_guard_direction == RANGE_BREAKOUT_NONE || + g_range_breakout_guard_until <= TimeCurrent()) + return false; + + bool blocked = ((g_range_breakout_guard_direction == RANGE_BREAKOUT_UP && orderType == 4) || + (g_range_breakout_guard_direction == RANGE_BREAKOUT_DOWN && orderType == 2)); + if(!blocked) + return false; + + reason = "direction=" + RangeBreakoutDirectionName(g_range_breakout_guard_direction) + + " until=" + TimeToString(g_range_breakout_guard_until, TIME_DATE | TIME_SECONDS); + return true; + } + //+------------------------------------------------------------------+ //| pendingコメントからH1候補時刻を復元する関数 //+------------------------------------------------------------------+ diff --git a/docs/MyProject/HIT-EA_spec_en.md b/docs/MyProject/HIT-EA_spec_en.md index 99387cf..4d625f1 100644 --- a/docs/MyProject/HIT-EA_spec_en.md +++ b/docs/MyProject/HIT-EA_spec_en.md @@ -37,6 +37,14 @@ - `m15_imbalance_min_avg_body_points = 1.0`: Minimum M15 average body in points. - `use_m15_imbalance_debug_log = false`: Enables detailed M15 impulse-confirmation logs. - `input_entry_max_candidate_age_minutes = 120`: Maximum age, in minutes, for using an H1 candidate for new execution. This blocks delayed entries when M15 confirmation appears too late. +- `input_range_breakout_guard_enabled = true`: Cancels adverse reversal Limit orders during range breakouts and temporarily blocks new ones. +- `input_range_breakout_guard_timeframe = PERIOD_M15`: Timeframe used by the range-breakout monitor. +- `input_range_breakout_lookback_bars = 20`: Closed-bar count used for range high/low and average range. The range starts from the bar before the latest closed bar. +- `input_range_breakout_max_width_multiplier = 6.0`: Maximum allowed range width versus average range. `0` disables the width filter. +- `input_range_breakout_buffer_multiplier = 0.20`: Average-range multiplier used as the confirmed-breakout buffer. +- `input_range_breakout_near_multiplier = 0.50`: Average-range multiplier used as the pre-breakout proximity band. +- `input_range_breakout_body_multiplier = 1.50`: Required latest closed-bar body multiple for momentum warning. +- `input_range_breakout_cooldown_bars = 4`: Number of monitor-timeframe bars to block adverse Limit entries after detection. - `input_sltp_manager_enabled = false`: Enables UI-driven existing-position SL management through `CSLTPManager`. - `input_sltp_show_panel = true`: Shows the SLTP control panel on the chart. - `input_sltp_use_breakeven = false`: Enables normal break-even. @@ -82,7 +90,7 @@ - `Include/MyLib/Signals/HITEntrySignal.mqh`: Entry preconditions, order-type price validation, M15 filter, and retry-state updates. - `Include/MyLib/Common/HITExternalProcess.mqh`: done/running files, external process handles, PID recovery, exit-code checks, and timeout recovery. - `Include/MyLib/Signals/HITPythonSignalGateway.mqh`: OHLC CSV export, Python batch launch, and `trend_state.txt` / `target_prices.txt` / `target_zones.txt` loading. -- `Include/MyLib/Trading/HITTradeManager.mqh`: Order sending, filling policy, pending-order expiration, order/position counting, expired order cancellation, and expired position closing. +- `Include/MyLib/Trading/HITTradeManager.mqh`: Order sending, filling policy, pending-order expiration, order/position counting, expired order cancellation, expired position closing, and adverse Limit cancellation during range breakouts. - `Include/MyLib/Trading/SLTPManager.mqh`: Existing-position break-even, elapsed-time break-even, active trailing, TP-progress SL, and high-volatility SL management. - `Include/MyLib/Panel/SLTPManagerPanel.mqh`: Chart panel that toggles `CSLTPManager` features, validates numeric inputs, and applies settings to the EA. @@ -150,6 +158,7 @@ New entries are allowed only when all conditions below are satisfied: - The order type is allowed by the H4 `market_state`. - The order-type price relationship and broker minimum distance rules are satisfied. - When `use_m15_entry_filter = true`, T2/T4 satisfy the M15 direction, rejection, and proximity checks. T1/T3 satisfy M15 impulse confirmation when `use_m15_imbalance_confirmation = true`. +- When `input_range_breakout_guard_enabled = true`, T4 Sell Limit is blocked during an upward range-breakout cooldown and T2 Buy Limit is blocked during a downward range-breakout cooldown. - This EA's pending orders plus positions are below `input_position_limit`. - When split entries are enabled, `target_zones.txt` has `res_chk = 1`, the predicted zone prices are valid, split lots fit `SYMBOL_VOLUME_MIN/MAX/STEP`, and no duplicate order or position exists for the same `candidate_id` + order type + slot. @@ -165,6 +174,14 @@ New entries are allowed only when all conditions below are satisfied: After an H4 `market_state` reload, the EA cancels existing pending orders whose order types are not allowed by the refreshed state. For `6 MARKET_TECHNICAL_ERROR_STOP`, it cancels all pending orders managed by this EA. +### Range Breakout Guard + +- The guard uses closed bars from `input_range_breakout_guard_timeframe`; the reference range is built from `input_range_breakout_lookback_bars` bars before the latest closed bar. +- It treats the market as a range only when the range width is within `input_range_breakout_max_width_multiplier` times the average range. A value of `0` disables this width check. +- If the latest closed bar closes above `rangeHigh + buffer`, or current Bid approaches the range high with upward momentum on the latest closed bar, the guard treats it as an upward warning/confirmation. It cancels existing T4 Sell Limit orders and blocks new T4 entries for `input_range_breakout_cooldown_bars` monitor bars. +- If the latest closed bar closes below `rangeLow - buffer`, or current Ask approaches the range low with downward momentum on the latest closed bar, the guard treats it as a downward warning/confirmation. It cancels existing T2 Buy Limit orders and blocks new T2 entries for `input_range_breakout_cooldown_bars` monitor bars. +- `buffer` and the proximity band use the larger of the average-range multiple and current spread to reduce over-triggering during live spread expansion. + ### Order-Type Price Rules - T1 Buy Stop: `Ask < entry`, `tp > entry`, `sl < entry` @@ -202,6 +219,7 @@ After an H4 `market_state` reload, the EA cancels existing pending orders whose - Split-entry order comments also include order type and slot number to prevent duplicate execution of the same strategy slot. - When `cancel_old_split_pending_on_new_zone = true`, a new valid `candidate_id` cancels older split pending orders, and an invalid or stopped `target_zones.txt` cancels all existing split pending orders for this EA/symbol/magic. - After an H4 state update, existing pending orders that contradict the refreshed `market_state` are cancelled. +- The range-breakout guard only targets pending orders matching `_Symbol` and `magic_number`; upward breakouts cancel T4 Sell Limit only, and downward breakouts cancel T2 Buy Limit only. - A failed pending-order cancellation is not retried for the same ticket until `input_cancel_retry_cooldown_seconds` has elapsed, preventing excessive per-tick trade-server requests. - SLTP management only targets existing positions matching `_Symbol` and `magic_number`, and it preserves each position's current TP. - SL changes are sent only when the candidate improves protection relative to the current SL and satisfies broker stop-level and freeze-level constraints. @@ -219,6 +237,11 @@ After an H4 `market_state` reload, the EA cancels existing pending orders whose ## 10. Changelog +### 2026-06-13 + +- Added a range-breakout guard that retreats adverse reversal Limit orders. Upward breakouts cancel existing T4 Sell Limit orders and temporarily block new T4 entries; downward breakouts cancel existing T2 Buy Limit orders and temporarily block new T2 entries. +- Added guard inputs for monitor timeframe, lookback bars, range-width multiplier, breakout buffer, proximity band, momentum body multiple, and cooldown bars. + ### 2026-06-06 - Updated the Python-side OpenAI default model to the official `gpt-5.5` slug and made Responses API calls send explicit `reasoning.effort` and `text.verbosity`. For GPT-5.5, the output contract is now documented as primarily enforced by Structured Outputs rather than duplicated prompt schema text. diff --git a/docs/MyProject/HIT-EA_spec_ja.md b/docs/MyProject/HIT-EA_spec_ja.md index 2c98b92..4c2d62c 100644 --- a/docs/MyProject/HIT-EA_spec_ja.md +++ b/docs/MyProject/HIT-EA_spec_ja.md @@ -29,6 +29,14 @@ - `m15_imbalance_min_avg_body_points = 1.0`: M15平均実体の最小値(point)。 - `use_m15_imbalance_debug_log = false`: M15初動確認の詳細ログを出すか。 - `input_entry_max_candidate_age_minutes = 120`: H1候補価格を新規発注に使用できる最大経過分数。古い候補でM15条件が後から整った場合の遅延エントリーを抑止する。 +- `input_range_breakout_guard_enabled = true`: レンジ上抜け/下抜け時に逆方向の逆張りLimit注文を退避し、新規発注も一時停止するか。 +- `input_range_breakout_guard_timeframe = PERIOD_M15`: レンジブレイクを監視する時間足。 +- `input_range_breakout_lookback_bars = 20`: レンジ高値・安値と平均レンジを計算する確定足本数。直近確定足の1本前から参照する。 +- `input_range_breakout_max_width_multiplier = 6.0`: 平均レンジに対して許容する最大レンジ幅。`0` の場合はレンジ幅フィルタを無効化する。 +- `input_range_breakout_buffer_multiplier = 0.20`: 確定ブレイク判定に使う平均レンジ倍率。 +- `input_range_breakout_near_multiplier = 0.50`: ブレイク警戒判定に使うレンジ境界への接近幅倍率。 +- `input_range_breakout_body_multiplier = 1.50`: ブレイク警戒に必要な直近確定足実体の平均実体倍率。 +- `input_range_breakout_cooldown_bars = 4`: ブレイク検知後に逆方向Limitの新規発注を停止する足数。 - `input_position_limit = 10`: 自EAの未約定注文 + ポジション数の実運用上限。内部上限 `POSITION_LIMIT` を超える指定は丸める。 - `use_split_entry_zone = false`: H1予測ゾーンを使った分割エントリーを有効化するか。 - `split_entry_count = 3`: 分割エントリー本数。1〜10に丸めて使用する。 @@ -82,7 +90,7 @@ - `Include/MyLib/Signals/HITEntrySignal.mqh`: エントリー前提条件、注文タイプ別価格判定、M15フィルタ、リトライ状態更新。 - `Include/MyLib/Common/HITExternalProcess.mqh`: done/runningファイル、外部プロセスハンドル、PID復元、終了コード確認、タイムアウト復旧。 - `Include/MyLib/Signals/HITPythonSignalGateway.mqh`: OHLC CSV出力、Pythonバッチ起動、`trend_state.txt` / `target_prices.txt` / `target_zones.txt` 読込。 -- `Include/MyLib/Trading/HITTradeManager.mqh`: 注文送信、執行ポリシー、有効期限設定、注文/ポジション数集計、期限切れ取消/クローズ。 +- `Include/MyLib/Trading/HITTradeManager.mqh`: 注文送信、執行ポリシー、有効期限設定、注文/ポジション数集計、期限切れ取消/クローズ、レンジブレイク時の逆方向Limit取消。 - `Include/MyLib/Trading/SLTPManager.mqh`: 既存ポジションに対するブレークイーブン、時間経過BE、アクティブトレーリング、TP進捗SL、急変時SL引き締めを管理する。 - `Include/MyLib/Panel/SLTPManagerPanel.mqh`: `CSLTPManager` の各設定をチャート上で切り替え、数値入力を検証してEAへ反映する操作パネル。 @@ -150,6 +158,7 @@ Pythonは `target_prices.txt` と `target_zones.txt` の両方を書き終えて - H4 `market_state` に対して注文タイプが許可されている。 - 注文タイプごとの価格整合条件とブローカー最小距離制約を満たす。 - `use_m15_entry_filter = true` の場合、T2/T4はM15確定足の方向・反発・接近条件を満たす。T1/T3は `use_m15_imbalance_confirmation = true` の場合にM15初動確認を満たす。 +- `input_range_breakout_guard_enabled = true` の場合、レンジ上抜け後のT4 Sell Limit、レンジ下抜け後のT2 Buy Limitはクールダウン終了まで新規発注しない。 - 自EAの注文 + ポジション数が `input_position_limit` 未満。 - 分割エントリー有効時は `target_zones.txt` の `res_chk = 1`、予測ゾーンの価格整合、分割ロットの `SYMBOL_VOLUME_MIN/MAX/STEP` 適合、同一 `candidate_id` + 注文タイプ + slot の重複注文/ポジション不存在を満たす。 @@ -165,6 +174,14 @@ Pythonは `target_prices.txt` と `target_zones.txt` の両方を書き終えて H4 `market_state` の再読込後は、更新後の状態で許可されない注文タイプの既存pending注文を取消する。`6 MARKET_TECHNICAL_ERROR_STOP` の場合は対象EAのpending注文をすべて取消する。 +### レンジブレイクガード + +- `input_range_breakout_guard_timeframe` の確定足を使い、直近確定足の1本前から `input_range_breakout_lookback_bars` 本の高値・安値をレンジ境界として計算する。 +- レンジ幅が平均レンジの `input_range_breakout_max_width_multiplier` 倍以内の場合のみ、レンジ相場としてブレイク監視を有効にする。倍率が `0` の場合はこの幅判定を行わない。 +- 直近確定足終値が `rangeHigh + buffer` を上回る場合、または現在Bidがレンジ上限に接近し直近確定足に上方向の勢いがある場合は上方向ブレイク警戒/確定とする。この時、既存のT4 Sell Limitを取消し、T4新規発注を `input_range_breakout_cooldown_bars` 本分停止する。 +- 直近確定足終値が `rangeLow - buffer` を下回る場合、または現在Askがレンジ下限に接近し直近確定足に下方向の勢いがある場合は下方向ブレイク警戒/確定とする。この時、既存のT2 Buy Limitを取消し、T2新規発注を `input_range_breakout_cooldown_bars` 本分停止する。 +- `buffer` と接近幅はM15平均レンジ倍率と現在スプレッドの大きい方を使い、ライブ時のスプレッド拡大で過剰検知しにくくする。 + ### 注文タイプごとの価格整合条件 - T1 Buy Stop: `Ask < entry`, `tp > entry`, `sl < entry` @@ -202,6 +219,7 @@ H4 `market_state` の再読込後は、更新後の状態で許可されない - 分割エントリーの注文コメントには注文タイプとslot番号も入れ、同一戦略slotの二重発注を抑止する。 - `cancel_old_split_pending_on_new_zone = true` の場合、新しい有効 `candidate_id` を読んだ時は旧candidateの分割pending注文を取消し、`target_zones.txt` が無効または停止値の場合は既存の分割pending注文をすべて取消する。 - H4状態更新後は、更新後の `market_state` と矛盾する既存pending注文を取消する。 +- レンジブレイクガードは `_Symbol` と `magic_number` が一致するpending注文だけを対象にし、上方向ブレイクではT4 Sell Limit、下方向ブレイクではT2 Buy Limitのみを取消する。 - pending取消に失敗したticketは `input_cancel_retry_cooldown_seconds` の間再送せず、毎tickの過剰な取引サーバー要求を防止する。 - SLTP管理は `_Symbol` と `magic_number` が一致する既存ポジションのみを対象とし、TPは既存値を維持する。 - SL変更は既存SLより利益保護方向へ改善する場合だけ実行し、ブローカーのstop level / freeze levelを満たさない候補は送信しない。 @@ -219,6 +237,11 @@ H4 `market_state` の再読込後は、更新後の状態で許可されない ## 10. 変更履歴 +### 2026-06-13 + +- レンジ上抜け/下抜け時に逆方向の逆張りLimitを退避するレンジブレイクガードを追加した。上方向ブレイクでは既存T4 Sell Limitを取消してT4新規発注を一時停止し、下方向ブレイクでは既存T2 Buy Limitを取消してT2新規発注を一時停止する。 +- ガード用inputとして、監視時間足、参照本数、レンジ幅倍率、ブレイクバッファ、接近幅、勢い判定倍率、クールダウン本数を追加した。 + ### 2026-06-06 - Python側のOpenAI既定モデルを公式slugの `gpt-5.5` へ更新し、Responses API呼び出しで `reasoning.effort` と `text.verbosity` を明示する仕様にした。GPT-5.5向けに、出力形式はプロンプト重複指定ではなくStructured Outputsを主契約として扱う。