#property strict #property version "1.00" #include input ENUM_TIMEFRAMES InpHigherTF = PERIOD_M10; // Higher timeframe for MA/cross points input int InpMAPeriod = 65; // MA period input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // MA method input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price input int InpHTFBarsToScan = 500; // HTF bars to scan for crossings input double InpLineTouchTolerance = 220; // Pullback touch tolerance (points) input double InpBreakBuffer = 110; // Break confirmation buffer (points) input double InpLots = 0.10; // Position size input long InpMagic = 26042501; // Magic number input bool InpDrawTrendline = true; // Draw detected trendline input bool InpUseSessionModeGate = true; // Block entries when symbol/session disallow opens input bool InpBypassGateInTester = true; // Ignore gate in Strategy Tester for optimization CTrade trade; int g_maHandle = INVALID_HANDLE; datetime g_lastBarTime = 0; string g_lineName = "SimpleTrendline_Basis"; datetime g_lastEntryBlockLog = 0; struct TrendlineModel { datetime t1; datetime t2; datetime t3; double p1; double p2; double p3; double a; double b; bool valid; }; bool IsNewBar() { datetime t = iTime(_Symbol, _Period, 0); if(t == 0) return false; if(t != g_lastBarTime) { g_lastBarTime = t; return true; } return false; } int FindRecentCrossPoints(datetime ×[], double &prices[]) { ArrayResize(times, 0); ArrayResize(prices, 0); if(g_maHandle == INVALID_HANDLE) return 0; int needBars = MathMax(InpHTFBarsToScan, InpMAPeriod + 20); MqlRates rates[]; double maBuf[]; int copiedRates = CopyRates(_Symbol, InpHigherTF, 0, needBars, rates); int copiedMa = CopyBuffer(g_maHandle, 0, 0, needBars, maBuf); if(copiedRates <= 5 || copiedMa <= 5) return 0; int bars = MathMin(copiedRates, copiedMa); ArraySetAsSeries(rates, true); ArraySetAsSeries(maBuf, true); for(int i = 2; i < bars - 1; i++) { double d0 = rates[i].close - maBuf[i]; double d1 = rates[i + 1].close - maBuf[i + 1]; if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0)) { int n = ArraySize(times); ArrayResize(times, n + 1); ArrayResize(prices, n + 1); times[n] = rates[i].time; prices[n] = rates[i].close; if(ArraySize(times) >= 3) break; } } return ArraySize(times); } bool BuildTrendlineFrom3Points(TrendlineModel &m) { m.valid = false; datetime ts[]; double ps[]; int n = FindRecentCrossPoints(ts, ps); if(n < 3) return false; // We collected from recent to older in series order. // Re-map as oldest -> newest to stabilize slope direction. datetime tOld[3]; double pOld[3]; for(int i = 0; i < 3; i++) { tOld[i] = ts[2 - i]; pOld[i] = ps[2 - i]; } long t0 = (long)tOld[0]; double x1 = 0.0; double x2 = (double)((long)tOld[1] - t0); double x3 = (double)((long)tOld[2] - t0); double y1 = pOld[0]; double y2 = pOld[1]; double y3 = pOld[2]; double sx = x1 + x2 + x3; double sy = y1 + y2 + y3; double sxx = x1 * x1 + x2 * x2 + x3 * x3; double sxy = x1 * y1 + x2 * y2 + x3 * y3; double den = 3.0 * sxx - sx * sx; if(MathAbs(den) < 1e-10) return false; m.a = (3.0 * sxy - sx * sy) / den; m.b = (sy - m.a * sx) / 3.0; m.t1 = tOld[0]; m.t2 = tOld[1]; m.t3 = tOld[2]; m.p1 = pOld[0]; m.p2 = pOld[1]; m.p3 = pOld[2]; m.valid = true; return true; } double TrendlinePriceAtTime(const TrendlineModel &m, datetime t) { if(!m.valid) return 0.0; double x = (double)((long)t - (long)m.t1); return m.a * x + m.b; } void DrawTrendline(const TrendlineModel &m) { if(!InpDrawTrendline || !m.valid) return; datetime tStart = m.t1; datetime tEnd = iTime(_Symbol, _Period, 0); if(tEnd <= tStart) tEnd = m.t3 + PeriodSeconds(_Period) * 20; double pStart = TrendlinePriceAtTime(m, tStart); double pEnd = TrendlinePriceAtTime(m, tEnd); if(ObjectFind(0, g_lineName) < 0) ObjectCreate(0, g_lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd); else { ObjectMove(0, g_lineName, 0, tStart, pStart); ObjectMove(0, g_lineName, 1, tEnd, pEnd); } ObjectSetInteger(0, g_lineName, OBJPROP_RAY_RIGHT, true); ObjectSetInteger(0, g_lineName, OBJPROP_COLOR, clrGold); ObjectSetInteger(0, g_lineName, OBJPROP_WIDTH, 2); } bool GetCurrentPosition(long &type, double &volume) { if(!PositionSelect(_Symbol)) return false; if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic) return false; type = PositionGetInteger(POSITION_TYPE); volume = PositionGetDouble(POSITION_VOLUME); return true; } bool IsWithinAnyTradeSession(const datetime nowServer) { MqlDateTime dt; TimeToStruct(nowServer, dt); ENUM_DAY_OF_WEEK day = (ENUM_DAY_OF_WEEK)dt.day_of_week; int nowSec = dt.hour * 3600 + dt.min * 60 + dt.sec; datetime from = 0; datetime to = 0; bool hasAny = false; for(uint idx = 0; idx < 16; idx++) { if(!SymbolInfoSessionTrade(_Symbol, day, idx, from, to)) break; hasAny = true; // SymbolInfoSessionTrade returns session boundaries as time-of-day values. MqlDateTime fdt, tdt; TimeToStruct(from, fdt); TimeToStruct(to, tdt); int fromSec = fdt.hour * 3600 + fdt.min * 60 + fdt.sec; int toSec = tdt.hour * 3600 + tdt.min * 60 + tdt.sec; // from==to on some brokers means full-day session. if(fromSec == toSec) { return true; } else if(fromSec < toSec) { if(nowSec >= fromSec && nowSec <= toSec) return true; } else { // Session passes midnight. if(nowSec >= fromSec || nowSec <= toSec) return true; } } // If broker does not expose sessions for this symbol, do not block by session. if(!hasAny) return true; return false; } bool CanOpenNewPositionNow(const ENUM_ORDER_TYPE orderType) { if(!InpUseSessionModeGate) return true; if(InpBypassGateInTester && (bool)MQLInfoInteger(MQL_TESTER)) return true; long tradeMode = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE); if(tradeMode == SYMBOL_TRADE_MODE_DISABLED || tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY) return false; if(orderType == ORDER_TYPE_BUY && tradeMode == SYMBOL_TRADE_MODE_SHORTONLY) return false; if(orderType == ORDER_TYPE_SELL && tradeMode == SYMBOL_TRADE_MODE_LONGONLY) return false; if(!IsWithinAnyTradeSession(TimeCurrent())) return false; return true; } void TryExitOnBreak(const TrendlineModel &m) { long posType; double vol; if(!GetCurrentPosition(posType, vol)) return; double close1 = iClose(_Symbol, _Period, 1); datetime t1 = iTime(_Symbol, _Period, 1); double line1 = TrendlinePriceAtTime(m, t1); double buf = InpBreakBuffer * _Point; bool closePos = false; if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf)) closePos = true; if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf)) closePos = true; if(closePos) trade.PositionClose(_Symbol); } void TryPullbackEntry(const TrendlineModel &m) { long posType; double vol; if(GetCurrentPosition(posType, vol)) return; MqlRates bars1[], bars2[]; if(CopyRates(_Symbol, _Period, 1, 1, bars1) != 1) return; if(CopyRates(_Symbol, _Period, 2, 1, bars2) != 1) return; if(ArraySize(bars1) < 1 || ArraySize(bars2) < 1) return; MqlRates b1 = bars1[0]; MqlRates b2 = bars2[0]; double line1 = TrendlinePriceAtTime(m, b1.time); double tol = InpLineTouchTolerance * _Point; bool upTrend = (m.a > 0.0); bool downTrend = (m.a < 0.0); if(upTrend) { bool touched = (b1.low <= (line1 + tol)); bool reclaim = (b1.close > line1); bool bullish = (b1.close > b1.open); bool stillHealthy = (b2.close >= TrendlinePriceAtTime(m, b2.time) - tol); if(touched && reclaim && bullish && stillHealthy) { if(!CanOpenNewPositionNow(ORDER_TYPE_BUY)) { datetime nowBar = iTime(_Symbol, _Period, 0); if(nowBar != g_lastEntryBlockLog) { g_lastEntryBlockLog = nowBar; Print("Buy entry skipped: symbol mode/session does not allow opening now"); } return; } trade.Buy(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback buy"); } } else if(downTrend) { bool touched = (b1.high >= (line1 - tol)); bool reject = (b1.close < line1); bool bearish = (b1.close < b1.open); bool stillWeak = (b2.close <= TrendlinePriceAtTime(m, b2.time) + tol); if(touched && reject && bearish && stillWeak) { if(!CanOpenNewPositionNow(ORDER_TYPE_SELL)) { datetime nowBar = iTime(_Symbol, _Period, 0); if(nowBar != g_lastEntryBlockLog) { g_lastEntryBlockLog = nowBar; Print("Sell entry skipped: symbol mode/session does not allow opening now"); } return; } trade.Sell(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback sell"); } } } int OnInit() { g_maHandle = iMA(_Symbol, InpHigherTF, InpMAPeriod, 0, InpMAMethod, InpAppliedPrice); if(g_maHandle == INVALID_HANDLE) return INIT_FAILED; trade.SetExpertMagicNumber(InpMagic); g_lastBarTime = 0; return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(g_maHandle != INVALID_HANDLE) IndicatorRelease(g_maHandle); if(ObjectFind(0, g_lineName) >= 0) ObjectDelete(0, g_lineName); } void OnTick() { if(!IsNewBar()) return; TrendlineModel m; if(!BuildTrendlineFrom3Points(m)) return; DrawTrendline(m); TryExitOnBreak(m); TryPullbackEntry(m); }