259 lines
12 KiB
MQL5
259 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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 <GK\GarmanKohlhagen.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 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
|
|
//+------------------------------------------------------------------+
|