//+------------------------------------------------------------------+ //| 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 //============================== 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; } //+------------------------------------------------------------------+