| //+------------------------------------------------------------------+
|
| //| AI_Gold_Trader |
|
| //| MT5 + Groq AI Trading Expert |
|
| //+------------------------------------------------------------------+
|
| #property strict
|
| #property version "1.00"
|
|
|
| #include <Trade/Trade.mqh>
|
|
|
| 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;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|