//+------------------------------------------------------------------+
//|                                                   GoldBolt.mq5   |
//|  Fibonacci retracement Expert Advisor for MetaTrader 5           |
//|  Default market: XAUUSD / gold. Works on FX, indices, energy.    |
//|                                                                  |
//|  Hard rules:                                                     |
//|   - No hedging (one position, never opposite)                    |
//|   - No martingale / grid / averaging                             |
//|   - No HFT (new-bar entries only, never tick scalping)           |
//|   - Stop loss on every trade                                     |
//|   - Skip the setup if min lot would exceed planned risk          |
//|   - Scans M5–H4 and all sessions so it stays hunting             |
//+------------------------------------------------------------------+
#property copyright "JADEJMA"
#property version   "1.21"
#property description "Gold Bolt — Trade Like JMA. Fibonacci on the chart with live BUY/SELL calls."

#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>

CTrade        trade;
CPositionInfo position;

enum ENUM_BOLT_DIR
  {
   BOLT_NONE = 0,
   BOLT_BUY  = 1,
   BOLT_SELL = -1
  };

#define BOLT_TF_MAX 5

input group "=== Identity ==="
input long              InpMagic            = 20260827;

input group "=== Risk ==="
input double            InpRiskPercent      = 0.80;
input double            InpMaxDailyLossPct  = 4.00;
input double            InpMaxDrawdownPct   = 10.00;
input int               InpMaxTradesPerDay  = 16;
input bool              InpCompounding      = true;
input bool              InpSkipIfMinLotHot  = true;
input double            InpMaxMinLotRiskPct = 2.00;
input double            InpMaxLot           = 0.0;
input bool              InpAggressiveUnder1k = true;
input double            InpAggressiveEquity  = 1000.0;

input group "=== Fibonacci ==="
input ENUM_TIMEFRAMES   InpTF               = PERIOD_CURRENT;
input ENUM_TIMEFRAMES   InpHTF              = PERIOD_H1;
input bool              InpUseHtfFilter     = false;
input int               InpSwingLookback    = 56;
input int               InpSwingStrength    = 3;
input double            InpMinImpulseAtr    = 0.55;
input bool              InpUseFib382        = true;
input bool              InpUseFib50         = true;
input bool              InpUseFib618        = true;
input double            InpFibSl            = 78.6;
input double            InpFibTp            = 0.0;
input double            InpMinRR            = 1.20;
input int               InpAtrPeriod        = 14;

input group "=== Timeframes ==="
input bool              InpScanAllTimeframes = true;
input bool              InpUseM5            = true;
input bool              InpUseM15           = true;
input bool              InpUseM30           = true;
input bool              InpUseH1            = true;
input bool              InpUseH4            = true;

input group "=== Trade management ==="
input double            InpBeAtR            = 0.80;
input double            InpTrailStartR      = 1.00;
input double            InpTrailAtrMult     = 1.00;
input double            InpPartialCloseR    = 0.90;
input double            InpPartialClosePct  = 50.0;
input int               InpMinHoldMinutes   = 5;
input int               InpMaxHoldHours     = 18;
input int               InpSlippagePoints   = 40;
input int               InpMaxSpreadPoints  = 180;

input group "=== Sessions (GMT) ==="
input bool              InpTradeAllHours    = true;
input bool              InpUseTokyo         = true;
input bool              InpUseLondon        = true;
input bool              InpUseNewYork       = true;
input int               InpFridayCloseHour  = 0;
input int               InpSkipNewsMinutes  = 0;

input group "=== Prop / safety ==="
input bool              InpCloseOnDailyHalt = true;
input bool              InpOnePerSymbol     = true;
input bool              InpAllowBuy         = true;
input bool              InpAllowSell        = true;
input bool              InpShowPanel        = true;
input bool              InpDrawFibo         = true;

int      hHtfEma;
int      g_tfCount = 0;
ENUM_TIMEFRAMES g_tfs[BOLT_TF_MAX];
int      g_atrH[BOLT_TF_MAX];
datetime g_lastBarTf[BOLT_TF_MAX];
datetime g_dayStart    = 0;
double   g_dayStartEq  = 0;
double   g_startEquity = 0;
bool     g_halted      = false;
string   g_haltReason  = "";
bool     g_partialDone = false;
bool     g_beDone      = false;
int      g_digits      = 2;
double   g_pendingSL   = 0;
double   g_pendingTP   = 0;
double   g_swingA      = 0;
double   g_swingB      = 0;
ENUM_TIMEFRAMES g_signalTf = PERIOD_CURRENT;

bool BoltHot()
  {
   if(!InpAggressiveUnder1k)
      return false;
   return (AccountInfoDouble(ACCOUNT_EQUITY) < InpAggressiveEquity);
  }

double BoltRisk()
  {
   return BoltHot() ? MathMax(InpRiskPercent, 2.50) : InpRiskPercent;
  }

double BoltDaily()
  {
   return BoltHot() ? MathMax(InpMaxDailyLossPct, 8.00) : InpMaxDailyLossPct;
  }

double BoltDD()
  {
   return BoltHot() ? MathMax(InpMaxDrawdownPct, 18.00) : InpMaxDrawdownPct;
  }

