|
|
//+------------------------------------------------------------------+
//| ManualProtection_EnergyExit.mq5 |
//| 手工单自动止损 + 追踪止损 + 能量衰竭平仓 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025"
#property link ""
#property version "1.10"
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
input group "══════════ 手工单保护 ══════════"
input bool InpAutoStopManual = true; // 自动设置止损 (固定350点)
input bool InpUseTrailing = true; // 启用追踪止损
input bool InpUseATRTrailing = true; // ATR动态距离 (否则固定点数)
input double InpTrailingATRMult = 1.5; // 追踪止损ATR倍数
input int InpFixedTrailing = 350; // 固定追踪点数
input int InpTrailingStep = 80; // 最小移动步长(点)
input int InpATRPeriod = 14; // ATR计算周期
input group "══════════ 能量衰竭平仓 ══════════"
input bool InpUseEnergyExit = true; // 启用能量衰竭平仓
input int RegPeriod = 20; // 线性回归周期
input double InpMinSlopeATR = 0.02; // 最小标准化斜率 (基准阈值)
input int InpATRPercentileWin = 100; // ATR百分位窗口(根K线)
input double InpChanWidthUp = 0.9; // 通道宽(仅用于判断波动率状态)
input double InpChanWidthDn = 2.1;
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
CTrade m_tradeManual; // 修改/平仓手工单 (无魔术号)
int atrHandle; // ATR指标句柄
//+------------------------------------------------------------------+
//| 线性回归 |
//+------------------------------------------------------------------+
bool CalcLinearRegression(const double &y[], int period, int startIdx,
double &a, double &b)
{
if(period <= 0 || startIdx < 0)
{ a = 0; b = 0; return false; }
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for(int i = 0; i < period; i++)
{
int idx = startIdx + i;
double x = i;
double v = y[idx];
sumX += x;
sumY += v;
sumXY += x * v;
sumX2 += x * x;
}
double n = (double)period;
double denom = n * sumX2 - sumX * sumX;
if(MathAbs(denom) < 1e-10)
{
a = 0;
b = sumY / n;
}
else
{
a = (n * sumXY - sumX * sumY) / denom;
b = (sumY - a * sumX) / n;
}
return true;
}
//+------------------------------------------------------------------+
//| ATR 百分位计算 |
//+------------------------------------------------------------------+
double CalcATRPercentile(int lookback)
{
if(lookback < 5) return 50.0;
double atr[];
ArraySetAsSeries(atr, true);
if(CopyBuffer(atrHandle, 0, 0, lookback, atr) < lookback)
return 50.0;
double current = atr[0];
int lessCnt = 0;
for(int i = 1; i < lookback; i++)
if(atr[i] < current) lessCnt++;
return 100.0 * (double)lessCnt / (double)(lookback - 1);
}
//+------------------------------------------------------------------+
//| 波动率状态 (低 / 正常 / 高) |
//+------------------------------------------------------------------+
enum ENUM_VOL_REGIME
{
VOL_LOW = 0,
VOL_NORMAL = 1,
VOL_HIGH = 2
};
ENUM_VOL_REGIME GetVolRegime()
{
double pct = CalcATRPercentile(InpATRPercentileWin);
if(pct < 25.0) return VOL_LOW;
if(pct > 75.0) return VOL_HIGH;
return VOL_NORMAL;
}
//+------------------------------------------------------------------+
//| 计算通道 (用于获取标准化斜率) |
//+------------------------------------------------------------------+
bool CalcChannel(double &normSlope)
{
int need = MathMax(RegPeriod + 5, InpATRPercentileWin + 5);
double close[], atr[];
ArraySetAsSeries(close, true);
ArraySetAsSeries(atr, true);
if(CopyClose(_Symbol, _Period, 0, need, close) < need) return false;
if(CopyBuffer(atrHandle, 0, 0, need, atr) < need) return false;
double slope, intercept;
if(!CalcLinearRegression(close, RegPeriod, 0, slope, intercept))
return false;
normSlope = (atr[0] > 0) ? slope / atr[0] : 0;
return true;
}
//+------------------------------------------------------------------+
//| 能量衰竭平仓 (所有手工单) |
//+------------------------------------------------------------------+
void CheckEnergyExhaustion()
{
if(!InpUseEnergyExit) return;
if(PositionsTotal() == 0) return;
double normSlope;
if(!CalcChannel(normSlope)) return;
double slopeAbs = MathAbs(normSlope);
ENUM_VOL_REGIME regime = GetVolRegime();
bool exit = false;
switch(regime)
{
case VOL_LOW: if(slopeAbs < InpMinSlopeATR * 0.5) exit = true; break;
case VOL_NORMAL: if(slopeAbs < InpMinSlopeATR * 0.3) exit = true; break;
case VOL_HIGH: if(slopeAbs < InpMinSlopeATR * 0.2) exit = true; break;
}
if(!exit) return;
// 平掉所有手工单
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double profit = PositionGetDouble(POSITION_PROFIT);
if(m_tradeManual.PositionClose(_Symbol))
PrintFormat("能量衰竭平仓(持仓 %I64u): 盈亏=%.2f 波动=%s 斜率=%.4f",
ticket, profit, (regime==0?"低" regime==1?"正常":"高")), normSlope);
}
}
//+------------------------------------------------------------------+
//| 手工单自动止损 (固定350点) |
//+------------------------------------------------------------------+
void AutoSetStopForManualPositions()
{
if(!InpAutoStopManual) return;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double sl = PositionGetDouble(POSITION_SL);
if(sl == 0)
{
long type = PositionGetInteger(POSITION_TYPE);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double tp = PositionGetDouble(POSITION_TP);
double slDist = 350 * _Point;
double newSl = (type == POSITION_TYPE_BUY) ?
NormalizeDouble(openPrice - slDist, _Digits) :
NormalizeDouble(openPrice + slDist, _Digits);
if(m_tradeManual.PositionModify(_Symbol, newSl, tp))
PrintFormat("手工单 %I64u 自动设置止损 %.5f", ticket, newSl);
}
}
}
//+------------------------------------------------------------------+
//| 手工单追踪止损 |
//+------------------------------------------------------------------+
void TrailingStop()
{
if(!InpUseTrailing) return;
double atr[1];
if(InpUseATRTrailing)
{
ArraySetAsSeries(atr, true);
if(CopyBuffer(atrHandle, 0, 0, 1, atr) != 1) return;
}
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
double trailingDist = InpUseATRTrailing ?
InpTrailingATRMult * atr[0] :
InpFixedTrailing * _Point;
double minStep = InpTrailingStep * _Point;
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
long type = PositionGetInteger(POSITION_TYPE);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(type == POSITION_TYPE_BUY)
{
double newSl = NormalizeDouble(bid - trailingDist, _Digits);
if(newSl > sl + minStep)
if(m_tradeManual.PositionModify(_Symbol, newSl, tp))
PrintFormat("追踪止损上调(持仓 %I64u): %.5f", ticket, newSl);
}
else
{
double newSl = NormalizeDouble(ask + trailingDist, _Digits);
if(newSl < sl - minStep)
if(m_tradeManual.PositionModify(_Symbol, newSl, tp))
PrintFormat("追踪止损下调(持仓 %I64u): %.5f", ticket, newSl);
}
}
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
m_tradeManual.SetExpertMagicNumber(0);
m_tradeManual.SetDeviationInPoints(30);
m_tradeManual.SetTypeFilling(ORDER_FILLING_FOK);
atrHandle = iATR(_Symbol, _Period, InpATRPeriod);
if(atrHandle == INVALID_HANDLE)
{
Print("ATR指标句柄创建失败");
return INIT_FAILED;
}
Print("手工单保护EA v1.10 (止损+追踪+能量衰竭)");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(atrHandle != INVALID_HANDLE)
IndicatorRelease(atrHandle);
Print("手工单保护EA卸载");
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
AutoSetStopForManualPositions(); // 补止损
TrailingStop(); // 追踪
CheckEnergyExhaustion(); // 能量衰竭平仓
}
//+------------------------------------------------------------------+
|
|