feat: 连续亏损控制
This commit is contained in:
@@ -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
@@ -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. **与其他功能的关系**
|
||||
- 冷冻期内不影响已有持仓的管理(止损、止盈、移动止损仍正常工作)
|
||||
- 仅阻止新开仓,不会强制平仓现有持仓
|
||||
- 可配合"单方向持仓限制"等功能使用
|
||||
|
||||
### 反向突破容忍度(新功能)⭐
|
||||
|
||||
**问题背景:**
|
||||
|
||||
Reference in New Issue
Block a user