mql5/Experts/LW_SMC_CHOCH_OCO_EA.mq5
aleks-feedback-gmail aleks-feedback-gmail 1898c71a56 new files added
2026-10-23 11:17:39 +03:00
877 lines
34 KiB
MQL5
//+------------------------------------------------------------------+
//| LW_SMC_CHOCH_OCO_EA.mq5 |
//| CHoCH -> OCO pending orders + trailing stop |
//| |
//| Структура рынка считается так же, как в индикаторе |
//| LW_MarketStructure_SMC.mq5 (фрактальные swing H/L + BOS + CHoCH). |
//| |
//| Логика: |
//| 1) На новом ЗАКРЫТОМ баре ищем последний CHoCH (Change of |
//| Character = пробой swing-уровня против текущего тренда). |
//| 2) На новый CHoCH выставляем два отложенных ордера (OCO): |
//| - OCO_STOP : BUY STOP выше цены + SELL STOP ниже цены; |
//| - OCO_LIMIT: BUY LIMIT ниже цены + SELL LIMIT выше цены. |
//| 3) Как только один ордер исполнился — второй удаляется (OCO). |
//| 4) Выход — по трейлинг-стопу (опц. безубыток / фикс. TP). |
//| |
//| ВАЖНО: MT5 не принимает отложенный ордер с ценой на "неверной" |
//| стороне рынка (BUY LIMIT выше цены отклоняется сервером). |
//| Поэтому "BUY сверху + SELL снизу" реализовано стоп-ордерами |
//| (OCO_STOP), а лимитные ордера возможны только в зеркальной |
//| геометрии (BUY ниже, SELL выше) — режим OCO_LIMIT. |
//+------------------------------------------------------------------+
#property copyright "Lonely Wolf"
#property version "1.01"
#property description "CHoCH (LW_MarketStructure_SMC) -> OCO pending orders (BUY above / SELL below) + trailing stop."
#property description "Detection uses closed bars only (no repaint)."
#include <Trade\Trade.mqh>
//============================== ENUMS ================================
enum ENUM_OCO_MODE
{
OCO_STOP = 0, // Пробой: BUY STOP выше + SELL STOP ниже
OCO_LIMIT = 1 // Откат: BUY LIMIT ниже + SELL LIMIT выше
};
enum ENUM_SL_MODE
{
SL_STRUCT = 0, // По структуре (swing-экстремум CHoCH)
SL_FIXED = 1 // Фиксированный в пунктах
};
enum ENUM_SL_ANCHOR
{
SL_ANCHOR_NEAR = 0, // Ближний swing (тейтовый SL)
SL_ANCHOR_FAR = 1 // Дальний swing (широкий SL)
};
//============================== INPUTS ===============================
input group "=== Идентификация ==="
input long InpMagic = 20261023; // Магический номер
input double InpLot = 0.10; // Объём (лоты), если риск-лот выключен
input bool InpUseRiskPercent = false; // Считать лот от риска (% баланса)
input double InpRiskPercent = 1.0; // Риск на сделку (% баланса)
input group "=== Детекция CHoCH (как в индикаторе) ==="
input int InpSwingN = 5; // Фрактал: баров с каждой стороны
input bool InpBreakOnClose = true; // Пробой подтверждается по CLOSE свечи
input int InpMaxBars = 1500; // Сколько последних закрытых баров анализировать
input bool InpTradeExistingSignal = false; // Торговать CHoCH, который уже был на старте EA
input group "=== OCO: отложенные ордера ==="
input ENUM_OCO_MODE InpOCOMode = OCO_STOP; // Тип OCO-сетапа
input double InpOffsetPoints = 100.0; // Отступ ордеров от цены (пункты)
input int InpExpireBars = 0; // Срок жизни ордеров (баров; 0 = не истекают)
input bool InpCancelOnNewSignal = true; // Снимать прошлые ордера при новом CHoCH
input bool InpOneSetupAtATime = true; // Не выставлять новый сетап, пока есть позиция
input int InpMaxSpreadPoints = 0; // Макс. спред (пункты; 0 = без контроля)
input int InpDeviationPoints = 30; // Допустимое отклонение цены (пункты)
input group "=== Стоп-лосс / тейк-профит ==="
input ENUM_SL_MODE InpSLMode = SL_STRUCT; // Режим Stop Loss
input ENUM_SL_ANCHOR InpSLAnchor = SL_ANCHOR_FAR; // Опора структурного SL (где стоит стоп)
input int InpSLBufferPoints = 30; // Буфер SL за swing-экстремум (пункты)
input double InpSLFallbackPoints = 500.0; // Резервный SL (пункты), если нет валидной структуры (0 = не входить)
input double InpMaxSLPoints = 1500.0; // Макс. ширина SL (пункты; 0 = без ограничения)
input double InpSLPoints = 500.0; // Фикс. SL от входа (пункты) для режима SL_FIXED
input double InpTPPoints = 0.0; // TP от входа (пункты; 0 = без TP)
input bool InpAllowNoSL = false; // ВНИМАНИЕ: разрешить вход без SL (риск)
input group "=== Трейлинг-стоп ==="
input bool InpUseTrailing = true; // Трейлинг-стоп
input int InpTrailStartPoints = 300; // Старт трейлинга при такой прибыли (пункты)
input int InpTrailDistPoints = 200; // Дистанция трейлинга (пункты)
input int InpTrailStepPoints = 10; // Мин. шаг перемещения SL (пункты)
input bool InpUseBreakEven = false; // Перевод в безубыток
input int InpBETriggerPoints = 200; // Прибыль для перевода в безубыток (пункты)
input int InpBEBufferPoints = 10; // Смещение SL за цену входа (пункты)
input group "=== Прочее ==="
input bool InpVerboseLog = true; // Подробный лог
//============================== TYPES ================================
struct SChochEvent
{
datetime time; // время бара подтверждения CHoCH
int dir; // +1 = бычий CHoCH (разворот вверх), -1 = медвежий
double level; // пробитый swing-уровень
double oppLevel; // противоположный swing (вторая опора для SL)
bool oppValid; // противоположный swing найден
};
struct SSymbolMetrics
{
double point;
int digits;
double tickSize;
double tickValue;
double volMin;
double volMax;
double volStep;
double stopsLevel; // в цене
double freezeLevel; // в цене
bool valid;
};
//============================== GLOBALS ==============================
CTrade trade;
SSymbolMetrics g_sym;
datetime g_lastBarTime = 0;
datetime g_lastSignalTime = 0;
ulong g_ticketBuy = 0; // тикеты текущего OCO-сетапа
ulong g_ticketSell = 0;
//============================== INIT =================================
int OnInit()
{
if(InpLot <= 0.0 && !InpUseRiskPercent)
{
Print("LW_SMC_CHOCH_OCO_EA: InpLot must be > 0 (или включите риск-лот)");
return INIT_PARAMETERS_INCORRECT;
}
if(InpSwingN < 1 || InpMaxBars < InpSwingN * 2 + 10)
{
Print("LW_SMC_CHOCH_OCO_EA: invalid detection inputs");
return INIT_PARAMETERS_INCORRECT;
}
if(InpSLMode == SL_FIXED && InpSLPoints <= 0.0)
{
Print("LW_SMC_CHOCH_OCO_EA: InpSLPoints must be > 0 in SL_FIXED mode");
return INIT_PARAMETERS_INCORRECT;
}
trade.SetExpertMagicNumber(InpMagic);
trade.SetDeviationInPoints(InpDeviationPoints);
trade.SetTypeFillingBySymbol(_Symbol);
trade.SetAsyncMode(false);
RefreshSymbolMetrics();
if(InpSLMode == SL_STRUCT && InpAllowNoSL)
Print("LW_SMC_CHOCH_OCO_EA: WARNING - SL по структуре может отсутствовать, вход без SL разрешён (InpAllowNoSL=true).");
if(!InpUseTrailing && !InpUseBreakEven && InpTPPoints <= 0.0)
Print("LW_SMC_CHOCH_OCO_EA: WARNING - выход не управляется: трейлинг/безубыток выключены и TP=0.");
if(InpOCOMode == OCO_LIMIT)
Print("LW_SMC_CHOCH_OCO_EA: OCO_LIMIT - BUY LIMIT ниже цены, SELL LIMIT выше цены (зеркальная геометрия).");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Отложенные ордера намеренно не удаляем: ими управляет OCO и срок жизни.
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnTick()
{
ManageOCO(); // OCO: снятие парного ордера + истечение
ManageTrailing(); // трейлинг-стоп / безубыток
datetime bt = (datetime)SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_LASTBAR_DATE);
if(bt == g_lastBarTime)
return;
g_lastBarTime = bt;
RefreshSymbolMetrics();
ProcessNewBar();
}
//============================== SYMBOL METRICS ========================
void RefreshSymbolMetrics()
{
g_sym.point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
g_sym.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
g_sym.tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(g_sym.tickSize <= 0.0)
g_sym.tickSize = g_sym.point;
g_sym.tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
g_sym.volMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
g_sym.volMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
g_sym.volStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
g_sym.stopsLevel = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * g_sym.point;
g_sym.freezeLevel= (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL) * g_sym.point;
g_sym.valid = (g_sym.point > 0.0);
}
//============================== MAIN LOGIC ===========================
void ProcessNewBar()
{
if(!g_sym.valid)
return;
SChochEvent ev;
if(!FindLastCHoCH(ev))
return; // в окне анализа CHoCH нет
if(g_lastSignalTime == 0)
{
if(!InpTradeExistingSignal)
{
g_lastSignalTime = ev.time; // сигналы до старта EA не торгуем
if(InpVerboseLog)
PrintFormat("LW_SMC_CHOCH_OCO_EA: старт, CHoCH %s пропущен (InpTradeExistingSignal=false)",
TimeToString(ev.time, TIME_DATE | TIME_MINUTES));
return;
}
}
else
if(ev.time == g_lastSignalTime)
return; // этот CHoCH уже обработан
g_lastSignalTime = ev.time;
if(!IsTradeAllowed())
return;
if(InpOneSetupAtATime && CountOurPositions() > 0)
{
if(InpVerboseLog)
PrintFormat("LW_SMC_CHOCH_OCO_EA: CHoCH %s пропущен - открыта позиция",
TimeToString(ev.time, TIME_DATE | TIME_MINUTES));
return;
}
if(InpCancelOnNewSignal)
CancelAllOurPendings();
if(InpMaxSpreadPoints > 0)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask > 0.0 && bid > 0.0)
{
double spreadPts = (ask - bid) / g_sym.point;
if(spreadPts > InpMaxSpreadPoints)
{
PrintFormat("LW_SMC_CHOCH_OCO_EA: CHoCH %s пропущен - спред %.1f > %.1f пт",
TimeToString(ev.time, TIME_DATE | TIME_MINUTES), spreadPts, (double)InpMaxSpreadPoints);
return;
}
}
}
PlaceOCO(ev);
}
//+------------------------------------------------------------------+
//| Поиск последнего CHoCH на закрытых барах |
//| (логика BOS/CHoCH повторяет индикатор LW_MarketStructure_SMC) |
//+------------------------------------------------------------------+
bool FindLastCHoCH(SChochEvent &ev)
{
int need = InpMaxBars + InpSwingN * 2 + 10;
MqlRates rates[];
ArraySetAsSeries(rates, false);
int n = CopyRates(_Symbol, PERIOD_CURRENT, 0, need, rates);
if(n < InpSwingN * 2 + 10)
return false;
int last = n - 2; // последний ЗАКРЫТЫЙ бар (без repaint)
if(last < InpSwingN * 2)
return false;
int start = (int)MathMax((double)InpSwingN, (double)(last - InpMaxBars + 1));
//--- Pass 1: фрактальные swing-экстремумы (как в индикаторе)
char sh[], sl[];
ArrayResize(sh, n);
ArrayResize(sl, n);
ArrayInitialize(sh, 0);
ArrayInitialize(sl, 0);
for(int i = start; i <= last - InpSwingN; i++)
{
bool isHigh = true, isLow = true;
for(int k = 1; k <= InpSwingN; k++)
{
if(rates[i].high <= rates[i - k].high || rates[i].high <= rates[i + k].high)
isHigh = false;
if(rates[i].low >= rates[i - k].low || rates[i].low >= rates[i + k].low)
isLow = false;
}
if(isHigh)
sh[i] = 1;
if(isLow)
sl[i] = 1;
}
//--- Pass 2: BOS / CHoCH
bool found = false;
int trend = 0; // 1 = up, -1 = down, 0 = neutral
double refHigh = 0.0, refLow = 0.0;
int refHighBar = -1, refLowBar = -1;
bool haveHigh = false, haveLow = false;
for(int i = start + InpSwingN; i <= last; i++)
{
int j = i - InpSwingN; // swing подтверждён на баре j
if(sh[j] == 1)
{
refHigh = rates[j].high;
refHighBar = j;
haveHigh = true;
}
if(sl[j] == 1)
{
refLow = rates[j].low;
refLowBar = j;
haveLow = true;
}
double upPx = InpBreakOnClose ? rates[i].close : rates[i].high;
double downPx = InpBreakOnClose ? rates[i].close : rates[i].low;
if(haveHigh && upPx > refHigh) // бычий пробой
{
bool isBOS = (trend != -1); // против нисходящего тренда = CHoCH
if(!isBOS)
{
ev.time = rates[i].time;
ev.dir = 1;
ev.level = refHigh;
ev.oppLevel = refLow;
ev.oppValid = haveLow;
found = true;
}
trend = 1;
haveHigh = false; // уровень израсходован
}
else
if(haveLow && downPx < refLow) // медвежий пробой
{
bool isBOS = (trend != 1); // против восходящего тренда = CHoCH
if(!isBOS)
{
ev.time = rates[i].time;
ev.dir = -1;
ev.level = refLow;
ev.oppLevel = refHigh;
ev.oppValid = haveHigh;
found = true;
}
trend = -1;
haveLow = false;
}
}
return found;
}
//============================== OCO PLACEMENT ========================
void PlaceOCO(const SChochEvent &ev)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask <= 0.0 || bid <= 0.0)
return;
g_ticketBuy = 0; // новая группа: старые танкеты недействительны
g_ticketSell = 0;
double point = g_sym.point;
double minDist = MathMax(g_sym.stopsLevel, g_sym.point);
double off = InpOffsetPoints * point;
if(off < minDist) // соблюдаем Stops Level
off = minDist;
double buyPx, sellPx;
if(InpOCOMode == OCO_STOP)
{
buyPx = ask + off; // BUY STOP выше цены
sellPx = bid - off; // SELL STOP ниже цены
}
else
{
buyPx = ask - off; // BUY LIMIT ниже цены
sellPx = bid + off; // SELL LIMIT выше цены
}
buyPx = NormalizeDouble(buyPx, g_sym.digits);
sellPx = NormalizeDouble(sellPx, g_sym.digits);
string cmt = "CHOCH_" + (string)(long)ev.time;
//--- BUY сторона
double buySL = 0.0;
bool buySLok = BuildSL(1, buyPx, ev, buySL);
if(buySLok || InpAllowNoSL)
{
double buyTP = BuildTP(1, buyPx);
double lot = CalcLot(buyPx, buySL);
if(lot > 0.0)
{
bool ok = (InpOCOMode == OCO_STOP)
? trade.BuyStop(lot, buyPx, _Symbol, buySL, buyTP, ORDER_TIME_GTC, 0, cmt)
: trade.BuyLimit(lot, buyPx, _Symbol, buySL, buyTP, ORDER_TIME_GTC, 0, cmt);
if(ok && trade.ResultRetcode() == TRADE_RETCODE_DONE)
{
g_ticketBuy = trade.ResultOrder();
if(InpVerboseLog)
PrintFormat("LW_SMC_CHOCH_OCO_EA: %s BUY %.2f @ %.5f SL %.5f TP %.5f #%I64u",
(InpOCOMode == OCO_STOP ? "BUY STOP" : "BUY LIMIT"),
lot, buyPx, buySL, buyTP, g_ticketBuy);
}
else
PrintFormat("LW_SMC_CHOCH_OCO_EA: BUY pending failed: %d %s",
trade.ResultRetcode(), trade.ResultComment());
}
}
else
PrintFormat("LW_SMC_CHOCH_OCO_EA: BUY ордер не выставлен - нет валидного SL (структура и резерв = 0)");
//--- SELL сторона
double sellSL = 0.0;
bool sellSLok = BuildSL(-1, sellPx, ev, sellSL);
if(sellSLok || InpAllowNoSL)
{
double sellTP = BuildTP(-1, sellPx);
double lot = CalcLot(sellPx, sellSL);
if(lot > 0.0)
{
bool ok = (InpOCOMode == OCO_STOP)
? trade.SellStop(lot, sellPx, _Symbol, sellSL, sellTP, ORDER_TIME_GTC, 0, cmt)
: trade.SellLimit(lot, sellPx, _Symbol, sellSL, sellTP, ORDER_TIME_GTC, 0, cmt);
if(ok && trade.ResultRetcode() == TRADE_RETCODE_DONE)
{
g_ticketSell = trade.ResultOrder();
if(InpVerboseLog)
PrintFormat("LW_SMC_CHOCH_OCO_EA: %s SELL %.2f @ %.5f SL %.5f TP %.5f #%I64u",
(InpOCOMode == OCO_STOP ? "SELL STOP" : "SELL LIMIT"),
lot, sellPx, sellSL, sellTP, g_ticketSell);
}
else
PrintFormat("LW_SMC_CHOCH_OCO_EA: SELL pending failed: %d %s",
trade.ResultRetcode(), trade.ResultComment());
}
}
else
PrintFormat("LW_SMC_CHOCH_OCO_EA: SELL ордер не выставлен - нет валидного SL (структура и резерв = 0)");
if(InpVerboseLog)
PrintFormat("LW_SMC_CHOCH_OCO_EA: CHoCH %s (%s) level %.5f -> OCO %s",
TimeToString(ev.time, TIME_DATE | TIME_MINUTES),
(ev.dir > 0 ? "bullish" : "bearish"), ev.level,
(InpOCOMode == OCO_STOP ? "stops" : "limits"));
}
//============================== SL / TP ==============================
//+------------------------------------------------------------------+
//| SL: фиксированный или по структуре. |
//| Для BUY берём опору НИЖЕ входа, для SELL — ВЫШЕ входа; |
//| из валидных опор (пробитый swing и противоположный swing) |
//| выбираем ближнюю (тейтовую) или дальнюю (широкую) по InpSLAnchor. |
//| Если валидной опоры нет — резервный SL в пунктах (если задан). |
//+------------------------------------------------------------------+
//| Ограничение ширины SL: если структурная опора слишком далеко, |
//| SL подрезается до InpMaxSLPoints. |
//+------------------------------------------------------------------+
double CapSL(const int dir, const double entry, const double sl)
{
if(InpMaxSLPoints <= 0.0 || sl <= 0.0)
return sl;
double maxD = InpMaxSLPoints * g_sym.point;
if(MathAbs(entry - sl) <= maxD)
return sl;
return (dir > 0) ? (entry - maxD) : (entry + maxD);
}
//+------------------------------------------------------------------+
bool BuildSL(const int dir, const double entry, const SChochEvent &ev, double &sl)
{
sl = 0.0;
double point = g_sym.point;
double minDist = MathMax(g_sym.stopsLevel, g_sym.point);
if(InpSLMode == SL_FIXED)
{
double d = InpSLPoints * point;
if(d < minDist)
d = minDist;
sl = (dir > 0) ? entry - d : entry + d;
sl = NormalizeDouble(sl, g_sym.digits);
return true;
}
double anchors[2];
int cnt = 0;
if(ev.level > 0.0)
anchors[cnt++] = ev.level;
if(ev.oppValid && ev.oppLevel > 0.0)
anchors[cnt++] = ev.oppLevel;
double buf = InpSLBufferPoints * point;
bool found = false;
for(int k = 0; k < cnt; k++)
{
double cand = (dir > 0) ? (anchors[k] - buf) : (anchors[k] + buf);
if(dir > 0)
{
if(cand > entry - minDist) // опора выше/слишком близко к входу
continue;
if(!found || (InpSLAnchor == SL_ANCHOR_FAR ? (cand < sl) : (cand > sl)))
{
sl = cand;
found = true;
}
}
else
{
if(cand < entry + minDist)
continue;
if(!found || (InpSLAnchor == SL_ANCHOR_FAR ? (cand > sl) : (cand < sl)))
{
sl = cand;
found = true;
}
}
}
if(!found)
{
// Структурной опоры нет (цена уже ушла от swing): резервный SL в пунктах.
if(InpSLFallbackPoints <= 0.0)
return false;
double d = InpSLFallbackPoints * point;
if(d < minDist)
d = minDist;
sl = CapSL(dir, entry, (dir > 0) ? entry - d : entry + d);
sl = NormalizeDouble(sl, g_sym.digits);
return true;
}
sl = CapSL(dir, entry, sl);
sl = NormalizeDouble(sl, g_sym.digits);
return true;
}
//+------------------------------------------------------------------+
double BuildTP(const int dir, const double entry)
{
if(InpTPPoints <= 0.0)
return 0.0;
double d = InpTPPoints * g_sym.point;
double tp = (dir > 0) ? entry + d : entry - d;
return NormalizeDouble(tp, g_sym.digits);
}
//============================== SIZING ===============================
double NormalizeLot(double lot)
{
double step = (g_sym.volStep > 0.0) ? g_sym.volStep : 0.01;
lot = MathRound(lot / step) * step;
if(lot < g_sym.volMin)
lot = g_sym.volMin;
if(lot > g_sym.volMax)
lot = g_sym.volMax;
return NormalizeDouble(lot, 3);
}
//+------------------------------------------------------------------+
double CalcLot(const double entry, const double sl)
{
if(!InpUseRiskPercent || sl <= 0.0)
return NormalizeLot(InpLot);
double slDist = MathAbs(entry - sl);
if(slDist <= 0.0 || g_sym.tickSize <= 0.0 || g_sym.tickValue <= 0.0)
return NormalizeLot(InpLot);
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskMoney = balance * InpRiskPercent / 100.0;
double lossPerLot = (slDist / g_sym.tickSize) * g_sym.tickValue;
if(lossPerLot <= 0.0)
return NormalizeLot(InpLot);
return NormalizeLot(riskMoney / lossPerLot);
}
//============================== OCO MANAGEMENT ======================
int CollectOurPendings(ulong &tickets[])
{
ArrayResize(tickets, 0);
int cnt = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong ticket = OrderGetTicket(i);
if(ticket == 0)
continue;
if(!IsOurPending(ticket))
continue;
cnt++;
ArrayResize(tickets, cnt);
tickets[cnt - 1] = ticket;
}
return cnt;
}
//+------------------------------------------------------------------+
bool IsOurPending(const ulong ticket)
{
if(ticket == 0)
return false;
if(!OrderSelect(ticket))
return false;
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
return false;
if(OrderGetInteger(ORDER_MAGIC) != InpMagic)
return false;
ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
return (t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT ||
t == ORDER_TYPE_BUY_STOP || t == ORDER_TYPE_SELL_STOP ||
t == ORDER_TYPE_BUY_STOP_LIMIT || t == ORDER_TYPE_SELL_STOP_LIMIT);
}
//+------------------------------------------------------------------+
int CountOurPositions()
{
int cnt = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
cnt++;
}
return cnt;
}
//+------------------------------------------------------------------+
bool GroupHasPosition(const string grp)
{
if(grp == "")
return false;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
if(PositionGetString(POSITION_COMMENT) == grp)
return true;
}
return false;
}
//+------------------------------------------------------------------+
void DeletePending(const ulong ticket)
{
if(ticket == 0)
return;
if(!IsOurPending(ticket))
return; // ордер уже исполнен/снят — не трогаем
if(!trade.OrderDelete(ticket))
PrintFormat("LW_SMC_CHOCH_OCO_EA: OrderDelete #%I64u failed: %d %s",
ticket, trade.ResultRetcode(), trade.ResultComment());
else
if(InpVerboseLog)
PrintFormat("LW_SMC_CHOCH_OCO_EA: снят отложенный ордер #%I64u (OCO)", ticket);
}
//+------------------------------------------------------------------+
void CancelAllOurPendings()
{
ulong tickets[];
int cnt = CollectOurPendings(tickets);
for(int k = 0; k < cnt; k++)
DeletePending(tickets[k]);
}
//+------------------------------------------------------------------+
void ManageOCO()
{
//--- (a) точная связка по тикетам текущего сетапа: сработал один -> снять второй
if(g_ticketBuy != 0 && g_ticketSell != 0)
{
bool buyFilled = PositionSelectByTicket(g_ticketBuy);
bool sellFilled = PositionSelectByTicket(g_ticketSell);
if(buyFilled && !sellFilled)
{
DeletePending(g_ticketSell);
g_ticketSell = 0;
}
else
if(sellFilled && !buyFilled)
{
DeletePending(g_ticketBuy);
g_ticketBuy = 0;
}
}
ulong tickets[];
int cnt = CollectOurPendings(tickets);
if(cnt == 0)
return;
//--- (b)/(c)/(d) страховки и срок жизни
for(int k = 0; k < cnt; k++)
{
ulong ticket = tickets[k];
if(!OrderSelect(ticket))
continue;
string grp = OrderGetString(ORDER_COMMENT);
// (b) по группе уже открыта позиция -> это исполненный OCO, снимаем остаток
if(GroupHasPosition(grp))
{
DeletePending(ticket);
continue;
}
// (c) срок жизни ордера истёк
if(InpExpireBars > 0)
{
datetime setup = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
if(TimeCurrent() - setup >= (long)InpExpireBars * PeriodSeconds())
{
DeletePending(ticket);
continue;
}
}
// (d) страховка после рестарта EA: позиция есть, а связка неизвестна
if(InpOneSetupAtATime && CountOurPositions() > 0)
DeletePending(ticket);
}
}
//============================== TRAILING ==============================
void ManageTrailing()
{
if(!InpUseTrailing && !InpUseBreakEven)
return;
double point = g_sym.point;
int digits = g_sym.digits;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(bid <= 0.0 || ask <= 0.0)
return;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
ENUM_POSITION_TYPE pt = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double price = (pt == POSITION_TYPE_BUY) ? bid : ask;
double profitPts = (pt == POSITION_TYPE_BUY) ? (bid - entry) / point
: (entry - ask) / point;
double bestSL = sl;
if(pt == POSITION_TYPE_BUY)
{
if(InpUseBreakEven && profitPts >= InpBETriggerPoints)
{
double be = NormalizeDouble(entry + InpBEBufferPoints * point, digits);
if(be > bestSL)
bestSL = be;
}
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
{
double tr = NormalizeDouble(price - InpTrailDistPoints * point, digits);
if(tr > bestSL)
bestSL = tr;
}
double maxSL = NormalizeDouble(price - MathMax(g_sym.stopsLevel, point), digits);
if(bestSL > maxSL)
bestSL = maxSL; // Stops Level
if(tp > 0.0 && bestSL > tp - point)
bestSL = NormalizeDouble(tp - point, digits); // SL ниже TP
if(bestSL <= 0.0)
continue;
if(sl > 0.0 && bestSL <= sl + InpTrailStepPoints * point)
continue; // шаг перемещения
if(sl == 0.0 || bestSL > sl)
if(!trade.PositionModify(ticket, bestSL, tp))
PrintFormat("LW_SMC_CHOCH_OCO_EA: BUY SL modify #%I64u failed: %d %s",
ticket, trade.ResultRetcode(), trade.ResultComment());
}
else
{
if(InpUseBreakEven && profitPts >= InpBETriggerPoints)
{
double be = NormalizeDouble(entry - InpBEBufferPoints * point, digits);
if(bestSL == 0.0 || be < bestSL)
bestSL = be;
}
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
{
double tr = NormalizeDouble(price + InpTrailDistPoints * point, digits);
if(bestSL == 0.0 || tr < bestSL)
bestSL = tr;
}
double minSL = NormalizeDouble(price + MathMax(g_sym.stopsLevel, point), digits);
if(bestSL == 0.0 || bestSL < minSL)
bestSL = minSL; // Stops Level
if(tp > 0.0 && bestSL < tp + point)
bestSL = NormalizeDouble(tp + point, digits); // SL выше TP
if(bestSL <= 0.0)
continue;
if(sl > 0.0 && bestSL >= sl - InpTrailStepPoints * point)
continue; // шаг перемещения
if(sl == 0.0 || bestSL < sl)
if(!trade.PositionModify(ticket, bestSL, tp))
PrintFormat("LW_SMC_CHOCH_OCO_EA: SELL SL modify #%I64u failed: %d %s",
ticket, trade.ResultRetcode(), trade.ResultComment());
}
}
}
//============================== COMMON ===============================
bool IsTradeAllowed()
{
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
{
Print("LW_SMC_CHOCH_OCO_EA: trading is disabled in the terminal");
return false;
}
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
{
Print("LW_SMC_CHOCH_OCO_EA: trading is disabled for this EA");
return false;
}
if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
{
Print("LW_SMC_CHOCH_OCO_EA: trading is disabled for this account");
return false;
}
if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
{
Print("LW_SMC_CHOCH_OCO_EA: automated trading is disabled for this account");
return false;
}
return true;
}
//+------------------------------------------------------------------+