//+------------------------------------------------------------------+ //| GannSwingChart v7 BT.mq5 | //| MQL5 port of the cTrader GannSwingChart v7 indicator | //| BT build: on/off toggle button ported from MarketProfile Canvas | //| v1.01 BT. Turning the indicator OFF removes its objects and | //| silences alerts; turning it ON rebuilds from chart history. | //| | //| MT5 adaptations: | //| * Candles are recolored with a native DRAW_COLOR_CANDLES plot | //| (XU Candles style); width is set with InpCandleWidth. | //| * Email uses the terminal's configured mail settings (SendMail);| //| there are no From/To indicator inputs. | //| * The dashboard is rendered with chart objects (labels and | //| rectangle labels) instead of cTrader UI controls. | //| * The signal engine, outside-bar rules, Swing Count, closed-bar | //| mode, undecided filter and DirectionBuffer (buffer 0) are | //| ported 1:1. | //+------------------------------------------------------------------+ #property copyright "Port of GannSwingChart v7 (cTrader)" #property version "7.00" #property description "Gann swing chart with toggle button, arrows/crosses, alerts and 9-timeframe dashboard" #property indicator_chart_window #property indicator_buffers 6 #property indicator_plots 2 #property indicator_type1 DRAW_NONE #property indicator_label1 "Gann Direction" #property indicator_type2 DRAW_COLOR_CANDLES #property indicator_label2 "Gann Candles" #property indicator_color2 clrLimeGreen,clrCrimson,clrGold #property indicator_width2 2 //--- enums enum ENUM_GSW_HL_RULE { Gann_HH_and_HL = 0, // Gann: HH and HL Krausz_HH_only = 1 // Krausz: HH only }; enum ENUM_GSW_OUTSIDE { UseNextBar = 0, // Use next bar Wait = 1 // Wait (v2 behavior) }; enum ENUM_GSW_CORNER { TopLeft = 0, TopRight = 1, BottomLeft = 2, BottomRight = 3 }; enum ENUM_GSW_BAR { BarInside = 0, BarHigher = 1, BarLower = 2, BarOutside = 3 }; //--- inputs input group "Logic" input ENUM_TIMEFRAMES InpSignalTF = PERIOD_CURRENT; // Signal Timeframe input int InpSwingCount = 1; // Swing Count (bars to turn) input ENUM_GSW_HL_RULE InpBarRule = Gann_HH_and_HL; // Higher/Lower Bar Rule input ENUM_GSW_OUTSIDE InpOutsideMode = UseNextBar; // Outside Bar Mode input bool InpClosedBarsOnly = false; // Signals On Closed HTF Bar Only input bool InpUndecidedFilter = true; // Undecided Filter (Yellow) input group "Signals" input bool InpShowArrows = false; // Show Arrows input color InpArrowUpColor = C'45,128,255'; // Arrow Up Color input color InpArrowDnColor = C'228,58,58'; // Arrow Down Color input bool InpShowCrosses = false; // Show Crosses (Undecided) input color InpCrossColor = clrGold; // Cross Color input group "Alerts" input bool InpEnableAlerts = false; // Enable Sound Alerts input bool InpAlertBuy = false; // Alert BUY input bool InpAlertSell = false; // Alert SELL input bool InpAlertCross = false; // Alert CROSS (Undecided) input bool InpEnableEmail = false; // Enable Email (terminal settings) input group "Dashboard" input bool InpShowDashboard = false; // Show Dashboard input ENUM_GSW_CORNER InpCorner = TopRight; // Panel Corner input int InpDashX = 20; // Panel X input int InpDashY = 30; // Panel Y input double InpStrMaxPct = 3.0; // Swing Range Bar 100% Cap (%) input int InpThreshSwing = 7; // Min TFs for SWING input int InpThreshIntraday = 5; // Min TFs for INTRADAY input int InpThreshScalp = 3; // Min TFs for SCALP input group "Style" input color InpLineColor = clrRed; // Line Color input int InpLineThickness = 1; // Line Thickness (1-5) input bool InpShowDots = true; // Show Dots input int InpDotWidth = 5; // Swing Dot Width (1-5) input color InpHighDotColor = clrOrangeRed; // Swing High Dot Color input color InpLowDotColor = clrDeepSkyBlue; // Swing Low Dot Color input group "Candles" input bool InpColorCandles = true; // Color Candles By Signal input int InpCandleWidth = 2; // Candle Width (1-5) input color InpUpCandleColor = clrLimeGreen; // Up Swing Candles input color InpDownCandleColor = clrCrimson; // Down Swing Candles input color InpUndecidedCandleColor = clrGold; // Undecided Candles input group "Performance" input int InpMaxBarsToDraw = 2000; // Max chart bars to draw (0 = all) input bool InpThrottleDashboard = true; // Throttle dashboard updates on ticks //Forex-Station copy & paste code; Button code start 11 input group "=== Toggle Button Settings ===" input string button_note1 = "------------------------------"; // ------------------------------ input int btn_Subwindow = 0; // What window to put the button on input ENUM_BASE_CORNER btn_corner = CORNER_LEFT_UPPER; // chart btn_corner for anchoring input string btn_text = "GSW: ON"; // Display label input string btn_Font = "Arial"; // button font name input int btn_FontSize = 9; // btn__font size input color btn_text_ON_color = clrLime; // ON color when the button is turned on input color btn_text_OFF_color = clrRed; // OFF color when the button is turned off input color btn_background_color = clrDimGray; // background color of the button input color btn_border_color = clrBlack; // border color the button input int button_x = 180; // Horizontal location input int button_y = 120; // Vertical location input int btn_Width = 80; // btn__width input int btn_Height = 20; // btn__height input string UniqueButtonID = "GannSwingBT"; // Unique Button ID input string button_note2 = "------------------------------"; // ------------------------------ //Forex-Station copy & paste code; Button code end 11 //--- signal constants #define SIG_UNDEC 0 #define SIG_UP 1 #define SIG_DOWN -1 //--- dashboard geometry const int TFC = 9; const int DashWarmupBars = 50; const int MaxDashLoadAttempts = 6; const int MaxWarmupAttempts = 30; const int PanelWidth = 268; const int PanelHeight = 380; const int RowHeight = 27; const int BarWidth = 54; const int OverallBarWidth = 248; const int HeaderH = 25; const int AccentH = 2; const int SubH = 17; const int SubBorder = 1; //--- dashboard object names #define DashPanelBorder "GSW_panel_border" #define DashPanelBg "GSW_panel_bg" #define DashHeaderBg "GSW_header_bg" #define DashFooterBg "GSW_footer_bg" #define DashAccent "GSW_accent" #define DashSubBg "GSW_sub_bg" #define DashSubBorder "GSW_sub_border" #define DashDivider "GSW_divider" #define DashDivider2 "GSW_divider2" #define DashLiveDot "GSW_live_dot" #define DashTitle "GSW_title" #define DashSymbol "GSW_symbol" #define DashSubTF "GSW_sub_tf" #define DashSubSwing "GSW_sub_swing" #define DashSubRange "GSW_sub_range" #define DashSubBar "GSW_sub_bar" #define DashOverallLabel "GSW_overall_label" #define DashVerdict "GSW_verdict" #define DashAvg "GSW_avg" #define DashAvgLabel "GSW_avg_label" #define DashScore "GSW_score" #define DashOverallTrack "GSW_overall_track" #define DashOverallFill "GSW_overall_fill" #define DashStyle "GSW_style" #define DashStyleDesc "GSW_style_desc" #define DASH_FONT "Arial" #define DASH_FONT_BOLD "Arial Bold" //--- dashboard palette const color ClrBackground = C'6,8,16'; const color ClrHeader = C'10,13,28'; const color ClrSubHeader = C'8,10,20'; const color ClrRowEven = C'9,11,21'; const color ClrRowOdd = C'7,8,16'; const color ClrFooter = C'6,8,17'; const color ClrBorder = C'30,42,98'; const color ClrAccent = C'28,50,135'; const color ClrDivider = C'18,28,72'; const color ClrTrack = C'14,17,38'; const color ClrTitle = C'100,125,200'; const color ClrDim = C'26,44,96'; const color ClrWarm = C'195,178,52'; const color ClrBullText = C'85,160,255'; const color ClrBullBar = C'45,128,255'; const color ClrBullPct = C'111,176,255'; const color ClrBearText = C'248,88,88'; const color ClrBearBar = C'185,36,36'; const color ClrBearPct = C'238,80,80'; const color ClrUndecText = C'232,195,60'; const color ClrUndecBar = C'195,178,52'; const color ClrSwing = C'50,220,120'; const color ClrIntraday = C'65,148,248'; const color ClrScalp = C'220,175,40'; const color ClrSideway = C'140,140,160'; const color ClrNeutral = C'195,178,52'; const color ClrLiveDot = C'34,200,95'; //--- timeframes for dashboard ENUM_TIMEFRAMES _tfs[9] = { PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1, PERIOD_MN1 }; string _tfNames[9] = { "M1", "M5", "M15", "M30", "H1", "H4", "D1", "WK", "MN" }; //--- indicator buffer double DirBuffer[]; //--- candle plot buffers (DRAW_COLOR_CANDLES, XU Candles style) double CandleO[], CandleH[], CandleL[], CandleC[], CandleCol[]; //--- state structures struct SwingPoint { datetime time; double price; bool isHigh; }; struct EngineState { bool initialized; int dir; double refHigh; double refLow; double extPrice; int extIndex; double pendPrice; int pendIndex; int turnCount; bool outsidePending; int outIndex; double outHigh; double outLow; int swingsCount; }; struct MiniState { bool init; int dir; int lastDecided; double refHigh; double refLow; double extPrice; double pendPrice; int turnCount; bool outsidePending; double outHigh; double outLow; }; //--- resolved inputs ENUM_TIMEFRAMES _signalTF; int _swingCount = 1; int _lineWidth = 1; int _dotWidth = 1; //--- HTF cache (chronological: 0 = oldest, count-1 = live) datetime _htfTime[]; double _htfHigh[]; double _htfLow[]; int _htfCount = 0; datetime _htfOldest = 0; //--- engine state EngineState _state; EngineState _saved; SwingPoint _swings[]; int _signals[]; int _carry[]; int _lastHtfIndex = -1; int _processedTo = -1; bool _alertBaseline = false; int _lastAlertState = 0; bool _paintedInit = false; int _lastPaintedSig = 0; datetime _lastPaintedOpen = 0; //--- chart / rebuild state datetime _chartOldest = 0; datetime _drawFromTime = 0; int _lastRatesTotal = 0; bool _needsRebuild = true; //--- warmup bool _htfWarmDone = false; int _warmupAttempts = 0; //--- dashboard state MiniState _dashState[]; int _dashProcessed[]; datetime _dashFirstOpen[]; int _loadAttempts[]; bool _dashBuilt = false; uint _lastDashTick = 0; int _panelChartW = -1; int _panelChartH = -1; string _rowDotName[]; string _rowSignalName[]; string _rowPctName[]; string _rowFillName[]; string _dashObj[]; int _dashOx[]; int _dashOy[]; int _dashOw[]; int _dashOh[]; //--- toggle button state bool g_indicatorEnabled = true; bool g_indicatorHidden = false; string g_buttonId = ""; string g_toggleButtonName = ""; string g_gvHidden = ""; //+------------------------------------------------------------------+ //| Helpers | //+------------------------------------------------------------------+ int IMin(const int a, const int b) { return (a < b ? a : b); } int IMax(const int a, const int b) { return (a > b ? a : b); } double PipSize() { int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); return ((digits == 3 || digits == 5) ? 10.0 * _Point : _Point); } string TfText(const ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN"; } return EnumToString(tf); } //+------------------------------------------------------------------+ //| HTF cache | //+------------------------------------------------------------------+ bool LoadHtfCache() { int total = Bars(_Symbol, _signalTF); if(total <= 0) { _htfCount = 0; ArrayResize(_htfTime, 0); ArrayResize(_htfHigh, 0); ArrayResize(_htfLow, 0); return false; } if(ArraySize(_htfTime) != total) { datetime t[]; double h[], l[]; int nt = CopyTime(_Symbol, _signalTF, 0, total, t); int nh = CopyHigh(_Symbol, _signalTF, 0, total, h); int nl = CopyLow(_Symbol, _signalTF, 0, total, l); int n = IMin(nt, IMin(nh, nl)); if(n <= 0) return (_htfCount > 0); ArrayResize(_htfTime, n); ArrayResize(_htfHigh, n); ArrayResize(_htfLow, n); bool newestFirst = (n > 1 && t[0] > t[n - 1]); for(int i = 0; i < n; i++) { int src = (newestFirst ? n - 1 - i : i); _htfTime[i] = t[src]; _htfHigh[i] = h[src]; _htfLow[i] = l[src]; } _htfCount = n; } // refresh the live bar each call if(_htfCount > 0) { datetime lt = iTime(_Symbol, _signalTF, 0); double lh = iHigh(_Symbol, _signalTF, 0); double ll = iLow(_Symbol, _signalTF, 0); if(lt > 0) _htfTime[_htfCount - 1] = lt; if(lh > 0) _htfHigh[_htfCount - 1] = lh; if(ll > 0) _htfLow[_htfCount - 1] = ll; } return (_htfCount > 0); } int HtfIndexFor(const datetime t) { if(_htfCount <= 0) return -1; if(t < _htfTime[0]) return -1; int lo = 0; int hi = _htfCount - 1; while(lo < hi) { int mid = (lo + hi + 1) / 2; if(_htfTime[mid] <= t) lo = mid; else hi = mid - 1; } return lo; } void RequestMoreHistory(const ENUM_TIMEFRAMES tf) { int have = Bars(_Symbol, tf); int want = have + 1000; if(want < 100) want = 100; double tmp[]; CopyHigh(_Symbol, tf, 0, want, tmp); } //+------------------------------------------------------------------+ //| Core engine | //+------------------------------------------------------------------+ int Classify(const double h, const double l) { bool hh = (h > _state.refHigh); bool ll = (l < _state.refLow); if(hh && ll) return BarOutside; if(InpBarRule == Gann_HH_and_HL) { if(hh && l > _state.refLow) return BarHigher; if(ll && h < _state.refHigh) return BarLower; return BarInside; } if(hh) return BarHigher; if(ll) return BarLower; return BarInside; } void AddPivot(const int htfIndex, const double price, const bool isHigh) { if(htfIndex < 0 || htfIndex >= _htfCount) return; int n = ArraySize(_swings); ArrayResize(_swings, n + 1, 64); _swings[n].time = _htfTime[htfIndex]; _swings[n].price = price; _swings[n].isHigh = isHigh; DrawSegment(n); } void StartDirection(const int dir, const int i, const double h, const double l) { _state.dir = dir; if(dir == 1) { _state.extPrice = h; _state.extIndex = i; _state.pendPrice = l; _state.pendIndex = i; } else { _state.extPrice = l; _state.extIndex = i; _state.pendPrice = h; _state.pendIndex = i; } _state.turnCount = 0; } void UpdateExtremes(const int i, const double h, const double l) { if(_state.dir == 1) { if(h > _state.extPrice) { _state.extPrice = h; _state.extIndex = i; _state.pendPrice = l; _state.pendIndex = i; } else if(l < _state.pendPrice) { _state.pendPrice = l; _state.pendIndex = i; } } else if(_state.dir == -1) { if(l < _state.extPrice) { _state.extPrice = l; _state.extIndex = i; _state.pendPrice = h; _state.pendIndex = i; } else if(h > _state.pendPrice) { _state.pendPrice = h; _state.pendIndex = i; } } } void Turn(const int newDir, const int i) { AddPivot(_state.extIndex, _state.extPrice, (_state.dir == 1)); _state.dir = newDir; _state.extIndex = _state.pendIndex; _state.extPrice = _state.pendPrice; int from = _state.extIndex; if(from < 0) from = 0; if(from > i) from = i; if(from >= _htfCount) from = _htfCount - 1; if(newDir == 1) { double pl = _htfLow[from]; int pi = from; for(int k = from + 1; k <= i && k < _htfCount; k++) { if(_htfLow[k] < pl) { pl = _htfLow[k]; pi = k; } } _state.pendPrice = pl; _state.pendIndex = pi; } else { double ph = _htfHigh[from]; int pi = from; for(int k = from + 1; k <= i && k < _htfCount; k++) { if(_htfHigh[k] > ph) { ph = _htfHigh[k]; pi = k; } } _state.pendPrice = ph; _state.pendIndex = pi; } _state.turnCount = 0; } int ResolveOutside(const int i, const bool resolvedUp, const double h, const double l) { int oi = _state.outIndex; double oh = _state.outHigh; double ol = _state.outLow; _state.outsidePending = false; _state.turnCount = 0; if(_state.dir == 0) { _state.dir = (resolvedUp ? 1 : -1); if(resolvedUp) { if(h >= oh) { _state.extPrice = h; _state.extIndex = i; } else { _state.extPrice = oh; _state.extIndex = oi; } _state.pendPrice = l; _state.pendIndex = i; } else { if(l <= ol) { _state.extPrice = l; _state.extIndex = i; } else { _state.extPrice = ol; _state.extIndex = oi; } _state.pendPrice = h; _state.pendIndex = i; } return _state.dir; } if(_state.dir == 1) { if(resolvedUp) { if(_swingCount == 1) { AddPivot(_state.extIndex, _state.extPrice, true); double pl = ol; int pli = oi; if(_state.pendPrice < pl) { pl = _state.pendPrice; pli = _state.pendIndex; } AddPivot(pli, pl, false); } if(h >= oh) { _state.extPrice = h; _state.extIndex = i; } else { _state.extPrice = oh; _state.extIndex = oi; } _state.pendPrice = l; _state.pendIndex = i; } else { if(oh > _state.extPrice) { _state.extPrice = oh; _state.extIndex = oi; _state.pendPrice = ol; _state.pendIndex = oi; } else if(ol < _state.pendPrice) { _state.pendPrice = ol; _state.pendIndex = oi; } UpdateExtremes(i, h, l); _state.turnCount = 1; if(_state.turnCount >= _swingCount) { Turn(-1, i); _state.pendPrice = h; _state.pendIndex = i; } } return _state.dir; } // dir == -1 (mirror) if(!resolvedUp) { if(_swingCount == 1) { AddPivot(_state.extIndex, _state.extPrice, false); double ph = oh; int phi = oi; if(_state.pendPrice > ph) { ph = _state.pendPrice; phi = _state.pendIndex; } AddPivot(phi, ph, true); } if(l <= ol) { _state.extPrice = l; _state.extIndex = i; } else { _state.extPrice = ol; _state.extIndex = oi; } _state.pendPrice = h; _state.pendIndex = i; } else { if(ol < _state.extPrice) { _state.extPrice = ol; _state.extIndex = oi; _state.pendPrice = oh; _state.pendIndex = oi; } else if(oh > _state.pendPrice) { _state.pendPrice = oh; _state.pendIndex = oi; } UpdateExtremes(i, h, l); _state.turnCount = 1; if(_state.turnCount >= _swingCount) { Turn(1, i); _state.pendPrice = l; _state.pendIndex = i; } } return _state.dir; } int ProcessHtfBar(const int i) { double h = _htfHigh[i]; double l = _htfLow[i]; if(!_state.initialized) { _state.initialized = true; _state.dir = 0; _state.refHigh = h; _state.refLow = l; _state.extPrice = h; _state.extIndex = i; _state.pendPrice = l; _state.pendIndex = i; return SIG_UNDEC; } int t = Classify(h, l); if(t == BarInside) return (_state.outsidePending ? SIG_UNDEC : _state.dir); if(t == BarOutside) { _state.turnCount = 0; _state.refHigh = h; _state.refLow = l; if(InpOutsideMode == UseNextBar) { _state.outsidePending = true; _state.outIndex = i; _state.outHigh = h; _state.outLow = l; return SIG_UNDEC; } if(_state.dir != 0) UpdateExtremes(i, h, l); return SIG_UNDEC; } bool up = (t == BarHigher); if(_state.outsidePending) { int sig = ResolveOutside(i, up, h, l); _state.refHigh = h; _state.refLow = l; return sig; } if(_state.dir == 0) { StartDirection(up ? 1 : -1, i, h, l); _state.refHigh = h; _state.refLow = l; return _state.dir; } UpdateExtremes(i, h, l); if(up && _state.dir == -1) { _state.turnCount++; if(_state.turnCount >= _swingCount) Turn(1, i); } else if(!up && _state.dir == 1) { _state.turnCount++; if(_state.turnCount >= _swingCount) Turn(-1, i); } else { _state.turnCount = 0; } _state.refHigh = h; _state.refLow = l; return _state.dir; } //+------------------------------------------------------------------+ //| Signals | //+------------------------------------------------------------------+ void RecordSignal(const int htfIndex, const int sig) { if(htfIndex < 0) return; while(ArraySize(_signals) <= htfIndex) { int n = ArraySize(_signals); ArrayResize(_signals, n + 1, 256); ArrayResize(_carry, n + 1, 256); _signals[n] = SIG_UNDEC; _carry[n] = (n > 0 ? _carry[n - 1] : SIG_UNDEC); } _signals[htfIndex] = sig; _carry[htfIndex] = (sig != SIG_UNDEC ? sig : (htfIndex > 0 ? _carry[htfIndex - 1] : SIG_UNDEC)); } int EffectiveSignalAtHtf(const int h) { if(h < 0 || h >= ArraySize(_signals)) return SIG_UNDEC; int sig = _signals[h]; if(!InpUndecidedFilter && sig == SIG_UNDEC) sig = _carry[h]; return sig; } //+------------------------------------------------------------------+ //| Engine driving | //+------------------------------------------------------------------+ void UpdateEngine(const int chartIndex, const datetime &time[]) { int hIdx = HtfIndexFor(time[chartIndex]); if(hIdx < 0 || _htfCount <= 0) return; if(hIdx < _lastHtfIndex) { // HTF indices shifted: request a full rebuild on the next call FullResetState(); _needsRebuild = true; return; } if(InpClosedBarsOnly) { for(int k = _processedTo + 1; k <= hIdx - 1; k++) RecordSignal(k, ProcessHtfBar(k)); if(hIdx - 1 > _processedTo) _processedTo = hIdx - 1; _lastHtfIndex = hIdx; RecordSignal(hIdx, (_state.outsidePending ? SIG_UNDEC : _state.dir)); return; } if(hIdx != _lastHtfIndex) { for(int k = _lastHtfIndex + 1; k < hIdx; k++) RecordSignal(k, ProcessHtfBar(k)); _lastHtfIndex = hIdx; _processedTo = hIdx; _state.swingsCount = ArraySize(_swings); _saved = _state; } else { _state = _saved; RollbackSwings(_state.swingsCount); } RecordSignal(hIdx, ProcessHtfBar(hIdx)); } //+------------------------------------------------------------------+ //| Drawing | //+------------------------------------------------------------------+ bool ShouldDrawTime(const datetime t) { if(InpMaxBarsToDraw <= 0) return true; return (t >= _drawFromTime); } void EnsureTrend(const string name, const datetime t1, const double p1, const datetime t2, const double p2, const color clr) { if(ObjectFind(0, name) < 0) { if(!ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) return; ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(0, name, OBJPROP_RAY_LEFT, false); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); } ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectMove(0, name, 0, t1, p1); ObjectMove(0, name, 1, t2, p2); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_WIDTH, _lineWidth); } void EnsureArrow(const string name, const ENUM_OBJECT type, const datetime t, const double p, const color clr, const int code) { if(ObjectFind(0, name) < 0) { if(!ObjectCreate(0, name, type, 0, t, p)) return; ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); if(type == OBJ_ARROW) ObjectSetInteger(0, name, OBJPROP_ARROWCODE, code); } ObjectMove(0, name, 0, t, p); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); } void EnsureDot(const string name, const datetime t, const double p, const color clr, const int width) { if(ObjectFind(0, name) >= 0 && (ENUM_OBJECT)ObjectGetInteger(0, name, OBJPROP_TYPE) != OBJ_TEXT) ObjectDelete(0, name); if(ObjectFind(0, name) < 0) { if(!ObjectCreate(0, name, OBJ_TEXT, 0, t, p)) return; ObjectSetString (0, name, OBJPROP_TEXT, "●"); ObjectSetString (0, name, OBJPROP_FONT, "Arial"); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_BACK, false); } ObjectMove(0, name, 0, t, p); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 4 + width * 2); } void EnsureCross(const string name, const datetime t, const double p, const color clr) { if(ObjectFind(0, name) < 0) { if(!ObjectCreate(0, name, OBJ_TEXT, 0, t, p)) return; ObjectSetString(0, name, OBJPROP_TEXT, "✕"); ObjectSetString(0, name, OBJPROP_FONT, "Segoe UI Symbol"); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 12); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); } ObjectMove(0, name, 0, t, p); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); } void RemoveMarker(const string name) { if(ObjectFind(0, name) >= 0) ObjectDelete(0, name); } void SetCandleEmpty(const int c) { CandleO[c] = EMPTY_VALUE; CandleH[c] = EMPTY_VALUE; CandleL[c] = EMPTY_VALUE; CandleC[c] = EMPTY_VALUE; CandleCol[c] = 0.0; } void DrawSegment(const int k) { if(g_indicatorHidden) return; if(k < 0 || k >= ArraySize(_swings)) return; SwingPoint b = _swings[k]; if(!ShouldDrawTime(b.time)) return; if(k > 0) { SwingPoint a = _swings[k - 1]; EnsureTrend("GSW_h_" + IntegerToString(k), a.time, a.price, b.time, a.price, InpLineColor); EnsureTrend("GSW_v_" + IntegerToString(k), b.time, a.price, (datetime)(b.time + 1), b.price, InpLineColor); } if(InpShowDots) EnsureDot("GSW_d_" + IntegerToString(k), b.time, b.price, (b.isHigh ? InpHighDotColor : InpLowDotColor), _dotWidth); } void DeleteLiveObjects() { ObjectDelete(0, "GSW_live_h"); ObjectDelete(0, "GSW_live_v"); ObjectDelete(0, "GSW_live_ext"); } void DrawLive(const int chartIndex, const datetime &time[]) { if(g_indicatorHidden) return; if(ArraySize(_swings) == 0 || _state.dir == 0 || _lastHtfIndex < 0 || _lastHtfIndex >= _htfCount) { DeleteLiveObjects(); return; } SwingPoint last = _swings[ArraySize(_swings) - 1]; double extPrice = _state.extPrice; int ei = _state.extIndex; if(ei < 0) ei = 0; if(ei >= _htfCount) ei = _htfCount - 1; datetime extTime = _htfTime[ei]; if(InpClosedBarsOnly && _lastHtfIndex < _htfCount) { double lh = _htfHigh[_lastHtfIndex]; double llo = _htfLow[_lastHtfIndex]; if(_state.dir == 1 && lh > extPrice) { extPrice = lh; extTime = _htfTime[_lastHtfIndex]; } else if(_state.dir == -1 && llo < extPrice) { extPrice = llo; extTime = _htfTime[_lastHtfIndex]; } } datetime nowTime = time[chartIndex]; EnsureTrend("GSW_live_h", last.time, last.price, extTime, last.price, InpLineColor); EnsureTrend("GSW_live_v", extTime, last.price, (datetime)(extTime + 1), extPrice, InpLineColor); EnsureTrend("GSW_live_ext", extTime, extPrice, nowTime, extPrice, InpLineColor); } void DrawMarkers(const int c, const datetime &time[], const double &high[], const double &low[], const int rates_total, const int sig) { if(InpMaxBarsToDraw > 0 && c < rates_total - InpMaxBarsToDraw) return; if(!InpShowArrows && !InpShowCrosses) return; string nUp = "GSW_arrUp_" + IntegerToString((long)time[c]); string nDn = "GSW_arrDn_" + IntegerToString((long)time[c]); string nX = "GSW_cross_" + IntegerToString((long)time[c]); int prev = SIG_UNDEC; if(c > 0) prev = EffectiveSignalAtHtf(HtfIndexFor(time[c - 1])); bool changed = (sig != prev); double range = high[c] - low[c]; double off = (range > 0 ? range * 0.5 : PipSize() * 5.0); if(InpShowArrows && changed && sig == SIG_UP) EnsureArrow(nUp, OBJ_ARROW_UP, time[c], low[c] - off, InpArrowUpColor, 0); else RemoveMarker(nUp); if(InpShowArrows && changed && sig == SIG_DOWN) EnsureArrow(nDn, OBJ_ARROW_DOWN, time[c], high[c] + off, InpArrowDnColor, 0); else RemoveMarker(nDn); if(InpShowCrosses && InpUndecidedFilter && changed && sig == SIG_UNDEC) EnsureCross(nX, time[c], high[c] + off, InpCrossColor); else RemoveMarker(nX); } void ApplyChartBar(const int c, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const int rates_total) { int h = HtfIndexFor(time[c]); if(h < 0 || h >= ArraySize(_signals)) { DirBuffer[c] = 0.0; SetCandleEmpty(c); return; } int sig = EffectiveSignalAtHtf(h); DirBuffer[c] = (double)sig; if(g_indicatorHidden) { SetCandleEmpty(c); return; } DrawMarkers(c, time, high, low, rates_total, sig); bool drawCandle = (InpColorCandles && (InpMaxBarsToDraw <= 0 || c >= rates_total - InpMaxBarsToDraw)); if(!drawCandle) { SetCandleEmpty(c); return; } CandleO[c] = open[c]; CandleH[c] = high[c]; CandleL[c] = low[c]; CandleC[c] = close[c]; CandleCol[c] = (sig == SIG_UP ? 0.0 : (sig == SIG_DOWN ? 1.0 : 2.0)); } //+------------------------------------------------------------------+ //| Reset / rebuild | //+------------------------------------------------------------------+ void RollbackSwings(const int count) { for(int k = ArraySize(_swings) - 1; k >= count; k--) { ObjectDelete(0, "GSW_h_" + IntegerToString(k)); ObjectDelete(0, "GSW_v_" + IntegerToString(k)); ObjectDelete(0, "GSW_d_" + IntegerToString(k)); } if(count < ArraySize(_swings)) ArrayResize(_swings, count); } void FullResetState() { RollbackSwings(0); DeleteLiveObjects(); ObjectsDeleteAll(0, "GSW_arrUp_", -1, -1); ObjectsDeleteAll(0, "GSW_arrDn_", -1, -1); ObjectsDeleteAll(0, "GSW_cross_", -1, -1); ZeroMemory(_state); ZeroMemory(_saved); ArrayResize(_swings, 0); ArrayResize(_signals, 0); ArrayResize(_carry, 0); _lastHtfIndex = -1; _processedTo = -1; _alertBaseline = false; _paintedInit = false; } void RebuildAll(const int rates_total, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[]) { FullResetState(); int from = 0; if(InpMaxBarsToDraw > 0 && rates_total > InpMaxBarsToDraw) from = rates_total - InpMaxBarsToDraw; _drawFromTime = time[from]; for(int c = 0; c < rates_total - 1; c++) { UpdateEngine(c, time); ApplyChartBar(c, time, open, high, low, close, rates_total); } } //+------------------------------------------------------------------+ //| Alerts | //+------------------------------------------------------------------+ void HandleAlerts(const int state) { if(g_indicatorHidden) return; if(!_alertBaseline) { _lastAlertState = state; _alertBaseline = true; return; } if(state == _lastAlertState) return; _lastAlertState = state; string msg; string snd; if(state == 1) { if(!InpAlertBuy) return; msg = "BUY - swing up (arrow)"; snd = "ok.wav"; } else if(state == -1) { if(!InpAlertSell) return; msg = "SELL - swing down (arrow)"; snd = "alert.wav"; } else { if(!InpUndecidedFilter) return; if(!InpAlertCross) return; msg = "CROSS - undecided (outside bar / waiting)"; snd = "timeout.wav"; } if(InpEnableAlerts) PlaySound(snd); if(InpEnableEmail) { string subject = "GannSwing Alert - " + _Symbol + " (" + TfText(_signalTF) + ")"; string body = msg + "\nSignal TF: " + TfText(_signalTF) + "\nTime: " + TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES); SendMail(subject, body); } } //+------------------------------------------------------------------+ //| Live processing | //+------------------------------------------------------------------+ void ProcessLive(const int index, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const int rates_total) { UpdateEngine(index, time); int hIdx = HtfIndexFor(time[index]); int liveSig = EffectiveSignalAtHtf(hIdx); if(hIdx < 0) DirBuffer[index] = 0.0; if(_lastHtfIndex >= 0 && _lastHtfIndex < _htfCount) { datetime htfOpen = _htfTime[_lastHtfIndex]; if(!_paintedInit || liveSig != _lastPaintedSig || htfOpen != _lastPaintedOpen) { int c = index; while(c > 0 && time[c - 1] >= htfOpen) c--; for(; c <= index; c++) ApplyChartBar(c, time, open, high, low, close, rates_total); _paintedInit = true; _lastPaintedSig = liveSig; _lastPaintedOpen = htfOpen; } else { ApplyChartBar(index, time, open, high, low, close, rates_total); } } DrawLive(index, time); HandleAlerts(liveSig); if(InpShowDashboard) { uint now = GetTickCount(); uint interval = (InpThrottleDashboard ? 200 : 1000); if(now - _lastDashTick >= interval) { _lastDashTick = now; UpdateDashboard(); } } } //+------------------------------------------------------------------+ //| Dashboard: mini swing engine | //+------------------------------------------------------------------+ void SetMiniDir(MiniState &st, const int dir) { st.dir = dir; if(dir != 0) st.lastDecided = dir; } void MiniUpdateExtremes(MiniState &st, const double h, const double l) { if(st.dir == 1) { if(h > st.extPrice) { st.extPrice = h; st.pendPrice = l; } else if(l < st.pendPrice) st.pendPrice = l; } else if(st.dir == -1) { if(l < st.extPrice) { st.extPrice = l; st.pendPrice = h; } else if(h > st.pendPrice) st.pendPrice = h; } } void MiniProcess(MiniState &st, const double h, const double l) { if(!st.init) { st.init = true; st.dir = 0; st.refHigh = h; st.refLow = l; st.extPrice = h; st.pendPrice = l; return; } bool hh = (h > st.refHigh); bool ll = (l < st.refLow); int t; if(hh && ll) t = BarOutside; else if(InpBarRule == Gann_HH_and_HL) t = (hh && l > st.refLow ? BarHigher : (ll && h < st.refHigh ? BarLower : BarInside)); else t = (hh ? BarHigher : (ll ? BarLower : BarInside)); if(t == BarInside) return; if(t == BarOutside) { st.turnCount = 0; st.refHigh = h; st.refLow = l; if(InpOutsideMode == UseNextBar) { st.outsidePending = true; st.outHigh = h; st.outLow = l; return; } if(st.dir != 0) MiniUpdateExtremes(st, h, l); return; } bool up = (t == BarHigher); if(st.outsidePending) { st.outsidePending = false; st.turnCount = 0; double oh = st.outHigh; double ol = st.outLow; if(st.dir == 0) { SetMiniDir(st, (up ? 1 : -1)); if(up) { st.extPrice = MathMax(h, oh); st.pendPrice = l; } else { st.extPrice = MathMin(l, ol); st.pendPrice = h; } } else if(st.dir == 1) { if(up) { st.extPrice = MathMax(st.extPrice, MathMax(h, oh)); st.pendPrice = l; } else { st.extPrice = MathMax(st.extPrice, oh); if(1 >= _swingCount) { SetMiniDir(st, -1); st.extPrice = MathMin(ol, l); st.pendPrice = h; } else st.turnCount = 1; } } else { if(!up) { st.extPrice = MathMin(st.extPrice, MathMin(l, ol)); st.pendPrice = h; } else { st.extPrice = MathMin(st.extPrice, ol); if(1 >= _swingCount) { SetMiniDir(st, 1); st.extPrice = MathMax(oh, h); st.pendPrice = l; } else st.turnCount = 1; } } st.refHigh = h; st.refLow = l; return; } if(st.dir == 0) { SetMiniDir(st, (up ? 1 : -1)); if(up) { st.extPrice = h; st.pendPrice = l; } else { st.extPrice = l; st.pendPrice = h; } st.turnCount = 0; st.refHigh = h; st.refLow = l; return; } MiniUpdateExtremes(st, h, l); if(up && st.dir == -1) { st.turnCount++; if(st.turnCount >= _swingCount) { SetMiniDir(st, 1); st.extPrice = st.pendPrice; st.pendPrice = l; st.turnCount = 0; } } else if(!up && st.dir == 1) { st.turnCount++; if(st.turnCount >= _swingCount) { SetMiniDir(st, -1); st.extPrice = st.pendPrice; st.pendPrice = h; st.turnCount = 0; } } else { st.turnCount = 0; } st.refHigh = h; st.refLow = l; } void TfDashSignal(const int i, int &dir, double &strength) { dir = -2; strength = 0.0; int total = Bars(_Symbol, _tfs[i]); if(total < DashWarmupBars) return; datetime first = iTime(_Symbol, _tfs[i], total - 1); if(first == 0) return; if(_dashFirstOpen[i] != first) { _dashFirstOpen[i] = first; ZeroMemory(_dashState[i]); _dashProcessed[i] = -1; } int lastClosed = total - 2; for(int k = _dashProcessed[i] + 1; k <= lastClosed; k++) { double h = iHigh(_Symbol, _tfs[i], total - 1 - k); double l = iLow(_Symbol, _tfs[i], total - 1 - k); if(h <= 0.0 && l <= 0.0) continue; MiniProcess(_dashState[i], h, l); } if(lastClosed > _dashProcessed[i]) _dashProcessed[i] = lastClosed; MiniState view = _dashState[i]; if(!InpClosedBarsOnly) { double lh = iHigh(_Symbol, _tfs[i], 0); double ll = iLow(_Symbol, _tfs[i], 0); if(lh > 0.0 || ll > 0.0) MiniProcess(view, lh, ll); } int sig = SIG_UNDEC; if(view.outsidePending) sig = SIG_UNDEC; else if(view.dir == 1) sig = SIG_UP; else if(view.dir == -1) sig = SIG_DOWN; if(sig == SIG_UNDEC && !InpUndecidedFilter && view.lastDecided != 0) sig = view.lastDecided; dir = sig; double close = iClose(_Symbol, _tfs[i], 0); if(close > 0.0 && view.dir != 0) strength = MathAbs(view.extPrice - view.pendPrice) / close * 100.0; } //+------------------------------------------------------------------+ //| Dashboard UI | //+------------------------------------------------------------------+ void RegisterDash(const string name, const int x, const int y, const int w, const int h) { int n = ArraySize(_dashObj); ArrayResize(_dashObj, n + 1, 128); ArrayResize(_dashOx, n + 1, 128); ArrayResize(_dashOy, n + 1, 128); ArrayResize(_dashOw, n + 1, 128); ArrayResize(_dashOh, n + 1, 128); _dashObj[n] = name; _dashOx[n] = x; _dashOy[n] = y; _dashOw[n] = w; _dashOh[n] = h; } void AddRect(const string name, const int x, const int y, const int w, const int h, const color clr) { if(ObjectFind(0, name) < 0) { if(!ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) return; ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT); } ObjectSetInteger(0, name, OBJPROP_XSIZE, w); ObjectSetInteger(0, name, OBJPROP_YSIZE, h); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clr); RegisterDash(name, x, y, -1, -1); } void AddLabel(const string name, const int x, const int y, const string text, const color clr, const int size, const bool bold, const ENUM_ANCHOR_POINT anchor) { if(ObjectFind(0, name) < 0) { if(!ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0)) return; ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_SELECTED, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size); ObjectSetString(0, name, OBJPROP_FONT, (bold ? DASH_FONT_BOLD : DASH_FONT)); } ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); RegisterDash(name, x, y, -1, -1); } void ApplyDashGeometry() { if(!_dashBuilt) return; int cw = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); int ch = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0); bool left = (InpCorner == TopLeft || InpCorner == BottomLeft); bool top = (InpCorner == TopLeft || InpCorner == TopRight); int px = (left ? InpDashX : IMax(0, cw - PanelWidth - InpDashX)); int py = (top ? InpDashY : IMax(0, ch - PanelHeight - InpDashY)); for(int i = 0; i < ArraySize(_dashObj); i++) { ObjectSetInteger(0, _dashObj[i], OBJPROP_XDISTANCE, px + _dashOx[i]); ObjectSetInteger(0, _dashObj[i], OBJPROP_YDISTANCE, py + _dashOy[i]); if(_dashOw[i] > 0) ObjectSetInteger(0, _dashObj[i], OBJPROP_XSIZE, _dashOw[i]); if(_dashOh[i] > 0) ObjectSetInteger(0, _dashObj[i], OBJPROP_YSIZE, _dashOh[i]); } _panelChartW = cw; _panelChartH = ch; } void EnsurePanelGeometry() { if(!_dashBuilt) return; int cw = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); int ch = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0); if(cw != _panelChartW || ch != _panelChartH) ApplyDashGeometry(); } void BuildDashboard() { if(!InpShowDashboard) return; ArrayResize(_dashObj, 0); ArrayResize(_dashOx, 0); ArrayResize(_dashOy, 0); ArrayResize(_dashOw, 0); ArrayResize(_dashOh, 0); _panelChartW = -1; _panelChartH = -1; ArrayResize(_dashState, TFC); ArrayResize(_dashProcessed, TFC); ArrayResize(_dashFirstOpen, TFC); ArrayResize(_loadAttempts, TFC); ArrayResize(_rowDotName, TFC); ArrayResize(_rowSignalName, TFC); ArrayResize(_rowPctName, TFC); ArrayResize(_rowFillName, TFC); for(int i = 0; i < TFC; i++) { ZeroMemory(_dashState[i]); _dashProcessed[i] = -1; _dashFirstOpen[i] = 0; _loadAttempts[i] = 0; _rowDotName[i] = "GSW_row_dot_" + IntegerToString(i); _rowSignalName[i] = "GSW_row_sig_" + IntegerToString(i); _rowPctName[i] = "GSW_row_pct_" + IntegerToString(i); _rowFillName[i] = "GSW_row_fill_" + IntegerToString(i); } // panel frame AddRect(DashPanelBorder, 0, 0, PanelWidth, PanelHeight, ClrBorder); AddRect(DashPanelBg, 1, 1, PanelWidth - 2, PanelHeight - 2, ClrBackground); // header AddRect(DashHeaderBg, 1, 1, PanelWidth - 2, HeaderH, ClrHeader); AddRect(DashAccent, 1, 1 + HeaderH, PanelWidth - 2, AccentH, ClrAccent); AddRect(DashLiveDot, 6, 11, 5, 5, ClrLiveDot); AddLabel(DashTitle, 18, 6, "A! GANN SWING", ClrTitle, 11, true, ANCHOR_LEFT_UPPER); AddLabel(DashSymbol, PanelWidth - 10, 8, _Symbol, ClrTitle, 10, false, ANCHOR_RIGHT_UPPER); // sub-header AddRect(DashSubBg, 1, 1 + HeaderH + AccentH, PanelWidth - 2, SubH, ClrSubHeader); AddRect(DashSubBorder, 1, 1 + HeaderH + AccentH + SubH, PanelWidth - 2, SubBorder, ClrBorder); int subY = 1 + HeaderH + AccentH + 3; AddLabel(DashSubTF, 10, subY, "TF", ClrTitle, 8, false, ANCHOR_LEFT_UPPER); AddLabel(DashSubSwing, 56, subY, "SWING", ClrTitle, 8, false, ANCHOR_LEFT_UPPER); AddLabel(DashSubRange, 114, subY, "RANGE %", ClrTitle, 8, false, ANCHOR_LEFT_UPPER); AddLabel(DashSubBar, 170, subY, "BAR", ClrTitle, 8, false, ANCHOR_LEFT_UPPER); // rows int rowsTop = 1 + HeaderH + AccentH + SubH + SubBorder; for(int i = 0; i < TFC; i++) { int y = rowsTop + i * RowHeight; color rowClr = ((i % 2 == 0) ? ClrRowEven : ClrRowOdd); AddRect("GSW_row_bg_" + IntegerToString(i), 1, y, PanelWidth - 2, RowHeight, rowClr); AddLabel("GSW_row_tf_" + IntegerToString(i), 10, y + 7, _tfNames[i], ClrTitle, 10, false, ANCHOR_LEFT_UPPER); AddRect(_rowDotName[i], 41, y + 8, 10, 10, ClrDim); AddLabel(_rowSignalName[i], 56, y + 7, "WARM", ClrWarm, 10, true, ANCHOR_LEFT_UPPER); AddLabel(_rowPctName[i], 114, y + 7, "", ClrDim, 10, false, ANCHOR_LEFT_UPPER); AddRect("GSW_row_track_" + IntegerToString(i), 170, y + 11, BarWidth, 5, ClrTrack); AddRect(_rowFillName[i], 170, y + 11, 1, 5, ClrTrack); } // footer int dividerY = rowsTop + TFC * RowHeight; AddRect(DashDivider, 1, dividerY, PanelWidth - 2, 1, ClrDivider); int footerTop = dividerY + 1; AddRect(DashFooterBg, 1, footerTop, PanelWidth - 2, PanelHeight - footerTop - 1, ClrFooter); AddLabel(DashOverallLabel, 10, footerTop + 6, "OVERALL BIAS", ClrTitle, 9, false, ANCHOR_LEFT_UPPER); AddLabel(DashVerdict, 10, footerTop + 18, "LOADING", ClrDim, 15, true, ANCHOR_LEFT_UPPER); AddLabel(DashAvg, PanelWidth - 10, footerTop + 18, "", ClrDim, 14, true, ANCHOR_RIGHT_UPPER); AddLabel(DashScore, 10, footerTop + 40, "", ClrDim, 9, false, ANCHOR_LEFT_UPPER); AddLabel(DashAvgLabel, PanelWidth - 10, footerTop + 40, "AVG RANGE", ClrTitle, 9, false, ANCHOR_RIGHT_UPPER); AddRect(DashOverallTrack, 10, footerTop + 54, OverallBarWidth, 5, ClrTrack); AddRect(DashOverallFill, 10, footerTop + 54, 1, 5, ClrTrack); AddRect(DashDivider2, 1, footerTop + 66, PanelWidth - 2, 1, ClrDivider); AddLabel(DashStyle, 10, footerTop + 70, "...", ClrDim, 10, true, ANCHOR_LEFT_UPPER); AddLabel(DashStyleDesc, 100, footerTop + 72, "", ClrDim, 9, false, ANCHOR_LEFT_UPPER); _dashBuilt = true; ApplyDashGeometry(); } void SetDashText(const string name, const string text, const color clr) { if(ObjectFind(0, name) < 0) return; ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); } void SetRectColor(const string name, const color clr) { if(ObjectFind(0, name) < 0) return; ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clr); } void SetRow(const int i, const string sig, const color textClr, const color barClr, const color pctClr, const double s) { SetDashText(_rowSignalName[i], sig, textClr); SetRectColor(_rowDotName[i], barClr); SetDashText(_rowPctName[i], DoubleToString(s, 2) + "%", pctClr); double cap = (InpStrMaxPct > 0.0 ? InpStrMaxPct : 3.0); int fill = (int)MathMax(1.0, MathMin(s / cap, 1.0) * (double)BarWidth); ObjectSetInteger(0, _rowFillName[i], OBJPROP_XSIZE, fill); SetRectColor(_rowFillName[i], barClr); } void SetWarmRow(const int i) { int have = Bars(_Symbol, _tfs[i]); if(have > DashWarmupBars) have = DashWarmupBars; SetDashText(_rowSignalName[i], "WARM", ClrWarm); SetRectColor(_rowDotName[i], ClrDim); SetDashText(_rowPctName[i], IntegerToString(have) + "/" + IntegerToString(DashWarmupBars), ClrWarm); int fill = (int)MathMax(1.0, MathMin((double)have / (double)DashWarmupBars, 1.0) * (double)BarWidth); ObjectSetInteger(0, _rowFillName[i], OBJPROP_XSIZE, fill); SetRectColor(_rowFillName[i], ClrWarm); } void UpdateDashboard() { if(g_indicatorHidden || !_dashBuilt) return; EnsurePanelGeometry(); int bull = 0; int bear = 0; int valid = 0; double sumS = 0.0; for(int i = 0; i < TFC; i++) { int dir; double s; TfDashSignal(i, dir, s); if(dir == 1) { SetRow(i, "▲ UP", ClrBullText, ClrBullBar, ClrBullPct, s); bull++; valid++; sumS += s; } else if(dir == -1) { SetRow(i, "▼ DOWN", ClrBearText, ClrBearBar, ClrBearPct, s); bear++; valid++; sumS += s; } else if(dir == 0) { SetRow(i, "✕ UNDEC", ClrUndecText, ClrUndecBar, ClrUndecText, s); } else { SetWarmRow(i); } } string vT; color vC; if(valid == 0) { vT = "LOADING"; vC = ClrDim; } else if(bull > bear) { vT = "▲ BULLISH"; vC = ClrBullText; } else if(bear > bull) { vT = "▼ BEARISH"; vC = ClrBearText; } else { vT = "NEUTRAL"; vC = ClrNeutral; } SetDashText(DashVerdict, vT, vC); int win = IMax(bull, bear); SetDashText(DashScore, IntegerToString(win) + " / " + IntegerToString(valid) + " TF", ClrDim); double avg = (valid > 0 ? sumS / (double)valid : 0.0); SetDashText(DashAvg, DoubleToString(avg, 2) + "%", (bull >= bear ? ClrBullPct : ClrBearPct)); double cap = (InpStrMaxPct > 0.0 ? InpStrMaxPct : 3.0); int oFill = (int)MathMax(1.0, MathMin(avg / cap, 1.0) * (double)OverallBarWidth); ObjectSetInteger(0, DashOverallFill, OBJPROP_XSIZE, oFill); SetRectColor(DashOverallFill, (bull >= bear ? ClrBullBar : ClrBearBar)); string tsLabel; string tsDesc; color tsColor; if(win >= InpThreshSwing) { tsLabel = "SWING"; tsColor = ClrSwing; tsDesc = "Strong trend - ride it"; } else if(win >= InpThreshIntraday) { tsLabel = "INTRADAY"; tsColor = ClrIntraday; tsDesc = "Directional - manage risk"; } else if(win >= InpThreshScalp) { tsLabel = "SCALP"; tsColor = ClrScalp; tsDesc = "Mixed - quick in/out"; } else { tsLabel = "SIDEWAY"; tsColor = ClrSideway; tsDesc = "Wait or range trade"; } string arrow = ""; if(tsLabel == "SWING" || tsLabel == "INTRADAY") arrow = (bear > bull ? "▼ " : "▲ "); SetDashText(DashStyle, arrow + tsLabel, tsColor); SetDashText(DashStyleDesc, tsDesc, ClrDim); } //+------------------------------------------------------------------+ //| Toggle button (ported from MarketProfile Canvas v1.01 BT) | //+------------------------------------------------------------------+ string GetToggleButtonText(const bool isEnabled) { string t = btn_text; if(isEnabled) { if(StringFind(t, "OFF") >= 0) StringReplace(t, "OFF", "ON"); else if(StringFind(t, "Off") >= 0) StringReplace(t, "Off", "On"); else if(StringFind(t, "off") >= 0) StringReplace(t, "off", "on"); } else { if(StringFind(t, "ON") >= 0) StringReplace(t, "ON", "OFF"); else if(StringFind(t, "On") >= 0) StringReplace(t, "On", "Off"); else if(StringFind(t, "on") >= 0) StringReplace(t, "on", "off"); } return t; } void CreateToggleButton() { if(ObjectFind(0, g_buttonId) < 0) { int y_pos = button_y; int obj_total = ObjectsTotal(0, -1, OBJ_BUTTON); bool collision = true; while(collision) { collision = false; for(int i = 0; i < obj_total; i++) { string name = ObjectName(0, i, -1, OBJ_BUTTON); if(name == g_buttonId) continue; if((ENUM_BASE_CORNER)ObjectGetInteger(0, name, OBJPROP_CORNER) != btn_corner) continue; if(ObjectGetInteger(0, name, OBJPROP_XDISTANCE) == button_x && ObjectGetInteger(0, name, OBJPROP_YDISTANCE) == y_pos) { y_pos += btn_Height + 4; collision = true; break; } } } ObjectCreate(0, g_buttonId, OBJ_BUTTON, btn_Subwindow, 0, 0); ObjectSetInteger(0, g_buttonId, OBJPROP_CORNER, btn_corner); ObjectSetInteger(0, g_buttonId, OBJPROP_XDISTANCE, button_x); ObjectSetInteger(0, g_buttonId, OBJPROP_YDISTANCE, y_pos); ObjectSetInteger(0, g_buttonId, OBJPROP_XSIZE, btn_Width); ObjectSetInteger(0, g_buttonId, OBJPROP_YSIZE, btn_Height); ObjectSetString (0, g_buttonId, OBJPROP_FONT, btn_Font); ObjectSetInteger(0, g_buttonId, OBJPROP_FONTSIZE, btn_FontSize); ObjectSetInteger(0, g_buttonId, OBJPROP_BGCOLOR, btn_background_color); ObjectSetInteger(0, g_buttonId, OBJPROP_BORDER_COLOR, btn_border_color); ObjectSetInteger(0, g_buttonId, OBJPROP_BORDER_TYPE, BORDER_RAISED); ObjectSetInteger(0, g_buttonId, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, g_buttonId, OBJPROP_HIDDEN, true); ObjectSetInteger(0, g_buttonId, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS); ObjectSetInteger(0, g_buttonId, OBJPROP_ZORDER, 100); } UpdateToggleButton(); } void UpdateToggleButton() { if(ObjectFind(0, g_buttonId) < 0) return; g_indicatorEnabled = !g_indicatorHidden; string labelText = GetToggleButtonText(g_indicatorEnabled); color textColor = (g_indicatorEnabled ? btn_text_ON_color : btn_text_OFF_color); ObjectSetString (0, g_buttonId, OBJPROP_TEXT, labelText); ObjectSetString (0, g_buttonId, OBJPROP_FONT, btn_Font); ObjectSetInteger(0, g_buttonId, OBJPROP_FONTSIZE, btn_FontSize); ObjectSetInteger(0, g_buttonId, OBJPROP_COLOR, textColor); ObjectSetInteger(0, g_buttonId, OBJPROP_BGCOLOR, btn_background_color); ObjectSetInteger(0, g_buttonId, OBJPROP_BORDER_COLOR, btn_border_color); ObjectSetInteger(0, g_buttonId, OBJPROP_CORNER, btn_corner); ObjectSetInteger(0, g_buttonId, OBJPROP_XDISTANCE, button_x); ObjectSetInteger(0, g_buttonId, OBJPROP_XSIZE, btn_Width); ObjectSetInteger(0, g_buttonId, OBJPROP_YSIZE, btn_Height); ObjectSetInteger(0, g_buttonId, OBJPROP_STATE, g_indicatorEnabled); ObjectSetInteger(0, g_buttonId, OBJPROP_ZORDER, 100); ChartRedraw(0); } void BringButtonsToFront() { int total = ObjectsTotal(0, -1, OBJ_BUTTON); for(int i = 0; i < total; i++) { string name = ObjectName(0, i, -1, OBJ_BUTTON); if(name != "" && ObjectGetInteger(0, name, OBJPROP_ZORDER) < 100) ObjectSetInteger(0, name, OBJPROP_ZORDER, 100); } } //--- full visual rebuild from chart history (works when market is closed) void ForceFullRedraw() { int total = Bars(_Symbol, _Period); if(total <= 0) return; datetime t[]; double o[]; double h[]; double l[]; double c[]; int nT = CopyTime(_Symbol, _Period, 0, total, t); int nO = CopyOpen(_Symbol, _Period, 0, total, o); int nH = CopyHigh(_Symbol, _Period, 0, total, h); int nL = CopyLow(_Symbol, _Period, 0, total, l); int nC = CopyClose(_Symbol, _Period, 0, total, c); if(nT <= 0 || nO <= 0 || nH <= 0 || nL <= 0 || nC <= 0) return; int n = IMin(nT, IMin(nO, IMin(nH, IMin(nL, nC)))); if(n <= 0 || ArraySize(t) < n || ArraySize(o) < n || ArraySize(h) < n || ArraySize(l) < n || ArraySize(c) < n) return; LoadHtfCache(); RebuildAll(n, t, o, h, l, c); ProcessLive(n - 1, t, o, h, l, c, n); _needsRebuild = false; _lastRatesTotal = n; _chartOldest = t[0]; _htfOldest = (_htfCount > 0 ? _htfTime[0] : 0); if(InpShowDashboard) { if(!_dashBuilt) BuildDashboard(); UpdateDashboard(); } } void ToggleIndicatorVisibility() { g_indicatorHidden = !g_indicatorHidden; g_indicatorEnabled = !g_indicatorHidden; if(g_gvHidden != "") GlobalVariableSet(g_gvHidden, g_indicatorHidden ? 1.0 : 0.0); // first live signal after toggling becomes the alert baseline _alertBaseline = false; if(g_indicatorHidden) { ObjectsDeleteAll(0, "GSW_", -1, -1); // candle plot is a native buffer plot: deleting objects cannot hide it ArrayInitialize(CandleO, EMPTY_VALUE); ArrayInitialize(CandleH, EMPTY_VALUE); ArrayInitialize(CandleL, EMPTY_VALUE); ArrayInitialize(CandleC, EMPTY_VALUE); ArrayInitialize(CandleCol, 0.0); _dashBuilt = false; _paintedInit = false; } else { ForceFullRedraw(); EventSetTimer(1); } UpdateToggleButton(); BringButtonsToFront(); ChartRedraw(0); } void InitToggleButtonState() { g_buttonId = "__GSW_BT__" + UniqueButtonID; g_toggleButtonName = g_buttonId; g_gvHidden = "GannSwing_BT_" + UniqueButtonID + "_" + IntegerToString((long)ChartID()) + "_Hidden"; // remove stale duplicate buttons from previous loads int total = ObjectsTotal(0, -1, OBJ_BUTTON); for(int i = total - 1; i >= 0; i--) { string bName = ObjectName(0, i, -1, OBJ_BUTTON); if(bName == "" || bName == g_buttonId) continue; if(StringFind(bName, "__GSW_BT__") == 0 || StringFind(bName, UniqueButtonID) >= 0) ObjectDelete(0, bName); } bool isParamChange = (UninitializeReason() == REASON_PARAMETERS); if(isParamChange && g_gvHidden != "") GlobalVariableDel(g_gvHidden); bool hasSavedState = false; bool savedHidden = false; if(!isParamChange) { if(ObjectFind(0, g_buttonId) >= 0) { string btnTxt = ObjectGetString(0, g_buttonId, OBJPROP_TEXT); long btnSt = ObjectGetInteger(0, g_buttonId, OBJPROP_STATE); if(StringFind(btnTxt, "OFF") >= 0 || StringFind(btnTxt, "Off") >= 0 || btnSt == 0) savedHidden = true; else if(StringFind(btnTxt, "ON") >= 0 || StringFind(btnTxt, "On") >= 0 || btnSt != 0) savedHidden = false; hasSavedState = true; } if(!hasSavedState && GlobalVariableCheck(g_gvHidden)) { savedHidden = (GlobalVariableGet(g_gvHidden) > 0.5); hasSavedState = true; } } g_indicatorHidden = (hasSavedState ? savedHidden : false); g_indicatorEnabled = !g_indicatorHidden; if(g_gvHidden != "") GlobalVariableSet(g_gvHidden, g_indicatorHidden ? 1.0 : 0.0); CreateToggleButton(); BringButtonsToFront(); } //+------------------------------------------------------------------+ //| Terminal events | //+------------------------------------------------------------------+ int OnInit() { InitToggleButtonState(); if(InpSignalTF == PERIOD_CURRENT) _signalTF = (ENUM_TIMEFRAMES)_Period; else _signalTF = InpSignalTF; _swingCount = InpSwingCount; if(_swingCount < 1) _swingCount = 1; if(_swingCount > 5) _swingCount = 5; _lineWidth = InpLineThickness; if(_lineWidth < 1) _lineWidth = 1; if(_lineWidth > 5) _lineWidth = 5; _dotWidth = InpDotWidth; if(_dotWidth < 1) _dotWidth = 1; if(_dotWidth > 5) _dotWidth = 5; SetIndexBuffer(0, DirBuffer, INDICATOR_DATA); SetIndexBuffer(1, CandleO, INDICATOR_DATA); SetIndexBuffer(2, CandleH, INDICATOR_DATA); SetIndexBuffer(3, CandleL, INDICATOR_DATA); SetIndexBuffer(4, CandleC, INDICATOR_DATA); SetIndexBuffer(5, CandleCol, INDICATOR_COLOR_INDEX); PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE); PlotIndexSetInteger(1, PLOT_DRAW_TYPE, (InpColorCandles ? DRAW_COLOR_CANDLES : DRAW_NONE)); PlotIndexSetInteger(1, PLOT_COLOR_INDEXES, 3); PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, InpUpCandleColor); PlotIndexSetInteger(1, PLOT_LINE_COLOR, 1, InpDownCandleColor); PlotIndexSetInteger(1, PLOT_LINE_COLOR, 2, InpUndecidedCandleColor); PlotIndexSetInteger(1, PLOT_LINE_WIDTH, MathMin(MathMax(InpCandleWidth, 1), 5)); PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); IndicatorSetString(INDICATOR_SHORTNAME, "Gann Swing Chart v7"); _htfWarmDone = (_signalTF == _Period); _needsRebuild = true; if(!g_indicatorHidden) { BuildDashboard(); if(InpShowDashboard) UpdateDashboard(); } if(InpOutsideMode == UseNextBar && _swingCount > 1) Print("GannSwingChart v7 (MT5): 'two movements in one bar' (reconstructed) applies only with Swing Count = 1."); EventSetTimer(1); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS) { ObjectDelete(0, g_buttonId); ObjectDelete(0, g_toggleButtonName); if(g_gvHidden != "") GlobalVariableDel(g_gvHidden); } ObjectsDeleteAll(0, "GSW_", -1, -1); ChartRedraw(); } void OnTimer() { bool allDone = true; if(!_htfWarmDone) { LoadHtfCache(); int chartTotal = Bars(_Symbol, _Period); datetime chartFirst = (chartTotal > 0 ? iTime(_Symbol, _Period, chartTotal - 1) : 0); bool covered = (_htfCount > 0 && chartFirst > 0 && _htfTime[0] <= chartFirst); if(covered || _warmupAttempts >= MaxWarmupAttempts) { _htfWarmDone = true; _needsRebuild = true; } else { _warmupAttempts++; RequestMoreHistory(_signalTF); allDone = false; } } if(InpShowDashboard) { bool dashDone = true; int loadIdx = -1; for(int i = 0; i < TFC; i++) { int total = Bars(_Symbol, _tfs[i]); if(total >= DashWarmupBars) continue; if(_loadAttempts[i] >= MaxDashLoadAttempts) continue; dashDone = false; if(loadIdx < 0) loadIdx = i; } if(loadIdx >= 0) { _loadAttempts[loadIdx]++; RequestMoreHistory(_tfs[loadIdx]); } UpdateDashboard(); if(!dashDone) allDone = false; } if(allDone) EventKillTimer(); ChartRedraw(); } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && (sparam == g_buttonId || sparam == g_toggleButtonName)) { ToggleIndicatorVisibility(); return; } if(id == CHARTEVENT_CHART_CHANGE) { BringButtonsToFront(); if(_dashBuilt) ApplyDashGeometry(); ChartRedraw(); } } int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total <= 0) return 0; datetime prevHtfOldest = _htfOldest; LoadHtfCache(); bool need = (_needsRebuild || prev_calculated == 0 || rates_total < _lastRatesTotal || (_chartOldest > 0 && time[0] != _chartOldest) || (_htfCount > 0 && prevHtfOldest > 0 && _htfTime[0] != prevHtfOldest) || (_htfCount > 0 && prevHtfOldest == 0 && _lastRatesTotal > 0)); if(need) { _htfOldest = (_htfCount > 0 ? _htfTime[0] : 0); _chartOldest = time[0]; RebuildAll(rates_total, time, open, high, low, close); ProcessLive(rates_total - 1, time, open, high, low, close, rates_total); _needsRebuild = false; _lastRatesTotal = rates_total; ChartRedraw(); return rates_total; } int from = 0; if(InpMaxBarsToDraw > 0 && rates_total > InpMaxBarsToDraw) from = rates_total - InpMaxBarsToDraw; _drawFromTime = time[from]; int start = prev_calculated - 1; if(start < 0) start = 0; if(start > rates_total - 1) start = rates_total - 1; for(int c = start; c < rates_total - 1; c++) { UpdateEngine(c, time); ApplyChartBar(c, time, open, high, low, close, rates_total); } ProcessLive(rates_total - 1, time, open, high, low, close, rates_total); _lastRatesTotal = rates_total; ChartRedraw(); return rates_total; } //+------------------------------------------------------------------+