//+------------------------------------------------------------------+ //| Pivot + Fractals EA v2.0 (single TF engine) | //+------------------------------------------------------------------+ #property strict #include #include #include #include #include #include CTrade trade; CPositionInfo position; COrderInfo order; CArrayLong DistTickets; CArrayDatetime DistStartTimes; //--- inputs input double Lots = 0.01; input int Slippage = 20; input int Magic = 260319; input int BB_Period = 20; input double BB_Deviation = 2.0; input bool ShowVisual = true; string VIS_PREFIX = "PFV_"; //--- индикаторы (единый движок) int handleFractals_M = INVALID_HANDLE; int handleFractals_H = INVALID_HANDLE; int handleBB = INVALID_HANDLE; int handleAO_M = INVALID_HANDLE; // быстрый AO (tfM) int handleAO_H = INVALID_HANDLE; // HTF AO (tfH) int handleATR_M = INVALID_HANDLE; int handleATRSlow = INVALID_HANDLE; int handleMA_H = INVALID_HANDLE; int ATR_Period = 14; int ATR_Slow_Period = 14; //--- TF int tfM = PERIOD_M5; int tfH = PERIOD_H1; int pending_tfM = PERIOD_M5; int pending_tfH = PERIOD_H1; //--- время баров datetime lastBarTimeMinutes = 0; datetime lastHTFBar = 0; datetime lastExitTime = 0; datetime aoFlipTime = 0; datetime lastTFUpdate = 0; //--- состояние сигнала double lastEntry = 0.0; double lastStop = 0.0; double lastTp = 0.0; int lastTrend = 0; int trendAtPlacement = 0; double lastPivot = 0.0; double lastLot = 0.0; //--- AO flip state int aoFlipDirection = 0; //--- AutoMinStopEngine double extraEffMinStop = 0.0; //--- флаги bool canPlaceNewOrder = true; //--- лог int logHandle = INVALID_HANDLE; string logFileName = ""; int logCurrentDate = 0; bool htfReady = false; ulong htfInitTime = 0; bool mtfReady = false; ulong mtfInitTime = 0; //--- фрактальные буферы (глобальные) double upHTF[]; // фракталы вверх double dnHTF[]; // фракталы вниз //+------------------------------------------------------------------+ //| Вспомогательные: TF → строка | //+------------------------------------------------------------------+ string TimeframeToString(int tf) { switch(tf) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; } return IntegerToString(tf); } //+------------------------------------------------------------------+ //| Логирование | //+------------------------------------------------------------------+ void Log(string level, string msg) { if(logHandle == INVALID_HANDLE) return; string timeStr = TimeToString(TimeCurrent(), TIME_SECONDS); string line = "[" + timeStr + "] [" + level + "] " + msg; FileSeek(logHandle, 0, SEEK_END); FileWrite(logHandle, line); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool InitLog() { datetime now = TimeCurrent(); MqlDateTime dt; TimeToStruct(now, dt); int today = dt.year*10000 + dt.mon*100 + dt.day; logCurrentDate = today; string todayStr = IntegerToString(dt.year) + "-" + StringFormat("%02d", dt.mon) + "-" + StringFormat("%02d", dt.day); logFileName = "EA_" + _Symbol + "_" + todayStr + ".log"; datetime y = now - 86400; MqlDateTime dty; TimeToStruct(y, dty); string yStr = IntegerToString(dty.year) + "-" + StringFormat("%02d", dty.mon) + "-" + StringFormat("%02d", dty.day); string yFile = "EA_" + _Symbol + "_" + yStr + ".log"; FileDelete(yFile); logHandle = FileOpen(logFileName, FILE_WRITE|FILE_READ|FILE_TXT|FILE_COMMON); if(logHandle == INVALID_HANDLE) { Print("❌ Cannot open log file: ", logFileName, " err=", GetLastError()); return(false); } FileSeek(logHandle, 0, SEEK_END); Log("INFO", "Log started for symbol " + _Symbol); return(true); } //+------------------------------------------------------------------+ //| Dynamic timeframe selection | //+------------------------------------------------------------------+ int GetDynamicTF(double relSpread) { if(relSpread < 0.0001) return PERIOD_M5; if(relSpread < 0.0003) return PERIOD_M15; if(relSpread < 0.0008) return PERIOD_M30; if(relSpread < 0.0020) return PERIOD_H1; if(relSpread < 0.0050) return PERIOD_H4; if(relSpread < 0.0100) return PERIOD_D1; return PERIOD_W1; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetDynamicHTF(double relSpread) { if(relSpread < 0.0001) return PERIOD_H1; if(relSpread < 0.0003) return PERIOD_H1; if(relSpread < 0.0008) return PERIOD_H4; if(relSpread < 0.0020) return PERIOD_D1; if(relSpread < 0.0050) return PERIOD_D1; return PERIOD_W1; } //+------------------------------------------------------------------+ //| Единый TF/индикатор движок | //+------------------------------------------------------------------+ void UpdateDynamicTF(double relSpread) { pending_tfM = GetDynamicTF(relSpread); pending_tfH = GetDynamicHTF(relSpread); } //+------------------------------------------------------------------+ //| GetTrendAndPivot на HTF | //+------------------------------------------------------------------+ int GetTrendAndPivot(double &pivot) { // Проверяем валидность handle if(handleAO_H == INVALID_HANDLE || handleMA_H == INVALID_HANDLE) { Print("⛔ HTF handles invalid"); return 0; } // Проверяем готовность буферов double aoTest[3]; double maTest[1]; int aoCount = CopyBuffer(handleAO_H, 0, 1, 3, aoTest); int maCount = CopyBuffer(handleMA_H, 0, 2, 1, maTest); if(aoCount < 3 || maCount < 1) { PrintFormat("⛔ HTF buffers not ready (ao=%d ma=%d)", aoCount, maCount); return 0; } // Теперь индикаторы готовы — читаем данные double h1 = iHigh(_Symbol, (ENUM_TIMEFRAMES)tfH, 2); double h2 = iHigh(_Symbol, (ENUM_TIMEFRAMES)tfH, 3); double l1 = iLow(_Symbol, (ENUM_TIMEFRAMES)tfH, 2); double l2 = iLow(_Symbol, (ENUM_TIMEFRAMES)tfH, 3); double ao0 = aoTest[0]; double ao1 = aoTest[1]; double ma = maTest[0]; double closePrev = iClose(_Symbol, (ENUM_TIMEFRAMES)tfH, 2); bool upStruct = (h1 > h2 && l1 > l2); bool downStruct = (h1 < h2 && l1 < l2); bool aoUp = (ao0 > ao1); bool aoDown = (ao0 < ao1); bool maUp = (closePrev > ma); bool maDown = (closePrev < ma); pivot = (h1 + l1 + closePrev) / 3.0; PrintFormat("TREND DEBUG: h1=%.5f h2=%.5f l1=%.5f l2=%.5f | upStruct=%d downStruct=%d | ao0=%.5f ao1=%.5f aoUp=%d aoDown=%d | ma=%.5f closePrev=%.5f maUp=%d maDown=%d | pivot=%.5f", h1,h2,l1,l2, upStruct,downStruct, ao0,ao1, aoUp,aoDown, ma,closePrev, maUp,maDown, pivot); // Строгий тренд-фильтр if(upStruct && aoUp && maUp) return +1; if(downStruct && aoDown && maDown) return -1; int prevTrend = lastTrend; int newTrend = 0; if(upStruct && aoUp && maUp) newTrend = +1; else if(downStruct && aoDown && maDown) newTrend = -1; else newTrend = 0; if(newTrend != prevTrend) { ExplainTrendChange(prevTrend, newTrend, upStruct, downStruct, ao0, ao1, closePrev, ma); } return newTrend; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ResetInternalState() { ClearVisualObjects(); lastEntry = 0; lastStop = 0; lastTp = 0; canPlaceNewOrder = true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ResetSignalState() { // Закрываем позиции по символу int total = (int)PositionsTotal(); for(int i = total - 1; i >= 0; i--) { if(position.SelectByIndex(i)) { if(position.Symbol() == _Symbol) { ulong ticket = position.Ticket(); trade.PositionClose(ticket); } } } // Удаляем pending ордера по символу int orders = (int)OrdersTotal(); for(int i = orders - 1; i >= 0; i--) { if(order.SelectByIndex(i)) { if(order.Symbol() == _Symbol) { ulong ticket = order.Ticket(); trade.OrderDelete(ticket); } } } Print("🔁 Reset signal state (no position, no pending)"); } //+------------------------------------------------------------------+ //| Visual helpers (pivot, levels, trail) | //+------------------------------------------------------------------+ void ClearVisualObjects() { if(!ShowVisual) return; int total = (int)ObjectsTotal(0, 0, -1); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(0, i, 0, -1); if(StringFind(name, VIS_PREFIX) == 0) ObjectDelete(0, name); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void DrawTrailSL(ulong ticket, double oldSL, double newSL) { if(!ShowVisual) return; datetime t = TimeCurrent(); string base = IntegerToString((int)ticket) + "_" + IntegerToString((int)t); string lineName = VIS_PREFIX + "TRAIL_LINE_" + base; ObjectCreate(0, lineName, OBJ_TREND, 0, t, oldSL, t, newSL); ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrGold); ObjectSetInteger(0, lineName, OBJPROP_STYLE, STYLE_DOT); string txtName = VIS_PREFIX + "TRAIL_TEXT_" + base; ObjectCreate(0, txtName, OBJ_TEXT, 0, t, newSL); ObjectSetString(0, txtName, OBJPROP_TEXT, "TRAIL SL"); ObjectSetInteger(0, txtName, OBJPROP_COLOR, clrGold); ObjectSetInteger(0, txtName, OBJPROP_FONTSIZE, 8); } //+------------------------------------------------------------------+ //| AutoMinStopEngine | //+------------------------------------------------------------------+ double AMSE_BaseMinStop() { long stopLevelPoints = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); double baseMinStop = (stopLevelPoints + 3) * _Point; return(baseMinStop); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double AMSE_ProfileMin() { double profileMin = 0.0; if(StringFind(_Symbol, "EURUSD") >= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "EURUSD.r")>= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "GBPUSD") >= 0) profileMin = 12 * _Point; if(StringFind(_Symbol, "GBPUSD.r")>= 0) profileMin = 12 * _Point; if(StringFind(_Symbol, "USDJPY") >= 0) profileMin = 0.01; if(StringFind(_Symbol, "USDJPY.r")>= 0) profileMin = 0.01; if(StringFind(_Symbol, "USDCHF") >= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "USDCHF.r")>= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "USDCAD") >= 0) profileMin = 12 * _Point; if(StringFind(_Symbol, "USDCAD.r")>= 0) profileMin = 12 * _Point; if(StringFind(_Symbol, "AUDUSD") >= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "AUDUSD.r")>= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "NZDUSD") >= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "NZDUSD.r")>= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "EURGBP") >= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "EURGBP.r")>= 0) profileMin = 10 * _Point; if(StringFind(_Symbol, "EURJPY") >= 0) profileMin = 0.01; if(StringFind(_Symbol, "EURJPY.r")>= 0) profileMin = 0.01; if(StringFind(_Symbol, "GBPJPY") >= 0) profileMin = 0.01; if(StringFind(_Symbol, "GBPJPY.r")>= 0) profileMin = 0.01; if(StringFind(_Symbol, "AUDJPY") >= 0) profileMin = 0.01; if(StringFind(_Symbol, "AUDJPY.r")>= 0) profileMin = 0.01; if(StringFind(_Symbol, "CADJPY") >= 0) profileMin = 0.01; if(StringFind(_Symbol, "CADJPY.r")>= 0) profileMin = 0.01; if(StringFind(_Symbol, "CHFJPY") >= 0) profileMin = 0.01; if(StringFind(_Symbol, "CHFJPY.r")>= 0) profileMin = 0.01; if(StringFind(_Symbol, "EURAUD") >= 0) profileMin = 15 * _Point; if(StringFind(_Symbol, "EURAUD.r")>= 0) profileMin = 15 * _Point; if(StringFind(_Symbol, "EURNZD") >= 0) profileMin = 15 * _Point; if(StringFind(_Symbol, "EURNZD.r")>= 0) profileMin = 15 * _Point; if(StringFind(_Symbol, "GBPAUD") >= 0) profileMin = 20 * _Point; if(StringFind(_Symbol, "GBPAUD.r")>= 0) profileMin = 20 * _Point; if(StringFind(_Symbol, "GBPNZD") >= 0) profileMin = 20 * _Point; if(StringFind(_Symbol, "GBPNZD.r")>= 0) profileMin = 20 * _Point; if(StringFind(_Symbol, "BTCUSD") >= 0) profileMin = 50 * _Point; if(StringFind(_Symbol, "BTCUSD.r")>= 0) profileMin = 50 * _Point; if(StringFind(_Symbol, "ETHUSD") >= 0) profileMin = 20 * _Point; if(StringFind(_Symbol, "ETHUSD.r")>= 0) profileMin = 20 * _Point; return(profileMin); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double AMSE_VolatilityMin() { if(handleATR_M == INVALID_HANDLE) return 0.0; double atrBuffer[]; if(CopyBuffer(handleATR_M, 0, 0, 1, atrBuffer) <= 0) return 0.0; double atr = atrBuffer[0]; if(atr <= 0.0) return 0.0; return atr * 0.5; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double AMSE_GetEffMinStop() { double baseMinStop = AMSE_BaseMinStop(); double profileMin = AMSE_ProfileMin(); double volaMin = AMSE_VolatilityMin(); double effMinStop = baseMinStop; effMinStop = MathMax(effMinStop, baseMinStop + extraEffMinStop); if(profileMin > 0.0) effMinStop = MathMax(effMinStop, profileMin); if(volaMin > 0.0) effMinStop = MathMax(effMinStop, volaMin); double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; effMinStop = MathMax(effMinStop, stopLevel); effMinStop = MathMax(effMinStop, 10 * _Point); return(effMinStop); } //+------------------------------------------------------------------+ //| AO / выход по AO | //+------------------------------------------------------------------+ int GetAOColor() { double ao0 = iAO(_Symbol, PERIOD_CURRENT); double ao1 = iAO(_Symbol, PERIOD_CURRENT); if(ao0 > ao1) return +1; if(ao0 < ao1) return -1; return 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong GetMyPosition() { int total = PositionsTotal(); for(int i = total - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; long magic = PositionGetInteger(POSITION_MAGIC); string sym = PositionGetString(POSITION_SYMBOL); if(magic == Magic && sym == _Symbol) return ticket; } return 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CloseMyPosition(ulong ticket) { if(!PositionSelectByTicket(ticket)) return; MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); int type = (int)PositionGetInteger(POSITION_TYPE); double vol = PositionGetDouble(POSITION_VOLUME); req.action = TRADE_ACTION_DEAL; req.symbol = _Symbol; req.volume = vol; req.magic = Magic; if(type == POSITION_TYPE_BUY) { req.type = ORDER_TYPE_SELL; req.price = SymbolInfoDouble(_Symbol, SYMBOL_BID); } else { req.type = ORDER_TYPE_BUY; req.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); } if(OrderSend(req, res)) LogTF(StringFormat("✅ Position closed by AO reversal, ticket=", ticket)); else LogTF(StringFormat("❌ Failed to close position by AO, retcode=", res.retcode)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CheckAOExit() { ulong ticket = GetMyPosition(); if(ticket == 0) return; if(!PositionSelectByTicket(ticket)) return; int type = (int)PositionGetInteger(POSITION_TYPE); int aoColor = GetAOColor(); if(type == POSITION_TYPE_BUY) { if(aoColor == -1 && aoFlipDirection == 0) { aoFlipDirection = -1; aoFlipTime = iTime(_Symbol, PERIOD_CURRENT, 0); LogTF("AO flip detected: BUY → first RED bar"); return; } if(aoFlipDirection == -1) { datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); if(currentBarTime != aoFlipTime) { if(aoColor == -1) { LogTF("⚠️ AO confirmed reversal: BUY → RED. Closing BUY."); CloseMyPosition(ticket); lastExitTime = TimeCurrent(); } aoFlipDirection = 0; } } } if(type == POSITION_TYPE_SELL) { if(aoColor == +1 && aoFlipDirection == 0) { aoFlipDirection = +1; aoFlipTime = iTime(_Symbol, PERIOD_CURRENT, 0); LogTF("AO flip detected: SELL → first GREEN bar"); return; } if(aoFlipDirection == +1) { datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); if(currentBarTime != aoFlipTime) { if(aoColor == +1) { LogTF("⚠️ AO confirmed reversal: SELL → GREEN. Closing SELL."); CloseMyPosition(ticket); lastExitTime = TimeCurrent(); } aoFlipDirection = 0; } } } } //+------------------------------------------------------------------+ //| Вспомогательные | //+------------------------------------------------------------------+ bool IsValidPrice(double price) { if(price <= 0.0) return(false); if(price == DBL_MAX) return(false); if(price == EMPTY_VALUE) return(false); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool HasPendingOrders() { int total = OrdersTotal(); for(int i = 0; i < total; i++) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; string sym = OrderGetString(ORDER_SYMBOL); long mg = OrderGetInteger(ORDER_MAGIC); int type = (int)OrderGetInteger(ORDER_TYPE); // фильтр по символу и magic if(sym != _Symbol || mg != Magic) continue; // pending-типы if(type == ORDER_TYPE_BUY_STOP || type == ORDER_TYPE_SELL_STOP || type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_SELL_LIMIT) { return true; } } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool HasOpenPosition() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == Magic) return true; } return false; } //+------------------------------------------------------------------+ //| CleanupPendingOrders (упрощённо) | //+------------------------------------------------------------------+ bool CleanupPendingOrders() { bool removed = false; for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); if(type != ORDER_TYPE_BUY_STOP && type != ORDER_TYPE_SELL_STOP) continue; // --- Удаляем ВСЕ pending по символу и magic --- if(OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == Magic) { MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_REMOVE; req.order = ticket; if(OrderSend(req, res)) { Print("🗑 Pending removed: ", ticket); removed = true; } } } return removed; } //+------------------------------------------------------------------+ //| FractalFilter / GetEntryAndSL / TrailByStructure / S/R | //+------------------------------------------------------------------+ bool FractalFilter(int trend, double pivot, int tfM_in, int tfH_in) { double up[500], down[500]; datetime t[500]; if(CopyBuffer(handleFractals_M, 0, 0, 500, up) <= 0) return true; if(CopyBuffer(handleFractals_M, 1, 0, 500, down) <= 0) return true; if(CopyTime(_Symbol, (ENUM_TIMEFRAMES)tfM_in, 0, 500, t) <= 0) return true; datetime hStart = iTime(_Symbol, (ENUM_TIMEFRAMES)tfH_in, 1); datetime hEnd = iTime(_Symbol, (ENUM_TIMEFRAMES)tfH_in, 0); struct Fract { double price; datetime time; bool isUp; }; Fract frs[]; ArrayResize(frs, 0); for(int i = 2; i < 500; i++) { if(t[i] < hStart || t[i] >= hEnd) continue; if(up[i] != 0.0 && up[i] != EMPTY_VALUE) { int n = ArraySize(frs); ArrayResize(frs, n+1); frs[n].price = up[i]; frs[n].time = t[i]; frs[n].isUp = true; } if(down[i] != 0.0 && down[i] != EMPTY_VALUE) { int n = ArraySize(frs); ArrayResize(frs, n+1); frs[n].price = down[i]; frs[n].time = t[i]; frs[n].isUp = false; } } if(ArraySize(frs) < 3) return true; // сортировка по времени for(int i = 0; i < ArraySize(frs)-1; i++) for(int j = i+1; j < ArraySize(frs); j++) if(frs[j].time < frs[i].time) { Fract tmp = frs[i]; frs[i] = frs[j]; frs[j] = tmp; } double lastUp1=0,lastUp2=0,lastDn1=0,lastDn2=0; // последние два up for(int i = ArraySize(frs)-1; i >= 0; i--) { if(frs[i].isUp) { if(lastUp1==0) lastUp1=frs[i].price; else if(lastUp2==0) { lastUp2=frs[i].price; break; } } } // последние два down for(int i = ArraySize(frs)-1; i >= 0; i--) { if(!frs[i].isUp) { if(lastDn1==0) lastDn1=frs[i].price; else if(lastDn2==0) { lastDn2=frs[i].price; break; } } } if(trend == 1) { double priceNow = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(priceNow <= pivot) { LogTF(StringFormat("🛑 BUY blocked: price below pivot (%.5f)", pivot)); return false; } if(lastUp1==0 || lastUp2==0 || lastDn1==0 || lastDn2==0) { LogTF("🛑 BUY blocked: insufficient fractal data"); return false; } bool hh_ok = (lastUp1 > lastUp2); bool hl_ok = (lastDn1 > lastDn2); if(!hh_ok || !hl_ok) { LogTF(StringFormat("🛑 BUY blocked: structure not bullish (HH=%s HL=%s)", hh_ok?"OK":"NO", hl_ok?"OK":"NO")); return false; } if(lastDn1 >= lastUp1) { LogTF(StringFormat("🛑 BUY blocked: SL fractal (%.5f) not below entry fractal (%.5f)", lastDn1, lastUp1)); return false; } return true; } if(trend == -1) { double priceNow = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(priceNow >= pivot) { LogTF(StringFormat("🛑 SELL blocked: price above pivot (%.5f)", pivot)); return false; } if(lastUp1==0 || lastUp2==0 || lastDn1==0 || lastDn2==0) { LogTF("🛑 SELL blocked: insufficient fractal data"); return false; } bool lh_ok = (lastUp1 < lastUp2); bool ll_ok = (lastDn1 < lastDn2); if(!lh_ok || !ll_ok) { LogTF(StringFormat("🛑 SELL blocked: structure not bearish (LH=%s LL=%s)", lh_ok?"OK":"NO", ll_ok?"OK":"NO")); return false; } if(lastUp1 <= lastDn1) { LogTF(StringFormat("🛑 SELL blocked: SL fractal (%.5f) not above entry fractal (%.5f)", lastUp1, lastDn1)); return false; } return true; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool GetEntryAndSL(int trend, double &entry, double &sl) { entry = 0.0; sl = 0.0; // --- Проверяем handle --- if(handleFractals_H == INVALID_HANDLE) { Print("⛔ GetEntryAndSL: fractal handle invalid"); return false; } // --- Читаем буферы --- double upBuf[10]; double dnBuf[10]; int upCount = CopyBuffer(handleFractals_H, 0, 0, 10, upBuf); int dnCount = CopyBuffer(handleFractals_H, 1, 0, 10, dnBuf); if(upCount <= 0 || dnCount <= 0) { LogTF(StringFormat("⛔ GetEntryAndSL: fractal buffers empty (up=%d dn=%d)", upCount, dnCount)); return false; } // --- Ищем последний фрактал --- double lastUp = 0.0; double lastDn = 0.0; for(int i = 0; i < upCount; i++) { if(upBuf[i] > 0 && upBuf[i] < 1000000000) { lastUp = upBuf[i]; break; } } for(int i = 0; i < dnCount; i++) { if(dnBuf[i] > 0 && dnBuf[i] < 1000000000) { lastDn = dnBuf[i]; break; } } // --- BUY --- if(trend == 1) { if(lastUp <= 0) { LogTF("⛔ GetEntryAndSL: BUY but no up fractal"); return false; } entry = lastUp; if(lastDn > 0) sl = lastDn; else sl = entry - 3 * _Point; // минимальный fallback // --- Проверки --- if(entry <= 0 || entry == DBL_MAX || entry > 1000000000) { LogTF(StringFormat("⛔ GetEntryAndSL: invalid BUY entry=%.5f", entry)); return false; } if(sl <= 0 || sl == DBL_MAX || sl >= entry) { LogTF(StringFormat("⛔ GetEntryAndSL: invalid BUY SL=%.5f (entry=%.5f)", sl, entry)); return false; } LogTF(StringFormat("✅ GetEntryAndSL BUY: entry=%.5f SL=%.5f", entry, sl)); return true; } // --- SELL --- if(trend == -1) { if(lastDn <= 0) { LogTF("⛔ GetEntryAndSL: SELL but no down fractal"); return false; } entry = lastDn; if(lastUp > 0) sl = lastUp; else sl = entry + 3 * _Point; if(entry <= 0 || entry == DBL_MAX || entry > 1000000000) { LogTF(StringFormat("⛔ GetEntryAndSL: invalid SELL entry=%.5f", entry)); return false; } if(sl <= entry || sl == DBL_MAX) { LogTF(StringFormat("⛔ GetEntryAndSL: invalid SELL SL=%.5f (entry=%.5f)", sl, entry)); return false; } LogTF(StringFormat("✅ GetEntryAndSL SELL: entry=%.5f SL=%.5f", entry, sl)); return true; } LogTF("⛔ GetEntryAndSL: trend=0"); return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void TrailByStructure(int tfM_in, int tfH_in, double pivot) { double up[200], down[200]; if(CopyBuffer(handleFractals_M, 0, 0, 200, up) <= 0) return; if(CopyBuffer(handleFractals_M, 1, 0, 200, down) <= 0) return; double atrCur = 0.0; double atrBuf[1]; if(CopyBuffer(handleATR_M, 0, 0, 1, atrBuf) > 0) atrCur = atrBuf[0]; if(atrCur <= 0.0) return; double buffer = atrCur * 0.3; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; string sym = PositionGetString(POSITION_SYMBOL); int mag = (int)PositionGetInteger(POSITION_MAGIC); int type = (int)PositionGetInteger(POSITION_TYPE); double sl = PositionGetDouble(POSITION_SL); double priceOpen = PositionGetDouble(POSITION_PRICE_OPEN); if(sym != _Symbol || mag != Magic) continue; if(type == POSITION_TYPE_BUY) { double lastDown = 0.0; for(int j=2; j<100; j++) { if(down[j] > pivot && down[j] > sl) { if(lastDown == 0.0 || down[j] > lastDown) lastDown = down[j]; } } if(lastDown > 0.0) { double newSL = lastDown - buffer; if(newSL > sl && newSL < priceOpen) { MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_SLTP; req.symbol = _Symbol; req.position = ticket; req.sl = newSL; req.tp = PositionGetDouble(POSITION_TP); if(OrderSend(req, res)) { DrawTrailSL(ticket, sl, newSL); PrintFormat("TRAIL BUY: oldSL=%.5f newSL=%.5f fractal=%.5f", sl, newSL, lastDown); } } } } else if(type == POSITION_TYPE_SELL) { double lastUp = 0.0; for(int j=2; j<100; j++) { if(up[j] < pivot && (sl == 0.0 || up[j] < sl)) { if(lastUp == 0.0 || up[j] < lastUp) lastUp = up[j]; } } if(lastUp > 0.0) { double newSL = lastUp + buffer; if((sl == 0.0 || newSL < sl) && newSL > priceOpen) { MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_SLTP; req.symbol = _Symbol; req.position = ticket; req.sl = newSL; req.tp = PositionGetDouble(POSITION_TP); if(OrderSend(req, res)) { DrawTrailSL(ticket, sl, newSL); PrintFormat("TRAIL SELL: oldSL=%.5f newSL=%.5f fractal=%.5f", sl, newSL, lastUp); } } } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetNearestSupport(double buffer, int tfM_in) { double down[200]; datetime t[200]; if(CopyBuffer(handleFractals_M, 1, 0, 200, down) <= 0) return 0.0; if(CopyTime(_Symbol, (ENUM_TIMEFRAMES)tfM_in, 0, 200, t) <= 0) return 0.0; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double best = 0.0; for(int i=2; i<200; i++) { if(down[i] != 0.0 && down[i] < bid - buffer) { if(best == 0.0 || down[i] > best) best = down[i]; } } return best; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetNearestResistance(double buffer, int tfM_in) { double up[200]; datetime t[200]; if(CopyBuffer(handleFractals_M, 0, 0, 200, up) <= 0) return 0.0; if(CopyTime(_Symbol, (ENUM_TIMEFRAMES)tfM_in, 0, 200, t) <= 0) return 0.0; double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double best = 0.0; for(int i=2; i<200; i++) { if(up[i] != 0.0 && up[i] > ask + buffer) { if(best == 0.0 || up[i] < best) best = up[i]; } } return best; } //+------------------------------------------------------------------+ //| FindEntrySignal | //+------------------------------------------------------------------+ bool FindEntrySignal() { // --- DEBUG вывод тренда и пивота --- PrintFormat("DEBUG FindEntrySignal: trend=%d pivot=%.5f", lastTrend, lastPivot); int trend = lastTrend; double pivot = lastPivot; // --- 1. Проверяем тренд --- if(trend == 0) { Print("⛔ CANCEL: trend=0 (нет тренда)"); return false; } // --- 2. Проверяем готовность HTF индикаторов --- if(!htfReady) { LogTF("⛔ CANCEL: HTF indicators not ready"); return false; } // --- 3. Получаем текущий тик --- MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return false; double bid = tick.bid; double ask = tick.ask; double mid = (bid + ask) * 0.5; double spread = ask - bid; double relSpread = (mid > 0 ? spread / mid : 0.0); // --- 4. Определяем динамический TF --- int tfM_local = GetDynamicTF(relSpread); // --- 5. Фильтр структуры (фракталы) --- if(!FractalFilter(trend, pivot, tfM_local, tfH)) { LogTF(StringFormat("⛔ CANCEL: FractalFilter отклонил вход (trend=%d pivot=%.5f)", trend, pivot)); return false; } // --- 6. Получаем уровень входа и SL от фрактала --- double entryPrice = 0.0; double slFromFractal = 0.0; if(!GetEntryAndSL(trend, entryPrice, slFromFractal)) { LogTF(StringFormat("⛔ CANCEL: GetEntryAndSL не дал вход (trend=%d pivot=%.5f)", trend, pivot)); return false; } // --- 7. Проверка валидности entry --- if(entryPrice <= 0 || entryPrice == DBL_MAX) { LogTF(StringFormat("⛔ CANCEL: invalid entryPrice=%.5f", entryPrice)); return false; } // --- 8. ATR фильтр (быстрый и сглаженный ATR) --- int atrFast = iATR(_Symbol, (ENUM_TIMEFRAMES)tfM_local, 14); int atrSlow = iATR(_Symbol, (ENUM_TIMEFRAMES)tfM_local, 50); double bufFast[1], bufSlow[1]; if(CopyBuffer(atrFast, 0, 0, 1, bufFast) <= 0 || CopyBuffer(atrSlow, 0, 0, 1, bufSlow) <= 0) { Print("⛔ ATR not available"); return false; } double atr_now = bufFast[0]; // текущая волатильность double atr_smooth = bufSlow[0]; // сглаженная волатильность double volRatio = (atr_smooth > 0 ? atr_now / atr_smooth : 1.0); // --- коэффициент силы рынка --- double coef = 0.45; if(volRatio < 0.8) coef = 0.30; else if(volRatio < 1.2) coef = 0.45; else if(volRatio < 1.5) coef = 0.60; else coef = 0.75; // --- SL = 2 ATR --- double slDistance = atr_now * 2.0; // --- 9. Risk-based lot --- double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskPercent = 0.0025; // 0.25% double riskMoney = balance * riskPercent; double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double pointValue = tickValue / tickSize; double lot = riskMoney / (slDistance / _Point * pointValue); // --- округление лота --- double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); lot = MathFloor(lot / lotStep) * lotStep; if(lot < minLot) { PrintFormat("⚠ lot=%.5f < minLot=%.5f -> setting lot to minLot", lot, minLot); lot = minLot; } lastLot = lot; // --- 10. SL и TP --- double stopLoss, takeProfit; if(trend == 1) // BUY { stopLoss = entryPrice - slDistance; takeProfit = entryPrice + slDistance; } else // SELL { stopLoss = entryPrice + slDistance; takeProfit = entryPrice - slDistance; } // --- 11. Проверка SL --- if(stopLoss <= 0 || stopLoss == DBL_MAX) { LogTF(StringFormat("⛔ CANCEL: invalid stopLoss=%.5f", stopLoss)); return false; } if(trend == 1 && stopLoss >= entryPrice) { LogTF("⛔ CANCEL: BUY SL >= entry"); return false; } if(trend == -1 && stopLoss <= entryPrice) { LogTF("⛔ CANCEL: SELL SL <= entry"); return false; } // --- ВАЖНО --- // STOP-логика поддержки/сопротивления удалена, // потому что LIMIT-входы проверяются в OnTick(). // --- 12. Финальное сохранение --- lastEntry = entryPrice; // уровень, НЕ цена входа lastStop = stopLoss; lastTp = takeProfit; lastLot = lot; LogTF(StringFormat("SIGNAL: trend=%d entry=%.5f SL=%.5f TP=%.5f lot=%.2f", trend, entryPrice, stopLoss, takeProfit, lot)); return true; } double GetATR(int period = 14) { double atr[]; if(CopyBuffer(iATR(_Symbol, PERIOD_CURRENT, period), 0, 0, 1, atr) > 0) return atr[0]; return 0; } void CancelOrder(ulong ticket) { MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_REMOVE; req.order = ticket; req.magic = Magic; req.symbol = _Symbol; if(!OrderSend(req, res)) PrintFormat("❌ Failed to remove pending order %I64d, retcode=%d", ticket, res.retcode); else PrintFormat("🧹 Pending order %I64d removed", ticket); } void CleanupLimitOrders() { double atr = GetATR(14); if(atr <= 0) return; double K_cancel = 1.0; // коэффициент удаления MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; double bid = tick.bid; double ask = tick.ask; // --- перебираем все pending ордера --- for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(!OrderSelect(ticket)) continue; // фильтруем по магику if(OrderGetInteger(ORDER_MAGIC) != Magic) continue; int type = (int)OrderGetInteger(ORDER_TYPE); // интересуют только LIMIT-ордера if(type != ORDER_TYPE_BUY_LIMIT && type != ORDER_TYPE_SELL_LIMIT) continue; double entry = OrderGetDouble(ORDER_PRICE_OPEN); // BUY LIMIT: рынок ушёл слишком далеко вниз if(type == ORDER_TYPE_BUY_LIMIT) { if(ask < entry - atr * K_cancel) { PrintFormat("🧹 Removing BUY LIMIT: market too far (ask=%.5f entry=%.5f)", ask, entry); CancelOrder(ticket); } } // SELL LIMIT: рынок ушёл слишком далеко вверх if(type == ORDER_TYPE_SELL_LIMIT) { if(bid > entry + atr * K_cancel) { PrintFormat("🧹 Removing SELL LIMIT: market too far (bid=%.5f entry=%.5f)", bid, entry); CancelOrder(ticket); } } } } //+------------------------------------------------------------------+ //| PlacePending | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE_FILLING GetFillingType() { long filling = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); if((filling & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK; if((filling & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC; return ORDER_FILLING_RETURN; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void PlacePending(int trend, double level, double sl, double tp) { double atr = GetATR(14); if(atr <= 0) return; MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_PENDING; req.symbol = _Symbol; req.magic = Magic; req.volume = lastLot; req.sl = sl; req.tp = tp; req.type_filling = ORDER_FILLING_RETURN; req.type_time = ORDER_TIME_GTC; // --- BUY LIMIT (вход на откате вверх) if(trend == 1) { req.type = ORDER_TYPE_BUY_LIMIT; req.price = level - atr * 0.3; // ретест уровня } // --- SELL LIMIT (вход на откате вниз) else { req.type = ORDER_TYPE_SELL_LIMIT; req.price = level + atr * 0.3; // ретест уровня } if(!OrderSend(req, res)) { PrintFormat("❌ OrderSend error: retcode=%d (%s) price=%.5f", res.retcode, res.comment, req.price); return; } PrintFormat("✅ Pending LIMIT placed: ticket=%I64d price=%.5f", res.order, req.price); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void RecreateAllIndicators() { if(handleFractals_M != INVALID_HANDLE) IndicatorRelease(handleFractals_M); if(handleFractals_H != INVALID_HANDLE) IndicatorRelease(handleFractals_H); if(handleAO_M != INVALID_HANDLE) IndicatorRelease(handleAO_M); if(handleAO_H != INVALID_HANDLE) IndicatorRelease(handleAO_H); if(handleBB != INVALID_HANDLE) IndicatorRelease(handleBB); if(handleATR_M != INVALID_HANDLE) IndicatorRelease(handleATR_M); if(handleATRSlow != INVALID_HANDLE) IndicatorRelease(handleATRSlow); if(handleMA_H != INVALID_HANDLE) IndicatorRelease(handleMA_H); handleFractals_M = iFractals(_Symbol, (ENUM_TIMEFRAMES)tfM); handleFractals_H = iFractals(_Symbol, (ENUM_TIMEFRAMES)tfH); handleAO_M = iAO(_Symbol, (ENUM_TIMEFRAMES)tfM); handleAO_H = iAO(_Symbol, (ENUM_TIMEFRAMES)tfH); handleBB = iBands(_Symbol, (ENUM_TIMEFRAMES)tfH, BB_Period, 0, BB_Deviation, PRICE_CLOSE); handleATR_M = iATR(_Symbol, (ENUM_TIMEFRAMES)tfM, ATR_Period); handleATRSlow = iATR(_Symbol, PERIOD_D1, ATR_Slow_Period); handleMA_H = iMA(_Symbol, (ENUM_TIMEFRAMES)tfH, 50, 0, MODE_EMA, PRICE_CLOSE); htfReady = false; htfInitTime = GetMicrosecondCount(); mtfReady = false; mtfInitTime = GetMicrosecondCount(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void LogCurrentTF() { PrintFormat("📊 Current TF: M=%s H=%s", TimeframeToString(tfM), TimeframeToString(tfH)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void LogTF(string msg) { PrintFormat("%s | TF: M=%s H=%s", msg, TimeframeToString(tfM), TimeframeToString(tfH)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string ExplainStructure(bool upStruct, bool downStruct) { if(upStruct) return "structure=HH/HL (bullish)"; if(downStruct) return "structure=LH/LL (bearish)"; return "structure=flat/undefined"; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string ExplainAO(double ao0, double ao1) { if(ao0 > ao1 && ao0 > 0) return "AO rising above zero (bullish)"; if(ao0 > ao1 && ao0 < 0) return "AO rising but still below zero (weak bullish)"; if(ao0 < ao1 && ao0 < 0) return "AO falling below zero (bearish)"; if(ao0 < ao1 && ao0 > 0) return "AO falling but still above zero (weak bearish)"; return "AO flat"; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string ExplainMA(double closePrev, double ma) { if(closePrev > ma) return "price above MA50 (bullish)"; if(closePrev < ma) return "price below MA50 (bearish)"; return "price near MA50 (neutral)"; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ExplainTrendChange(int prevTrend, int newTrend, bool upStruct, bool downStruct, double ao0, double ao1, double closePrev, double ma) { PrintFormat("TREND CHANGE: %d → %d", prevTrend, newTrend); Print(" • ", ExplainStructure(upStruct, downStruct)); Print(" • ", ExplainAO(ao0, ao1)); Print(" • ", ExplainMA(closePrev, ma)); if(newTrend == 0) Print("TREND CANCELLED: conditions no longer aligned"); else Print("TREND CONFIRMED: all filters aligned"); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsPriceAligned(double price) { double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); double normalized = MathRound(price / tickSize) * tickSize; return MathAbs(price - normalized) < (tickSize / 2.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetLastFractalUp(const double &up[]) { for(int i = 5; i < 300; i++) // начинаем с 5, чтобы фрактал был подтверждён { if(up[i] != 0.0 && up[i] != EMPTY_VALUE) return up[i]; } return EMPTY_VALUE; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetLastFractalDown(const double &dn[]) { for(int i = 5; i < 300; i++) { if(dn[i] != 0.0 && dn[i] != EMPTY_VALUE) return dn[i]; } return EMPTY_VALUE; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateFractalsHTF() { if(handleFractals_H != INVALID_HANDLE) { ArrayResize(upHTF, 300); ArrayResize(dnHTF, 300); CopyBuffer(handleFractals_H, 0, 0, 300, upHTF); CopyBuffer(handleFractals_H, 1, 0, 300, dnHTF); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool TrendReversedStrongly(int trend) { bool structureBreak = false; bool emaBreak = false; bool aoFlip = false; // --- 1. STRUCTURE BREAK --- double lastUp = GetLastFractalUp(upHTF); double lastDn = GetLastFractalDown(dnHTF); if(trend == 1) // BUY-тренд { if(lastDn != EMPTY_VALUE && iLow(_Symbol, PERIOD_CURRENT, 1) < lastDn) structureBreak = true; } else if(trend == -1) // SELL-тренд { if(lastUp != EMPTY_VALUE && iHigh(_Symbol, PERIOD_CURRENT, 1) > lastUp) structureBreak = true; } // --- 2. EMA BREAK --- // --- READ EMA --- double maBuf[1]; CopyBuffer(handleMA_H, 0, 2, 1, maBuf); double ema = maBuf[0]; // --- EMA BREAK --- if(trend == 1) { if(iClose(_Symbol, (ENUM_TIMEFRAMES)tfH, 1) < ema && iClose(_Symbol, (ENUM_TIMEFRAMES)tfH, 2) < ema) emaBreak = true; } else if(trend == -1) { if(iClose(_Symbol, (ENUM_TIMEFRAMES)tfH, 1) > ema && iClose(_Symbol, (ENUM_TIMEFRAMES)tfH, 2) > ema) emaBreak = true; } // AO static int handleAO = INVALID_HANDLE; if(handleAO == INVALID_HANDLE) handleAO = iAO(_Symbol, (ENUM_TIMEFRAMES)tfH); // --- READ AO --- double aoBuf[3]; CopyBuffer(handleAO_H, 0, 1, 3, aoBuf); double ao0 = aoBuf[0]; double ao1 = aoBuf[1]; if(trend == 1) { if(ao0 < 0 && ao1 > 0) aoFlip = true; } else if(trend == -1) { if(ao0 > 0 && ao1 < 0) aoFlip = true; } return (structureBreak && emaBreak && aoFlip); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool FilterBars(int shift, ENUM_TIMEFRAMES tf) { // --- 0. Получаем текущие данные --- double open = iOpen(_Symbol, tf, shift); double close = iClose(_Symbol, tf, shift); double high = iHigh(_Symbol, tf, shift); double low = iLow(_Symbol, tf, shift); double body = MathAbs(close - open); double range = high - low; // --- 1. ATR адаптивный --- int atrHandle = iATR(_Symbol, tf, 14); if(atrHandle == INVALID_HANDLE) return true; double atrBuf[1]; if(CopyBuffer(atrHandle, 0, shift, 1, atrBuf) <= 0) return true; double atr = atrBuf[0]; if(atr <= 0) return true; // Средний ATR инструмента (адаптация под инструмент) int atrSlowHandle = iATR(_Symbol, tf, 50); double atrSlowBuf[1]; if(CopyBuffer(atrSlowHandle, 0, shift, 1, atrSlowBuf) <= 0) return true; double atrAvg = atrSlowBuf[0]; // Коэффициент силы волатильности double volaRatio = (atrAvg > 0 ? atr / atrAvg : 1.0); // --- 2. Адаптивный порог ATR --- double minAtr = atrAvg * 0.35; // вместо фиксированных 40 пунктов if(atr < minAtr) return false; // --- 3. Размер тела свечи --- double bodyRatio = (range > 0 ? body / range : 0); // Если тренд сильный — допускаем 15% // Если тренд слабый — требуем 25% double minBodyRatio = (MathAbs(lastTrend) == 1 ? 0.15 : 0.25); if(bodyRatio < minBodyRatio) return false; // --- 4. Диапазон последних N свечей --- int N = 10; int idxHigh = iHighest(_Symbol, tf, MODE_HIGH, N, shift); int idxLow = iLowest(_Symbol, tf, MODE_LOW, N, shift); double recentHigh = iHigh(_Symbol, tf, idxHigh); double recentLow = iLow(_Symbol, tf, idxLow); double recentRange = recentHigh - recentLow; // Адаптивный порог double minRecentRange = atrAvg * 0.7; if(recentRange < minRecentRange) return false; // --- 5. AO + MACD (мягкий фильтр) --- int aoHandle = iAO(_Symbol, tf); double aoBuf2[2]; if(CopyBuffer(aoHandle, 0, shift, 2, aoBuf2) <= 0) return true; double ao0 = aoBuf2[0]; double ao1 = aoBuf2[1]; int macdHandle = iMACD(_Symbol, tf, 12, 26, 9, PRICE_CLOSE); double macdHist[1]; if(CopyBuffer(macdHandle, 2, shift, 1, macdHist) <= 0) return true; double hist = macdHist[0]; // Если тренд сильный — допускаем несовпадение if(MathAbs(lastTrend) == 1) { if((ao0 > 0 && hist < 0) && volaRatio < 0.8) return false; } else { // Если тренд слабый — требуем совпадение if((ao0 > 0 && hist < 0) || (ao0 < 0 && hist > 0)) return false; } return true; } //+------------------------------------------------------------------+ //| OnInit / OnDeinit / OnTick | //+------------------------------------------------------------------+ int OnInit() { Print("🔄 Initializing EA for symbol ", _Symbol); if(!InitLog()) Print("⚠️ Logging disabled (cannot init log)"); // первичная инициализация индикаторов MqlTick tick; if(SymbolInfoTick(_Symbol, tick)) { double mid = (tick.bid + tick.ask) * 0.5; double relSpread = (mid > 0 ? (tick.ask - tick.bid) / mid : 0.0); UpdateDynamicTF(relSpread); } // мягкий сброс — не трогаем pending/позиции RecreateAllIndicators(); LogCurrentTF(); ResetInternalState(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(handleFractals_M != INVALID_HANDLE) IndicatorRelease(handleFractals_M); if(handleFractals_H != INVALID_HANDLE) IndicatorRelease(handleFractals_H); if(handleAO_M != INVALID_HANDLE) IndicatorRelease(handleAO_M); if(handleAO_H != INVALID_HANDLE) IndicatorRelease(handleAO_H); if(handleBB != INVALID_HANDLE) IndicatorRelease(handleBB); if(handleATR_M != INVALID_HANDLE) IndicatorRelease(handleATR_M); if(handleATRSlow != INVALID_HANDLE) IndicatorRelease(handleATRSlow); if(handleMA_H != INVALID_HANDLE) IndicatorRelease(handleMA_H); if(logHandle != INVALID_HANDLE) FileClose(logHandle); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnTick() { MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; double bid = tick.bid; double ask = tick.ask; double mid = (bid + ask) * 0.5; double spread = ask - bid; double relSpread = (mid > 0 ? spread / mid : 0.0); // 1. Обновляем TF/индикаторы UpdateDynamicTF(relSpread); UpdateFractalsHTF(); // 2. Ждём HTF if(!htfReady) { if(GetMicrosecondCount() - htfInitTime < 300000) return; htfReady = true; Print("✅ HTF indicators loaded"); } // 3. Ждём MTF if(!mtfReady) { if(GetMicrosecondCount() - mtfInitTime < 300000) return; if(handleFractals_M != INVALID_HANDLE && handleAO_M != INVALID_HANDLE && handleATR_M != INVALID_HANDLE) { mtfReady = true; Print("✅ MTF indicators loaded"); } else { Print("⛔ MTF indicators still invalid — waiting..."); mtfInitTime = GetMicrosecondCount(); return; } } // 4. Новый бар tfM? datetime curBarTime = iTime(_Symbol, (ENUM_TIMEFRAMES)tfM, 0); bool newM = (curBarTime != lastBarTimeMinutes); if(newM) { lastBarTimeMinutes = curBarTime; double mid = (tick.bid + tick.ask) * 0.5; double relSpread = (mid > 0 ? (tick.ask - tick.bid) / mid : 0.0); UpdateDynamicTF(relSpread); if(pending_tfM != tfM || pending_tfH != tfH) { PrintFormat("🔄 Reinitializing indicators: M=%s -> %s, H=%s -> %s", TimeframeToString(tfM), TimeframeToString(pending_tfM), TimeframeToString(tfH), TimeframeToString(pending_tfH)); tfM = pending_tfM; tfH = pending_tfH; RecreateAllIndicators(); LogCurrentTF(); return; } } // 5. Новый бар HTF → тренд datetime htfBar = iTime(_Symbol, (ENUM_TIMEFRAMES)tfH, 0); if(htfBar != lastHTFBar) { lastHTFBar = htfBar; lastTrend = GetTrendAndPivot(lastPivot); PrintFormat("TREND UPDATE: trend=%d pivot=%.5f (tfH=%s)", lastTrend, lastPivot, TimeframeToString(tfH)); } // 6. Если нет нового бара — выходим if(!newM) return; // 7. Проверяем M-индикаторы if(handleFractals_M == INVALID_HANDLE || handleAO_M == INVALID_HANDLE || handleATR_M == INVALID_HANDLE) { Print("⏳ Waiting MTF indicators to load..."); return; } // 8. Проверяем позиции bool hasPos = HasOpenPosition(); bool hasPend = HasPendingOrders(); if(!hasPos && !hasPend) { ResetInternalState(); if(lastTrend != 0 && FindEntrySignal()) { Print("📌 Entry signal found — preparing LIMIT pending..."); // --- ATR для адаптивного входа --- double atr = GetATR(14); if(atr <= 0) { Print("⛔ ATR invalid"); return; } // --- объявляем и инициализируем переменные заранее --- double entry = lastEntry; double sl = lastStop; double tp = lastTp; // --- BUY LIMIT --- if(lastTrend == 1) { entry = lastEntry - atr * 0.3; if(entry >= ask) { PrintFormat("⛔ BUY LIMIT rejected: entry %.5f >= ask %.5f", entry, ask); return; } double dist = ask - entry; double stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; if(dist < stopsLevel) { PrintFormat("⛔ BUY LIMIT too close: dist=%.5f stopsLevel=%.5f", dist, stopsLevel); return; } } // --- SELL LIMIT --- if(lastTrend == -1) { entry = lastEntry + atr * 0.3; if(entry <= bid) { PrintFormat("⛔ SELL LIMIT rejected: entry %.5f <= bid %.5f", entry, bid); return; } double dist = entry - bid; double stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; if(dist < stopsLevel) { PrintFormat("⛔ SELL LIMIT too close: dist=%.5f stopsLevel=%.5f", dist, stopsLevel); return; } } // --- SL/TP проверки остаются прежними --- double stopsLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; double mktPrice = (lastTrend == 1 ? ask : bid); double distSL_mkt = MathAbs(sl - mktPrice); double distTP_mkt = MathAbs(tp - mktPrice); if(distSL_mkt < stopsLevel || distTP_mkt < stopsLevel) { PrintFormat("⛔ SL/TP too close: SLdist=%.5f TPdist=%.5f stopsLevel=%.5f", distSL_mkt, distTP_mkt, stopsLevel); return; } // --- ставим LIMIT pending --- PlacePending(lastTrend, entry, sl, tp); trendAtPlacement = lastTrend; } return; } // 9. Управление позицией if(hasPos) { TrailByStructure(tfM, tfH, lastPivot); CheckAOExit(); } // 10. Если есть pending и тренд сменился — удаляем if(hasPend) { if(!trendAtPlacement) { Print("🧹 Trend changed — deleting pending"); CleanupPendingOrders(); } CleanupLimitOrders(); } }