diff --git a/20260516-突破策略/20260516_Breakout.mq5 b/20260516-突破策略/20260516_Breakout.mq5 index 7d82780..76c195f 100644 --- a/20260516-突破策略/20260516_Breakout.mq5 +++ b/20260516-突破策略/20260516_Breakout.mq5 @@ -21,7 +21,7 @@ input double InpPullbackTolerance = 0.0; // 反向突破容忍度(%) 0= input group "=== 风险管理参数 ===" input double InpStopLossPercent = 0.05; // 止损百分比(%) input double InpRiskRewardRatio = 1.5; // 盈亏比 -input bool InpUseTrailingStop = true; // 使用移动止损 +input bool InpUseTrailingStop = false; // 使用移动止损 input int InpMaxHoldingMinutes = 5; // 最大持仓时间(分钟) input group "=== 仓位管理参数 ===" @@ -29,6 +29,11 @@ input bool InpUseCompounding = true; // 使用复利模式 input double InpFixedLots = 0.01; // 固定手数 input double InpLotsPer500 = 0.05; // 每500$开仓手数(复利模式) input int InpMaxPositions = 99; // 最大开仓手数 +input bool InpOnePositionPerDirection = true; // 单方向最多持有一单 + +input group "=== 连续亏损保护 ===" +input int InpConsecutiveLosses = 3; // 连续亏损次数触发冷冻(0=禁用) +input int InpFreezeBarCount = 60; // 冷冻K线根数(0=禁用) input group "=== 调试选项 ===" input bool InpShowDebugInfo = false; // 显示调试信息 @@ -53,6 +58,11 @@ struct ValidWaveInfo { ValidWaveInfo latest_wave; // 最新有效波段 +// 连续亏损保护相关变量 +int consecutive_loss_count = 0; // 连续亏损计数器 +datetime freeze_until_time = 0; // 冷冻结束时间(0表示未冷冻) +int freeze_bar_index = 0; // 冷冻起始K线索引 + // 极值点结构体定义 struct ExtremePoint { datetime time; @@ -113,6 +123,11 @@ int OnInit() Print("移动止损:", (InpUseTrailingStop ? "启用" : "禁用")); Print("最大持仓时间:", InpMaxHoldingMinutes, "分钟"); Print("最大开仓手数:", InpMaxPositions); + Print("单方向持仓限制:", (InpOnePositionPerDirection ? "启用 (每方向最多1单)" : "禁用")); + if(InpConsecutiveLosses > 0 && InpFreezeBarCount > 0) + Print("连续亏损保护: 启用 (", InpConsecutiveLosses, "次亏损→冷冻", InpFreezeBarCount, "根K线)"); + else + Print("连续亏损保护: 禁用"); Print("手数模式:", (InpUseCompounding ? "复利" : "固定"), InpUseCompounding ? StringFormat(" (每500$开%.2f手)", InpLotsPer500) : StringFormat(" (%.2f手)", InpFixedLots)); Print("========================================"); @@ -134,6 +149,79 @@ void OnDeinit(const int reason) Print("突破交易策略EA已卸载"); } +//+------------------------------------------------------------------+ +//| 交易事务处理函数 - 用于检测亏损单 | +//+------------------------------------------------------------------+ +void OnTradeTransaction(const MqlTradeTransaction& trans, + const MqlTradeRequest& request, + const MqlTradeResult& result) +{ + // 只在功能启用时处理 + if(InpConsecutiveLosses <= 0 || InpFreezeBarCount <= 0) + return; + + // 只处理订单成交事件 + if(trans.type != TRADE_TRANSACTION_DEAL_ADD) + return; + + // 需要先选择历史记录 + if(!HistorySelect(0, TimeCurrent())) + return; + + // 获取最新的Deal + int total_deals = HistoryDealsTotal(); + if(total_deals <= 0) + return; + + ulong deal_ticket = HistoryDealGetTicket(total_deals - 1); + if(deal_ticket == 0) + return; + + // 检查魔术号 + long deal_magic = HistoryDealGetInteger(deal_ticket, DEAL_MAGIC); + if(deal_magic != InpMagicNumber) + return; + + // 检查是否是平仓交易 + ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); + if(entry != DEAL_ENTRY_OUT) + return; + + // 获取交易详情 + double profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); + double commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); + double swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP); + double net_profit = profit + commission + swap; + + // 判断是亏损还是盈利 + if(net_profit < 0) + { + // 亏损:增加计数器 + consecutive_loss_count++; + Print("【连续亏损保护】亏损 ", consecutive_loss_count, "/", InpConsecutiveLosses, + " | 净亏损: $", DoubleToString(net_profit, 2)); + + // 检查是否达到冷冻阈值 + if(consecutive_loss_count >= InpConsecutiveLosses) + { + // 进入冷冻期 + freeze_bar_index = Bars(_Symbol, InpTimeframe) + InpFreezeBarCount; + freeze_until_time = TimeCurrent(); + + Print("!!! 触发交易冷冻 !!! 冷冻", InpFreezeBarCount, "根K线"); + } + } + else if(net_profit > 0) + { + // 盈利:重置计数器 + if(consecutive_loss_count > 0) + { + Print("【连续亏损保护】盈利 - 计数器重置: ", consecutive_loss_count, " → 0 | 净盈利: $", DoubleToString(net_profit, 2)); + consecutive_loss_count = 0; + } + } +} + //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ @@ -545,8 +633,33 @@ void CheckOpenSignals() if(!latest_wave.exists) return; - // 检查当前持仓数量是否已达上限 + // 检查是否处于冷冻期 + if(InpConsecutiveLosses > 0 && InpFreezeBarCount > 0 && freeze_bar_index > 0) + { + int current_bars = Bars(_Symbol, InpTimeframe); + if(current_bars < freeze_bar_index) + { + // 仍在冷冻期内 - 静默拒绝开仓 + return; + } + else + { + // 冷冻期结束 + if(freeze_bar_index > 0) + { + Print("【连续亏损保护】冷冻解除,恢复交易"); + consecutive_loss_count = 0; + freeze_bar_index = 0; + freeze_until_time = 0; + } + } + } + + // 检查当前持仓数量和方向 int total_positions = 0; + int buy_positions = 0; // 多单数量 + int sell_positions = 0; // 空单数量 + for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!PositionSelectByTicket(PositionGetTicket(i))) continue; @@ -554,7 +667,15 @@ void CheckOpenSignals() continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; + total_positions++; + + // 统计各方向持仓数量 + ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if(pos_type == POSITION_TYPE_BUY) + buy_positions++; + else if(pos_type == POSITION_TYPE_SELL) + sell_positions++; } if(total_positions >= InpMaxPositions) { @@ -571,25 +692,37 @@ void CheckOpenSignals() // 检查多单信号:突破高点(且该高点未使用过) if(!latest_wave.high_used && current_price > latest_wave.high_price) { - if(InpShowDebugInfo) - Print("检测到多单信号 - 价格:", current_price, " 突破高点:", latest_wave.high_price); - - if(OpenPosition(ORDER_TYPE_BUY, latest_wave.high_price, latest_wave.low_price, wave_threshold_points)) { - latest_wave.high_used = true; // 标记高点已使用,该波段高点失效 + // 如果启用了单方向持仓限制,检查多单数量 + if(InpOnePositionPerDirection && buy_positions >= 1) { if(InpShowDebugInfo) - Print("高点已使用,等待新的有效波段"); + Print("多单信号被忽略 - 已有多单持仓: ", buy_positions); + } else { + if(InpShowDebugInfo) + Print("检测到多单信号 - 价格:", current_price, " 突破高点:", latest_wave.high_price); + + if(OpenPosition(ORDER_TYPE_BUY, latest_wave.high_price, latest_wave.low_price, wave_threshold_points)) { + latest_wave.high_used = true; // 标记高点已使用,该波段高点失效 + if(InpShowDebugInfo) + Print("高点已使用,等待新的有效波段"); + } } } // 检查空单信号:突破低点(且该低点未使用过) if(!latest_wave.low_used && current_price < latest_wave.low_price) { - if(InpShowDebugInfo) - Print("检测到空单信号 - 价格:", current_price, " 突破低点:", latest_wave.low_price); - - if(OpenPosition(ORDER_TYPE_SELL, latest_wave.high_price, latest_wave.low_price, wave_threshold_points)) { - latest_wave.low_used = true; // 标记低点已使用,该波段低点失效 + // 如果启用了单方向持仓限制,检查空单数量 + if(InpOnePositionPerDirection && sell_positions >= 1) { if(InpShowDebugInfo) - Print("低点已使用,等待新的有效波段"); + Print("空单信号被忽略 - 已有空单持仓: ", sell_positions); + } else { + if(InpShowDebugInfo) + Print("检测到空单信号 - 价格:", current_price, " 突破低点:", latest_wave.low_price); + + if(OpenPosition(ORDER_TYPE_SELL, latest_wave.high_price, latest_wave.low_price, wave_threshold_points)) { + latest_wave.low_used = true; // 标记低点已使用,该波段低点失效 + if(InpShowDebugInfo) + Print("低点已使用,等待新的有效波段"); + } } } } diff --git a/20260516-突破策略/README.md b/20260516-突破策略/README.md index ba60f95..74e05bb 100644 --- a/20260516-突破策略/README.md +++ b/20260516-突破策略/README.md @@ -35,14 +35,19 @@ ### 风险管理参数 - 止损百分比:默认 **0.05%**(基于开仓价计算,最小1美元/100点) - 盈亏比:默认 **1.5**(止盈 = 止损金额 × 盈亏比) -- 使用移动止损:默认 **true**(启用移动止损功能) +- 使用移动止损:默认 **false**(禁用移动止损功能) - 最大持仓时间:默认 **5分钟** ### 仓位管理参数 -- 使用复利模式:默认 **false**(使用固定手数模式) +- 使用复利模式:默认 **true**(启用复利模式) - 固定手数:默认 **0.01手** - 每500$开仓手数:默认 **0.05手**(复利模式有效) - 最大开仓手数:默认 **99手** +- 单方向最多持有一单:默认 **true**(每个方向最多持有1单) + +### 连续亏损保护参数 +- 连续亏损次数触发冷冻:默认 **3**(设为0可禁用该功能) +- 冷冻K线根数:默认 **60**(设为0可禁用该功能) ## 补充说明 @@ -55,6 +60,155 @@ - 开仓后该波段立即失效 - 必须等新的有效波段形成才能再次开仓 +### 单方向持仓限制 +此参数用于控制同一方向的持仓数量,避免单边风险过大。 + +**参数说明:** +- **false(默认)**:允许同方向开多单,仅受"最大开仓手数"限制 + - 例如:最大开仓手数=3时,可以同时持有3个多单,或3个空单,或2多1空等 +- **true**:每个方向最多持有1单 + - 多单方向:最多持有1个多单 + - 空单方向:最多持有1个空单 + - 最多同时持有1多+1空=2单 + +**使用场景:** +- **保守型交易者**:建议设置为 **true** + - 严格控制风险敞口 + - 避免单边持仓过重 + - 适合波动较大的行情 + +- **激进型交易者**:可设置为 **false** + - 允许同方向加仓 + - 在强趋势行情中获取更多盈利 + - 但需要配合"最大开仓手数"参数控制总风险 + +**示例:** +``` +设置:单方向最多持有一单 = true + +场景1:当前持有1个多单 +- 新的多单信号出现 → ❌ 被忽略(已有多单) +- 新的空单信号出现 → ✅ 可以开仓(空单方向无持仓) + +场景2:当前持有1个多单 + 1个空单 +- 新的多单信号出现 → ❌ 被忽略(已有多单) +- 新的空单信号出现 → ❌ 被忽略(已有空单) +``` + +### 连续亏损保护功能 ⭐ + +此功能用于在连续亏损后自动暂停交易,避免在不利行情中持续亏损。 + +**核心机制:** +- 实时监控每笔交易的盈亏结果(通过 `OnTradeTransaction` 事件) +- 当连续亏损达到设定次数时,自动进入"冷冻期" +- 冷冻期内禁止开新仓,直到指定数量的K线走完 +- 冷冻期结束后自动解除,连续亏损计数器重置为0 + +**参数说明:** + +1. **连续亏损次数触发冷冻**:默认 `3` + - 设置为 `0`:功能完全禁用 + - 设置为 `3`:连续亏损3笔后触发冷冻(推荐值) + - 任何盈利单都会将计数器归零 + +2. **冷冻K线根数**:默认 `60` + - 设置为 `0`:功能完全禁用 + - 设置为 `60`:冷冻期间禁止开仓,直到60根K线走完(M1周期约1小时) + - K线周期取决于"K线周期"参数设置 + +**工作流程:** + +``` +步骤1:监控交易结果 +- 订单平仓后,检测盈亏(包含手续费和隔夜利息) +- 亏损 → 连续亏损计数器 +1 +- 盈利 → 连续亏损计数器归零 + +步骤2:触发冷冻条件 +- 当连续亏损计数器 >= 设定次数时 +- 记录当前K线数量 +- 计算冷冻结束K线数 = 当前K线数 + 冷冻K线根数 +- 打印冷冻提示信息 + +步骤3:冷冻期检查 +- 每次开仓前检查是否处于冷冻期 +- 如果当前K线数 < 冷冻结束K线数 → 拒绝开仓 +- 如果当前K线数 >= 冷冻结束K线数 → 解除冷冻,重置计数器 + +步骤4:冷冻解除 +- 自动打印解除提示 +- 连续亏损计数器重置为0 +- 恢复正常交易 +``` + +**使用场景:** + +1. **震荡行情保护**(默认配置) + - 设置:连续亏损3次 + 冷冻60根K线 + - 效果:在频繁假突破的震荡行情中,连续亏损3次后暂停1小时(M1周期) + - 避免在不利行情中持续开仓亏损 + +2. **回测优化** + - 通过历史数据测试不同参数组合 + - 找到最佳的连续亏损阈值和冷冻时长 + - 提高策略整体盈利能力 + +3. **风险控制** + - 配合"最大开仓手数"和"单方向持仓限制" + - 多层次控制交易风险 + - 保守型交易者建议启用 + +**示例:** + +``` +参数设置: +- 连续亏损次数触发冷冻 = 3 +- 冷冻K线根数 = 10 +- K线周期 = M1 + +交易流程: +时间 09:00 → 开仓多单 → 止损平仓(亏损)→ 连续亏损计数 = 1 +时间 09:05 → 开仓空单 → 止损平仓(亏损)→ 连续亏损计数 = 2 +时间 09:10 → 开仓多单 → 止损平仓(亏损)→ 连续亏损计数 = 3 + +触发冷冻! +- 当前K线数:150 +- 冷冻至K线数:160(150 + 10) +- 打印:=== 触发交易冷冻 === + +冷冻期内(09:10 - 09:20): +- 所有开仓信号被忽略 +- 调试模式下打印:交易冷冻中 - 剩余K线数: X + +时间 09:20(K线数达到160): +- 自动解除冷冻 +- 打印:=== 交易冷冻解除 === +- 连续亏损计数器重置为0 +- 恢复正常交易 +``` + +**注意事项:** + +1. **功能启用条件** + - 两个参数都必须 > 0 才会启用 + - 任一参数为 0,功能完全禁用 + +2. **计数逻辑** + - 仅统计"净亏损"(盈亏 + 手续费 + 隔夜利息) + - 盈亏持平(净盈利约等于0)不会重置计数器 + - 任何盈利单(净盈利 > 0)立即重置计数器 + +3. **冷冻期计算** + - 基于K线根数,不是绝对时间 + - 周末或节假日市场休市时,K线不增加,冷冻期相应延长 + - 建议根据具体交易品种和周期调整参数 + +4. **与其他功能的关系** + - 冷冻期内不影响已有持仓的管理(止损、止盈、移动止损仍正常工作) + - 仅阻止新开仓,不会强制平仓现有持仓 + - 可配合"单方向持仓限制"等功能使用 + ### 反向突破容忍度(新功能)⭐ **问题背景:**