462 lines
20 KiB
MQL5
462 lines
20 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| GexMap.mq5 |
|
||
|
|
//| MMQ — Muhammad Minhas Qamar |
|
||
|
|
//| www.mql5.com/en/articles/23410 |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property copyright "MMQ — Muhammad Minhas Qamar"
|
||
|
|
#property link "https://www.mql5.com/en/articles/23410"
|
||
|
|
#property version "1.10"
|
||
|
|
#property description "Dealer gamma-exposure (GEX) map: per-strike exposure, call/put walls, and the zero-gamma flip, drawn on the price chart at real strike levels."
|
||
|
|
#property indicator_chart_window
|
||
|
|
#property indicator_plots 0
|
||
|
|
#property indicator_buffers 0
|
||
|
|
|
||
|
|
#include <Canvas/Canvas.mqh>
|
||
|
|
#include <GEX/GexData.mqh>
|
||
|
|
#include <GEX/GexProviderNative.mqh>
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Inputs |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
enum ENUM_GEX_SOURCE
|
||
|
|
{
|
||
|
|
GEX_SOURCE_CSV = 0, // read a chain CSV from MQL5\Files
|
||
|
|
GEX_SOURCE_NATIVE = 1 // read the broker's native option symbols
|
||
|
|
};
|
||
|
|
|
||
|
|
enum ENUM_GEX_THEME
|
||
|
|
{
|
||
|
|
GEX_THEME_DARK = 0, // dark labels/bars (for a dark chart)
|
||
|
|
GEX_THEME_LIGHT = 1 // light labels/bars (for a light chart)
|
||
|
|
};
|
||
|
|
|
||
|
|
enum ENUM_GEX_SCALE
|
||
|
|
{
|
||
|
|
GEX_SCALE_OWN = 0, // self-contained band: always visible on any chart
|
||
|
|
GEX_SCALE_SNAP = 1 // snap to the chart's real price axis (same-underlying only)
|
||
|
|
};
|
||
|
|
|
||
|
|
input ENUM_GEX_SOURCE InpSource = GEX_SOURCE_CSV; // data source
|
||
|
|
input string InpCsvFile = "GEX\\gex_chain_sample.csv"; // CSV chain (in MQL5\Files)
|
||
|
|
input string InpUnderlying = ""; // native: base symbol (empty = chart symbol)
|
||
|
|
input double InpRiskFree = 0.045; // risk-free rate for the inversion
|
||
|
|
input double InpMultiplier = 100.0; // contract multiplier (100 = US equity options)
|
||
|
|
input int InpRefreshSec = 0; // native re-poll seconds (0 = manual, press R)
|
||
|
|
input int InpProfileW = 260; // profile width in pixels (right margin)
|
||
|
|
input ENUM_GEX_SCALE InpScaleMode = GEX_SCALE_OWN; // vertical scale: own band, or snap to price axis
|
||
|
|
input double InpFillPct = 0.80; // own scale: fraction of chart height to fill
|
||
|
|
input ENUM_GEX_THEME InpTheme = GEX_THEME_DARK; // label/bar theme
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Globals |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
CCanvas g_canvas;
|
||
|
|
CGexProfile g_profile;
|
||
|
|
string g_objName = "";
|
||
|
|
datetime g_lastRefresh = 0;
|
||
|
|
bool g_haveData = false;
|
||
|
|
int g_lastW = -1, g_lastH = -1; // last canvas pixel size, to detect real resizes
|
||
|
|
|
||
|
|
//--- own-scale band (price -> y), recomputed each draw from the strike range
|
||
|
|
double g_bandLo = 0, g_bandHi = 0; // price bounds of the profile band
|
||
|
|
int g_bandTop = 0, g_bandBot = 0; // pixel bounds of the profile band
|
||
|
|
|
||
|
|
//--- resolved palette (set in OnInit from the theme)
|
||
|
|
uint g_panel, g_grid, g_text, g_textDim, g_pos, g_neg, g_flip, g_spot, g_wall;
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Resolve the theme into a small ARGB palette once, so every draw |
|
||
|
|
//| call pulls from the same consistent set of colours. The bar and |
|
||
|
|
//| line colours read the same on either chart theme; only the panel |
|
||
|
|
//| fill and the text swap. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void ResolvePalette(void)
|
||
|
|
{
|
||
|
|
if(InpTheme == GEX_THEME_LIGHT)
|
||
|
|
{
|
||
|
|
g_panel = ColorToARGB(clrWhiteSmoke, 220); // near-opaque card
|
||
|
|
g_grid = ColorToARGB(clrSilver);
|
||
|
|
g_text = ColorToARGB(clrBlack);
|
||
|
|
g_textDim = ColorToARGB(clrDimGray);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
g_panel = ColorToARGB(C'22,24,30', 225); // near-opaque card
|
||
|
|
g_grid = ColorToARGB(C'70,74,86');
|
||
|
|
g_text = ColorToARGB(clrWhiteSmoke);
|
||
|
|
g_textDim = ColorToARGB(clrSilver);
|
||
|
|
}
|
||
|
|
g_pos = ColorToARGB(C'38,166,91', 235); // long-gamma (positive) green
|
||
|
|
g_neg = ColorToARGB(C'207,64,64', 235); // short-gamma (negative) red
|
||
|
|
g_flip = ColorToARGB(clrGold); // zero-gamma flip line
|
||
|
|
g_spot = ColorToARGB(clrDeepSkyBlue); // current spot line
|
||
|
|
g_wall = ColorToARGB(clrOrange); // call/put wall markers
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| (Re)load the chosen data source and rebuild the profile. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool Reload(void)
|
||
|
|
{
|
||
|
|
OptionQuote quotes[];
|
||
|
|
bool ok = false;
|
||
|
|
if(InpSource == GEX_SOURCE_CSV)
|
||
|
|
{
|
||
|
|
CGexProviderCSV csv;
|
||
|
|
ok = csv.Load(InpCsvFile, quotes);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
string under = (InpUnderlying == "") ? _Symbol : InpUnderlying;
|
||
|
|
CGexProviderNative nat;
|
||
|
|
ok = nat.Load(under, InpRiskFree, quotes);
|
||
|
|
}
|
||
|
|
if(!ok)
|
||
|
|
return(false);
|
||
|
|
|
||
|
|
g_profile.Multiplier(InpMultiplier);
|
||
|
|
g_haveData = g_profile.Build(quotes);
|
||
|
|
return(g_haveData);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Set up the own-scale band: map the strike range onto a centred |
|
||
|
|
//| vertical span covering InpFillPct of the chart height. Padded a |
|
||
|
|
//| little beyond the strikes so spot/flip lines that sit just |
|
||
|
|
//| outside the outermost strikes still land inside the band. Called |
|
||
|
|
//| once per draw before any PriceY() query in own-scale mode. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void SetupBand(void)
|
||
|
|
{
|
||
|
|
int H = g_canvas.Height();
|
||
|
|
int nk = g_profile.NStrikes();
|
||
|
|
double lo = g_profile.Strike(0);
|
||
|
|
double hi = g_profile.Strike(nk - 1);
|
||
|
|
double pad = 0.06 * (hi - lo);
|
||
|
|
g_bandLo = lo - pad;
|
||
|
|
g_bandHi = hi + pad;
|
||
|
|
|
||
|
|
double fill = MathMax(0.3, MathMin(0.95, InpFillPct));
|
||
|
|
int span = (int)(H * fill);
|
||
|
|
g_bandTop = (H - span) / 2; // centred vertically
|
||
|
|
g_bandBot = g_bandTop + span;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Map a price to a y pixel, either by snapping to the chart's real |
|
||
|
|
//| price axis (SNAP mode, for a same-underlying chart) or across |
|
||
|
|
//| the tool's own centred band (OWN mode, which is always visible |
|
||
|
|
//| regardless of the chart symbol). Returns false when the price |
|
||
|
|
//| falls off-screen so callers simply skip that level. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool PriceY(const double price, int &y)
|
||
|
|
{
|
||
|
|
if(InpScaleMode == GEX_SCALE_SNAP)
|
||
|
|
{
|
||
|
|
int x = 0, sub = 0;
|
||
|
|
datetime t = TimeCurrent(); // any visible time works; only y is used
|
||
|
|
if(!ChartTimePriceToXY(0, sub, t, price, x, y))
|
||
|
|
return(false);
|
||
|
|
int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
|
||
|
|
if(y < -50 || y > h + 50)
|
||
|
|
return(false); // well off-screen: don't draw it
|
||
|
|
return(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- own scale: linear map of the strike band onto [g_bandTop, g_bandBot]
|
||
|
|
if(g_bandHi <= g_bandLo)
|
||
|
|
return(false);
|
||
|
|
double f = (price - g_bandLo) / (g_bandHi - g_bandLo); // 0 at bandLo, 1 at bandHi
|
||
|
|
if(f < -0.02 || f > 1.02)
|
||
|
|
return(false); // outside the padded band
|
||
|
|
y = (int)MathRound(g_bandBot - f * (g_bandBot - g_bandTop));
|
||
|
|
return(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Draw the GEX profile in the right margin, each bar sitting at its|
|
||
|
|
//| strike's price level. In SNAP mode that level is the chart's own |
|
||
|
|
//| price axis; in OWN mode it is the tool's centred band, so the |
|
||
|
|
//| profile is visible on any chart. Bars grow leftward from the |
|
||
|
|
//| right edge; positive (dealer long gamma) is green, negative |
|
||
|
|
//| (short gamma) is red. Walls, spot and the flip are marked lines. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawMap(void)
|
||
|
|
{
|
||
|
|
int W = g_canvas.Width();
|
||
|
|
int H = g_canvas.Height();
|
||
|
|
g_canvas.Erase(0); // fully transparent: the chart shows through
|
||
|
|
|
||
|
|
if(!g_haveData || g_profile.NStrikes() < 2)
|
||
|
|
{
|
||
|
|
DrawTitle(W, "GEX: no data. Check CSV path / source. Press R.");
|
||
|
|
g_canvas.Update(true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- own-scale mode maps the strike range onto a centred band
|
||
|
|
if(InpScaleMode == GEX_SCALE_OWN)
|
||
|
|
SetupBand();
|
||
|
|
|
||
|
|
//--- right-margin geometry
|
||
|
|
int rightPad = 8;
|
||
|
|
int baseX = W - rightPad; // bars anchor here and grow left
|
||
|
|
int maxLen = MathMax(60, InpProfileW); // longest bar
|
||
|
|
int labelX = baseX - maxLen - 10; // strike labels sit left of the bars
|
||
|
|
|
||
|
|
//--- in own-scale mode, back the band with a faint panel so it reads as a
|
||
|
|
//--- self-contained gauge rather than floating bars over the candles
|
||
|
|
if(InpScaleMode == GEX_SCALE_OWN)
|
||
|
|
{
|
||
|
|
int px0 = labelX - 52;
|
||
|
|
g_canvas.FillRectangle(px0, g_bandTop - 10, baseX + rightPad, g_bandBot + 10, g_panel);
|
||
|
|
g_canvas.Rectangle(px0, g_bandTop - 10, baseX + rightPad, g_bandBot + 10, g_grid);
|
||
|
|
g_canvas.LineVertical(baseX, g_bandTop - 10, g_bandBot + 10, g_grid); // zero line bars grow from
|
||
|
|
}
|
||
|
|
|
||
|
|
int nk = g_profile.NStrikes();
|
||
|
|
double maxAbs = 1e-30;
|
||
|
|
for(int i = 0; i < nk; i++)
|
||
|
|
maxAbs = MathMax(maxAbs, MathAbs(g_profile.Gex(i)));
|
||
|
|
|
||
|
|
//--- bar half-height from the on-screen spacing of adjacent strikes
|
||
|
|
int yA, yB;
|
||
|
|
int barH = 8;
|
||
|
|
if(PriceY(g_profile.Strike(0), yA) && PriceY(g_profile.Strike(1), yB))
|
||
|
|
barH = MathMax(3, (int)(MathAbs(yB - yA) * 0.42));
|
||
|
|
|
||
|
|
//--- per-strike bars at real price levels
|
||
|
|
g_canvas.FontSet("Segoe UI", 14);
|
||
|
|
for(int i = 0; i < nk; i++)
|
||
|
|
{
|
||
|
|
int y;
|
||
|
|
if(!PriceY(g_profile.Strike(i), y))
|
||
|
|
continue;
|
||
|
|
double gex = g_profile.Gex(i);
|
||
|
|
int len = (int)MathRound(maxLen * (MathAbs(gex) / maxAbs));
|
||
|
|
uint clr = (gex >= 0.0) ? g_pos : g_neg;
|
||
|
|
g_canvas.FillRectangle(baseX - len, y - barH, baseX, y + barH, clr);
|
||
|
|
//--- strike label just left of the profile
|
||
|
|
g_canvas.TextOut(labelX, y - 10, StringFormat("%.0f", g_profile.Strike(i)), g_textDim, TA_RIGHT);
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- reference lines drawn across the profile band at their true prices
|
||
|
|
//--- (unlabelled: the colour key in the title card identifies them)
|
||
|
|
int lineX0 = labelX - 40;
|
||
|
|
DrawLevel(g_profile.Spot(), lineX0, baseX, g_spot);
|
||
|
|
if(g_profile.Flip() > 0.0)
|
||
|
|
DrawLevel(g_profile.Flip(), lineX0, baseX, g_flip);
|
||
|
|
DrawLevel(g_profile.CallWall(), lineX0, baseX, g_wall);
|
||
|
|
DrawLevel(g_profile.PutWall(), lineX0, baseX, g_wall);
|
||
|
|
|
||
|
|
//--- title / regime card, top-left
|
||
|
|
double net = g_profile.NetGex();
|
||
|
|
string underName = (InpSource == GEX_SOURCE_NATIVE && InpUnderlying != "") ? InpUnderlying : _Symbol;
|
||
|
|
string line1 = StringFormat("Gamma Exposure - %s - exp %s",
|
||
|
|
underName, TimeToString(g_profile.Expiry(), TIME_DATE));
|
||
|
|
string regime = (net >= 0.0) ? "NET LONG GAMMA (mean-reverting)" : "NET SHORT GAMMA (trending)";
|
||
|
|
string line2 = StringFormat("%s net GEX %.3g flip %s",
|
||
|
|
regime, net,
|
||
|
|
(g_profile.Flip() > 0.0 ? StringFormat("%.2f", g_profile.Flip()) : "none"));
|
||
|
|
DrawTitleTwo(line1, line2, (net >= 0.0) ? g_pos : g_neg);
|
||
|
|
|
||
|
|
g_canvas.Update(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Draw one reference level (spot / flip / wall) as a plain line |
|
||
|
|
//| across the profile band. No caption: the lines are kept clean |
|
||
|
|
//| and the colour key in the title card names them, so they do not |
|
||
|
|
//| clutter the profile. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawLevel(const double price, const int x0, const int x1, const uint clr)
|
||
|
|
{
|
||
|
|
int y;
|
||
|
|
if(!PriceY(price, y))
|
||
|
|
return;
|
||
|
|
g_canvas.LineHorizontal(x0, x1, y, clr);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Single-line title card (used only for the "no data" message). |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawTitle(const int W, const string text)
|
||
|
|
{
|
||
|
|
g_canvas.FontSet("Segoe UI", 15, FW_BOLD);
|
||
|
|
int w = g_canvas.TextWidth(text) + 36;
|
||
|
|
g_canvas.FillRectangle(8, 8, 8 + w, 8 + 38, g_panel);
|
||
|
|
g_canvas.Rectangle(8, 8, 8 + w, 8 + 38, g_grid);
|
||
|
|
g_canvas.TextOut(22, 16, text, g_text, TA_LEFT);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Small colour swatch + label, used to build the reference-line |
|
||
|
|
//| key. Returns the x just past the label so keys chain left to |
|
||
|
|
//| right. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
int KeyItem(const int x, const int y, const uint clr, const string label)
|
||
|
|
{
|
||
|
|
g_canvas.FillRectangle(x, y + 3, x + 16, y + 17, clr);
|
||
|
|
g_canvas.FontSet("Segoe UI", 13);
|
||
|
|
g_canvas.TextOut(x + 22, y, label, g_textDim, TA_LEFT);
|
||
|
|
return(x + 22 + g_canvas.TextWidth(label) + 20);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Title / regime card, top-left, on an opaque panel so it stays |
|
||
|
|
//| readable over any candles behind it. Three rows: the heading, |
|
||
|
|
//| the net-GEX regime verdict, and a colour key that names the |
|
||
|
|
//| reference lines so they can stay unlabelled on the profile. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawTitleTwo(const string line1, const string line2, const uint regimeClr)
|
||
|
|
{
|
||
|
|
g_canvas.FontSet("Segoe UI", 18, FW_BOLD);
|
||
|
|
int w1 = g_canvas.TextWidth(line1);
|
||
|
|
g_canvas.FontSet("Segoe UI", 15, FW_BOLD);
|
||
|
|
int w2 = g_canvas.TextWidth(line2);
|
||
|
|
int cardW = MathMax(w1, w2) + 36;
|
||
|
|
cardW = MathMax(cardW, 360);
|
||
|
|
int cardH = 104;
|
||
|
|
|
||
|
|
g_canvas.FillRectangle(8, 8, 8 + cardW, 8 + cardH, g_panel);
|
||
|
|
g_canvas.Rectangle(8, 8, 8 + cardW, 8 + cardH, g_grid);
|
||
|
|
g_canvas.FontSet("Segoe UI", 18, FW_BOLD);
|
||
|
|
g_canvas.TextOut(22, 16, line1, g_text, TA_LEFT);
|
||
|
|
g_canvas.FontSet("Segoe UI", 15, FW_BOLD);
|
||
|
|
g_canvas.TextOut(22, 46, line2, regimeClr, TA_LEFT);
|
||
|
|
|
||
|
|
//--- reference-line colour key
|
||
|
|
int kx = 22, ky = 76;
|
||
|
|
kx = KeyItem(kx, ky, g_spot, "spot");
|
||
|
|
kx = KeyItem(kx, ky, g_flip, "flip");
|
||
|
|
kx = KeyItem(kx, ky, g_wall, "walls");
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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 = 1200;
|
||
|
|
if(h <= 0)
|
||
|
|
h = 600;
|
||
|
|
|
||
|
|
g_canvas.Destroy();
|
||
|
|
if(g_objName != "")
|
||
|
|
ObjectDelete(0, g_objName);
|
||
|
|
|
||
|
|
g_objName = "GexMapCanvas_" + IntegerToString(ChartID());
|
||
|
|
if(!g_canvas.CreateBitmapLabel(0, 0, g_objName, 0, 0, w, h, COLOR_FORMAT_ARGB_NORMALIZE))
|
||
|
|
{
|
||
|
|
PrintFormat("GexMap: 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); // draw over the candles
|
||
|
|
g_lastW = w;
|
||
|
|
g_lastH = h;
|
||
|
|
return(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Rebuild the bitmap only when the chart pixel size actually |
|
||
|
|
//| changed. A scroll or a scale change keeps the same canvas and |
|
||
|
|
//| only needs a redraw, which is far cheaper than a rebuild. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
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, "GEX Map");
|
||
|
|
ResolvePalette();
|
||
|
|
|
||
|
|
if(!BuildCanvas())
|
||
|
|
return(INIT_FAILED);
|
||
|
|
|
||
|
|
Reload();
|
||
|
|
DrawMap();
|
||
|
|
|
||
|
|
EventSetTimer(1);
|
||
|
|
g_lastRefresh = TimeCurrent();
|
||
|
|
return(INIT_SUCCEEDED);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Timer: re-poll the native source on its interval and redraw when |
|
||
|
|
//| fresh data arrived. The profile is static between reloads, so |
|
||
|
|
//| the timer alone does not force redraws. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnTimer(void)
|
||
|
|
{
|
||
|
|
if(InpSource == GEX_SOURCE_NATIVE && InpRefreshSec > 0 &&
|
||
|
|
TimeCurrent() - g_lastRefresh >= InpRefreshSec)
|
||
|
|
{
|
||
|
|
Reload();
|
||
|
|
g_lastRefresh = TimeCurrent();
|
||
|
|
DrawMap();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Indicator calculation: this tool draws from the option chain, |
|
||
|
|
//| not the price series, so it does no per-bar work. We keep the |
|
||
|
|
//| required return contract only. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
|
||
|
|
{
|
||
|
|
return(rates_total);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Chart events: R reloads the data. Because the profile is pinned |
|
||
|
|
//| to the real price axis, any chart change (scroll, zoom, scale) |
|
||
|
|
//| shifts the price-to-pixel mapping, so we rebuild-if-resized and |
|
||
|
|
//| always redraw to keep the bars locked to their strikes. |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
||
|
|
{
|
||
|
|
if(id == CHARTEVENT_KEYDOWN)
|
||
|
|
{
|
||
|
|
if((int)lparam == 'R' || (int)lparam == 'r')
|
||
|
|
{
|
||
|
|
Reload();
|
||
|
|
DrawMap();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
if(id == CHARTEVENT_CHART_CHANGE)
|
||
|
|
{
|
||
|
|
RebuildIfResized(); // only rebuilds the bitmap on a real size change
|
||
|
|
DrawMap(); // always redraw: the price axis may have moved
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Cleanup |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnDeinit(const int reason)
|
||
|
|
{
|
||
|
|
EventKillTimer();
|
||
|
|
g_canvas.Destroy();
|
||
|
|
if(g_objName != "")
|
||
|
|
ObjectDelete(0, g_objName);
|
||
|
|
ChartRedraw(0);
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|