int BoltMaxTrades()
  {
   return BoltHot() ? MathMax(InpMaxTradesPerDay, 28) : InpMaxTradesPerDay;
  }

double BoltImpulse()
  {
   return BoltHot() ? MathMin(InpMinImpulseAtr, 0.35) : InpMinImpulseAtr;
  }

double BoltMinRR()
  {
   return BoltHot() ? 1.00 : InpMinRR;
  }

int BoltHold()
  {
   return BoltHot() ? MathMin(InpMinHoldMinutes, 2) : InpMinHoldMinutes;
  }

double BoltMinLotCap()
  {
   return BoltHot() ? MathMax(InpMaxMinLotRiskPct, 6.00) : InpMaxMinLotRiskPct;
  }

bool BoltSkipHot()
  {
   return BoltHot() ? false : InpSkipIfMinLotHot;
  }

double BoltFibSl()
  {
   return BoltHot() ? MathMin(InpFibSl, 70.0) : InpFibSl;
  }

void AddTf(const ENUM_TIMEFRAMES tf)
  {
   if(g_tfCount >= BOLT_TF_MAX)
      return;
   for(int i = 0; i < g_tfCount; i++)
      if(g_tfs[i] == tf)
         return;
   g_tfs[g_tfCount++] = tf;
  }

void BuildTfList()
  {
   g_tfCount = 0;
   if(InpScanAllTimeframes)
     {
      if(InpUseM5)  AddTf(PERIOD_M5);
      if(InpUseM15) AddTf(PERIOD_M15);
      if(InpUseM30) AddTf(PERIOD_M30);
      if(InpUseH1)  AddTf(PERIOD_H1);
      if(InpUseH4)  AddTf(PERIOD_H4);
     }
   if(g_tfCount == 0)
      AddTf(InpTF == PERIOD_CURRENT ? (ENUM_TIMEFRAMES)Period() : InpTF);
  }

int OnInit()
  {
   if(InpRiskPercent <= 0 || InpRiskPercent > 8)
     {
      Print("Gold Bolt: InpRiskPercent must be between 0 and 5.");
      return INIT_PARAMETERS_INCORRECT;
     }
   if(!InpUseFib382 && !InpUseFib50 && !InpUseFib618)
     {
      Print("Gold Bolt: enable at least one Fibonacci entry (38.2 / 50 / 61.8).");
      return INIT_PARAMETERS_INCORRECT;
     }

   g_digits      = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   g_startEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   g_dayStartEq  = g_startEquity;
   g_dayStart    = DayStart(TimeCurrent());
   g_halted      = false;
   g_haltReason  = "";

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippagePoints);
   trade.SetAsyncMode(false);
   ApplyFilling();

   BuildTfList();
   for(int i = 0; i < g_tfCount; i++)
     {
      g_atrH[i] = iATR(_Symbol, g_tfs[i], InpAtrPeriod);
      g_lastBarTf[i] = 0;
      if(g_atrH[i] == INVALID_HANDLE)
        {
         Print("Gold Bolt: failed to create ATR on ", EnumToString(g_tfs[i]));
         return INIT_FAILED;
        }
     }

   hHtfEma = iMA(_Symbol, InpHTF, 50, 0, MODE_EMA, PRICE_CLOSE);
   if(hHtfEma == INVALID_HANDLE)
     {
      Print("Gold Bolt: failed to create HTF EMA.");
      return INIT_FAILED;
     }

   Print("Gold Bolt on ", _Symbol,
         " | scan ", (InpScanAllTimeframes ? "M5-H4" : EnumToString(g_tfs[0])),
         " | hours ", (InpTradeAllHours ? "24h" : "session"),
         " | fib 38.2/", (InpUseFib382 ? "on" : "off"),
         " 50/", (InpUseFib50 ? "on" : "off"),
         " 61.8/", (InpUseFib618 ? "on" : "off"),
         " | risk ", DoubleToString(BoltRisk(), 2), "%",
         " | ", (BoltHot() ? "AGGRESSIVE under $1k" : "standard"),
         " | no hedge / no martingale / no HFT");
   UpdateChartArt();
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   for(int i = 0; i < g_tfCount; i++)
      if(g_atrH[i] != INVALID_HANDLE)
         IndicatorRelease(g_atrH[i]);
   IndicatorRelease(hHtfEma);
   ObjectsDeleteAll(0, "JMA_");
   ObjectDelete(0, "GoldBolt_Fibo");
   Comment("");
  }

void OnTick()
  {
   RollDay();
   ManageOpen();
   CheckHalts();
   if(InpShowPanel)
      DrawPanel();
   static datetime lastArt = 0;
   if(TimeCurrent() != lastArt)
     {
      lastArt = TimeCurrent();
      UpdateChartArt();
     }

   if(g_halted)
      return;
   if(!TerminalReady())
      return;
   if(HasOurPosition())
      return;
   if(InpOnePerSymbol && HasAnySymbolPosition())
      return;
   if(CountEntriesToday() >= BoltMaxTrades())
      return;
   if(!InpTradeAllHours && !InSession())
      return;
   if(IsNewsWindow())
      return;
   if(IsFridayFlattenWindow())
      return;
   if(!SpreadOk())
      return;

   for(int i = 0; i < g_tfCount; i++)
     {
      if(!IsNewBarTf(i))
         continue;
      ENUM_BOLT_DIR dir = SignalOn(g_tfs[i], g_atrH[i]);
      if(dir == BOLT_NONE)
         continue;
      g_signalTf = g_tfs[i];
      OpenTrade(dir);
      return;
     }
  }

