//+------------------------------------------------------------------+
//| GoldVRPCheck.mq5 - example script                                |
//| Reads gold's options-implied volatility from a published feed,   |
//| measures realized volatility from the terminal's own gold bars,  |
//| and prints the premium between them. Reads only. Never trades.   |
//+------------------------------------------------------------------+
#property script_show_inputs

input string          FeedURL     = "https://raw.githubusercontent.com/GeneTheStoic/gold-vrp-feed/main/feed.json"; // implied volatility feed
input string          GoldSymbol  = "XAUUSD";    // gold symbol in Market Watch
input ENUM_TIMEFRAMES RVTimeframe = PERIOD_D1;   // timeframe for realized volatility
input int             RVWindow    = 30;          // bars of realized volatility, matches the 30-day implied horizon
input int             TimeoutMs   = 5000;        // web request timeout

#define TRADING_DAYS 252.0

//+------------------------------------------------------------------+
//| Read a numeric field out of a flat JSON object                   |
//+------------------------------------------------------------------+
double JsonNumber(const string json, const string key)
  {
   string tag = "\"" + key + "\"";
   int at = StringFind(json, tag);
   if(at < 0)
      return 0.0;
   int colon = StringFind(json, ":", at + StringLen(tag));
   if(colon < 0)
      return 0.0;
   int end = colon + 1;
   int len = StringLen(json);
   while(end < len)
     {
      ushort ch = StringGetCharacter(json, end);
      if(ch == ',' || ch == '}' || ch == '\n' || ch == '\r')
         break;
      end++;
     }
   string raw = StringSubstr(json, colon + 1, end - colon - 1);
   StringTrimLeft(raw);
   StringTrimRight(raw);
   return StringToDouble(raw);
  }
//+------------------------------------------------------------------+
//| Read a text field out of a flat JSON object                      |
//+------------------------------------------------------------------+
string JsonText(const string json, const string key)
  {
   string tag = "\"" + key + "\"";
   int at = StringFind(json, tag);
   if(at < 0)
      return "";
   int colon = StringFind(json, ":", at + StringLen(tag));
   if(colon < 0)
      return "";
   int open = StringFind(json, "\"", colon + 1);
   if(open < 0)
      return "";
   int close = StringFind(json, "\"", open + 1);
   if(close < 0)
      return "";
   return StringSubstr(json, open + 1, close - open - 1);
  }
//+------------------------------------------------------------------+
//| Convert an ISO 8601 timestamp into a datetime value              |
//+------------------------------------------------------------------+
datetime FeedTime(const string iso)
  {
   string s = iso;
   StringReplace(s, "-", ".");
   StringReplace(s, "T", " ");
   StringReplace(s, "Z", "");
   return StringToTime(s);
  }
//+------------------------------------------------------------------+
//| Annualized standard deviation of log returns, from the terminal  |
//+------------------------------------------------------------------+
double RealizedVolatility(const string symbol, ENUM_TIMEFRAMES tf, const int window)
  {
   double close[];
   int need = window + 1;
   if(CopyClose(symbol, tf, 0, need, close) < need)
      return 0.0;
   ArraySetAsSeries(close, true);

   double sum = 0.0, sumsq = 0.0;
   int n = 0;
   for(int i = 0; i < window; i++)
     {
      if(close[i] <= 0.0 || close[i + 1] <= 0.0)
         continue;
      double r = MathLog(close[i] / close[i + 1]);
      sum += r;
      sumsq += r * r;
      n++;
     }
   if(n < 2)
      return 0.0;

   double mean = sum / n;
   double variance = (sumsq - n * mean * mean) / (n - 1);
   if(variance < 0.0)
      variance = 0.0;
   return MathSqrt(variance) * MathSqrt(TRADING_DAYS);
  }
//+------------------------------------------------------------------+
//| Plain-language reading of the implied to realized ratio          |
//+------------------------------------------------------------------+
string RegimeText(const double ratio)
  {
   if(ratio >= 1.20)
      return "Protection is expensive: options price far more movement than gold has delivered";
   if(ratio >= 1.05)
      return "A premium is priced in: options expect more movement than gold has delivered";
   if(ratio >= 0.95)
      return "Expectations are in line with what gold has actually been doing";
   return "Realized is outrunning implied: gold is moving more than options expected";
  }
//+------------------------------------------------------------------+
//| Download the feed; returns an empty string on failure            |
//+------------------------------------------------------------------+
string DownloadFeed(const string url, const int timeout)
  {
   char post[], result[];
   string result_headers;

   ResetLastError();
   int status = WebRequest("GET", url, "", timeout, post, result, result_headers);
   if(status == -1)
     {
      int err = GetLastError();
      if(err == 4014)
         Print("This URL is not allowed. Open Tools -> Options -> Expert Advisors, ",
               "tick 'Allow WebRequest for listed URL' and add: ", url);
      else
         Print("Download failed, error ", err);
      return "";
     }
   if(status != 200)
     {
      Print("Feed returned HTTP status ", status);
      return "";
     }
   return CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 1. fetch the implied volatility that the terminal cannot compute
   string json = DownloadFeed(FeedURL, TimeoutMs);
   if(json == "")
      return;

   double impliedVol = JsonNumber(json, "iv_30d");
   string feedSymbol = JsonText(json, "symbol");
   string updated = JsonText(json, "updated_utc");
   if(impliedVol <= 0.0)
     {
      Print("The feed did not contain a usable implied volatility.");
      return;
     }

//--- 2. measure realized volatility locally, from the broker's own bars
   double realizedVol = RealizedVolatility(GoldSymbol, RVTimeframe, RVWindow);
   if(realizedVol <= 0.0)
     {
      Print("Not enough history for ", GoldSymbol, ". Open its chart once and retry.");
      return;
     }

//--- 3. compare the two
   double premium = impliedVol - realizedVol;
   double ratio = impliedVol / realizedVol;

//--- 4. how old is the reading
   datetime stamp = FeedTime(updated);
   int ageMinutes = (stamp > 0 ? (int)((TimeGMT() - stamp) / 60) : -1);

   PrintFormat("Implied volatility (%s options, 30 days): %.2f%%",
               feedSymbol, impliedVol * 100.0);
   PrintFormat("Realized volatility (%s, last %d %s bars): %.2f%%",
               GoldSymbol, RVWindow, EnumToString(RVTimeframe), realizedVol * 100.0);
   PrintFormat("Premium: %+.2f volatility points   Ratio: %.2f",
               premium * 100.0, ratio);
   Print(RegimeText(ratio));
   if(ageMinutes >= 0)
      PrintFormat("Feed updated %d minutes ago (%s UTC).", ageMinutes, updated);
  }
//+------------------------------------------------------------------+
