//+------------------------------------------------------------------+ //| mvp.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MAGNUM TECH" #property link "https://www.mql5.com" #property version "2.00" //add input param to use a fixed sl points OR //build a logic to adjust for as per Instrument input bool useFixedSL; // whethe to add a buffer of symbol's mini stop level //input double SL_POINTS; input double lot; #include CTrade trade; double fixedSL; struct SwingPoint { double price; datetime time; }; SwingPoint lastHigh; SwingPoint lastLow; datetime lastBarTime = 0; // Temporary Counters int countD1 = 0; int countH1 = 0; int countM5 = 0; int countM1 = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize swing points lastHigh.price = 0; lastHigh.time = 0; lastLow.price = 0; lastLow.time = 0; //getFixedSL(fixedSL); Print("TickSize: ",SYMBOL_TRADE_TICK_SIZE/_Point,"\nSpread: ",SYMBOL_SPREAD/_Point,"\nstopLevels: ",SYMBOL_TRADE_STOPS_LEVEL/_Point); /*if(useFixedSL){*/Print("SLFixed choosen is ",fixedSL);//} Alert(""); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Print("Maturity Counts -- D1: ", countD1, " | H1: ", countH1, " | M5: ", countM5, " | M1: ", countM1); } //+------------------------------------------------------------------+ //| Candlestick Functions | //+------------------------------------------------------------------+ int IsEngulfing(MqlRates &r[]) { // Check r[1] (latest closed) vs r[2] (previous) bool bull = r[1].close > r[1].open; bool bear = r[1].close < r[1].open; bool prevBear = r[2].close < r[2].open; bool prevBull = r[2].close > r[2].open; // Bullish Engulfing: Current Bull, Prev Bear, Body engulfs Prev Body if (bull && r[1].high > r[2].high && r[1].low < r[2].low) return 1; // Bearish Engulfing: Current Bear, Prev Bull, Body engulfs Prev Body if (bear && r[1].low < r[2].low && r[1].high > r[2].high) return -1; return 0; } int IsGreenlight(MqlRates &r[]) { // Check r[1] double upperWick = r[1].high - MathMax(r[1].open, r[1].close); double lowerWick = MathMin(r[1].open, r[1].close) - r[1].low; if (r[1].high < r[2].high) { // catching bullish move early if (upperWick < lowerWick) return 1; } else if (r[1].low > r[2].low) { // catching bearish move early if (lowerWick < upperWick) return -1; } return 0; } //+------------------------------------------------------------------+ //| Swing Detection | //+------------------------------------------------------------------+ void DetectSwings(MqlRates &r[]) { // r[2] is the candidate swing candle // r[3] is left, r[1] is right bool isHigh = r[2].high > r[3].high && r[2].high > r[1].high; bool isLow = r[2].low < r[3].low && r[2].low < r[1].low; // Exception: Outside bar engulfing both sides if (isHigh && isLow) { lastHigh.price = r[2].high; lastHigh.time = r[2].time; lastLow.price = r[2].low; lastLow.time = r[2].time; Print("Dual Swing Detected at ", TimeToString(r[2].time)); } else { if (isHigh) { lastHigh.price = r[2].high; lastHigh.time = r[2].time; //Print("Swing High Detected at ", TimeToString(r[2].time)); } if (isLow) { lastLow.price = r[2].low; lastLow.time = r[2].time; //Print("Swing Low Detected at ", TimeToString(r[2].time)); } } } //+------------------------------------------------------------------+ //| Model 1 Signal Logic | //+------------------------------------------------------------------+ int model1(MqlRates &r[]) { int eng = IsEngulfing(r); int gl = IsGreenlight(r); int signal = 0; if (eng == 1 || gl == 1) signal = 1; if (eng == -1 || gl == -1) signal = -1; if (signal == 1) { // Bullish signal: Check if backed by swing low // Swing low should exist and preferably be below current price (support) if (lastLow.time != 0 && lastLow.price < r[1].low) { return 1; } } if (signal == -1) { // Bearish signal: Check if backed by swing high // Swing high should exist and preferably be above current price (resistance) if (lastHigh.time != 0 && lastHigh.price > r[1].high) { return -1; } } return 0; } //+------------------------------------------------------------------+ //| Trade Signal Structure & Enums | //+------------------------------------------------------------------+ enum ENUM_SIGNAL_MATURITY { MATURITY_NONE, MATURITY_D1, MATURITY_H1, MATURITY_M5, MATURITY_M1_EXECUTION, MATURITY_INVALID }; struct LevelParams { double sl; double tp; datetime time; bool active; }; struct TradeSignal { string id; // Composite: Symbol_TimeD1 string symbol; int type; // 1: Buy, -1: Sell double entryPrice; // Signal Price LevelParams matchD1; LevelParams matchH1; LevelParams matchM5; LevelParams matchM1; // Current operational pointers double currentSL; double currentTP; ENUM_SIGNAL_MATURITY maturity; bool isActive; string comment; }; TradeSignal activeSignals[]; //+------------------------------------------------------------------+ //| Helper Functions | //+------------------------------------------------------------------+ /*might as well be problem */string GetSignalID(string symbol, datetime time) { return symbol + "_" + TimeToString(time, TIME_DATE|TIME_MINUTES); } int FindSignal(string id) { for(int i=0; i=0; i--) { //if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetString(POSITION_COMMENT) == comment) return true; //} } return false; } // Check for New Bar per timeframe bool IsNewBar(string symbol, ENUM_TIMEFRAMES tf, datetime &last_time) { datetime current_time = iTime(symbol, tf, 0); if(last_time == 0) { last_time = current_time; return false; } if(current_time != last_time) { last_time = current_time; return true; } return false; } // Same logic as DetectSwings but returns value for arbitrary array void FindRecentSwings(MqlRates &rates[], double &outHigh, double &outLow) { outHigh = 0; outLow = 0; /*is this the right logic??*/ int limit = MathMin(ArraySize(rates)-2, 50); for(int i=2; i r[i+1] && r[i] > r[i-1] as per DetectSwings if (outHigh == 0) { if (rates[i].high > rates[i+1].high && rates[i].high > rates[i-1].high) { outHigh = rates[i].high; } } // Low Logic: r[i] < r[i+1] && r[i] < r[i-1] if (outLow == 0) { if (rates[i].low < rates[i+1].low && rates[i].low < rates[i-1].low) { outLow = rates[i].low; } } if (outHigh != 0 && outLow != 0) break; } } void DemoteSignal(int index) { if (index < 0 || index >= ArraySize(activeSignals)) return; ENUM_SIGNAL_MATURITY oldLevel = activeSignals[index].maturity; Print("Signal Demoted: ", activeSignals[index].id, " from ", EnumToString(oldLevel)); if (oldLevel == MATURITY_H1) { activeSignals[index].maturity = MATURITY_D1; activeSignals[index].currentSL = activeSignals[index].matchD1.sl; activeSignals[index].currentTP = activeSignals[index].matchD1.tp; } else if (oldLevel == MATURITY_M5) { activeSignals[index].maturity = MATURITY_H1; activeSignals[index].currentSL = activeSignals[index].matchH1.sl; activeSignals[index].currentTP = activeSignals[index].matchH1.tp; } else if (oldLevel == MATURITY_M1_EXECUTION) { activeSignals[index].maturity = MATURITY_M5; activeSignals[index].currentSL = activeSignals[index].matchM5.sl; activeSignals[index].currentTP = activeSignals[index].matchM5.tp; } else { activeSignals[index].isActive = false; // D1 or lower invalid } } void PromoteSignal(int index, ENUM_SIGNAL_MATURITY newLevel, MqlRates &rates[]) { if (index < 0 || index >= ArraySize(activeSignals)) return; double swingHigh = 0, swingLow = 0; FindRecentSwings(rates, swingHigh, swingLow); // rates[1] is the signal candle double signalLow = rates[1].low; double signalHigh = rates[1].high; LevelParams params; params.time = rates[1].time; params.active = true; if (activeSignals[index].type == 1) { // BUY params.sl = signalLow; params.tp = swingHigh; } else { // SELL params.sl = signalHigh; params.tp = swingLow; } // Fallback if no swing found if (params.tp == 0) params.tp = (activeSignals[index].type == 1) ? params.sl + (params.sl*0.01) : params.sl - (params.sl*0.01); if (newLevel == MATURITY_H1) activeSignals[index].matchH1 = params; else if (newLevel == MATURITY_M5) activeSignals[index].matchM5 = params; else if (newLevel == MATURITY_M1_EXECUTION) activeSignals[index].matchM1 = params; activeSignals[index].currentSL = params.sl; activeSignals[index].currentTP = params.tp; activeSignals[index].maturity = newLevel; if (newLevel == MATURITY_H1) countH1++; else if (newLevel == MATURITY_M5) countM5++; else if (newLevel == MATURITY_M1_EXECUTION){ if(activeSignals[index].type==1){ ///*buy*/ if(!PositionExists(activeSignals[index].id)){} //trade.Buy(lot,_Symbol,0,(activeSignals[index].currentSL-fixedSL),activeSignals[index].matchD1.tp,activeSignals[index].id); //instead try not inluding sl in position opening //rather have a function killing trades right when the price touch it } else if(activeSignals[index].type==-1){ //sell if(!PositionExists(activeSignals[index].id)){} //trade.Sell(lot,_Symbol,0,(activeSignals[index].currentSL+fixedSL),activeSignals[index].matchD1.tp,activeSignals[index].id); } countM1++; } Print("Signal Promoted: ", activeSignals[index].id, " to ", EnumToString(newLevel)); } //double getFixedSL(double &fixedSL){ // if(useFixedSL){ // fixedSL = SL_POINTS / _Point; // } else { // fixedSL=SYMBOL_TRADE_TICK_SIZE*safetyFactor*_Point; // } // return fixedSL; //} //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static datetime lastD1=0, lastH1=0, lastM5=0, lastM1=0; bool isNewD1 = IsNewBar(_Symbol, PERIOD_D1, lastD1); bool isNewH1 = IsNewBar(_Symbol, PERIOD_H1, lastH1); bool isNewM5 = IsNewBar(_Symbol, PERIOD_M5, lastM5); bool isNewM1 = IsNewBar(_Symbol, PERIOD_M1, lastM1); // --- D1 SCAN --- if (isNewD1) { printf("new D bar formed!"); MqlRates avgD1[]; ArraySetAsSeries(avgD1, true); if(CopyRates(_Symbol, PERIOD_D1, 0, 5, avgD1) == 5) { DetectSwings(avgD1); // Update Globals for model1 int sigD1 = model1(avgD1); if (sigD1 != 0) { // string sigID = GetSignalID(_Symbol, avgD1[1].time); //give ID if (FindSignal(sigID) == -1) { //new ID found int newIdx = ArrayResize(activeSignals, (ArraySize(activeSignals) + 1)); //make space for new signal activeSignals[newIdx-1].id = sigID; activeSignals[newIdx-1].symbol = _Symbol; activeSignals[newIdx-1].type = sigD1; activeSignals[newIdx-1].maturity = MATURITY_D1; activeSignals[newIdx-1].isActive = true; activeSignals[newIdx-1].matchD1.time = avgD1[1].time; activeSignals[newIdx-1].matchD1.active = true; // Assign Initial D1 Targets if(sigD1 == 1) { // Buy activeSignals[newIdx-1].matchD1.sl = avgD1[1].low; activeSignals[newIdx-1].matchD1.tp = lastHigh.price; // Fallback if TP is not logical (e.g. Price > lastHigh) //if (activeSignals[newIdx-1].matchD1.tp <= avgD1[1].close) // activeSignals[newIdx-1].matchD1.tp = avgD1[1].close + (avgD1[1].close - lastLow.price)*2; } else { // Sell activeSignals[newIdx-1].matchD1.sl = avgD1[1].high; activeSignals[newIdx-1].matchD1.tp = lastLow.price; // Fallback //if (activeSignals[newIdx-1].matchD1.tp >= avgD1[1].close) // activeSignals[newIdx-1].matchD1.tp = avgD1[1].close - (lastHigh.price - avgD1[1].close)*2; } activeSignals[newIdx-1].currentSL = activeSignals[newIdx-1].matchD1.sl; activeSignals[newIdx-1].currentTP = activeSignals[newIdx-1].matchD1.tp; countD1++; Print("New D1 Signal Found: ", sigID, " SL: ", activeSignals[newIdx-1].currentSL, " TP: ", activeSignals[newIdx-1].currentTP); } } } } // --- MATURITY LOOP --- for(int i=0; i 0 && activeSignals[i].currentTP > 0) { bool hitSL = false, hitTP = false; if (activeSignals[i].type == 1) { // Buy if (currentBid <= activeSignals[i].currentSL) hitSL = true; if (currentBid >= activeSignals[i].currentTP) hitTP = true; } else { // Sell if (currentAsk >= activeSignals[i].currentSL) hitSL = true; if (currentAsk <= activeSignals[i].currentTP) hitTP = true; } if (hitSL || hitTP) { DemoteSignal(i); continue; } } // PROMOTION (Check on target TF New Bar) MqlRates tfRates[]; ArraySetAsSeries(tfRates, true); if (activeSignals[i].maturity == MATURITY_D1 /*lookback if prev candle was a signal*/&& isNewH1) { if(CopyRates(_Symbol, PERIOD_H1, 0, 50, tfRates) >= 5) { if (IsGreenlight(tfRates) == activeSignals[i].type) PromoteSignal(i, MATURITY_H1, tfRates); } } else if (activeSignals[i].maturity == MATURITY_H1 /*lookback if prev candle was a signal*/&& isNewM5) { if(CopyRates(_Symbol, PERIOD_M5, 0, 50, tfRates) >= 5) { if (IsGreenlight(tfRates) == activeSignals[i].type) PromoteSignal(i, MATURITY_M5, tfRates); } } else if (activeSignals[i].maturity == MATURITY_M5 /*lookback if prev candle was a signal*/&& isNewM1) { if(CopyRates(_Symbol, PERIOD_M1, 0, 50, tfRates) >= 5) { if (IsGreenlight(tfRates) == activeSignals[i].type) { PromoteSignal(i, MATURITY_M1_EXECUTION, tfRates); Print(" >>> EXECUTION SIGNAL <<< ", activeSignals[i].id); } } } } }