gk-fx-smile/Indicators/GK/GKSmile.mq5

629 lines
26 KiB
MQL5
Raw Permalink Normal View History

2026-08-01 05:36:57 +00:00
//+------------------------------------------------------------------+
//| GKSmile.mq5 |
//| 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 description "Garman-Kohlhagen FX smile: reconstruct the strike-space volatility smile from delta-space ATM/RR/BF quotes, with the strike ladder printed on the curve."
#property indicator_chart_window
#property indicator_plots 0
#property indicator_buffers 0
#include <Canvas\Canvas.mqh>
#include <GK\GKData.mqh>
#include <GK\GKProviderNative.mqh>
//+------------------------------------------------------------------+
//| Inputs |
//+------------------------------------------------------------------+
enum ENUM_GK_SOURCE
{
GK_SOURCE_CSV = 0, // read a delta-space vol sheet from MQL5\Files
GK_SOURCE_NATIVE = 1 // read the broker's native FX-option symbols
};
enum ENUM_GK_XAXIS
{
GK_XAXIS_STRIKE = 0, // strike on the x axis (natural)
GK_XAXIS_DELTA = 1 // delta pillars evenly spaced (FX-native)
};
enum ENUM_GK_THEME
{
GK_THEME_DARK = 0, // light curve on a dark card
GK_THEME_LIGHT = 1 // dark curve on a light card
};
input ENUM_GK_SOURCE InpSource = GK_SOURCE_CSV; // data source
input string InpCsvFile = "GK\\EURUSD.csv"; // CSV vol sheet (in MQL5\Files)
input string InpUnderlying = ""; // native: base symbol (empty = chart symbol)
input ENUM_GK_DELTA InpDeltaConv = GK_DELTA_SPOT; // delta convention (per pair!)
input ENUM_GK_ATM InpAtmConv = GK_ATM_DNS; // ATM convention
input double InpRateDom = 0.0430; // native: domestic rate
input double InpRateFor = 0.0250; // native: foreign rate
input ENUM_GK_XAXIS InpXAxis = GK_XAXIS_DELTA; // x axis: delta pillars or strike
input int InpTenor = 1; // active tenor index (0-based)
input bool InpShowAll = true; // draw the other tenors faintly
input int InpRefreshSec = 0; // native re-poll seconds (0 = manual, press R)
input ENUM_GK_THEME InpTheme = GK_THEME_DARK; // card / curve theme
//+------------------------------------------------------------------+
//| Globals |
//+------------------------------------------------------------------+
CCanvas g_canvas;
CGKSmile g_smile;
string g_objName = "";
datetime g_lastRefresh = 0;
bool g_haveData = false;
int g_lastW = -1, g_lastH = -1;
int g_active = 0; // active tenor index
ENUM_GK_XAXIS g_xaxis = GK_XAXIS_DELTA; // live axis mode, seeded from the input and toggled by X
double g_fs = 1.0; // font / spacing scale, from the canvas height
//--- plot geometry, recomputed each draw
int g_plotX0, g_plotX1, g_plotY0, g_plotY1;
double g_xMin, g_xMax, g_yMin, g_yMax;
//--- resolved palette (set in OnInit from the theme)
uint g_panel, g_card2, g_grid, g_text, g_textDim, g_line, g_fill, g_put, g_call, g_atm, g_faint;
//+------------------------------------------------------------------+
//| Resolve the theme into an opaque ARGB palette once. The bitmap is|
//| drawn in RAW mode, so the card is fully opaque and the area tone |
//| is pre-mixed against it rather than leaning on alpha blending. |
//| Put and call wings get their own accent so the skew reads at a |
//| glance: a rich put wing is the market pricing downside. |
//+------------------------------------------------------------------+
void ResolvePalette(void)
{
g_line = ColorToARGB(C'46,196,214', 255); // smile curve (cyan)
g_put = ColorToARGB(C'232,120,120', 255); // put-wing accent
g_call = ColorToARGB(C'120,200,140', 255); // call-wing accent
g_atm = ColorToARGB(C'220,200,90', 255); // ATM marker (amber)
if(InpTheme == GK_THEME_LIGHT)
{
g_panel = ColorToARGB(clrWhiteSmoke, 255);
g_card2 = ColorToARGB(C'235,237,242', 255);
g_grid = ColorToARGB(clrSilver);
g_text = ColorToARGB(clrBlack);
g_textDim = ColorToARGB(clrDimGray);
g_fill = ColorToARGB(C'170,215,222', 255);
g_faint = ColorToARGB(C'190,192,198', 255);
}
else
{
g_panel = ColorToARGB(C'22,24,30', 255);
g_card2 = ColorToARGB(C'28,31,39', 255);
g_grid = ColorToARGB(C'70,74,86');
g_text = ColorToARGB(clrWhiteSmoke);
g_textDim = ColorToARGB(clrSilver);
g_fill = ColorToARGB(C'28,60,70', 255);
g_faint = ColorToARGB(C'60,64,76', 255);
}
}
//+------------------------------------------------------------------+
//| (Re)load the chosen data source and reconstruct the smile under |
//| the selected delta and ATM conventions. Clamps the active tenor |
//| to the range the data actually carries. |
//+------------------------------------------------------------------+
bool Reload(void)
{
SmileTenor raw[];
bool ok = false;
if(InpSource == GK_SOURCE_CSV)
{
CGKProviderCSV csv;
ok = csv.Load(InpCsvFile, raw);
}
else
{
string under = (InpUnderlying == "") ? _Symbol : InpUnderlying;
CGKProviderNative nat;
ok = nat.Load(under, InpRateDom, InpRateFor, InpDeltaConv, raw);
}
if(ok)
ok = g_smile.Build(raw, InpDeltaConv, InpAtmConv);
g_haveData = ok;
if(g_haveData)
{
g_active = InpTenor;
if(g_active < 0)
g_active = 0;
if(g_active >= g_smile.NTenors())
g_active = g_smile.NTenors() - 1;
}
return(g_haveData);
}
//+------------------------------------------------------------------+
//| Font sizes and row spacings are authored against a 580-pixel-tall|
//| card and scaled from there. Pixel-constant fonts are the classic |
//| canvas mistake: they look right on the chart you developed on |
//| and shrink into illegibility on a large one, since the card |
//| grows with the chart while the type does not. The clamp keeps |
//| both extremes sane. |
//+------------------------------------------------------------------+
int SC(const int base)
{
return((int)MathRound(base * g_fs));
}
//+------------------------------------------------------------------+
//| Right (call/put) implied by a pillar label. The put pillars |
//| carry "P"; ATM is treated as a call for its ~0.5 delta. |
//+------------------------------------------------------------------+
ENUM_OPT_RIGHT PillarRight(const string label)
{
return((StringFind(label, "P") >= 0) ? OPT_PUT : OPT_CALL);
}
//+------------------------------------------------------------------+
//| Plot x-coordinate of a pillar. In delta mode the pillars are |
//| spaced evenly by index (the classic FX smile look); in strike |
//| mode they sit at their reconstructed strike. |
//+------------------------------------------------------------------+
int PxOf(const int tenor, const int j)
{
int np = g_smile.NPoints(tenor);
double f;
if(g_xaxis == GK_XAXIS_DELTA)
f = (np <= 1) ? 0.5 : (double)j / (np - 1);
else
f = (g_smile.PtStrike(tenor, j) - g_xMin) / (g_xMax - g_xMin);
return(g_plotX0 + (int)MathRound(f * (g_plotX1 - g_plotX0)));
}
//+------------------------------------------------------------------+
//| Map a volatility to a y pixel inside the plot area. |
//+------------------------------------------------------------------+
int YOf(const double vol)
{
double f = (vol - g_yMin) / (g_yMax - g_yMin);
if(f < 0.0)
f = 0.0;
if(f > 1.0)
f = 1.0;
return(g_plotY1 - (int)MathRound(f * (g_plotY1 - g_plotY0)));
}
//+------------------------------------------------------------------+
//| The reconstruction result, printed at the pillar it belongs to: |
//| the strike we solved for, its vol and its delta. Reading a |
//| strike off a curve whose x axis is delta is impossible, so these |
//| three numbers are what turns the picture into a quotable ladder. |
//| The block sits above the marker unless that would leave the plot,|
//| in which case it flips underneath. In delta mode the x axis |
//| already names the pillar, so the block carries numbers only and |
//| the accent colour does the naming; in strike mode it labels |
//| itself, because there the axis is spelling out strikes instead. |
//+------------------------------------------------------------------+
void DrawPillarBox(const int tenor, const int j, const int px, const int py, const uint accent)
{
bool needLabel = (g_xaxis == GK_XAXIS_STRIKE);
int nLines = needLabel ? 3 : 2;
string kTx = StringFormat("%.4f", g_smile.PtStrike(tenor, j));
string vTx = StringFormat("%.2f%% %+.3f", 100.0 * g_smile.PtVol(tenor, j), g_smile.PtDelta(tenor, j));
//--- The outer pillars sit on the frame itself, so a block centred on the
//--- marker spills over the axis labels beside it. Measure the widest line
//--- and pull the block back inside the plot; the marker still anchors it.
g_canvas.FontSet("Consolas", SC(11));
int wide = g_canvas.TextWidth(vTx);
g_canvas.FontSet("Consolas", SC(13), FW_BOLD);
wide = MathMax(wide, g_canvas.TextWidth(kTx));
int half = wide / 2 + SC(4);
int cx = px;
if(cx - half < g_plotX0)
cx = g_plotX0 + half;
if(cx + half > g_plotX1)
cx = g_plotX1 - half;
bool above = (py - SC(14 + 18 * nLines) >= g_plotY0);
int y = above ? py - SC(12) : py + SC(12);
int step = above ? -SC(18) : SC(18);
uint va = above ? TA_BOTTOM : TA_TOP;
if(needLabel)
{
g_canvas.FontSet("Segoe UI", SC(12), FW_BOLD);
g_canvas.TextOut(cx, y, g_smile.PtLabel(tenor, j), accent, TA_CENTER | va);
y += step;
}
g_canvas.FontSet("Consolas", SC(13), FW_BOLD);
g_canvas.TextOut(cx, y, kTx, accent, TA_CENTER | va);
g_canvas.FontSet("Consolas", SC(11));
g_canvas.TextOut(cx, y + step, vTx, g_textDim, TA_CENTER | va);
}
//+------------------------------------------------------------------+
//| Draw one tenor's smile as a polyline through its pillars. The |
//| active tenor is drawn bright with filled wings and labelled dot |
//| markers; the others (when shown) are faint context lines. |
//+------------------------------------------------------------------+
void DrawSmile(const int tenor, const bool activeTenor)
{
int np = g_smile.NPoints(tenor);
if(np < 2)
return;
int xs[], ys[];
ArrayResize(xs, np);
ArrayResize(ys, np);
for(int j = 0; j < np; j++)
{
xs[j] = PxOf(tenor, j);
ys[j] = YOf(g_smile.PtVol(tenor, j));
}
//--- the y axis is scaled to the active tenor alone, so a context line
//--- can run off the box.
if(!activeTenor)
{
for(int j = 0; j < np - 1; j++)
{
double v0 = g_smile.PtVol(tenor, j);
double v1 = g_smile.PtVol(tenor, j + 1);
if(v0 < g_yMin || v0 > g_yMax || v1 < g_yMin || v1 > g_yMax)
continue;
g_canvas.LineAA(xs[j], ys[j], xs[j + 1], ys[j + 1], g_faint);
}
return;
}
//--- filled area under the active smile, column by column
for(int j = 0; j < np - 1; j++)
{
for(int x = xs[j]; x <= xs[j + 1]; x++)
{
double w = (xs[j + 1] == xs[j]) ? 0.0 : (double)(x - xs[j]) / (xs[j + 1] - xs[j]);
int y = (int)MathRound(ys[j] + w * (ys[j + 1] - ys[j]));
if(y < g_plotY1)
g_canvas.LineVertical(x, y, g_plotY1, g_fill);
}
}
g_canvas.PolylineAA(xs, ys, g_line);
//--- pillar markers, each carrying its own reconstructed numbers
for(int j = 0; j < np; j++)
{
string lab = g_smile.PtLabel(tenor, j);
uint c = (lab == "ATM") ? g_atm : (PillarRight(lab) == OPT_PUT ? g_put : g_call);
g_canvas.FillCircle(xs[j], ys[j], SC(4), c);
DrawPillarBox(tenor, j, xs[j], ys[j], c);
}
}
//+------------------------------------------------------------------+
//| Plot axes. The y axis carries a few volatility gridlines; the x |
//| axis shows either the pillar labels (delta mode) or evenly |
//| spaced strikes (strike mode). |
//+------------------------------------------------------------------+
void DrawAxis(void)
{
g_canvas.LineHorizontal(g_plotX0, g_plotX1, g_plotY1, g_grid);
g_canvas.LineVertical(g_plotX0, g_plotY0, g_plotY1, g_grid);
g_canvas.FontSet("Segoe UI", SC(11));
//--- y: volatility ticks. Two decimals, because the axis now hugs the
//--- active smile and one decimal would print repeated labels.
int yt = 4;
for(int i = 0; i <= yt; i++)
{
double v = g_yMin + (g_yMax - g_yMin) * i / yt;
int y = YOf(v);
g_canvas.LineHorizontal(g_plotX0, g_plotX1, y, g_grid);
g_canvas.TextOut(g_plotX0 - SC(6), y, StringFormat("%.2f", 100.0 * v), g_textDim, TA_RIGHT | TA_VCENTER);
}
//--- x: pillar labels (delta) or strike ticks
if(g_xaxis == GK_XAXIS_DELTA)
{
int np = g_smile.NPoints(g_active);
g_canvas.FontSet("Segoe UI", SC(12), FW_BOLD);
for(int j = 0; j < np; j++)
{
string lab = g_smile.PtLabel(g_active, j);
uint c = (lab == "ATM") ? g_atm : (PillarRight(lab) == OPT_PUT ? g_put : g_call);
g_canvas.TextOut(PxOf(g_active, j), g_plotY1 + SC(6), lab, c, TA_CENTER | TA_TOP);
}
}
else
{
int xt = 6;
for(int i = 0; i <= xt; i++)
{
double k = g_xMin + (g_xMax - g_xMin) * i / xt;
int x = g_plotX0 + (int)MathRound((double)i / xt * (g_plotX1 - g_plotX0));
g_canvas.TextOut(x, g_plotY1 + SC(6), StringFormat("%.4f", k), g_textDim, TA_CENTER | TA_TOP);
}
}
g_canvas.FontSet("Segoe UI", SC(11));
g_canvas.TextOut(g_plotX0 - SC(6), g_plotY0 - SC(16), "vol %", g_textDim, TA_LEFT | TA_TOP);
}
//+------------------------------------------------------------------+
//| Compact term-structure strip: ATM, 25d RR and 25d BF across all |
//| tenors, with the active tenor flagged. It is the cross-tenor |
//| view the single smile cannot show, kept to one thin row. |
//+------------------------------------------------------------------+
void DrawTermStrip(const int sx0, const int sy0, const int sx1, const int sy1)
{
int n = g_smile.NTenors();
if(n < 1)
return;
g_canvas.FontSet("Segoe UI", SC(11));
g_canvas.TextOut(sx0, sy0 - SC(16), "term structure (25-delta, vol pts)", g_textDim, TA_LEFT | TA_TOP);
//--- name the three rows down the left margin, so the strip reads without
//--- having to map it back onto the caption
g_canvas.FontSet("Consolas", SC(11));
g_canvas.TextOut(sx0, sy0 + SC(18), "ATM", g_textDim, TA_LEFT | TA_TOP);
g_canvas.TextOut(sx0, sy0 + SC(34), "RR", g_textDim, TA_LEFT | TA_TOP);
g_canvas.TextOut(sx0, sy0 + SC(50), "BF", g_textDim, TA_LEFT | TA_TOP);
for(int i = 0; i < n; i++)
{
int x = sx0 + (int)((double)(i + 0.5) / n * (sx1 - sx0));
bool act = (i == g_active);
uint c = act ? g_text : g_textDim;
g_canvas.FontSet("Segoe UI", SC(act ? 12 : 11), act ? FW_BOLD : FW_NORMAL);
g_canvas.TextOut(x, sy0, g_smile.Label(i), c, TA_CENTER | TA_TOP);
g_canvas.FontSet("Consolas", SC(11));
g_canvas.TextOut(x, sy0 + SC(18), StringFormat("%.2f", 100.0 * g_smile.Atm(i)), c, TA_CENTER | TA_TOP);
g_canvas.TextOut(x, sy0 + SC(34), StringFormat("%+.2f", 100.0 * g_smile.RR25(i)), (g_smile.RR25(i) < 0 ? g_put : g_call), TA_CENTER | TA_TOP);
g_canvas.TextOut(x, sy0 + SC(50), StringFormat("%.2f", 100.0 * g_smile.BF25(i)), c, TA_CENTER | TA_TOP);
if(act)
g_canvas.Rectangle(x - SC(30), sy0 - SC(2), x + SC(30), sy0 + SC(68), g_grid);
}
}
//+------------------------------------------------------------------+
//| Header block: pair, tenor, spot / forward, the two rates and the |
//| active convention, then the quotes the smile was built from and |
//| the pair of rate sensitivities. Both rhos live here rather than |
//| on a per-pillar row because they are quoted at the ATM strike |
//| only; keeping them side by side is the point, since they carry |
//| opposite signs and an FX book has to hedge two curves, not one. |
//+------------------------------------------------------------------+
void DrawHeader(const int cardX0, const int cardY0)
{
string under = (InpSource == GK_SOURCE_NATIVE && InpUnderlying != "") ? InpUnderlying :
(InpSource == GK_SOURCE_NATIVE ? _Symbol : InpCsvFile);
string convName[4] = {"spot", "forward", "spot p-adj", "fwd p-adj"};
double S = g_smile.Spot(g_active);
double F = g_smile.Forward(g_active);
double rd = g_smile.Rd(g_active);
double rf = g_smile.Rf(g_active);
double T = g_smile.T(g_active);
int dd = (int)MathRound(g_smile.Days(g_active));
//--- rate Greeks at the ATM strike, in desk units (per 1% of rate)
int np = g_smile.NPoints(g_active);
double Katm = g_smile.PtStrike(g_active, (np - 1) / 2);
double vatm = g_smile.Atm(g_active);
double rhod = GKRhoDom(OPT_CALL, S, Katm, rd, rf, vatm, T) / 100.0;
double rhof = GKRhoFor(OPT_CALL, S, Katm, rd, rf, vatm, T) / 100.0;
int tx = cardX0 + SC(16);
g_canvas.FontSet("Segoe UI", SC(17), FW_BOLD);
g_canvas.TextOut(tx, cardY0 + SC(12), StringFormat("Garman-Kohlhagen FX Smile - %s - %s (%d days)",
under, g_smile.Label(g_active), dd), g_text, TA_LEFT | TA_TOP);
g_canvas.FontSet("Segoe UI", SC(13));
g_canvas.TextOut(tx, cardY0 + SC(42), StringFormat("spot %.5f fwd %.5f r_dom %.2f%% r_for %.2f%% delta: %s",
S, F, 100.0 * rd, 100.0 * rf, convName[InpDeltaConv]), g_textDim, TA_LEFT | TA_TOP);
g_canvas.TextOut(tx, cardY0 + SC(64), StringFormat("25d RR %+.2f 25d BF %+.2f (vol pts) ATM rho_dom %+.4f rho_for %+.4f (per 1%%)",
100.0 * g_smile.RR25(g_active), 100.0 * g_smile.BF25(g_active),
rhod, rhof), g_textDim, TA_LEFT | TA_TOP);
g_canvas.FontSet("Segoe UI", SC(11));
g_canvas.TextOut(tx, cardY0 + SC(88), "keys: R reload , . tenor X axis", g_textDim, TA_LEFT | TA_TOP);
}
//+------------------------------------------------------------------+
//| Render the whole tool: an opaque card, the header, the smile |
//| plot with its axes, the ladder/Greeks panel, and the term strip. |
//+------------------------------------------------------------------+
void Draw(void)
{
int W = g_canvas.Width();
int H = g_canvas.Height();
g_canvas.Erase(0);
g_fs = (double)H / 580.0;
if(g_fs < 1.0)
g_fs = 1.0;
if(g_fs > 2.4)
g_fs = 2.4;
int cardX0 = 20, cardY0 = 20, cardX1 = W - 20, cardY1 = H - 20;
g_canvas.FillRectangle(cardX0, cardY0, cardX1, cardY1, g_panel);
g_canvas.Rectangle(cardX0, cardY0, cardX1, cardY1, g_grid);
if(!g_haveData || g_smile.NTenors() < 1)
{
g_canvas.FontSet("Segoe UI", SC(16), FW_BOLD);
g_canvas.TextOut(cardX0 + SC(20), cardY0 + SC(20), "GK Smile: no data. Check CSV path / source, then press R.", g_text, TA_LEFT | TA_TOP);
g_canvas.Update(true);
return;
}
DrawHeader(cardX0, cardY0);
//--- the plot owns the full card width; the side margins are only wide
//--- enough for the outermost pillar's number block to stay on the card
int bodyY0 = cardY0 + SC(108);
int stripH = SC(88);
g_plotX0 = cardX0 + SC(64);
g_plotX1 = cardX1 - SC(70);
g_plotY0 = bodyY0 + SC(8);
g_plotY1 = cardY1 - stripH - SC(44);
//--- Axis window from the ACTIVE tenor only.
g_yMin = 1e9;
g_yMax = -1e9;
double kLo = 1e18, kHi = -1e18;
int npa = g_smile.NPoints(g_active);
for(int j = 0; j < npa; j++)
{
double v = g_smile.PtVol(g_active, j);
g_yMin = MathMin(g_yMin, v);
g_yMax = MathMax(g_yMax, v);
kLo = MathMin(kLo, g_smile.PtStrike(g_active, j));
kHi = MathMax(kHi, g_smile.PtStrike(g_active, j));
}
double pad = 0.18 * (g_yMax - g_yMin) + 1e-6;
g_yMin -= pad;
g_yMax += pad;
double kpad = 0.04 * (kHi - kLo) + 1e-9;
g_xMin = kLo - kpad;
g_xMax = kHi + kpad;
DrawAxis();
if(InpShowAll)
for(int i = 0; i < g_smile.NTenors(); i++)
if(i != g_active)
DrawSmile(i, false);
DrawSmile(g_active, true);
//--- term strip, on the same full width as the plot above it
DrawTermStrip(g_plotX0, cardY1 - stripH + 8, g_plotX1, cardY1 - 12);
g_canvas.Update(true);
}
//+------------------------------------------------------------------+
//| Create (or recreate) a full-chart bitmap the size of the chart. |
//+------------------------------------------------------------------+
bool BuildCanvas(void)
{
int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
if(w <= 0)
w = 1280;
if(h <= 0)
h = 720;
g_canvas.Destroy();
if(g_objName != "")
ObjectDelete(0, g_objName);
g_objName = "GKSmileCanvas_" + IntegerToString(ChartID());
if(!g_canvas.CreateBitmapLabel(0, 0, g_objName, 0, 0, w, h, COLOR_FORMAT_ARGB_RAW))
{
PrintFormat("GKSmile: CreateBitmapLabel failed (err %d)", GetLastError());
return(false);
}
ObjectSetInteger(0, g_objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, g_objName, OBJPROP_XDISTANCE, 0);
ObjectSetInteger(0, g_objName, OBJPROP_YDISTANCE, 0);
ObjectSetInteger(0, g_objName, OBJPROP_BACK, false);
g_lastW = w;
g_lastH = h;
return(true);
}
//+------------------------------------------------------------------+
//| Rebuild the bitmap only when the chart pixel size changed. |
//+------------------------------------------------------------------+
bool RebuildIfResized(void)
{
int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
if(w <= 0 || h <= 0)
return(false);
if(w == g_lastW && h == g_lastH)
return(false);
return(BuildCanvas());
}
//+------------------------------------------------------------------+
//| Initialization |
//+------------------------------------------------------------------+
int OnInit(void)
{
IndicatorSetString(INDICATOR_SHORTNAME, "GK Smile");
ResolvePalette();
g_xaxis = InpXAxis;
if(!BuildCanvas())
return(INIT_FAILED);
Reload();
Draw();
EventSetTimer(1);
g_lastRefresh = TimeCurrent();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Timer: re-poll the native source on its interval and redraw. |
//+------------------------------------------------------------------+
void OnTimer(void)
{
if(InpSource == GK_SOURCE_NATIVE && InpRefreshSec > 0 &&
TimeCurrent() - g_lastRefresh >= InpRefreshSec)
{
Reload();
g_lastRefresh = TimeCurrent();
Draw();
}
}
//+------------------------------------------------------------------+
//| Indicator calculation: this tool draws from the vol quotes, not |
//| the price series, so it does no per-bar work. |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
return(rates_total);
}
//+------------------------------------------------------------------+
//| Chart events: R reloads, comma/period step the active tenor, X |
//| toggles between the delta and strike x axis, and a size change |
//| rebuilds the bitmap. None of these touch the reconstruction, so |
//| they only redraw; the smile itself is static until R. |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_KEYDOWN)
{
int key = (int)lparam;
if(key == 'R')
{
Reload();
Draw();
}
else if((key == 188 || key == 190) && g_haveData) // ',' and '.'
{
g_active += (key == 190) ? 1 : -1;
if(g_active < 0)
g_active = g_smile.NTenors() - 1;
if(g_active >= g_smile.NTenors())
g_active = 0;
Draw();
}
else if(key == 'X' && g_haveData)
{
g_xaxis = (g_xaxis == GK_XAXIS_DELTA) ? GK_XAXIS_STRIKE : GK_XAXIS_DELTA;
Draw();
}
}
if(id == CHARTEVENT_CHART_CHANGE)
{
if(RebuildIfResized())
Draw();
}
}
//+------------------------------------------------------------------+
//| Cleanup |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
g_canvas.Destroy();
if(g_objName != "")
ObjectDelete(0, g_objName);
ChartRedraw(0);
}
//+------------------------------------------------------------------+