void ApplyFilling()
  {
   uint filling = (uint)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
   if((filling & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
      trade.SetTypeFilling(ORDER_FILLING_IOC);
   else if((filling & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
      trade.SetTypeFilling(ORDER_FILLING_FOK);
   else
      trade.SetTypeFilling(ORDER_FILLING_RETURN);
  }

bool IsNewBarTf(const int idx)
  {
   datetime t = iTime(_Symbol, g_tfs[idx], 0);
   if(t == 0)
      return false;
   if(t == g_lastBarTf[idx])
      return false;
   g_lastBarTf[idx] = t;
   return true;
  }

datetime DayStart(const datetime t)
  {
   MqlDateTime dt;
   TimeToStruct(t, dt);
   dt.hour = 0;
   dt.min  = 0;
   dt.sec  = 0;
   return StructToTime(dt);
  }

void RollDay()
  {
   datetime start = DayStart(TimeCurrent());
   if(start != g_dayStart)
     {
      g_dayStart   = start;
      g_dayStartEq = AccountInfoDouble(ACCOUNT_EQUITY);
      g_halted     = false;
      g_haltReason = "";
     }
  }

bool TerminalReady()
  {
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      return false;
   if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
      return false;
   if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
      return false;
   if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
      return false;
   return true;
  }

bool SpreadOk()
  {
   if(InpMaxSpreadPoints <= 0)
      return true;
   return ((int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) <= InpMaxSpreadPoints);
  }

int GmtHour()
  {
   MqlDateTime dt;
   TimeToStruct(TimeGMT(), dt);
   return dt.hour;
  }

int GmtDow()
  {
   MqlDateTime dt;
   TimeToStruct(TimeGMT(), dt);
   return dt.day_of_week;
  }

bool InSession()
  {
   if(InpTradeAllHours)
      return true;
   int h = GmtHour();
   bool tokyo  = InpUseTokyo  && (h >= 0 && h < 9);
   bool london = InpUseLondon && (h >= 7 && h < 16);
   bool ny     = InpUseNewYork && (h >= 12 && h < 22);
   return (tokyo || london || ny);
  }

bool IsFridayFlattenWindow()
  {
   if(InpFridayCloseHour <= 0)
      return false;
   return (GmtDow() == 5 && GmtHour() >= InpFridayCloseHour);
  }

bool IsNewsWindow()
  {
   if(InpSkipNewsMinutes <= 0)
      return false;
   MqlDateTime dt;
   TimeToStruct(TimeGMT(), dt);
   int minutes = dt.hour * 60 + dt.min;
   int windows[5] = {12 * 60 + 30, 13 * 60 + 30, 14 * 60, 14 * 60 + 30, 18 * 60};
   for(int i = 0; i < 5; i++)
      if(MathAbs(minutes - windows[i]) < InpSkipNewsMinutes)
         return true;
   return false;
  }

double DailyLossPct()
  {
   if(g_dayStartEq <= 0)
      return 0;
   return (g_dayStartEq - AccountInfoDouble(ACCOUNT_EQUITY)) / g_dayStartEq * 100.0;
  }

double StaticDdPct()
  {
   if(g_startEquity <= 0)
      return 0;
   return (g_startEquity - AccountInfoDouble(ACCOUNT_EQUITY)) / g_startEquity * 100.0;
  }

void CheckHalts()
  {
   if(g_halted)
      return;
   if(DailyLossPct() >= BoltDaily())
     {
      TripHalt("daily loss halt");
      return;
     }
   if(StaticDdPct() >= BoltDD())
      TripHalt("max drawdown halt");
  }

void TripHalt(const string reason)
  {
   g_halted = true;
   g_haltReason = reason;
   Print("Gold Bolt HALT: ", reason);
   if(InpCloseOnDailyHalt)
      CloseOurPosition("halt");
  }

bool CopyAtrHandle(const int handle, const int count, double &out[])
  {
   ArraySetAsSeries(out, true);
   return (CopyBuffer(handle, 0, 0, count, out) >= count);
  }

bool CopyAtr(const int count, double &out[])
  {
   int h = (g_tfCount > 0 ? g_atrH[0] : INVALID_HANDLE);
   if(h == INVALID_HANDLE)
      return false;
   return CopyAtrHandle(h, count, out);
  }

bool IsFractalHigh(const ENUM_TIMEFRAMES tf, const int shift, const int str)
  {
   double h = iHigh(_Symbol, tf, shift);
   for(int k = 1; k <= str; k++)
     {
      if(h <= iHigh(_Symbol, tf, shift - k) || h <= iHigh(_Symbol, tf, shift + k))
         return false;
     }
   return true;
  }

bool IsFractalLow(const ENUM_TIMEFRAMES tf, const int shift, const int str)
  {
   double l = iLow(_Symbol, tf, shift);
   for(int k = 1; k <= str; k++)
     {
      if(l >= iLow(_Symbol, tf, shift - k) || l >= iLow(_Symbol, tf, shift + k))
         return false;
     }
   return true;
  }

double FibLevel(const double swingStart, const double swingEnd, const double pct)
  {
   return swingEnd - (swingEnd - swingStart) * (pct / 100.0);
  }

bool HtfAllows(const ENUM_BOLT_DIR dir)
  {
   if(!InpUseHtfFilter)
      return true;
   double ema[];
   ArraySetAsSeries(ema, true);
   if(CopyBuffer(hHtfEma, 0, 0, 3, ema) < 3)
      return true;
   double c = iClose(_Symbol, InpHTF, 1);
   if(dir == BOLT_BUY)
      return (c >= ema[1]);
   return (c <= ema[1]);
  }

ENUM_BOLT_DIR SignalOn(const ENUM_TIMEFRAMES tf, const int atrHandle)
  {
   double atr[];
   if(!CopyAtrHandle(atrHandle, 4, atr))
      return BOLT_NONE;

   int str = MathMax(2, InpSwingStrength);
   int hi = -1, lo = -1;
   int from = str + 1;
   int to   = MathMin(InpSwingLookback, iBars(_Symbol, tf) - str - 2);
   for(int s = from; s <= to; s++)
     {
      if(hi < 0 && IsFractalHigh(tf, s, str))
         hi = s;
      if(lo < 0 && IsFractalLow(tf, s, str))
         lo = s;
      if(hi >= 0 && lo >= 0)
         break;
     }
   if(hi < 0 || lo < 0)
      return BOLT_NONE;

   bool bullish = (hi < lo);
   ENUM_BOLT_DIR dir = (bullish ? BOLT_BUY : BOLT_SELL);
   if(dir == BOLT_BUY && !InpAllowBuy)
      return BOLT_NONE;
   if(dir == BOLT_SELL && !InpAllowSell)
      return BOLT_NONE;
   if(!HtfAllows(dir))
      return BOLT_NONE;

   double swingStart = bullish ? iLow(_Symbol, tf, lo) : iHigh(_Symbol, tf, hi);
   double swingEnd   = bullish ? iHigh(_Symbol, tf, hi) : iLow(_Symbol, tf, lo);
   double impulse    = MathAbs(swingEnd - swingStart);
   if(impulse < atr[1] * BoltImpulse())
      return BOLT_NONE;

   double c1 = iClose(_Symbol, tf, 1);
   double o1 = iOpen(_Symbol, tf, 1);
   double h1 = iHigh(_Symbol, tf, 1);
   double l1 = iLow(_Symbol, tf, 1);
   double overshoot = atr[1] * 0.20;

   if(bullish)
     {
      if(l1 < MathMin(swingStart, swingEnd) - overshoot || c1 > swingEnd + overshoot)
         return BOLT_NONE;
     }
   else
     {
      if(h1 > MathMax(swingStart, swingEnd) + overshoot || c1 < swingEnd - overshoot)
         return BOLT_NONE;
     }

   double entries[3];
   int n = 0;
   if(InpUseFib382)
      entries[n++] = 38.2;
   if(InpUseFib50)
      entries[n++] = 50.0;
   if(InpUseFib618)
      entries[n++] = 61.8;

   double usedPct = -1;
   for(int i = 0; i < n; i++)
     {
      double level = FibLevel(swingStart, swingEnd, entries[i]);
      bool touched = (l1 <= level && h1 >= level);
      bool rejected = bullish ? (c1 > level) : (c1 < level);
      if(touched && rejected)
        {
         usedPct = entries[i];
         break;
        }
     }
   if(usedPct < 0)
      return BOLT_NONE;

   double slLevel = FibLevel(swingStart, swingEnd, BoltFibSl());
   double tpSwing = FibLevel(swingStart, swingEnd, InpFibTp);
   double buffer  = (double)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point + _Point * 2;
   g_pendingSL = bullish ? slLevel - buffer : slLevel + buffer;
   g_pendingTP = tpSwing;
   g_swingA = swingStart;
   g_swingB = swingEnd;

   if(InpDrawFibo)
      DrawFibo(swingStart, swingEnd, iTime(_Symbol, tf, MathMax(hi, lo)), iTime(_Symbol, tf, MathMin(hi, lo)));

   return dir;
  }

void JmaLabel(const string id, const int x, const int y, const string text, const color clr, const int size)
  {
   string n = "JMA_" + id;
   if(ObjectFind(0, n) < 0)
      ObjectCreate(0, n, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
   ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, n, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, n, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, n, OBJPROP_FONTSIZE, size);
   ObjectSetString(0, n, OBJPROP_FONT, "Arial Bold");
   ObjectSetString(0, n, OBJPROP_TEXT, text);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, n, OBJPROP_BACK, false);
  }

void JmaHLine(const string id, const double price, const color clr, const int style, const int width)
  {
   string n = "JMA_" + id;
   if(ObjectFind(0, n) < 0)
      ObjectCreate(0, n, OBJ_HLINE, 0, 0, price);
   ObjectSetDouble(0, n, OBJPROP_PRICE, price);
   ObjectSetInteger(0, n, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, n, OBJPROP_STYLE, style);
   ObjectSetInteger(0, n, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, n, OBJPROP_BACK, true);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, n, OBJPROP_RAY_RIGHT, true);
  }

void JmaText(const string id, const datetime t, const double price, const string text, const color clr)
  {
   string n = "JMA_" + id;
   if(ObjectFind(0, n) < 0)
      ObjectCreate(0, n, OBJ_TEXT, 0, t, price);
   ObjectMove(0, n, 0, t, price);
   ObjectSetString(0, n, OBJPROP_TEXT, "  " + text);
   ObjectSetInteger(0, n, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, n, OBJPROP_FONTSIZE, 9);
   ObjectSetString(0, n, OBJPROP_FONT, "Arial Bold");
   ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
  }

void JmaArrow(const datetime t, const double price, const bool buy)
  {
   string n = "JMA_ARW";
   if(ObjectFind(0, n) < 0)
      ObjectCreate(0, n, OBJ_ARROW, 0, t, price);
   ObjectMove(0, n, 0, t, price);
   ObjectSetInteger(0, n, OBJPROP_ARROWCODE, buy ? 233 : 234);
   ObjectSetInteger(0, n, OBJPROP_COLOR, buy ? C'15,159,110' : C'214,69,69');
   ObjectSetInteger(0, n, OBJPROP_WIDTH, 3);
   ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
  }

bool ScanSwing(const ENUM_TIMEFRAMES tf, int &hi, int &lo, bool &bullish, double &swingStart, double &swingEnd)
  {
   int str = MathMax(2, InpSwingStrength);
   hi = -1;
   lo = -1;
   int from = str + 1;
   int to   = MathMin(InpSwingLookback, iBars(_Symbol, tf) - str - 2);
   if(to <= from)
      return false;
   for(int s = from; s <= to; s++)
     {
      if(hi < 0 && IsFractalHigh(tf, s, str))
         hi = s;
      if(lo < 0 && IsFractalLow(tf, s, str))
         lo = s;
      if(hi >= 0 && lo >= 0)
         break;
     }
   if(hi < 0 || lo < 0)
      return false;
   bullish = (hi < lo);
   swingStart = bullish ? iLow(_Symbol, tf, lo) : iHigh(_Symbol, tf, hi);
   swingEnd   = bullish ? iHigh(_Symbol, tf, hi) : iLow(_Symbol, tf, lo);
   return (MathAbs(swingEnd - swingStart) > _Point);
  }

void DrawLiveBoard(const ENUM_TIMEFRAMES tf)
  {
   int hi, lo;
   bool bullish;
   double swingStart, swingEnd;
   if(!ScanSwing(tf, hi, lo, bullish, swingStart, swingEnd))
      return;

   datetime tLeft  = iTime(_Symbol, tf, MathMax(hi, lo));
   datetime tRight = iTime(_Symbol, PERIOD_CURRENT, 0) + PeriodSeconds() * 8;
   datetime tLab   = iTime(_Symbol, PERIOD_CURRENT, 1);
   if(tLab <= 0)
      tLab = TimeCurrent();

   DrawFibo(swingStart, swingEnd, tLeft, iTime(_Symbol, tf, MathMin(hi, lo)));

   double p0   = FibLevel(swingStart, swingEnd, 0);
   double p382 = FibLevel(swingStart, swingEnd, 38.2);
   double p50  = FibLevel(swingStart, swingEnd, 50);
   double p618 = FibLevel(swingStart, swingEnd, 61.8);
   double p786 = FibLevel(swingStart, swingEnd, BoltFibSl());
   double p100 = FibLevel(swingStart, swingEnd, 100);

   color buyClr  = C'15,159,110';
   color sellClr = C'214,69,69';
   color fibClr  = C'45,125,255';
   color sideClr = bullish ? buyClr : sellClr;
   string side   = bullish ? "BUY" : "SELL";

   JmaHLine("L100", p100, C'107,130,153', STYLE_DOT, 1);
   JmaHLine("L786", p786, sellClr, STYLE_DASH, 1);
   JmaHLine("L618", p618, fibClr, STYLE_SOLID, 2);
   JmaHLine("L50",  p50,  fibClr, STYLE_SOLID, 1);
   JmaHLine("L382", p382, fibClr, STYLE_DOT, 1);
   JmaHLine("L0",   p0,   buyClr, STYLE_DASH, 1);

   JmaText("T100", tLab, p100, "100  impulse", C'107,130,153');
   JmaText("T786", tLab, p786, DoubleToString(BoltFibSl(), 1) + "  STOP", sellClr);
   JmaText("T618", tLab, p618, InpUseFib618 ? ("61.8  " + side) : "61.8", sideClr);
   JmaText("T50",  tLab, p50,  InpUseFib50  ? ("50  " + side) : "50", sideClr);
   JmaText("T382", tLab, p382, InpUseFib382 ? ("38.2  " + side) : "38.2", sideClr);
   JmaText("T0",   tLab, p0,   "0  TARGET", buyClr);

   string zone = "JMA_ZONE";
   if(ObjectFind(0, zone) < 0)
      ObjectCreate(0, zone, OBJ_RECTANGLE, 0, tLeft, p50, tRight, p618);
   ObjectSetInteger(0, zone, OBJPROP_TIME, 0, tLeft);
   ObjectSetDouble(0, zone, OBJPROP_PRICE, 0, p50);
   ObjectSetInteger(0, zone, OBJPROP_TIME, 1, tRight);
   ObjectSetDouble(0, zone, OBJPROP_PRICE, 1, p618);
   ObjectSetInteger(0, zone, OBJPROP_COLOR, sideClr);
   ObjectSetInteger(0, zone, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, zone, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, zone, OBJPROP_FILL, true);
   ObjectSetInteger(0, zone, OBJPROP_BACK, true);
   ObjectSetInteger(0, zone, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, zone, OBJPROP_HIDDEN, true);

   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double impulse = MathAbs(swingEnd - swingStart);
   double near = impulse * 0.08;
   string call = "WAIT  " + side + "  at  38.2 / 50 / 61.8";
   double mark = p618;
   bool now = false;

   if(InpUseFib382 && MathAbs(bid - p382) <= near) { call = side + " NOW  ·  38.2"; mark = p382; now = true; }
   if(InpUseFib50  && MathAbs(bid - p50)  <= near) { call = side + " NOW  ·  50";   mark = p50;  now = true; }
   if(InpUseFib618 && MathAbs(bid - p618) <= near) { call = side + " NOW  ·  61.8"; mark = p618; now = true; }

   if(HasOurPosition() && position.SelectByTicket(OurTicket()))
     {
      bool inBuy = (position.PositionType() == POSITION_TYPE_BUY);
      call = (inBuy ? "IN BUY" : "IN SELL") + "  ·  TRADE LIKE JMA";
      mark = position.PriceOpen();
      now = true;
      JmaArrow((datetime)position.Time(), position.PriceOpen(), inBuy);
     }
   else if(now)
      JmaArrow(tLab, mark, bullish);

   color callClr = now ? sideClr : fibClr;
   JmaLabel("CALL", 16, 78, call, callClr, 11);
  }

void UpdateChartArt()
  {
   if(!InpDrawFibo && !InpShowPanel)
      return;

   JmaLabel("BRAND", 16, 16, "TRADE LIKE JMA", C'45,125,255', 18);
   string sub = "Gold Bolt  ·  " + _Symbol + "  ·  " + (g_halted ? "HALTED" : (BoltHot() ? "AGGRESSIVE" : "ACTIVE"));
   JmaLabel("SUB", 16, 42, sub, C'232,241,251', 10);
   JmaLabel("TAG", 16, 60, "Fibonacci 38.2 / 50 / 61.8   stop 78.6   created by JADEJMA", C'123,184,255', 8);

   ENUM_TIMEFRAMES vis = (ENUM_TIMEFRAMES)Period();
   DrawLiveBoard(vis);
   ChartRedraw(0);
  }

void DrawFibo(const double p100, const double p0, const datetime t100, const datetime t0)
  {
   string name = "GoldBolt_Fibo";
   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_FIBO, 0, t100, p100, t0, p0);
   ObjectMove(0, name, 0, t100, p100);
   ObjectMove(0, name, 1, t0, p0);
   ObjectSetInteger(0, name, OBJPROP_COLOR, C'45,125,255');
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
   ObjectSetInteger(0, name, OBJPROP_BACK, true);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_LEVELS, 6);
   ObjectSetDouble(0, name, OBJPROP_LEVELVALUE, 0, 0.0);
   ObjectSetString(0, name, OBJPROP_LEVELTEXT, 0, "0 Target");
   ObjectSetInteger(0, name, OBJPROP_LEVELCOLOR, 0, C'15,159,110');
   ObjectSetDouble(0, name, OBJPROP_LEVELVALUE, 1, 0.382);
   ObjectSetString(0, name, OBJPROP_LEVELTEXT, 1, "38.2");
   ObjectSetInteger(0, name, OBJPROP_LEVELCOLOR, 1, C'45,125,255');
   ObjectSetDouble(0, name, OBJPROP_LEVELVALUE, 2, 0.5);
   ObjectSetString(0, name, OBJPROP_LEVELTEXT, 2, "50");
   ObjectSetInteger(0, name, OBJPROP_LEVELCOLOR, 2, C'45,125,255');
   ObjectSetDouble(0, name, OBJPROP_LEVELVALUE, 3, 0.618);
   ObjectSetString(0, name, OBJPROP_LEVELTEXT, 3, "61.8");
   ObjectSetInteger(0, name, OBJPROP_LEVELCOLOR, 3, C'45,125,255');
   ObjectSetDouble(0, name, OBJPROP_LEVELVALUE, 4, 0.786);
   ObjectSetString(0, name, OBJPROP_LEVELTEXT, 4, "78.6 Stop");
   ObjectSetInteger(0, name, OBJPROP_LEVELCOLOR, 4, C'214,69,69');
   ObjectSetDouble(0, name, OBJPROP_LEVELVALUE, 5, 1.0);
   ObjectSetString(0, name, OBJPROP_LEVELTEXT, 5, "100 Impulse");
   ObjectSetInteger(0, name, OBJPROP_LEVELCOLOR, 5, C'107,130,153');
  }

