diff --git a/20260516-突破策略/20260516_Breakout.mq5 b/20260516-突破策略/20260516_Breakout.mq5 index b138308..ffbebaa 100644 --- a/20260516-突破策略/20260516_Breakout.mq5 +++ b/20260516-突破策略/20260516_Breakout.mq5 @@ -14,14 +14,12 @@ input group "=== 波段识别参数 ===" input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; // K线周期 input int InpMAPeriod = 14; // MA周期 -input bool InpUsePercentMode = true; // 使用百分比模式 -input int InpWaveThreshold = 1000; // 波段阈值(点数/固定模式) -input double InpWavePercent = 0.25; // 波段阈值(百分比/百分比模式) +input double InpWavePercent = 0.1; // 波段阈值百分比(%) input double InpPullbackTolerance = 0.0; // 反向突破容忍度(%) 0=不容忍 input group "=== 风险管理参数 ===" -input int InpStopLoss = 200; // 止损点数 -input int InpTakeProfit = 300; // 止盈点数 +input double InpStopLossPercent = 0.05; // 止损百分比(%) +input double InpRiskRewardRatio = 1.5; // 盈亏比 input int InpMaxHoldingMinutes = 5; // 最大持仓时间(分钟) input group "=== 仓位管理参数 ===" @@ -72,7 +70,6 @@ void CheckOpenSignals(); bool OpenPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low, double threshold_points); double CalculateLotSize(); void ManagePositions(); -bool CheckTakeProfitReached(ulong ticket); void CheckTrailingStop(ulong ticket); //+------------------------------------------------------------------+ @@ -105,15 +102,12 @@ int OnInit() Print("品种:", _Symbol); Print("K线周期:", EnumToString(InpTimeframe)); Print("MA周期:", InpMAPeriod); - if(InpUsePercentMode) - Print("波段阈值模式: 百分比 - ", InpWavePercent, "%"); - else - Print("波段阈值模式: 固定点数 - ", InpWaveThreshold, "点"); + Print("波段阈值: ", InpWavePercent, "%"); if(InpPullbackTolerance > 0) Print("反向突破容忍度: ", DoubleToString(InpPullbackTolerance, 1), "% (启用)"); else Print("反向突破容忍度: 0% (禁用 - 保持原有逻辑)"); - Print("止损:", InpStopLoss, "点 | 止盈:", InpTakeProfit, "点"); + Print("止损:", InpStopLossPercent, "% | 盈亏比:", InpRiskRewardRatio); Print("最大持仓时间:", InpMaxHoldingMinutes, "分钟"); Print("最大开仓手数:", InpMaxPositions); Print("手数模式:", (InpUseCompounding ? "复利" : "固定"), @@ -358,16 +352,9 @@ void UpdateLatestValidWave() double price_diff = MathAbs(extremes[i].price - extremes[i-1].price); double price_diff_points = price_diff / _Point; - // 计算阈值 - double threshold = 0; - if(InpUsePercentMode) { - // 百分比模式:以前一个极值点价格为基准计算百分比 - double base_price = extremes[i-1].price; - threshold = (base_price * InpWavePercent / 100.0) / _Point; - } else { - // 固定点数模式 - threshold = InpWaveThreshold; - } + // 计算阈值(百分比模式:以前一个极值点价格为基准计算百分比) + double base_price = extremes[i-1].price; + double threshold = (base_price * InpWavePercent / 100.0) / _Point; if(price_diff_points >= threshold) { extremes[i-1].is_valid = true; @@ -385,14 +372,9 @@ void UpdateLatestValidWave() double price_diff = MathAbs(extremes[i].price - extremes[i-1].price); double price_diff_points = price_diff / _Point; - // 计算阈值 - double threshold = 0; - if(InpUsePercentMode) { - double base_price = extremes[i-1].price; - threshold = (base_price * InpWavePercent / 100.0) / _Point; - } else { - threshold = InpWaveThreshold; - } + // 计算阈值(百分比模式) + double base_price = extremes[i-1].price; + double threshold = (base_price * InpWavePercent / 100.0) / _Point; if(price_diff_points >= threshold) { // 找到最新的有效波段 @@ -420,13 +402,10 @@ void UpdateLatestValidWave() } if(InpShowDebugInfo) { - string threshold_info = InpUsePercentMode ? - StringFormat("%.2f%% (%.0f点)", InpWavePercent, threshold) : - StringFormat("%d点", InpWaveThreshold); Print("更新最新有效波段 - 高:", DoubleToString(high, _Digits), " 低:", DoubleToString(low, _Digits), " 价差:", (int)price_diff_points, "点", - " 阈值:", threshold_info); + " 阈值:", StringFormat("%.2f%% (%.0f点)", InpWavePercent, threshold)); } } break; @@ -578,16 +557,9 @@ void CheckOpenSignals() double current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID); - // 计算当前波段的实际阈值 - double wave_threshold_points = 0; - if(InpUsePercentMode) { - // 百分比模式:取高低点的平均值作为基准 - double base_price = (latest_wave.high_price + latest_wave.low_price) / 2.0; - wave_threshold_points = (base_price * InpWavePercent / 100.0) / _Point; - } else { - // 固定点数模式 - wave_threshold_points = InpWaveThreshold; - } + // 计算当前波段的实际阈值(百分比模式:取高低点的平均值作为基准) + double base_price = (latest_wave.high_price + latest_wave.low_price) / 2.0; + double wave_threshold_points = (base_price * InpWavePercent / 100.0) / _Point; // 检查多单信号:突破高点(且该高点未使用过) if(!latest_wave.high_used && current_price > latest_wave.high_price) { @@ -627,6 +599,17 @@ bool OpenPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low, SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); + // 计算止损和止盈(基于开仓价的百分比) + double stop_loss_amount = current_price * InpStopLossPercent / 100.0; + + // 确保止损金额不小于1美元(100点) + double min_stop_loss = 1.0; // 1美元 = 100点 + if(stop_loss_amount < min_stop_loss) { + stop_loss_amount = min_stop_loss; + } + + double take_profit_amount = stop_loss_amount * InpRiskRewardRatio; + double sl = 0, tp = 0; bool result = false; @@ -638,24 +621,28 @@ bool OpenPosition(ENUM_ORDER_TYPE order_type, double wave_high, double wave_low, threshold_points); if(order_type == ORDER_TYPE_BUY) { - sl = current_price - InpStopLoss * _Point; - tp = current_price + InpTakeProfit * _Point; + sl = current_price - stop_loss_amount; + tp = current_price + take_profit_amount; result = trade.Buy(lots, _Symbol, 0, sl, tp, comment); if(result) { Print("开多单成功 - 手数:", lots, " 波段:H:", wave_high, " L:", wave_low, - " 止损:", sl, " 止盈:", tp); + " 开仓价:", current_price, + " 止损:", sl, "(", InpStopLossPercent, "%)", + " 止盈:", tp, "(盈亏比", InpRiskRewardRatio, ")"); } } else { - sl = current_price + InpStopLoss * _Point; - tp = current_price - InpTakeProfit * _Point; + sl = current_price + stop_loss_amount; + tp = current_price - take_profit_amount; result = trade.Sell(lots, _Symbol, 0, sl, tp, comment); if(result) { Print("开空单成功 - 手数:", lots, " 波段:H:", wave_high, " L:", wave_low, - " 止损:", sl, " 止盈:", tp); + " 开仓价:", current_price, + " 止损:", sl, "(", InpStopLossPercent, "%)", + " 止盈:", tp, "(盈亏比", InpRiskRewardRatio, ")"); } } @@ -717,53 +704,12 @@ void ManagePositions() continue; } - // 手动检查止盈(防止跳空未触发) - ulong ticket = PositionGetTicket(i); - if(CheckTakeProfitReached(ticket)) { - trade.PositionClose(ticket); - Print("手动止盈平仓 - Ticket:", ticket); - continue; - } - // 检查移动止损 + ulong ticket = PositionGetTicket(i); CheckTrailingStop(ticket); } } -//+------------------------------------------------------------------+ -//| 检查是否达到止盈(防止跳空未触发) | -//+------------------------------------------------------------------+ -bool CheckTakeProfitReached(ulong ticket) -{ - if(!PositionSelectByTicket(ticket)) - return false; - - // 如果止盈设置为0,不检查 - if(InpTakeProfit <= 0) - return false; - - double open_price = PositionGetDouble(POSITION_PRICE_OPEN); - ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); - double current_price = (pos_type == POSITION_TYPE_BUY) ? - SymbolInfoDouble(_Symbol, SYMBOL_BID) : - SymbolInfoDouble(_Symbol, SYMBOL_ASK); - - // 计算浮盈点数 - double profit_points = 0; - if(pos_type == POSITION_TYPE_BUY) { - profit_points = (current_price - open_price) / _Point; - } else { - profit_points = (open_price - current_price) / _Point; - } - - // 检查是否达到或超过止盈点数 - if(profit_points >= InpTakeProfit) { - return true; - } - - return false; -} - //+------------------------------------------------------------------+ //| 检查移动止损 | //+------------------------------------------------------------------+ @@ -779,16 +725,25 @@ void CheckTrailingStop(ulong ticket) SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); - // 计算浮盈点数 - double profit_points = 0; - if(pos_type == POSITION_TYPE_BUY) { - profit_points = (current_price - open_price) / _Point; - } else { - profit_points = (open_price - current_price) / _Point; + // 计算止损金额(开仓价的百分比) + double stop_loss_amount = open_price * InpStopLossPercent / 100.0; + + // 确保止损金额不小于1美元(100点) + double min_stop_loss = 1.0; // 1美元 = 100点 + if(stop_loss_amount < min_stop_loss) { + stop_loss_amount = min_stop_loss; } - // 浮盈达到止损点数,移动止损至成本价 - if(profit_points >= InpStopLoss) { + // 计算浮盈 + double profit_amount = 0; + if(pos_type == POSITION_TYPE_BUY) { + profit_amount = current_price - open_price; + } else { + profit_amount = open_price - current_price; + } + + // 浮盈达到止损金额,移动止损至成本价 + if(profit_amount >= stop_loss_amount) { double new_sl = open_price; // 检查是否需要更新 diff --git a/20260516-突破策略/README.md b/20260516-突破策略/README.md index 2ccdb49..9b69330 100644 --- a/20260516-突破策略/README.md +++ b/20260516-突破策略/README.md @@ -18,7 +18,8 @@ ## 开仓逻辑 - 价格突破有效波段的高点开多单,价格突破有效波点的低点开空单 - 该开仓逻辑仅适用最新的一个有效波段 -- 浮盈达到止损点数后,即可将止损移动至成本价 +- 止损和止盈基于开仓价的百分比计算,止盈 = 止损金额 × 盈亏比 +- 浮盈达到止损金额后,即可将止损移动至成本价 - 已经开仓后,继续监控新的有效波段突破,每单开仓后就与有效波段无关了 ## 参数设置 @@ -26,14 +27,12 @@ ### 波段识别参数 - K线周期:支持参数设置,不跟随图表,默认 **1 Minute** - MA周期:默认 **14** -- 使用百分比模式:默认 **false**(使用固定点数模式) -- 波段阈值点数:默认 **1000点**(固定模式有效) -- 波段阈值百分比:默认 **0.1%**(百分比模式有效) +- 波段阈值百分比:默认 **0.1%** - **反向突破容忍度:默认0.0%(新功能 - 详见下方说明)** ### 风险管理参数 -- 止损点数:默认 **200点** -- 止盈点数:默认 **300点** +- 止损百分比:默认 **0.05%**(基于开仓价计算,最小1美元/100点) +- 盈亏比:默认 **1.5**(止盈 = 止损金额 × 盈亏比) - 最大持仓时间:默认 **5分钟** ### 仓位管理参数 @@ -90,14 +89,32 @@ 3. 建议先回测不同容忍度,找到最适合的数值 4. 容忍度只影响波段识别,不影响止损止盈等其他逻辑 -### 波段阈值模式说明 -**固定点数模式:** -- 相邻极值点价差达到固定点数即为有效波段 -- 适合明确知道想要的波段大小的场景 -- 例如:设置1000点,则任何1000点以上的波段都有效 - -**百分比模式:** +### 波段阈值说明 +本策略使用**百分比模式**计算波段阈值: - 以前一个极值点价格为基准,按百分比计算阈值 - 动态适应不同价格水平 -- 例如:设置1%,价格4700时阈值为47美元(4700点),价格2350时阈值为23.5美元(2350点) -- 适合价格波动较大或需要根据价格水平动态调整的场景 +- 例如:设置0.1%,价格2600时阈值为2.6美元(260点),价格4700时阈值为4.7美元(470点) +- 百分比模式能够根据价格水平自动调整,更好地适应市场变化 + +### 止损止盈说明 +本策略使用**百分比模式**计算止损和止盈: + +- **止损计算**:止损金额 = MAX(开仓价 × 止损百分比, 1美元) + - 示例1(高价位):开仓价2600,止损0.05%,计算值 = 2600 × 0.05% = 1.3美元 > 1美元,使用1.3美元 + - 多单止损价 = 2600 - 1.3 = 2598.7 + - 空单止损价 = 2600 + 1.3 = 2601.3 + - 示例2(低价位):开仓价1500,止损0.05%,计算值 = 1500 × 0.05% = 0.75美元 < 1美元,使用最小值1美元 + - 多单止损价 = 1500 - 1 = 1499.0 + - 空单止损价 = 1500 + 1 = 1501.0 + +- **止盈计算**:止盈金额 = 止损金额 × 盈亏比 + - 示例1:止损金额1.3美元,盈亏比1.5,止盈金额 = 1.3 × 1.5 = 1.95美元 + - 多单止盈价 = 2600 + 1.95 = 2601.95 + - 空单止盈价 = 2600 - 1.95 = 2598.05 + - 示例2:止损金额1美元,盈亏比1.5,止盈金额 = 1 × 1.5 = 1.5美元 + - 多单止盈价 = 1500 + 1.5 = 1501.5 + - 空单止盈价 = 1500 - 1.5 = 1498.5 + +- **移动止损**:当浮盈达到止损金额时,自动将止损移至成本价(开仓价) + +- **最小止损保护**:无论百分比设置多小,止损金额始终不低于1美元(100点),避免止损过小导致频繁触发