feat: 连续亏损控制

This commit is contained in:
zhangyangbin
2026-05-17 11:40:12 +08:00
parent 7aa785cc0c
commit b2c6ef04e8
2 changed files with 303 additions and 16 deletions
+147 -14
View File
@@ -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("低点已使用,等待新的有效波段");
}
}
}
}
+156 -2
View File
@@ -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线数:160150 + 10
- 打印:=== 触发交易冷冻 ===
冷冻期内(09:10 - 09:20):
- 所有开仓信号被忽略
- 调试模式下打印:交易冷冻中 - 剩余K线数: X
时间 09:20K线数达到160):
- 自动解除冷冻
- 打印:=== 交易冷冻解除 ===
- 连续亏损计数器重置为0
- 恢复正常交易
```
**注意事项:**
1. **功能启用条件**
- 两个参数都必须 > 0 才会启用
- 任一参数为 0,功能完全禁用
2. **计数逻辑**
- 仅统计"净亏损"(盈亏 + 手续费 + 隔夜利息)
- 盈亏持平(净盈利约等于0)不会重置计数器
- 任何盈利单(净盈利 > 0)立即重置计数器
3. **冷冻期计算**
- 基于K线根数,不是绝对时间
- 周末或节假日市场休市时,K线不增加,冷冻期相应延长
- 建议根据具体交易品种和周期调整参数
4. **与其他功能的关系**
- 冷冻期内不影响已有持仓的管理(止损、止盈、移动止损仍正常工作)
- 仅阻止新开仓,不会强制平仓现有持仓
- 可配合"单方向持仓限制"等功能使用
### 反向突破容忍度(新功能)⭐
**问题背景:**