double PointValuePerLot()
  {
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   if(tickSize <= 0)
      return 0;
   return tickValue / tickSize;
  }

double NormalizeLots(double lots)
  {
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double step   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   if(InpMaxLot > 0)
      maxLot = MathMin(maxLot, InpMaxLot);
   if(step <= 0)
      step = 0.01;
   lots = MathFloor(lots / step + 1e-12) * step;
   lots = MathMax(minLot, MathMin(maxLot, lots));
   int volDigits = 2;
   if(step < 0.01)
      volDigits = 3;
   else if(step >= 1)
      volDigits = 0;
   else if(step >= 0.1)
      volDigits = 1;
   return NormalizeDouble(lots, volDigits);
  }

double ComputeLots(const double slDistance, bool &skippedHot)
  {
   skippedHot = false;
   double equity = InpCompounding ? AccountInfoDouble(ACCOUNT_EQUITY)
                                  : MathMax(g_startEquity, AccountInfoDouble(ACCOUNT_BALANCE));
   double riskMoney = equity * (BoltRisk() / 100.0);
   double pv = PointValuePerLot();
   if(pv <= 0 || slDistance <= 0 || riskMoney <= 0)
      return 0;

   double lots = riskMoney / (slDistance * pv);
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   if(lots < minLot)
     {
      double minRisk = minLot * slDistance * pv;
      double cap = equity * (BoltMinLotCap() / 100.0);
      if(BoltSkipHot() && minRisk > cap)
        {
         skippedHot = true;
         return 0;
        }
      lots = minLot;
     }
   return NormalizeLots(lots);
  }

