From 4e510ca48907b5cf6f871db75be9437383f1dc6e Mon Sep 17 00:00:00 2001 From: zhangyangbin Date: Fri, 22 May 2026 16:42:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AA=81=E7=A0=B4=E9=A9=AC=E4=B8=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 20260520-突破马丁/20260520_Breakout.mq5 | 572 +++++++++++++++++++++--- 20260520-突破马丁/README.md | 62 +-- 20260520-突破马丁/XAU_1min_300000.set | Bin 2242 -> 2312 bytes 3 files changed, 510 insertions(+), 124 deletions(-) diff --git a/20260520-突破马丁/20260520_Breakout.mq5 b/20260520-突破马丁/20260520_Breakout.mq5 index 341cb37..98d8102 100644 --- a/20260520-突破马丁/20260520_Breakout.mq5 +++ b/20260520-突破马丁/20260520_Breakout.mq5 @@ -3,7 +3,7 @@ //| 突破策略 - 单笔突破开仓 | //+------------------------------------------------------------------+ #property copyright "Breakout Strategy" -#property version "3.07" +#property version "3.14" #property strict #include @@ -28,20 +28,21 @@ input group "=== 波段识别参数 ===" input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // K线周期 input int InpMAPeriod = 14; // MA周期 input double InpMinWavePercent = 0.1; // 最小波段阈值百分比(%) -input double InpMaxWavePercent = 1.0; // 最大波段阈值百分比(%) +input double InpMaxWavePercent = 0.25; // 最大波段阈值百分比(%) input double InpPullbackTolerance = 0.05; // 反向突破容忍度(%) 0=不容忍 input group "=== 突破交易参数 ===" -input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_FOLLOW; // 开仓方向 -input double InpTakeProfitBalancePct = 1.0; // 止盈:持仓浮盈达结余比例(%)全部清仓,0=关 +input ENUM_BREAKOUT_DIRECTION InpBreakoutDirection = BREAKOUT_DIR_RANDOM; // 开仓方向 +input int InpBatchTakeProfitPoints = 300; // 按批统盈点数(0=关):均价±点数平该批 input double InpStopLossBalancePct = 30.0; // 止损:持仓浮亏达结余比例(%)全部清仓,0=关 +input bool InpEnablePullbackReverse = true; // 启用突破回落反向 +input int InpPullbackReverseWatchBars = 3; // 回落监测K线数(突破K+后2根=3) input group "=== 仓位管理参数 ===" -input double InpFixedLots = 0.5; // 基准手数(同向首笔/每组首笔) -input double InpMartingaleAddLotBoost = 2.0; // 同向加码:每组+手数(0=始终基准) -input int InpMartingaleTierInterval = 5; // 同向加码:档间隔N(每N笔为一组) -input double InpMaxLots = 30.0; // 同向最大合计手数 -input double InpMinSameDirAddSpacingPercent = 0.1; // 同向最小开仓间距(%) 0=不限制 +input string InpTierLotsList = + "0.05,0.05,0.05,0.8,0.8,1.1,1,2,2,1,1,1,2,2,1,1,1,5,5"; // 各档手数(逗号分隔,档数=最多开仓笔数) +input double InpMaxLots = 99.0; // 同向最大合计手数(0=不限制) +input double InpMinSameDirAddSpacingPercent = 0.0; // 同向最小开仓间距(%) 0=不限制 input ENUM_DUAL_FULL_ACTION InpDualFullAction = DUAL_FULL_CLEAR_ALL; // 双向满档后处理 input group "=== 调试选项 ===" @@ -64,6 +65,14 @@ struct ValidWaveInfo { datetime update_time; // 更新时间 bool high_breakout_used; // 高点-突破开仓已用(每极值限1次) bool low_breakout_used; // 低点-突破开仓已用(每极值限1次) + bool high_pullback_reverse_used; // 高点-回落反向已用(每极值限1次) + bool low_pullback_reverse_used; // 低点-回落反向已用(每极值限1次) +}; + +struct PullbackWatchState { + bool active; + double extreme_price; + int bars_checked; }; ValidWaveInfo latest_wave; // 最新有效波段 @@ -76,6 +85,16 @@ bool g_breakout_opened_on_bar_low = false; int g_trade_round = 1; // 当前交易轮次(新轮仅统计该轮持仓) bool g_dual_full_handled = false; // 本轮双向满档是否已处理 +double g_tier_lots[]; +int g_tier_lots_count = 0; + +PullbackWatchState g_high_pullback_watch; +PullbackWatchState g_low_pullback_watch; +datetime g_pullback_bar_time_high = 0; +bool g_pullback_opened_on_bar_high = false; +datetime g_pullback_bar_time_low = 0; +bool g_pullback_opened_on_bar_low = false; + // 极值点结构体定义 struct ExtremePoint { datetime time; @@ -102,17 +121,34 @@ bool IsDirectionFullActive(const long pos_type); void ResetBreakoutWaveEntryFlags(); void CheckDualSideFullCapacity(); double NormalizeVolume(double lots); -double LotForSameDirectionTier(const int tier_index, const double base_lot); +bool InitTierLotsFromInput(); +int GetMaxTierSlots(); +double LotForTierIndex(const int tier_index); double CalculateLotSize(const long pos_type); +bool IsSameDirectionTierSlotsFull(const long pos_type); long PosTypeOnHighBreakout(); long PosTypeOnLowBreakout(); void CheckBreakoutSignals(); +void CheckPullbackReverseSignals(); +void TryArmPullbackWatch(); +void ProcessPullbackReverseOnNewBar(); +ENUM_ORDER_TYPE OrderTypeOnHighPullbackReverse(); +ENUM_ORDER_TYPE OrderTypeOnLowPullbackReverse(); +string FormatPullbackReverseComment(const int round); +bool IsPullbackBarOpenAllowed(const bool for_high_extreme); +void MarkPullbackBarOpened(const bool for_high_extreme); +bool OpenPullbackReversePosition(ENUM_ORDER_TYPE order_type, const bool from_high_extreme, + const double extreme_price); ENUM_ORDER_TYPE OrderTypeOnHighBreakout(); ENUM_ORDER_TYPE OrderTypeOnLowBreakout(); void CheckAndCloseManualOrders(); double TotalBreakoutFloatingPL(); bool CloseAllBreakoutPositions(const string reason); -void CheckBreakoutBalanceExit(); +void CollectAllTradeRounds(int &rounds[], int &n_rounds); +bool BatchRoundAvgPrice(const int round_id, const long pos_type, double &avg, double &sum_lots); +bool CloseBreakoutRoundBatch(const int round_id, const long pos_type, const string reason); +void CheckBreakoutBatchTakeProfit(); +void CheckBreakoutBalanceStopLoss(); bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low, const bool from_high_extreme); void SyncBreakoutBarLock(const bool for_high_extreme); @@ -145,12 +181,21 @@ int OnInit() latest_wave.update_time = 0; latest_wave.high_breakout_used = false; latest_wave.low_breakout_used = false; + latest_wave.high_pullback_reverse_used = false; + latest_wave.low_pullback_reverse_used = false; + g_high_pullback_watch.active = false; + g_low_pullback_watch.active = false; MathSrand((int)(TimeLocal() ^ GetTickCount())); g_trade_round = 1; g_dual_full_handled = false; + if(!InitTierLotsFromInput()) { + Print("解析各档手数失败,请检查 InpTierLotsList"); + return(INIT_FAILED); + } + Print("========================================"); Print("突破EA初始化成功 (极值点突破开仓)"); Print("品种:", _Symbol); @@ -168,10 +213,19 @@ int OnInit() else Print("开仓方向: 随机(每次突破独立随机)"); Print("开仓限制: 每个极值点(破高/破低)各最多1笔,可多笔并存"); - Print("止盈(结余%):", InpTakeProfitBalancePct, " (0=关闭,达标全部清仓)"); + Print("按批统盈点数:", InpBatchTakeProfitPoints, " (0=关闭,均价±点数平该轮该向)"); Print("止损(结余%):", InpStopLossBalancePct, " (0=关闭,达标全部清仓)"); - Print("同向手数: 基准=", InpFixedLots, " 档间隔=", InpMartingaleTierInterval, - " 每组+", InpMartingaleAddLotBoost, " 同向上限=", InpMaxLots); + if(InpEnablePullbackReverse) + Print("回落反向: 开 破极值后监测", InpPullbackReverseWatchBars, + "根K收盘回到极值内开反向单(与突破开仓独立)"); + else + Print("回落反向: 关"); + Print("同向各档手数: 共", g_tier_lots_count, "档(第1档=", g_tier_lots[0], + " 末档=", g_tier_lots[g_tier_lots_count - 1], ")"); + if(InpMaxLots > 0.0) + Print("同向合计手数上限:", InpMaxLots); + else + Print("同向合计手数上限: 不限制"); if(InpMinSameDirAddSpacingPercent > 0.0) Print("同向最小间距%:", InpMinSameDirAddSpacingPercent, " (多:现价须≤上一笔×(1-%); 空:现价须≥上一笔×(1+%))"); @@ -213,13 +267,17 @@ void OnTick() // 2. 更新最新有效波段 UpdateLatestValidWave(); - // 3. 结余比例止盈/止损(全部持仓,含历史轮) - CheckBreakoutBalanceExit(); + // 3. 按批统盈(均价+点数) / 结余比例止损(全部持仓) + CheckBreakoutBatchTakeProfit(); + CheckBreakoutBalanceStopLoss(); // 4. 当前轮双向同向均满档 → 清仓或开新轮 CheckDualSideFullCapacity(); - // 5. 突破信号(破极值开仓,每极值点各限1笔) + // 5. 突破回落反向(破极值后K线收盘回到极值内) + CheckPullbackReverseSignals(); + + // 6. 突破信号(破极值开仓,每极值点各限1笔,与回落反向独立) CheckBreakoutSignals(); } @@ -479,10 +537,16 @@ void UpdateLatestValidWave() latest_wave.update_time = extremes[i].time; // 极值价格更新 → 该侧可再开1笔(与是否已有其它持仓无关) - if(high != prev_high) + if(high != prev_high) { latest_wave.high_breakout_used = false; - if(low != prev_low) + latest_wave.high_pullback_reverse_used = false; + g_high_pullback_watch.active = false; + } + if(low != prev_low) { latest_wave.low_breakout_used = false; + latest_wave.low_pullback_reverse_used = false; + g_low_pullback_watch.active = false; + } // 绘制最新有效波段 if(InpShowMarkers) { @@ -625,22 +689,22 @@ ENUM_ORDER_TYPE RandomBreakoutOrderType() int ParseRoundFromComment(const string &comment) { - const string prefix = "[突破-R"; - const int i = StringFind(comment, prefix); - if(i == 0) { - const int start = i + StringLen(prefix); - const int end_br = StringFind(comment, "]", start); - if(end_br > start) - return (int)StringToInteger(StringSubstr(comment, start, end_br - start)); - } - if(StringFind(comment, "[突破]") == 0) + if(StringFind(comment, "[突破") != 0) + return 0; + const int i_r = StringFind(comment, "-R"); + if(i_r < 0) return 1; - return 0; + const int start = i_r + 2; + const int end_br = StringFind(comment, "]", start); + if(end_br <= start) + return 1; + const int r = (int)StringToInteger(StringSubstr(comment, start, end_br - start)); + return (r >= 1) ? r : 1; } bool IsEaBreakoutComment(const string &comment) { - return (ParseRoundFromComment(comment) > 0); + return (StringFind(comment, "[突破") == 0); } string FormatBreakoutComment(const int round) @@ -650,10 +714,202 @@ string FormatBreakoutComment(const int round) return StringFormat("[突破-R%d]", round); } +string FormatPullbackReverseComment(const int round) +{ + if(round <= 1) + return "[突破-回落]"; + return StringFormat("[突破-回落-R%d]", round); +} + +ENUM_ORDER_TYPE OrderTypeOnHighPullbackReverse() +{ + return ORDER_TYPE_SELL; +} + +ENUM_ORDER_TYPE OrderTypeOnLowPullbackReverse() +{ + return ORDER_TYPE_BUY; +} + +void SyncPullbackBarLock(const bool for_high_extreme) +{ + const datetime bar_time = iTime(_Symbol, InpTimeframe, 0); + if(bar_time == 0) + return; + if(for_high_extreme) { + if(bar_time != g_pullback_bar_time_high) { + g_pullback_bar_time_high = bar_time; + g_pullback_opened_on_bar_high = false; + } + } else { + if(bar_time != g_pullback_bar_time_low) { + g_pullback_bar_time_low = bar_time; + g_pullback_opened_on_bar_low = false; + } + } +} + +bool IsPullbackBarOpenAllowed(const bool for_high_extreme) +{ + SyncPullbackBarLock(for_high_extreme); + if(for_high_extreme) + return !g_pullback_opened_on_bar_high; + return !g_pullback_opened_on_bar_low; +} + +void MarkPullbackBarOpened(const bool for_high_extreme) +{ + SyncPullbackBarLock(for_high_extreme); + if(for_high_extreme) + g_pullback_opened_on_bar_high = true; + else + g_pullback_opened_on_bar_low = true; +} + +bool OpenPullbackReversePosition(ENUM_ORDER_TYPE order_type, const bool from_high_extreme, + const double extreme_price) +{ + if(!IsPullbackBarOpenAllowed(from_high_extreme)) + return false; + + const long pos_type = (order_type == ORDER_TYPE_BUY) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL; + const double lots = CalculateLotSize(pos_type); + if(lots <= 0.0) + return false; + + if(InpMaxLots > 0.0) { + const double cur_lots = TotalLotsInDirection(pos_type); + if(cur_lots + lots > InpMaxLots + 1e-8) { + if(InpShowDebugInfo) + Print("【回落反向】开仓跳过 - 同向合计将超上限 ", InpMaxLots); + return false; + } + } + + if(!IsSameDirMinSpacingSatisfied(pos_type)) { + if(InpShowDebugInfo) + Print("【回落反向】开仓跳过 - 未达同向最小间距%"); + return false; + } + + const string comment = FormatPullbackReverseComment(g_trade_round); + bool result = false; + if(order_type == ORDER_TYPE_BUY) + result = trade.Buy(lots, _Symbol, 0, 0, 0, comment); + else + result = trade.Sell(lots, _Symbol, 0, 0, 0, comment); + + if(!result) { + Print("【回落反向】开仓失败: ", trade.ResultRetcode(), " - ", + trade.ResultRetcodeDescription()); + return false; + } + + MarkPullbackBarOpened(from_high_extreme); + Print("【回落反向】开仓成功 ", (order_type == ORDER_TYPE_BUY ? "多" : "空"), + " 手数:", lots, " 极值:", extreme_price, " 备注:", comment); + return true; +} + +void TryArmPullbackWatch() +{ + if(!InpEnablePullbackReverse || !latest_wave.exists) + return; + + const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + + if(ask > latest_wave.high_price && !latest_wave.high_pullback_reverse_used) { + if(!g_high_pullback_watch.active || + g_high_pullback_watch.extreme_price != latest_wave.high_price) { + g_high_pullback_watch.active = true; + g_high_pullback_watch.extreme_price = latest_wave.high_price; + g_high_pullback_watch.bars_checked = 0; + if(InpShowDebugInfo) + Print("【回落反向】开始监测破高 极值:", latest_wave.high_price, + " 共", InpPullbackReverseWatchBars, "根K"); + } + } + + if(bid < latest_wave.low_price && !latest_wave.low_pullback_reverse_used) { + if(!g_low_pullback_watch.active || + g_low_pullback_watch.extreme_price != latest_wave.low_price) { + g_low_pullback_watch.active = true; + g_low_pullback_watch.extreme_price = latest_wave.low_price; + g_low_pullback_watch.bars_checked = 0; + if(InpShowDebugInfo) + Print("【回落反向】开始监测破低 极值:", latest_wave.low_price, + " 共", InpPullbackReverseWatchBars, "根K"); + } + } +} + +void ProcessPullbackReverseOnNewBar() +{ + if(!InpEnablePullbackReverse || !latest_wave.exists) + return; + + const int max_bars = MathMax(1, InpPullbackReverseWatchBars); + + if(g_high_pullback_watch.active && !latest_wave.high_pullback_reverse_used) { + const double close1 = iClose(_Symbol, InpTimeframe, 1); + if(close1 > 0.0 && close1 <= g_high_pullback_watch.extreme_price) { + if(OpenPullbackReversePosition(OrderTypeOnHighPullbackReverse(), true, + g_high_pullback_watch.extreme_price)) + latest_wave.high_pullback_reverse_used = true; + g_high_pullback_watch.active = false; + } else { + g_high_pullback_watch.bars_checked++; + if(g_high_pullback_watch.bars_checked >= max_bars) { + if(InpShowDebugInfo) + Print("【回落反向】破高监测结束 未回落极值内"); + g_high_pullback_watch.active = false; + } + } + } + + if(g_low_pullback_watch.active && !latest_wave.low_pullback_reverse_used) { + const double close1 = iClose(_Symbol, InpTimeframe, 1); + if(close1 > 0.0 && close1 >= g_low_pullback_watch.extreme_price) { + if(OpenPullbackReversePosition(OrderTypeOnLowPullbackReverse(), false, + g_low_pullback_watch.extreme_price)) + latest_wave.low_pullback_reverse_used = true; + g_low_pullback_watch.active = false; + } else { + g_low_pullback_watch.bars_checked++; + if(g_low_pullback_watch.bars_checked >= max_bars) { + if(InpShowDebugInfo) + Print("【回落反向】破低监测结束 未回升极值内"); + g_low_pullback_watch.active = false; + } + } + } +} + +void CheckPullbackReverseSignals() +{ + if(!InpEnablePullbackReverse) + return; + + TryArmPullbackWatch(); + + static datetime s_last_bar_time = 0; + const datetime bar_time = iTime(_Symbol, InpTimeframe, 0); + if(bar_time == 0 || bar_time == s_last_bar_time) + return; + s_last_bar_time = bar_time; + + ProcessPullbackReverseOnNewBar(); +} + void ResetBreakoutWaveEntryFlags() { latest_wave.high_breakout_used = false; latest_wave.low_breakout_used = false; + latest_wave.high_pullback_reverse_used = false; + latest_wave.low_pullback_reverse_used = false; + g_high_pullback_watch.active = false; + g_low_pullback_watch.active = false; } int CountAllBreakoutPositions() @@ -715,16 +971,27 @@ double TotalLotsInDirection(const long pos_type) return sum; } +bool IsSameDirectionTierSlotsFull(const long pos_type) +{ + const int tier = CountBreakoutPositions(pos_type); + if(tier >= g_tier_lots_count) + return true; + const double next_lot = LotForTierIndex(tier); + if(next_lot <= 0.0) + return true; + if(InpMaxLots > 0.0) { + const double cur = TotalLotsInDirection(pos_type); + if(cur >= InpMaxLots - 1e-8) + return true; + if(cur + next_lot > InpMaxLots + 1e-8) + return true; + } + return false; +} + bool IsDirectionFullActive(const long pos_type) { - const double cur = TotalLotsInDirection(pos_type); - if(cur >= InpMaxLots - 1e-8) - return true; - // 按下一笔实际加码手数判断(非仅基准手): 如19.5手+下一笔4.5>20亦视为满档 - const double next_lot = CalculateLotSize(pos_type); - if(next_lot <= 0.0) - return (cur > 0.0); - return (cur + next_lot > InpMaxLots + 1e-8); + return IsSameDirectionTierSlotsFull(pos_type); } void CheckDualSideFullCapacity() @@ -842,26 +1109,53 @@ double NormalizeVolume(double lots) return lots; } -// tier_index: 0=该方向首笔, 1=第2笔…;grp=tier/N,组内手数相同,每组=基准+grp×加码 -double LotForSameDirectionTier(const int tier_index, const double base_lot) +bool InitTierLotsFromInput() { - if(tier_index < 0 || base_lot <= 0.0) + ArrayResize(g_tier_lots, 0); + g_tier_lots_count = 0; + + string parts[]; + const int n = StringSplit(InpTierLotsList, ',', parts); + if(n <= 0) + return false; + + for(int i = 0; i < n; i++) { + string token = parts[i]; + StringTrimLeft(token); + StringTrimRight(token); + if(StringLen(token) == 0) + continue; + const double v = StringToDouble(token); + if(v <= 0.0) + continue; + ArrayResize(g_tier_lots, g_tier_lots_count + 1); + g_tier_lots[g_tier_lots_count++] = v; + } + return (g_tier_lots_count > 0); +} + +int GetMaxTierSlots() +{ + return g_tier_lots_count; +} + +double LotForTierIndex(const int tier_index) +{ + if(tier_index < 0 || tier_index >= g_tier_lots_count) return 0.0; - const int n = MathMax(1, InpMartingaleTierInterval); - const int grp = tier_index / n; - if(InpMartingaleAddLotBoost <= 0.0) - return NormalizeVolume(base_lot); - double vol = base_lot + (double)grp * InpMartingaleAddLotBoost; - return NormalizeVolume(MathMin(vol, InpMaxLots)); + double vol = g_tier_lots[tier_index]; + if(InpMaxLots > 0.0) + vol = MathMin(vol, InpMaxLots); + return NormalizeVolume(vol); } //+------------------------------------------------------------------+ -//| 按同向持仓笔数(档)计算本笔手数 | +//| 按同向已有笔数取下一档手数(tier=0为第1笔) | //+------------------------------------------------------------------+ double CalculateLotSize(const long pos_type) { const int tier = CountBreakoutPositions(pos_type); - return LotForSameDirectionTier(tier, InpFixedLots); + return LotForTierIndex(tier); } bool GetLastSameDirectionOpenPrice(const long pos_type, double &last_open) @@ -964,8 +1258,146 @@ bool CloseAllBreakoutPositions(const string reason) return ok && n > 0; } -void CheckBreakoutBalanceExit() +void CollectAllTradeRounds(int &rounds[], int &n_rounds) { + n_rounds = 0; + ArrayResize(rounds, 0); + const int total = PositionsTotal(); + for(int i = 0; i < total; i++) { + if(!PositionSelectByTicket(PositionGetTicket(i))) + continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) + continue; + const int r = ParseRoundFromComment(PositionGetString(POSITION_COMMENT)); + if(r < 1) + continue; + bool found = false; + for(int j = 0; j < n_rounds; j++) { + if(rounds[j] == r) { + found = true; + break; + } + } + if(!found) { + ArrayResize(rounds, n_rounds + 1); + rounds[n_rounds++] = r; + } + } +} + +bool BatchRoundAvgPrice(const int round_id, const long pos_type, double &avg, double &sum_lots) +{ + avg = 0.0; + sum_lots = 0.0; + double sum_px_vol = 0.0; + const int total = PositionsTotal(); + for(int i = 0; i < total; i++) { + if(!PositionSelectByTicket(PositionGetTicket(i))) + continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) + continue; + if(PositionGetInteger(POSITION_TYPE) != pos_type) + continue; + if(ParseRoundFromComment(PositionGetString(POSITION_COMMENT)) != round_id) + continue; + const double v = PositionGetDouble(POSITION_VOLUME); + const double p = PositionGetDouble(POSITION_PRICE_OPEN); + sum_px_vol += p * v; + sum_lots += v; + } + if(sum_lots <= 1e-12) + return false; + avg = sum_px_vol / sum_lots; + return true; +} + +bool CloseBreakoutRoundBatch(const int round_id, const long pos_type, const string reason) +{ + ulong tickets[]; + int n = 0; + const int total = PositionsTotal(); + for(int i = 0; i < total; i++) { + if(!PositionSelectByTicket(PositionGetTicket(i))) + continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) + continue; + if(PositionGetInteger(POSITION_TYPE) != pos_type) + continue; + if(ParseRoundFromComment(PositionGetString(POSITION_COMMENT)) != round_id) + continue; + ArrayResize(tickets, n + 1); + tickets[n++] = PositionGetTicket(i); + } + if(n <= 0) + return false; + + bool ok = true; + for(int j = 0; j < n; j++) { + if(!trade.PositionClose(tickets[j])) + ok = false; + } + if(ok) { + Print("【突破】", (pos_type == POSITION_TYPE_BUY ? "多单" : "空单"), + " 第", round_id, "轮批平仓 笔数=", n, " 原因:", reason); + if(round_id == g_trade_round && CountBreakoutPositions(pos_type) == 0) + g_dual_full_handled = false; + } + return ok; +} + +void CheckBreakoutBatchTakeProfitForRoundDir(const int round_id, const long pos_type) +{ + double avg = 0.0, sum_lots = 0.0; + if(!BatchRoundAvgPrice(round_id, pos_type, avg, sum_lots)) + return; + + const double tp_off = (double)InpBatchTakeProfitPoints * _Point; + bool hit = false; + + if(pos_type == POSITION_TYPE_BUY) { + const double px = SymbolInfoDouble(_Symbol, SYMBOL_BID); + hit = (px >= avg + tp_off); + } else { + const double px = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + hit = (px <= avg - tp_off); + } + + if(!hit) + return; + + const double target = (pos_type == POSITION_TYPE_BUY) ? (avg + tp_off) : (avg - tp_off); + CloseBreakoutRoundBatch(round_id, pos_type, + StringFormat("统盈 均价%s+%d点 目标%s", + DoubleToString(avg, _Digits), InpBatchTakeProfitPoints, + DoubleToString(target, _Digits))); +} + +void CheckBreakoutBatchTakeProfit() +{ + if(InpBatchTakeProfitPoints <= 0) + return; + + int rounds[]; + int n_rounds = 0; + CollectAllTradeRounds(rounds, n_rounds); + + for(int i = 0; i < n_rounds; i++) { + CheckBreakoutBatchTakeProfitForRoundDir(rounds[i], POSITION_TYPE_BUY); + CheckBreakoutBatchTakeProfitForRoundDir(rounds[i], POSITION_TYPE_SELL); + } +} + +void CheckBreakoutBalanceStopLoss() +{ + if(InpStopLossBalancePct <= 0.0) + return; + if(CountAllBreakoutPositions() == 0) return; @@ -974,23 +1406,10 @@ void CheckBreakoutBalanceExit() return; const double fpl = TotalBreakoutFloatingPL(); - - if(InpTakeProfitBalancePct > 0.0) { - const double tp_need = balance * (InpTakeProfitBalancePct / 100.0); - if(fpl >= tp_need) { - CloseAllBreakoutPositions( - StringFormat("止盈:浮盈%.2f达结余%.2f%%", fpl, InpTakeProfitBalancePct)); - return; - } - } - - if(InpStopLossBalancePct > 0.0) { - const double sl_need = balance * (InpStopLossBalancePct / 100.0); - if(fpl <= -sl_need) { - CloseAllBreakoutPositions( - StringFormat("止损:浮亏%.2f达结余%.2f%%", fpl, InpStopLossBalancePct)); - } - } + const double sl_need = balance * (InpStopLossBalancePct / 100.0); + if(fpl <= -sl_need) + CloseAllBreakoutPositions( + StringFormat("止损:浮亏%.2f达结余%.2f%%", fpl, InpStopLossBalancePct)); } void SyncBreakoutBarLock(const bool for_high_extreme) @@ -1040,12 +1459,18 @@ bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double w if(lots <= 0.0) return false; - const double cur_lots = TotalLotsInDirection(pos_type); - if(cur_lots + lots > InpMaxLots + 1e-8) { - Print("【突破】开仓跳过 - 同向合计将超上限 ", InpMaxLots, - " 当前:", cur_lots, " 拟开:", lots); + if(lots <= 0.0) { + Print("【突破】开仓跳过 - 同向已达最大档数 ", g_tier_lots_count); return false; } + if(InpMaxLots > 0.0) { + const double cur_lots = TotalLotsInDirection(pos_type); + if(cur_lots + lots > InpMaxLots + 1e-8) { + Print("【突破】开仓跳过 - 同向合计将超上限 ", InpMaxLots, + " 当前:", cur_lots, " 拟开:", lots); + return false; + } + } if(!IsSameDirMinSpacingSatisfied(pos_type)) { if(InpShowDebugInfo) { @@ -1072,10 +1497,9 @@ bool OpenBreakoutPosition(ENUM_ORDER_TYPE order_type, double wave_high, double w } MarkBreakoutBarOpened(from_high_extreme); - const int tier = CountBreakoutPositions(pos_type) - 1; - const int grp = (tier >= 0) ? (tier / MathMax(1, InpMartingaleTierInterval)) : 0; + const int tier = CountBreakoutPositions(pos_type); Print("【突破】开仓成功 ", (order_type == ORDER_TYPE_BUY ? "多" : "空"), - " 手数:", lots, " 轮次:", g_trade_round, " 同向第", (tier + 1), "笔/组", (grp + 1), + " 手数:", lots, " 轮次:", g_trade_round, " 同向第", tier, "/", g_tier_lots_count, "档", " 备注:", comment, " 波段 H:", wave_high, " L:", wave_low); return true; } diff --git a/20260520-突破马丁/README.md b/20260520-突破马丁/README.md index e565360..8ab84ac 100644 --- a/20260520-突破马丁/README.md +++ b/20260520-突破马丁/README.md @@ -1,62 +1,24 @@ # 突破策略(极值点突破开仓) -MQL5 EA:`20260520_Breakout.mq5`(v3.07)。 +MQL5 EA:`20260520_Breakout.mq5`(v3.14)。 ## 参数默认值(与输入界面一致) | 分组 | 参数 | 默认 | |------|------|------| | 波段 | K线周期 / MA周期 | M1 / 14 | -| 波段 | 最小/最大波段% | 0.1 / **1.0** | +| 波段 | 最小/最大波段% | 0.1 / **0.25** | | 波段 | 反向突破容忍度% | **0.05** | -| 交易 | 开仓方向 | **顺势**(破高多/破低空) | -| 交易 | 止盈:浮盈达结余比例(%) | **1**(0=关) | -| 交易 | 止损:浮亏达结余比例(%) | **30**(0=关) | -| 仓位 | 基准手数 / 每组+ / 档间隔N / 同向上限 / 同向最小间距% | **0.5** / **2** / 5 / **30** / **0.1** | -| 仓位 | 双向满档后处理 | **清仓**(平掉全部 EA 持仓) | -| 调试 | 调试信息 / 极值标记 / Magic / 禁手工单 | false / true / 20260520 / true | +| 交易 | 开仓方向 | **随机** | +| 交易 | 按批统盈点数 / 止损% | **300** / **30** | +| 交易 | 回落反向 / 监测K线数 | **开** / **3** | +| 仓位 | 各档手数(19档) | 见下 | +| 仓位 | 同向最大合计 / 最小间距% / 满档后 | 99 / **0** / **清仓** | +| 调试 | 调试 / 极值标记 / Magic / 禁手工单 | false / true / 20260520 / true | -## 开仓 +**各档手数:** `0.05,0.05,0.05,0.8,0.8,1.1,1,2,2,1,1,1,2,2,1,1,1,5,5`(19 档) -- 识别**最新有效波段**后,价格**破高**或**破低**即尝试开仓。 -- **破高、破低各算一个极值点,每个极值点最多对应 1 笔**(当前轮内)。 -- 可同时持有多笔;**仅统计当前轮**的手数、笔数、间距、满档。 -- 第 1 轮备注 `[突破]`,第 2 轮起 `[突破-R2]`、`[突破-R3]`… +## 说明 -### 同向加码手数(类马丁) - -- 按**当前轮、本方向已有笔数**定档。 -- `组号 = tier ÷ 档间隔N`;**本笔手数** = `基准 + 组号 × 每组+`。 -- **当前轮**该方向合计不得超过 `同向最大合计手数`。 - -### 同向最小开仓间距 - -- **0 = 不限制**;仅相对**当前轮**最近一笔同向开仓价判断。 - -### 开仓方向 - -| 选项 | 破高 | 破低 | -|------|------|------| -| **顺势** | 多 | 空 | -| **反向** | 空 | 多 | -| **随机** | 每次突破独立随机 | | - -## 双向满档(当前轮多空均达同向上限) - -当**当前轮**多单、空单均**无法再开下一笔**(合计已达上限,或「当前合计 + 下一档手数」会超限,例如 19.5+4.5>20)时触发一次: - -| 模式 | 行为 | -|------|------| -| **清仓**(默认) | 平掉本 EA 全部持仓(各轮一并平掉),轮次重置为 1,极值标记重置 | -| **不处理旧仓,开始下一轮** | 旧仓保留;轮次 +1;新单从基准手数重新加码 | - -触发后需等当前轮任一侧未满档,才会在再次双向满档时重复处理。 - -## 平仓(结余比例) - -- 统计**全部轮次**持仓浮盈浮亏(含 swap)。 -- 止盈 / 止损达标 → **全部清仓**(各轮一并平掉)。 - -## 手工单 - -- `禁止手工单=true` 时自动平掉本品种 Magic=0 的手工单。 +- 突破开仓与回落反向共用档位手数;列表长度 = 同向最多开仓笔数。 +- 按批统盈、结余%止损、双向满档逻辑见 EA 输入注释。 diff --git a/20260520-突破马丁/XAU_1min_300000.set b/20260520-突破马丁/XAU_1min_300000.set index 20d167d4374fa74f9b066ae0b500ab160006244a..1f40d2d9727522ec7b375a6fb0bf7a2c1baefd5b 100644 GIT binary patch delta 454 zcmX>k*da7QOW24(fx(c$jKPY*1V~#km@=4dv|Y%=YQUh!U_ALTQ|9C#<}OB~$#CZfx(c$h{1}%7zjb6*+$!iOss|sdJG1WA2VgL0%-=*$^Y4GCs(oZ zOrFB*#Aq=2I6HqJ%i1Qed!7|A}Fa=^41l(oGSmPeh-CnD5rPfk8Gs>749uVG$Ra-Z z027O55zvfGppEHZyHgn)fhNPub^_Xv53~W~3L~&9fI4i^b%X%*rviQ92{fey%qjz$ iWear^#AOgSnNHrwA`G;bZE_A97qc;g!RAV~ekK4XcSUgk