//+------------------------------------------------------------------+ //| GKData.mqh | //| MMQ — Muhammad Minhas Qamar | //| www.mql5.com/en/articles/23807 | //+------------------------------------------------------------------+ #property copyright "MMQ — Muhammad Minhas Qamar" #property link "https://www.mql5.com/en/articles/23807" #property version "1.00" #property strict #ifndef GK_GKDATA_MQH #define GK_GKDATA_MQH #include //+------------------------------------------------------------------+ //| One reconstructed point on a tenor's smile: the strike a quoted | //| delta pillar maps to, the volatility that lives there, and the | //| realised delta at that strike (which pins the pillar it came | //| from). Ordered left to right by strike, these five points are | //| the smile the FX market only ever quotes in delta space. | //+------------------------------------------------------------------+ struct SmilePoint { string label; // "10P","25P","ATM","25C","10C" double strike; // reconstructed strike double vol; // volatility at this strike (decimal) double delta; // realised delta at the strike (signed) }; //+------------------------------------------------------------------+ //| One tenor of the surface: the raw ATM / risk-reversal / butter- | //| fly quotes the desk publishes, the market data they price | //| against, and — once Build has run — the strike-space smile they | //| reconstruct into. Vols are held as decimals (0.08, not 8.0); the | //| provider divides the percent quotes on the way in. | //+------------------------------------------------------------------+ struct SmileTenor { string label; // "1W","1M","3M"... double days; // calendar days to expiry double spot; // FX spot for the pair double rd, rf; // domestic / foreign continuous rates double atm; // ATM vol double rr25, bf25; // 25-delta risk reversal / butterfly double rr10, bf10; // 10-delta risk reversal / butterfly bool has10; // are the 10-delta wings supplied? //--- filled by CGKSmile::Build double T; // days / 365 double F; // forward SmilePoint pts[]; // reconstructed smile, ascending strike }; //+------------------------------------------------------------------+ //| The reconstructed volatility smile across tenors. Build takes | //| the raw delta-space quotes and, under a chosen delta and ATM | //| convention, turns each tenor's ATM/RR/BF into strike-space | //| pillars: it derives the pillar vols, then solves each pillar's | //| delta back to the strike it sits on. | //| | //| The pillar-vol step uses the simplified "smile strangle" reading | //| — sigma(25c)=ATM+BF+RR/2, sigma(25p)=ATM+BF-RR/2 — where the | //| quoted butterfly is taken directly as the smile's wing offset. | //| A desk's "market strangle" quote would need an extra calibration | //| step; we keep the simplified reading, which is exact for the | //| pillars themselves and is what most reconstruction tools use. | //+------------------------------------------------------------------+ class CGKSmile { private: SmileTenor m_tenors[]; ENUM_GK_DELTA m_dconv; ENUM_GK_ATM m_aconv; void BuildTenor(SmileTenor &t); void AddPoint(SmileTenor &t, const string label, const double strike, const double vol, const double delta); public: CGKSmile(void) : m_dconv(GK_DELTA_SPOT), m_aconv(GK_ATM_FORWARD) {} bool Build(SmileTenor &raw[], const ENUM_GK_DELTA dconv, const ENUM_GK_ATM aconv); //--- tenor-level accessors int NTenors(void) const { return(ArraySize(m_tenors)); } string Label(const int i) const { return(m_tenors[i].label); } double Days(const int i) const { return(m_tenors[i].days); } double T(const int i) const { return(m_tenors[i].T); } double Spot(const int i) const { return(m_tenors[i].spot); } double Rd(const int i) const { return(m_tenors[i].rd); } double Rf(const int i) const { return(m_tenors[i].rf); } double Forward(const int i) const { return(m_tenors[i].F); } double Atm(const int i) const { return(m_tenors[i].atm); } double RR25(const int i) const { return(m_tenors[i].rr25); } double BF25(const int i) const { return(m_tenors[i].bf25); } //--- point-level accessors int NPoints(const int i) const { return(ArraySize(m_tenors[i].pts)); } string PtLabel(const int i, const int j) const { return(m_tenors[i].pts[j].label); } double PtStrike(const int i, const int j) const { return(m_tenors[i].pts[j].strike); } double PtVol(const int i, const int j) const { return(m_tenors[i].pts[j].vol); } double PtDelta(const int i, const int j) const { return(m_tenors[i].pts[j].delta); } }; //+------------------------------------------------------------------+ //| Append one reconstructed pillar to a tenor, recording the strike | //| the solver returned, its vol, and the delta actually realised | //| there (so a caller can confirm a 25-delta pillar really sits at | //| 0.25). Skips a pillar whose strike solve failed. | //+------------------------------------------------------------------+ void CGKSmile::AddPoint(SmileTenor &t, const string label, const double strike, const double vol, const double delta) { if(strike <= 0.0) return; int n = ArraySize(t.pts); ArrayResize(t.pts, n + 1); t.pts[n].label = label; t.pts[n].strike = strike; t.pts[n].vol = vol; t.pts[n].delta = delta; } //+------------------------------------------------------------------+ //| Reconstruct one tenor's smile. Derive the pillar vols from the | //| ATM/RR/BF quotes, solve each pillar delta back to a strike using | //| that pillar's own vol, and record the realised delta. The put | //| pillars are solved to negative deltas, the calls to positive; a | //| correct convention returns strikes that stay in ascending order | //| 10P < 25P < ATM < 25C < 10C. | //+------------------------------------------------------------------+ void CGKSmile::BuildTenor(SmileTenor &t) { t.T = t.days / 365.0; t.F = GKForward(t.spot, t.rd, t.rf, t.T); ArrayResize(t.pts, 0); //--- pillar vols from the delta-space quotes (simplified strangle) double s25c = t.atm + t.bf25 + 0.5 * t.rr25; double s25p = t.atm + t.bf25 - 0.5 * t.rr25; //--- pillar strikes (put pillars first so points stay ascending) if(t.has10) { double s10c = t.atm + t.bf10 + 0.5 * t.rr10; double s10p = t.atm + t.bf10 - 0.5 * t.rr10; double k10p = GKStrikeFromDelta(OPT_PUT, m_dconv, 0.10, t.spot, t.rd, t.rf, s10p, t.T); AddPoint(t, "10P", k10p, s10p, GKDelta(OPT_PUT, m_dconv, t.spot, k10p, t.rd, t.rf, s10p, t.T)); } double k25p = GKStrikeFromDelta(OPT_PUT, m_dconv, 0.25, t.spot, t.rd, t.rf, s25p, t.T); AddPoint(t, "25P", k25p, s25p, GKDelta(OPT_PUT, m_dconv, t.spot, k25p, t.rd, t.rf, s25p, t.T)); double kAtm = GKATMStrike(m_aconv, m_dconv, t.F, t.atm, t.T); AddPoint(t, "ATM", kAtm, t.atm, GKDelta(OPT_CALL, m_dconv, t.spot, kAtm, t.rd, t.rf, t.atm, t.T)); double k25c = GKStrikeFromDelta(OPT_CALL, m_dconv, 0.25, t.spot, t.rd, t.rf, s25c, t.T); AddPoint(t, "25C", k25c, s25c, GKDelta(OPT_CALL, m_dconv, t.spot, k25c, t.rd, t.rf, s25c, t.T)); if(t.has10) { double s10c = t.atm + t.bf10 + 0.5 * t.rr10; double k10c = GKStrikeFromDelta(OPT_CALL, m_dconv, 0.10, t.spot, t.rd, t.rf, s10c, t.T); AddPoint(t, "10C", k10c, s10c, GKDelta(OPT_CALL, m_dconv, t.spot, k10c, t.rd, t.rf, s10c, t.T)); } } //+------------------------------------------------------------------+ //| Reconstruct every tenor under the given conventions and take | //| ownership of the result. Returns false only if there is nothing | //| to build. | //+------------------------------------------------------------------+ bool CGKSmile::Build(SmileTenor &raw[], const ENUM_GK_DELTA dconv, const ENUM_GK_ATM aconv) { int n = ArraySize(raw); if(n == 0) return(false); m_dconv = dconv; m_aconv = aconv; ArrayResize(m_tenors, n); for(int i = 0; i < n; i++) { m_tenors[i] = raw[i]; BuildTenor(m_tenors[i]); } return(true); } //+------------------------------------------------------------------+ //| CSV provider. Reads a delta-space vol sheet from MQL5\Files with | //| one row per tenor and the fixed column order: | //| tenor,days,spot,atm,rr25,bf25,rr10,bf10,r_dom,r_for | //| Vols are in percent (8.0 = 8%), rates in decimals. Set rr10 and | //| bf10 to a blank/zero when the 10-delta wings are not quoted; the | //| row then carries three pillars instead of five. | //+------------------------------------------------------------------+ class CGKProviderCSV { public: bool Load(const string filename, SmileTenor &out[]); }; //+------------------------------------------------------------------+ //| Parse the vol sheet into raw tenors. Skips the header line and | //| any blank line, converts the percent vols to decimals, and | //| flags whether the 10-delta wings were supplied. | //+------------------------------------------------------------------+ bool CGKProviderCSV::Load(const string filename, SmileTenor &out[]) { int h = FileOpen(filename, FILE_READ | FILE_CSV | FILE_ANSI, ','); if(h == INVALID_HANDLE) { PrintFormat("CGKProviderCSV: cannot open %s (err %d)", filename, GetLastError()); return(false); } ArrayResize(out, 0); bool header = true; while(!FileIsEnding(h)) { string sTenor = FileReadString(h); if(FileIsLineEnding(h) && StringLen(sTenor) == 0) continue; string sDays = FileReadString(h); string sSpot = FileReadString(h); string sAtm = FileReadString(h); string sRr25 = FileReadString(h); string sBf25 = FileReadString(h); string sRr10 = FileReadString(h); string sBf10 = FileReadString(h); string sRd = FileReadString(h); string sRf = FileReadString(h); if(header) { header = false; // skip the column titles continue; } if(StringLen(sDays) == 0) continue; int n = ArraySize(out); ArrayResize(out, n + 1); out[n].label = sTenor; out[n].days = StringToDouble(sDays); out[n].spot = StringToDouble(sSpot); out[n].atm = StringToDouble(sAtm) / 100.0; out[n].rr25 = StringToDouble(sRr25) / 100.0; out[n].bf25 = StringToDouble(sBf25) / 100.0; out[n].rr10 = StringToDouble(sRr10) / 100.0; out[n].bf10 = StringToDouble(sBf10) / 100.0; out[n].rd = StringToDouble(sRd); out[n].rf = StringToDouble(sRf); out[n].has10 = (StringLen(sBf10) > 0 && MathAbs(out[n].bf10) + MathAbs(out[n].rr10) > 0.0); } FileClose(h); PrintFormat("CGKProviderCSV: loaded %d tenors from %s", ArraySize(out), filename); return(ArraySize(out) > 0); } #endif // GK_GKDATA_MQH //+------------------------------------------------------------------+