void OpenTrade(const ENUM_BOLT_DIR dir)
  {
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double entry = (dir == BOLT_BUY ? ask : bid);
   double sl = g_pendingSL;
   double tp = g_pendingTP;

   long stops = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
   double minDist = MathMax(stops * _Point, _Point * 10);
   if(dir == BOLT_BUY && entry - sl < minDist)
      sl = entry - minDist;
   if(dir == BOLT_SELL && sl - entry < minDist)
      sl = entry + minDist;

   double slDist = MathAbs(entry - sl);
   if(MathAbs(tp - entry) < slDist * BoltMinRR())
      tp = (dir == BOLT_BUY) ? entry + slDist * BoltMinRR() : entry - slDist * BoltMinRR();

   bool skippedHot = false;
   double lots = ComputeLots(slDist, skippedHot);
   if(lots <= 0)
     {
      if(skippedHot)
         Print("Gold Bolt: skipped — min lot would risk more than ",
               DoubleToString(BoltMinLotCap(), 2), "% on ", _Symbol,
               ". Use a micro/cent gold symbol or wait for a deeper bounce.");
      return;
     }

   sl = NormalizeDouble(sl, g_digits);
   tp = NormalizeDouble(tp, g_digits);

   ResetLastError();
   bool ok;
   if(dir == BOLT_BUY)
      ok = trade.Buy(lots, _Symbol, 0.0, sl, tp, "GoldBolt");
   else
      ok = trade.Sell(lots, _Symbol, 0.0, sl, tp, "GoldBolt");

   if(!ok)
     {
      if(dir == BOLT_BUY)
         ok = trade.Buy(lots, _Symbol, 0.0, 0.0, 0.0, "GoldBolt");
      else
         ok = trade.Sell(lots, _Symbol, 0.0, 0.0, 0.0, "GoldBolt");
      if(ok)
        {
         Sleep(200);
         if(HasOurPosition())
           {
            ulong ticket = OurTicket();
            if(!trade.PositionModify(ticket, sl, tp))
               Print("Gold Bolt: opened but failed to set SL/TP: ", trade.ResultRetcodeDescription());
           }
        }
     }

   if(ok)
     {
      g_partialDone = false;
      g_beDone = false;
      Print("Gold Bolt ", (dir == BOLT_BUY ? "BUY" : "SELL"),
            " ", DoubleToString(lots, 3),
            " on ", EnumToString(g_signalTf),
            " SL ", DoubleToString(sl, g_digits),
            " TP ", DoubleToString(tp, g_digits));
     }
   else
      Print("Gold Bolt: order failed ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription());
  }

bool HasOurPosition()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!position.SelectByIndex(i))
         continue;
      if(position.Symbol() == _Symbol && position.Magic() == InpMagic)
         return true;
     }
   return false;
  }

