//+------------------------------------------------------------------+ //| AI_Gold_Trader | //| MT5 + Groq AI Trading Expert | //+------------------------------------------------------------------+ #property strict #property version "1.00" #include CTrade trade; //==================================================================== // INPUTS //==================================================================== input string InpGroqAPIKey = ""; input bool UseAI = true; input bool UseLocalFallback = true; input string InpModel = "llama-3.3-70b-versatile"; input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M5; input double RiskPercent = 0.25; input double MaxTotalRiskPercent = 0.75; input double MaxLot = 0.01; input int MinConfidence = 65; input int ATR_Period = 14; input double SL_ATR_Multiplier = 1.35; input double TP_ATR_Multiplier = 0.85; input double BreakEvenTriggerATR = 0.45; input double BreakEvenLockATR = 0.12; input double TrailingStartATR = 0.35; input double TrailingATRMultiplier = 0.65; input double TrailingStepATR = 0.10; input double ZoneATRDistance = 0.60; input int ZoneLookbackBars = 30; input int MaxEntriesPerZone = 3; input int MaxOpenPositions = 1; input int AnalysisIntervalMinutes = 5; input int MaxTradesPerDay = 0; input double ZoneResetATR = 1.50; input ulong MagicNumber = 20260909; input bool EnableTrading = true; //==================================================================== string API_URL = "https://api.groq.com/openai/v1/chat/completions"; datetime LastAnalysisTime = 0; double ActiveZonePrice = 0.0; int ActiveZoneType = 0; int EntriesInActiveZone = 0; datetime LastReversalBarTime = 0; //==================================================================== // INIT //==================================================================== int OnInit() { trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(30); Print("=========================================="); Print("GROQ AI GOLD TRADER STARTED"); Print("Symbol: ", _Symbol); Print("Timeframe: ", EnumToString(InpTimeframe)); Print("Groq Model: ", InpModel); Print("Groq AI: ", UseAI); Print("Local M5 fallback: ", UseLocalFallback); Print("Trading: ", EnableTrading); Print("=========================================="); return(INIT_SUCCEEDED); } //==================================================================== // TICK //==================================================================== void OnTick() { ManageOpenPositions(); CheckReversalAndSwitch(); datetime now = TimeCurrent(); if(LastAnalysisTime > 0 && (now - LastAnalysisTime) < AnalysisIntervalMinutes * 60) return; LastAnalysisTime = now; AnalyzeMarket(); } //==================================================================== // MARKET ANALYSIS //==================================================================== void AnalyzeMarket() { if(!TerminalInfoInteger(TERMINAL_CONNECTED)) { Print("Terminal is not connected."); return; } double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(ask <= 0 || bid <= 0) return; double spread = (ask - bid) / _Point; Print("Current spread (information only): ", spread); //================================================================= // INDICATORS //================================================================= double ema20 = GetEMA(20); double ema50 = GetEMA(50); double ema200 = GetEMA(200); double rsi = GetRSI(); double macdMain = 0; double macdSignal = 0; GetMACD(macdMain, macdSignal); double atr = GetATR(); if(ema20 == 0 || ema50 == 0 || ema200 == 0 || rsi == 0 || atr == 0) { Print("Indicator data unavailable."); return; } //================================================================= // STRONG ZONE //================================================================= double zonePrice = 0.0; int zoneType = 0; bool hasZone = GetStrongZone( atr, zonePrice, zoneType ); if(hasZone) { UpdateZoneState( zonePrice, zoneType, atr ); if(EntriesInActiveZone >= MaxEntriesPerZone) { Print("Maximum entries for active zone reached."); return; } } else { Print("No nearby strong zone. Local M5 strategy may still trade."); } //================================================================= // CANDLE DATA //================================================================= string candles = GetCandleData(); //================================================================= // AI PROMPT //================================================================= string prompt = "You are an algorithmic trading analysis engine. " "Analyze XAUUSD using the supplied market data. " "You must be conservative. " "Do NOT invent missing data. " "Return HOLD when the setup is unclear. " "Only return BUY or SELL when multiple indicators agree. " "Minimum confidence for a trade is 65. " "Use the zone as a confirmation when a valid zone exists, " "but do not force HOLD when no zone exists. " "Prefer a realistic nearby target rather than an excessively distant target. " "Market information: " "Symbol=" + _Symbol + ", Timeframe=M5" + ", Bid=" + DoubleToString(bid,2) + ", Ask=" + DoubleToString(ask,2) + ", Spread=" + DoubleToString(spread,1) + ", EMA20=" + DoubleToString(ema20,2) + ", EMA50=" + DoubleToString(ema50,2) + ", EMA200=" + DoubleToString(ema200,2) + ", RSI=" + DoubleToString(rsi,2) + ", MACD=" + DoubleToString(macdMain,5) + ", MACD_SIGNAL=" + DoubleToString(macdSignal,5) + ", ATR=" + DoubleToString(atr,2) + ", Zone=" + (zoneType == 1 ? "SUPPORT" : (zoneType == -1 ? "RESISTANCE" : "NONE")) + ", ZonePrice=" + DoubleToString(zonePrice,2) + ", RecentCandles=" + candles + ". " "Return ONLY valid JSON in exactly this format: " "{\"decision\":\"BUY|SELL|HOLD\"," "\"confidence\":0," "\"reason\":\"short reason\"}"; string response = ""; string decision = "HOLD"; int confidence = 0; string reason = ""; bool success = false; //================================================================= // GROQ //================================================================= if(UseAI && StringLen(InpGroqAPIKey) >= 20) { success = SendToGroq( prompt, response ); } else if(UseAI) { Print("Groq API key is missing. Using local M5 strategy."); } //================================================================= // AI RESPONSE //================================================================= if(success) { Print("AI RAW RESPONSE:"); Print(response); decision = ExtractJSONText( response, "decision" ); confidence = ExtractJSONInt( response, "confidence" ); reason = ExtractJSONText( response, "reason" ); StringToUpper(decision); } else if(UseLocalFallback || !UseAI) { confidence = LocalM5Signal( ema20, ema50, ema200, rsi, macdMain, macdSignal, bid, ask, zoneType ); decision = LocalM5Decision( ema20, ema50, ema200, rsi, macdMain, macdSignal, zoneType ); if(confidence < MinConfidence) decision = "HOLD"; reason = "Local M5 strategy"; } if(decision == "") decision = "HOLD"; Print("=========================================="); Print("SIGNAL: ", decision); Print("CONFIDENCE: ", confidence); Print("REASON: ", reason); Print("=========================================="); //================================================================= // HOLD //================================================================= if(decision == "HOLD") { Print("Signal is HOLD. No trade."); return; } //================================================================= // CONFIDENCE //================================================================= if(confidence < MinConfidence) { Print("Confidence below threshold."); return; } //================================================================= // POSITION LIMIT //================================================================= if(CountOpenPositions() >= MaxOpenPositions) { Print("Maximum open positions reached."); return; } //================================================================= // DAILY LIMIT //================================================================= if(MaxTradesPerDay > 0 && CountTradesToday() >= MaxTradesPerDay) { Print("Maximum trades for today reached."); return; } //================================================================= // ZONE LIMIT //================================================================= if(hasZone && EntriesInActiveZone >= MaxEntriesPerZone) { Print("Maximum entries in this zone reached."); return; } //================================================================= // ZONE DIRECTION AGREEMENT //================================================================= if((decision == "BUY" && zoneType != 0 && zoneType != 1) || (decision == "SELL" && zoneType != 0 && zoneType != -1)) { Print("AI decision does not agree with active zone. No trade."); return; } //================================================================= // SL / TP //================================================================= double slDistance = atr * SL_ATR_Multiplier; double tpDistance = atr * TP_ATR_Multiplier; double lot = CalculateLotSize( slDistance ); if(lot <= 0) { Print("Invalid lot size."); return; } //================================================================= // BUY //================================================================= if(decision == "BUY") { double sl = ask - slDistance; double tp = ask + tpDistance; sl = NormalizeDouble( sl, _Digits ); tp = NormalizeDouble( tp, _Digits ); Print("BUY request"); Print("Lot: ", lot); Print("Entry: ", ask); Print("SL: ", sl); Print("TP: ", tp); if(EnableTrading) { bool result = trade.Buy( lot, _Symbol, 0, sl, tp, "AI BUY" ); if(result) { EntriesInActiveZone++; Print( "BUY ORDER SUCCESS. Zone entries: ", EntriesInActiveZone ); } else { Print( "BUY ORDER FAILED: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription() ); } } return; } //================================================================= // SELL //================================================================= if(decision == "SELL") { double sl = bid + slDistance; double tp = bid - tpDistance; sl = NormalizeDouble( sl, _Digits ); tp = NormalizeDouble( tp, _Digits ); Print("SELL request"); Print("Lot: ", lot); Print("Entry: ", bid); Print("SL: ", sl); Print("TP: ", tp); if(EnableTrading) { bool result = trade.Sell( lot, _Symbol, 0, sl, tp, "AI SELL" ); if(result) { EntriesInActiveZone++; Print( "SELL ORDER SUCCESS. Zone entries: ", EntriesInActiveZone ); } else { Print( "SELL ORDER FAILED: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription() ); } } return; } Print( "Unknown AI decision: ", decision ); } //==================================================================== // LOCAL M5 FALLBACK SIGNAL //==================================================================== int LocalM5Signal( double ema20, double ema50, double ema200, double rsi, double macdMain, double macdSignal, double bid, double ask, int zoneType ) { int score = 0; MqlRates rates[]; ArraySetAsSeries( rates, true ); if(CopyRates( _Symbol, InpTimeframe, 1, 3, rates ) < 3) return 0; double body = MathAbs( rates[0].close - rates[0].open ); double range = rates[0].high - rates[0].low; double upperWick = rates[0].high - MathMax( rates[0].open, rates[0].close ); double lowerWick = MathMin( rates[0].open, rates[0].close ) - rates[0].low; bool bullishCandle = rates[0].close > rates[0].open; bool bearishCandle = rates[0].close < rates[0].open; bool bullishEngulf = bullishCandle && rates[1].close < rates[1].open && rates[0].open <= rates[1].close && rates[0].close >= rates[1].open; bool bearishEngulf = bearishCandle && rates[1].close > rates[1].open && rates[0].open >= rates[1].close && rates[0].close <= rates[1].open; bool bullishReject = range > 0 && lowerWick > body * 1.2 && rates[0].close > rates[0].low + range * 0.60; bool bearishReject = range > 0 && upperWick > body * 1.2 && rates[0].close < rates[0].high - range * 0.60; if(ema20 > ema50) score += 25; if(ema50 > ema200) score += 15; if(ema20 < ema50) score += 25; if(ema50 < ema200) score += 15; if(rsi >= 51.0 && rsi <= 68.0) score += 10; if(rsi >= 32.0 && rsi <= 49.0) score += 10; if(macdMain >= macdSignal) score += 10; if(macdMain <= macdSignal) score += 10; if(zoneType == 1) score += 10; if(zoneType == -1) score += 10; if(bullishEngulf || bullishReject) score += 10; if(bearishEngulf || bearishReject) score += 10; if(ask > bid) score += 5; if(score > 100) score = 100; return score; } //==================================================================== // LOCAL M5 DECISION //==================================================================== string LocalM5Decision( double ema20, double ema50, double ema200, double rsi, double macdMain, double macdSignal, int zoneType ) { MqlRates rates[]; ArraySetAsSeries( rates, true ); if(CopyRates( _Symbol, InpTimeframe, 1, 3, rates ) < 3) return "HOLD"; double body = MathAbs( rates[0].close - rates[0].open ); double range = rates[0].high - rates[0].low; double upperWick = rates[0].high - MathMax( rates[0].open, rates[0].close ); double lowerWick = MathMin( rates[0].open, rates[0].close ) - rates[0].low; bool bullish = rates[0].close > rates[0].open; bool bearish = rates[0].close < rates[0].open; bool bullEngulf = bullish && rates[1].close < rates[1].open && rates[0].open <= rates[1].close && rates[0].close >= rates[1].open; bool bearEngulf = bearish && rates[1].close > rates[1].open && rates[0].open >= rates[1].close && rates[0].close <= rates[1].open; bool bullReject = range > 0 && lowerWick > body * 1.2 && rates[0].close > rates[0].low + range * 0.60; bool bearReject = range > 0 && upperWick > body * 1.2 && rates[0].close < rates[0].high - range * 0.60; int buy = 0; int sell = 0; if(ema20 > ema50) buy += 2; if(ema50 > ema200) buy += 1; if(rsi >= 50.0 && rsi <= 70.0) buy += 1; if(macdMain >= macdSignal) buy += 1; if(bullish || bullEngulf || bullReject) buy += 2; if(zoneType == 1) buy += 2; if(ema20 < ema50) sell += 2; if(ema50 < ema200) sell += 1; if(rsi >= 30.0 && rsi <= 50.0) sell += 1; if(macdMain <= macdSignal) sell += 1; if(bearish || bearEngulf || bearReject) sell += 2; if(zoneType == -1) sell += 2; if(buy >= 6 && buy > sell + 1) return "BUY"; if(sell >= 6 && sell > buy + 1) return "SELL"; return "HOLD"; } //==================================================================== // GROQ REQUEST //==================================================================== bool SendToGroq( string prompt, string &response ) { if(StringLen(InpGroqAPIKey) < 20) { Print("Groq API key is missing."); return false; } string escapedPrompt = JsonEscape(prompt); string json = "{" "\"model\":\"" + InpModel + "\"," "\"messages\":[{" "\"role\":\"user\"," "\"content\":\"" + escapedPrompt + "\"}" "]," "\"temperature\":0," "\"max_completion_tokens\":300" "}"; string headers = "Content-Type: application/json\r\n" "Authorization: Bearer " + InpGroqAPIKey + "\r\n"; char post[]; char result[]; string resultHeaders; StringToCharArray( json, post, 0, StringLen(json), CP_UTF8 ); ResetLastError(); int status = WebRequest( "POST", API_URL, headers, 30000, post, result, resultHeaders ); if(status == -1) { Print( "Groq WebRequest ERROR: ", GetLastError() ); return false; } response = CharArrayToString( result, 0, -1, CP_UTF8 ); Print( "GROQ HTTP STATUS: ", status ); if(status != 200) { Print("Groq HTTP ERROR"); Print(response); return false; } string content = ExtractMessageContent( response ); if(StringLen(content) > 0) response = content; return true; } //==================================================================== // EXTRACT GROQ MESSAGE CONTENT //==================================================================== string ExtractMessageContent( string json ) { string key = "\"content\":\""; int start = StringFind( json, key ); if(start < 0) return ""; start += StringLen(key); int end = start; bool escaped = false; while( end < StringLen(json) ) { ushort c = StringGetCharacter( json, end ); if(c == '\\' && !escaped) { escaped = true; end++; continue; } if(c == '"' && !escaped) break; escaped = false; end++; } if(end <= start) return ""; string value = StringSubstr( json, start, end - start ); value = JsonUnescape( value ); return value; } //==================================================================== // JSON TEXT //==================================================================== string ExtractJSONText( string json, string key ) { string search = "\"" + key + "\":\""; int start = StringFind( json, search ); if(start < 0) return ""; start += StringLen(search); int end = start; bool escaped = false; while( end < StringLen(json) ) { ushort c = StringGetCharacter( json, end ); if(c == '\\' && !escaped) { escaped = true; end++; continue; } if(c == '"' && !escaped) break; escaped = false; end++; } if(end <= start) return ""; return JsonUnescape( StringSubstr( json, start, end - start ) ); } //==================================================================== // JSON INT //==================================================================== int ExtractJSONInt( string json, string key ) { string search = "\"" + key + "\":"; int start = StringFind( json, search ); if(start < 0) return 0; start += StringLen(search); string number = ""; for( int i = start; i < StringLen(json); i++ ) { ushort c = StringGetCharacter( json, i ); if( (c >= '0' && c <= '9') || c == '-' ) { number += CharToString( (uchar)c ); } else { break; } } return( (int)StringToInteger( number ) ); } //==================================================================== // JSON ESCAPE //==================================================================== string JsonEscape( string text ) { StringReplace( text, "\\", "\\\\" ); StringReplace( text, "\"", "\\\"" ); StringReplace( text, "\r", "\\r" ); StringReplace( text, "\n", "\\n" ); return text; } //==================================================================== // JSON UNESCAPE //==================================================================== string JsonUnescape( string text ) { StringReplace( text, "\\\"", "\"" ); StringReplace( text, "\\\\", "\\" ); StringReplace( text, "\\n", "\n" ); StringReplace( text, "\\r", "\r" ); return text; } //==================================================================== // EMA //==================================================================== double GetEMA( int period ) { int handle = iMA( _Symbol, InpTimeframe, period, 0, MODE_EMA, PRICE_CLOSE ); if(handle == INVALID_HANDLE) return 0; double buffer[]; ArraySetAsSeries( buffer, true ); double value = 0; if( CopyBuffer( handle, 0, 1, 1, buffer ) > 0 ) { value = buffer[0]; } IndicatorRelease( handle ); return value; } //==================================================================== // RSI //==================================================================== double GetRSI() { int handle = iRSI( _Symbol, InpTimeframe, 14, PRICE_CLOSE ); if(handle == INVALID_HANDLE) return 0; double buffer[]; ArraySetAsSeries( buffer, true ); double value = 0; if( CopyBuffer( handle, 0, 1, 1, buffer ) > 0 ) { value = buffer[0]; } IndicatorRelease( handle ); return value; } //==================================================================== // MACD //==================================================================== void GetMACD( double &mainValue, double &signalValue ) { int handle = iMACD( _Symbol, InpTimeframe, 12, 26, 9, PRICE_CLOSE ); if(handle == INVALID_HANDLE) return; double mainBuffer[]; double signalBuffer[]; ArraySetAsSeries( mainBuffer, true ); ArraySetAsSeries( signalBuffer, true ); if( CopyBuffer( handle, 0, 1, 1, mainBuffer ) > 0 ) { mainValue = mainBuffer[0]; } if( CopyBuffer( handle, 1, 1, 1, signalBuffer ) > 0 ) { signalValue = signalBuffer[0]; } IndicatorRelease( handle ); } //==================================================================== // ATR //==================================================================== double GetATR() { int handle = iATR( _Symbol, InpTimeframe, ATR_Period ); if(handle == INVALID_HANDLE) return 0; double buffer[]; ArraySetAsSeries( buffer, true ); double value = 0; if( CopyBuffer( handle, 0, 1, 1, buffer ) > 0 ) { value = buffer[0]; } IndicatorRelease( handle ); return value; } //==================================================================== // CANDLE DATA //==================================================================== string GetCandleData() { MqlRates rates[]; ArraySetAsSeries( rates, true ); int copied = CopyRates( _Symbol, InpTimeframe, 1, 10, rates ); if(copied <= 0) return ""; string result = ""; for( int i = 0; i < copied; i++ ) { result += "[O=" + DoubleToString( rates[i].open, 2 ) + ",H=" + DoubleToString( rates[i].high, 2 ) + ",L=" + DoubleToString( rates[i].low, 2 ) + ",C=" + DoubleToString( rates[i].close, 2 ) + ",V=" + IntegerToString( (int)rates[i].tick_volume ) + "]"; if(i < copied - 1) result += ","; } return result; } //==================================================================== // STRONG ZONE DETECTION //==================================================================== bool GetStrongZone( double atr, double &zonePrice, int &zoneType ) { zonePrice = 0.0; zoneType = 0; if(atr <= 0) return false; MqlRates rates[]; ArraySetAsSeries( rates, true ); int copied = CopyRates( _Symbol, InpTimeframe, 1, ZoneLookbackBars, rates ); if(copied < 10) return false; double ask = SymbolInfoDouble( _Symbol, SYMBOL_ASK ); double bid = SymbolInfoDouble( _Symbol, SYMBOL_BID ); if(ask <= 0 || bid <= 0) return false; double recentLow = rates[0].low; double recentHigh = rates[0].high; for( int i = 1; i < copied; i++ ) { if( rates[i].low < recentLow ) { recentLow = rates[i].low; } if( rates[i].high > recentHigh ) { recentHigh = rates[i].high; } } double supportDistance = MathAbs( bid - recentLow ); double resistanceDistance = MathAbs( ask - recentHigh ); bool nearSupport = supportDistance <= atr * ZoneATRDistance; bool nearResistance = resistanceDistance <= atr * ZoneATRDistance; if(!nearSupport && !nearResistance) return false; if( nearSupport && nearResistance ) { if( supportDistance <= resistanceDistance ) { zonePrice = recentLow; zoneType = 1; } else { zonePrice = recentHigh; zoneType = -1; } return true; } if(nearSupport) { zonePrice = recentLow; zoneType = 1; return true; } zonePrice = recentHigh; zoneType = -1; return true; } //==================================================================== // ZONE STATE //==================================================================== void UpdateZoneState( double zonePrice, int zoneType, double atr ) { if( zonePrice <= 0 || zoneType == 0 || atr <= 0 ) return; if( ActiveZoneType != zoneType || ActiveZonePrice <= 0 || MathAbs( zonePrice - ActiveZonePrice ) > atr * 0.50 ) { ActiveZonePrice = zonePrice; ActiveZoneType = zoneType; EntriesInActiveZone = 0; Print( "NEW ACTIVE ZONE: ", (zoneType == 1 ? "SUPPORT" : "RESISTANCE"), " @ ", DoubleToString( zonePrice, _Digits ) ); } double price = ( zoneType == 1 ? SymbolInfoDouble( _Symbol, SYMBOL_BID ) : SymbolInfoDouble( _Symbol, SYMBOL_ASK ) ); if(price <= 0) return; if( MathAbs( price - ActiveZonePrice ) > atr * ZoneResetATR ) { ActiveZonePrice = 0.0; ActiveZoneType = 0; EntriesInActiveZone = 0; Print( "Active zone cleared: price moved away." ); } } //==================================================================== // OPEN POSITION COUNT //==================================================================== int CountOpenPositions() { int count = 0; for( int i = PositionsTotal() - 1; i >= 0; i-- ) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; string symbol = PositionGetString( POSITION_SYMBOL ); long magic = PositionGetInteger( POSITION_MAGIC ); if( symbol == _Symbol && (ulong)magic == MagicNumber ) { count++; } } return count; } //==================================================================== // POSITION MANAGEMENT //==================================================================== void ManageOpenPositions() { double atr = GetATR(); if(atr <= 0) return; double trigger = atr * BreakEvenTriggerATR; double lock = atr * BreakEvenLockATR; for( int i = PositionsTotal() - 1; i >= 0; i-- ) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; string symbol = PositionGetString( POSITION_SYMBOL ); long magic = PositionGetInteger( POSITION_MAGIC ); if( symbol != _Symbol || (ulong)magic != MagicNumber ) continue; long type = PositionGetInteger( POSITION_TYPE ); double openPrice = PositionGetDouble( POSITION_PRICE_OPEN ); double sl = PositionGetDouble( POSITION_SL ); double tp = PositionGetDouble( POSITION_TP ); double bid = SymbolInfoDouble( _Symbol, SYMBOL_BID ); double ask = SymbolInfoDouble( _Symbol, SYMBOL_ASK ); //============================================================== // BUY MANAGEMENT //============================================================== if(type == POSITION_TYPE_BUY) { double profitDistance = bid - openPrice; if( profitDistance >= trigger ) { double protectedSL = NormalizeDouble( openPrice + lock, _Digits ); if( sl == 0 || protectedSL > sl + _Point ) { if( trade.PositionModify( ticket, protectedSL, tp ) ) { sl = protectedSL; Print( "BUY profit protected at: ", protectedSL ); } } } //=========================================================== // BUY TRAILING //=========================================================== if( profitDistance >= atr * TrailingStartATR ) { double trailSL = NormalizeDouble( bid - atr * TrailingATRMultiplier, _Digits ); if( trailSL > openPrice && ( sl == 0 || trailSL > sl + atr * TrailingStepATR ) ) { if( trade.PositionModify( ticket, trailSL, tp ) ) { sl = trailSL; Print( "BUY trailing SL: ", trailSL ); } } } } //============================================================== // SELL MANAGEMENT //============================================================== else if(type == POSITION_TYPE_SELL) { double profitDistance = openPrice - ask; if( profitDistance >= trigger ) { double protectedSL = NormalizeDouble( openPrice - lock, _Digits ); if( sl == 0 || protectedSL < sl - _Point ) { if( trade.PositionModify( ticket, protectedSL, tp ) ) { sl = protectedSL; Print( "SELL profit protected at: ", protectedSL ); } } } //=========================================================== // SELL TRAILING //=========================================================== if( profitDistance >= atr * TrailingStartATR ) { double trailSL = NormalizeDouble( ask + atr * TrailingATRMultiplier, _Digits ); if( trailSL < openPrice && ( sl == 0 || trailSL < sl - atr * TrailingStepATR ) ) { if( trade.PositionModify( ticket, trailSL, tp ) ) { sl = trailSL; Print( "SELL trailing SL: ", trailSL ); } } } } } } //==================================================================== // REVERSAL: SELL -> BUY / BUY -> SELL //==================================================================== void CheckReversalAndSwitch() { if(!EnableTrading) return; if(CountOpenPositions() <= 0) return; datetime closedBar = iTime( _Symbol, InpTimeframe, 1 ); if( closedBar <= 0 || closedBar == LastReversalBarTime ) return; MqlRates r[]; ArraySetAsSeries( r, true ); if( CopyRates( _Symbol, InpTimeframe, 1, 3, r ) < 3 ) return; double ema20 = GetEMA(20); double ema50 = GetEMA(50); double rsi = GetRSI(); double macdMain = 0; double macdSignal = 0; GetMACD( macdMain, macdSignal ); double body = MathAbs( r[0].close - r[0].open ); double range = r[0].high - r[0].low; double upperWick = r[0].high - MathMax( r[0].open, r[0].close ); double lowerWick = MathMin( r[0].open, r[0].close ) - r[0].low; bool bullish = r[0].close > r[0].open; bool bearish = r[0].close < r[0].open; bool bullEngulf = bullish && r[1].close < r[1].open && r[0].open <= r[1].close && r[0].close >= r[1].open; bool bearEngulf = bearish && r[1].close > r[1].open && r[0].open >= r[1].close && r[0].close <= r[1].open; bool bullReject = range > 0 && lowerWick > body * 1.2 && r[0].close > r[0].low + range * 0.60; bool bearReject = range > 0 && upperWick > body * 1.2 && r[0].close < r[0].high - range * 0.60; int bullScore = 0; int bearScore = 0; if(bullish) bullScore += 2; if(bearish) bearScore += 2; if(bullEngulf) bullScore += 3; if(bearEngulf) bearScore += 3; if(bullReject) bullScore += 2; if(bearReject) bearScore += 2; if(ema20 > ema50) bullScore += 1; if(ema20 < ema50) bearScore += 1; if(rsi >= 52.0) bullScore += 1; if(rsi <= 48.0) bearScore += 1; if(macdMain > macdSignal) bullScore += 1; if(macdMain < macdSignal) bearScore += 1; for( int i = PositionsTotal() - 1; i >= 0; i-- ) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; string symbol = PositionGetString( POSITION_SYMBOL ); long magic = PositionGetInteger( POSITION_MAGIC ); if( symbol != _Symbol || (ulong)magic != MagicNumber ) continue; long type = PositionGetInteger( POSITION_TYPE ); //============================================================== // SELL -> BUY //============================================================== if( type == POSITION_TYPE_SELL && bullScore >= 6 && bullScore > bearScore + 1 ) { LastReversalBarTime = closedBar; Print( "CONFIRMED REVERSAL: SELL -> BUY" ); if( trade.PositionClose( ticket ) ) { OpenReversePosition( POSITION_TYPE_BUY, "REVERSAL BUY" ); } else { Print( "SELL close failed: ", trade.ResultRetcodeDescription() ); } return; } //============================================================== // BUY -> SELL //============================================================== if( type == POSITION_TYPE_BUY && bearScore >= 6 && bearScore > bullScore + 1 ) { LastReversalBarTime = closedBar; Print( "CONFIRMED REVERSAL: BUY -> SELL" ); if( trade.PositionClose( ticket ) ) { OpenReversePosition( POSITION_TYPE_SELL, "REVERSAL SELL" ); } else { Print( "BUY close failed: ", trade.ResultRetcodeDescription() ); } return; } } } //==================================================================== // OPEN REVERSE POSITION //==================================================================== void OpenReversePosition( long type, string comment ) { if(CountOpenPositions() > 0) return; double atr = GetATR(); if(atr <= 0) return; double slDistance = atr * SL_ATR_Multiplier; double tpDistance = atr * TP_ATR_Multiplier; double lot = CalculateLotSize( slDistance ); if(lot <= 0) return; bool result = false; //================================================================= // REVERSE BUY //================================================================= if(type == POSITION_TYPE_BUY) { double ask = SymbolInfoDouble( _Symbol, SYMBOL_ASK ); double sl = NormalizeDouble( ask - slDistance, _Digits ); double tp = NormalizeDouble( ask + tpDistance, _Digits ); result = trade.Buy( lot, _Symbol, 0, sl, tp, comment ); if(result) { Print( "REVERSAL BUY OPENED. Lot=", lot, " SL=", sl, " TP=", tp ); } } //================================================================= // REVERSE SELL //================================================================= else if(type == POSITION_TYPE_SELL) { double bid = SymbolInfoDouble( _Symbol, SYMBOL_BID ); double sl = NormalizeDouble( bid + slDistance, _Digits ); double tp = NormalizeDouble( bid - tpDistance, _Digits ); result = trade.Sell( lot, _Symbol, 0, sl, tp, comment ); if(result) { Print( "REVERSAL SELL OPENED. Lot=", lot, " SL=", sl, " TP=", tp ); } } if(!result) { Print( "Reverse order failed: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription() ); } } //==================================================================== // TODAY'S TRADE COUNT //==================================================================== int CountTradesToday() { datetime dayStart = StringToTime( TimeToString( TimeCurrent(), TIME_DATE ) ); if(dayStart <= 0) return 0; if( !HistorySelect( dayStart, TimeCurrent() ) ) return 0; int count = 0; int total = HistoryDealsTotal(); for( int i = 0; i < total; i++ ) { ulong ticket = HistoryDealGetTicket(i); if(ticket == 0) continue; string symbol = HistoryDealGetString( ticket, DEAL_SYMBOL ); long magic = HistoryDealGetInteger( ticket, DEAL_MAGIC ); long entry = HistoryDealGetInteger( ticket, DEAL_ENTRY ); if( symbol == _Symbol && (ulong)magic == MagicNumber && entry == DEAL_ENTRY_IN ) { count++; } } return count; } //==================================================================== // CURRENT OPEN RISK //==================================================================== double CurrentOpenRiskMoney() { double tickSize = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_SIZE ); double tickValue = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_VALUE ); if( tickSize <= 0 || tickValue <= 0 ) return 0; double totalRisk = 0; for( int i = PositionsTotal() - 1; i >= 0; i-- ) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; string symbol = PositionGetString( POSITION_SYMBOL ); long magic = PositionGetInteger( POSITION_MAGIC ); if( symbol != _Symbol || (ulong)magic != MagicNumber ) continue; double sl = PositionGetDouble( POSITION_SL ); double openPrice = PositionGetDouble( POSITION_PRICE_OPEN ); double volume = PositionGetDouble( POSITION_VOLUME ); if( sl <= 0 || openPrice <= 0 || volume <= 0 ) continue; double distance = MathAbs( openPrice - sl ); totalRisk += distance / tickSize * tickValue * volume; } return totalRisk; } //==================================================================== // LOT CALCULATION //==================================================================== double CalculateLotSize( double stopDistance ) { double balance = AccountInfoDouble( ACCOUNT_BALANCE ); double desiredRiskMoney = balance * RiskPercent / 100.0; double maxTotalRiskMoney = balance * MaxTotalRiskPercent / 100.0; double currentRiskMoney = CurrentOpenRiskMoney(); double remainingRiskMoney = maxTotalRiskMoney - currentRiskMoney; if(remainingRiskMoney <= 0) { Print( "Total risk cap reached. No new position." ); return 0; } double riskMoney = MathMin( desiredRiskMoney, remainingRiskMoney ); double tickSize = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_SIZE ); double tickValue = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_VALUE ); if( tickSize <= 0 || tickValue <= 0 || stopDistance <= 0 ) { return 0; } double lossPerLot = stopDistance / tickSize * tickValue; if(lossPerLot <= 0) return 0; double lot = riskMoney / lossPerLot; double minLot = SymbolInfoDouble( _Symbol, SYMBOL_VOLUME_MIN ); double maxBrokerLot = SymbolInfoDouble( _Symbol, SYMBOL_VOLUME_MAX ); double lotStep = SymbolInfoDouble( _Symbol, SYMBOL_VOLUME_STEP ); lot = MathMin( lot, MaxLot ); lot = MathMin( lot, maxBrokerLot ); if(lot < minLot) lot = minLot; if(lotStep > 0) { lot = MathFloor( lot / lotStep ) * lotStep; } int digits = 2; if(lotStep == 1.0) digits = 0; else if(lotStep == 0.1) digits = 1; else if(lotStep == 0.01) digits = 2; lot = NormalizeDouble( lot, digits ); return lot; } //+------------------------------------------------------------------+