bool HasAnySymbolPosition()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!position.SelectByIndex(i))
         continue;
      if(position.Symbol() == _Symbol)
         return true;
     }
   return false;
  }

ulong OurTicket()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!position.SelectByIndex(i))
         continue;
      if(position.Symbol() == _Symbol && position.Magic() == InpMagic)
         return position.Ticket();
     }
   return 0;
  }

int CountEntriesToday()
  {
   if(!HistorySelect(g_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;
      if((long)HistoryDealGetInteger(ticket, DEAL_MAGIC) != InpMagic)
         continue;
      if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol)
         continue;
      if((ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY) != DEAL_ENTRY_IN)
         continue;
      ENUM_DEAL_TYPE typ = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(typ != DEAL_TYPE_BUY && typ != DEAL_TYPE_SELL)
         continue;
      count++;
     }
   return count;
  }

void CloseOurPosition(const string reason)
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!position.SelectByIndex(i))
         continue;
      if(position.Symbol() != _Symbol || position.Magic() != InpMagic)
         continue;
      if(trade.PositionClose(position.Ticket()))
         Print("Gold Bolt: closed (", reason, ")");
     }
   g_partialDone = false;
   g_beDone = false;
  }

void ManageOpen()
  {
   if(!HasOurPosition())
     {
      g_partialDone = false;
      g_beDone = false;
      return;
     }
   if(!position.SelectByTicket(OurTicket()))
      return;

   if(IsFridayFlattenWindow())
     {
      CloseOurPosition("Friday close");
      return;
     }

   double openPrice = position.PriceOpen();
   double sl        = position.StopLoss();
   double tp        = position.TakeProfit();
   double vol       = position.Volume();
   datetime openTime = (datetime)position.Time();
   int dir = (position.PositionType() == POSITION_TYPE_BUY ? 1 : -1);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double now = (dir > 0 ? bid : ask);

   double slDist = MathAbs(openPrice - sl);
   if(slDist <= 0 && tp > 0)
      slDist = MathAbs(tp - openPrice) / MathMax(BoltMinRR(), 1.0);
   if(slDist <= 0)
      return;

   double rNow = ((now - openPrice) * dir) / slDist;
   int holdMin = (int)((TimeCurrent() - openTime) / 60);

   if(holdMin < BoltHold() && rNow < InpBeAtR)
      return;

   if(InpPartialCloseR > 0 && !g_partialDone && rNow >= InpPartialCloseR && InpPartialClosePct > 0 && InpPartialClosePct < 100)
     {
      double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
      double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
      double closeVol = MathFloor((vol * InpPartialClosePct / 100.0) / step) * step;
      if(closeVol >= minLot && vol - closeVol >= minLot)
        {
         if(trade.PositionClosePartial(position.Ticket(), closeVol))
           {
            g_partialDone = true;
            Print("Gold Bolt: partial close ", DoubleToString(closeVol, 3), " at ", DoubleToString(rNow, 2), "R");
           }
        }
      else
         g_partialDone = true;
     }

   if(!g_beDone && rNow >= InpBeAtR)
     {
      double be = openPrice + dir * SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point;
      be = NormalizeDouble(be, g_digits);
      bool improve = (dir > 0 && (sl == 0 || be > sl)) || (dir < 0 && (sl == 0 || be < sl));
      if(improve)
        {
         if(trade.PositionModify(position.Ticket(), be, tp))
           {
            g_beDone = true;
            sl = be;
            Print("Gold Bolt: break-even");
           }
        }
      else
         g_beDone = true;
     }

   if(rNow >= InpTrailStartR)
     {
      double atr[];
      if(CopyAtr(3, atr))
        {
         double trail = now - dir * atr[0] * InpTrailAtrMult;
         trail = NormalizeDouble(trail, g_digits);
         bool improve = (dir > 0 && trail > sl) || (dir < 0 && (sl == 0 || trail < sl));
         if(improve)
            trade.PositionModify(position.Ticket(), trail, tp);
        }
     }

   if(holdMin >= InpMaxHoldHours * 60 && rNow < 0.3)
      CloseOurPosition("time stop");
  }

string TfListText()
  {
   string s = "";
   for(int i = 0; i < g_tfCount; i++)
     {
      if(i > 0)
         s += " ";
      s += EnumToString(g_tfs[i]);
     }
   return s;
  }

void DrawPanel()
  {
   string halt = g_halted ? ("HALTED — " + g_haltReason) : "TRADE LIKE JMA";
   string pos  = HasOurPosition() ? "in a trade" : "no position";
   Comment(
      "TRADE LIKE JMA\n",
      "GOLD BOLT  ·  Fibonacci  ·  no hedge  ·  no martingale  ·  no HFT\n",
      _Symbol, "  ", TfListText(), "\n",
      "status   ", halt, "  |  ", pos, "\n",
      "entries  38.2 ", (InpUseFib382 ? "on" : "off"),
      "   50 ", (InpUseFib50 ? "on" : "off"),
      "   61.8 ", (InpUseFib618 ? "on" : "off"),
      "   SL ", DoubleToString(BoltFibSl(), 1), "%\n",
      "risk     ", DoubleToString(BoltRisk(), 2), "%   daily ",
      DoubleToString(DailyLossPct(), 2), "/", DoubleToString(BoltDaily(), 2), "%",
      (BoltHot() ? "   AGGRESSIVE under $1k\n" : "\n"),
      "drawdown ", DoubleToString(StaticDdPct(), 2), "/", DoubleToString(BoltDD(), 2),
      "%   trades today ", IntegerToString(CountEntriesToday()), "/", IntegerToString(BoltMaxTrades()), "\n",
      "spread   ", IntegerToString((int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)),
      " pts   hours ", (InpTradeAllHours || InSession() ? "open" : "closed"),
      "   equity ", DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2)
   );
  }

double OnTester()
  {
   double profit = TesterStatistics(STAT_PROFIT);
   double dd = TesterStatistics(STAT_EQUITY_DDREL_PERCENT);
   double pf = TesterStatistics(STAT_PROFIT_FACTOR);
   double trades = TesterStatistics(STAT_TRADES);
   if(dd > InpMaxDrawdownPct * 1.5)
      return 0;
   if(trades < 15)
      return 0;
   return (profit / MathMax(dd, 1.0)) * MathMin(pf, 3.0);
  }
//+------------------------------------------------------------------+
