Stats on Amplitude, Spread and Clock

Author: 2021, Mateus Matucuma Teixeira
Price Data Components
Series array that contains the highest prices of each barSeries array that contains the lowest prices of each barSeries array that contains close prices for each bar
0 Views
0 Downloads
0 Favorites
Stats on Amplitude, Spread and Clock
/*
Copyright (C) 2021 Mateus Matucuma Teixeira

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef SASC_H
#define SASC_H
//+------------------------------------------------------------------+
//|                         Stats on Amplitude, Spread and Clock.mq5 |
//|                     Copyright (C) 2021, Mateus Matucuma Teixeira |
//|                                            mateusmtoss@gmail.com |
//| GNU General Public License version 2 - GPL-2.0                   |
//| https://opensource.org/licenses/gpl-2.0.php                      |
//+------------------------------------------------------------------+
// https://github.com/BRMateus2/Simple-MQL5-Amplitude-Spread-and-Clock-Labels/
//---- Main Properties
#property copyright "2021, Mateus Matucuma Teixeira"
#property link "https://github.com/BRMateus2/Simple-MQL5-Amplitude-Spread-and-Clock-Labels/"
#property description "This simple indicator is just a statistical label showing Last and Current Candle Amplitude (MinMax), Last and Current Day Amplitude, Current Tick Amplitude and Time Remaining for next Candle.\n"
#property description "It also shows Server Time (Market Watch) and Local PC Time so you can focus more on the graph and adapt to market hours.\n"
#property description "You can get the source code at \n\thttps://github.com/BRMateus2/Simple-MQL5-Amplitude-Spread-and-Clock-Labels/"
#property version "1.07"
#property strict
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0
//---- Imports
//---- Include Libraries and Modules
//#include <MT-Utilities.mqh>
//---- Definitions
#ifndef ErrorPrint
#define ErrorPrint(Dp_error) Print("ERROR: " + Dp_error + " at \"" + __FUNCTION__ + ":" + IntegerToString(__LINE__) + "\", last internal error: " + IntegerToString(GetLastError()) + " (" + __FILE__ + ")"); ResetLastError(); DebugBreak(); // It should be noted that the GetLastError() function doesn't zero the _LastError variable. Usually the ResetLastError() function is called before calling a function, after which an error appearance is checked.
#endif
//#define INPUT const
#ifndef INPUT
#define INPUT input
#endif
//---- Input Parameters
//---- "Label Settings"
input group "Label Settings"
const string iName = "StatsAndClock";
INPUT color lColor = clrYellow; // Text Color
INPUT ENUM_BASE_CORNER lCorner = CORNER_RIGHT_UPPER; // Text Corner
INPUT long lFontSize = 12; // Font Size
INPUT string lFontName = "DejaVu Sans Mono"; // Font Name (system naming)
INPUT long oDistX = 0; // X Distance or Horizontal Position Offset
INPUT long oDistY = 0; // Y Distance or Vertical Position Offset
INPUT long oSpacingY = 3; // Vertical gap between objects
//---- "Statistics"
input group "Statistics"
INPUT bool oStatsShow = true; // Show Statistics line?
enum SFormat { // SFormat
    kSFormatFirst, // Format Ampl(xA/xB)/(yA/yB) Spr(z) mm:ss - Amplitude/Spread
    kSFormatSecond // Format Chg(dA/dB) W([m,M]) Spr(z) mm:ss - Chg/WeekRange/Spread
};
INPUT int weekRange = 52; // How many weeks should the range contain? Normally 52 weeks
INPUT SFormat sFormat = kSFormatSecond; // Amplitude and Spread Stats Format
INPUT bool oStatsShowMktClosed = true; // If there is no new ticks for 5m, show "Closed"/"Disconnected"?
int oStatsShowMktClosedTimer = 60; // Inactivity timer, which if there are no new quotes, will show as "Mkt Closed"
//---- "Clock"
input group "Clock"
enum DateFormat {
    kTimeSeconds, // Format hh:mm:ss
    kTimeMinutes, // Format hh:mm
    kTimeDateSeconds, // Format YYYY.MM.DD hh:mm:ss
    kTimeDateMinutes, // Format YYYY.MM.DD hh:mm
};
INPUT DateFormat dFormat = kTimeDateSeconds; // Date Format
INPUT bool dShowOffset = true; // Show UTC Timezone Offset?
INPUT bool dShowSameLine = true; // Show both dates on the same line?
//---- "Server Clock"
input group "Server Clock"
INPUT bool dUseServer = true; // Use Server Date instead of Local/UTC Date?
INPUT int dServerOffset = 0; // Offset in seconds
INPUT bool oCSLatencyAppend = false; // DEBUG: append latency of the last 20 calls to Server Label
INPUT bool oCSLatencyHP = false; // DEBUG: use 1ms OnTimer() instead of power-saving 1000ms
//---- "Local Clock"
input group "Local Clock"
INPUT bool dShowLocal = true; // Show Local Time Label?
INPUT bool dShowLocalAsUTC = false; // Use UTC+0 / GMT Time instead of Local Timezone?
INPUT int dLocalOffset = 0; // Offset in seconds
//---- "Alerts"
//input group "TODO Alerts"
//INPUT bool enableAlertMkt = true; // Enable Market Open and Market Close Alerts
//bool enableAlertMktOpen = false; // Auxiliary
//---- Objects
const string oStats = "AmplitudeAndSpread"; // Object Stats, used for naming
const string oCS = "ClockServer"; // Object Clock Server, used for naming
const string oCL = "ClockLocal"; // Object Clock Local, used for naming
//---- OnTimer() statsUpdate() optimization, calls once per second, if for some reason the Stats object was missing data
datetime last = 0;
//---- Special oCSLatencyAppend, not guaranteed to average correctly if GetMicrosecondCount() returns 0
static ulong getMicrosecondCountBuf[20] = {};
static ulong getMicrosecondCountLast = 0;
static uint getMicrosecondCountI = 0;
static uint getMicrosecondCountJ = 0;
static ulong getMicrosecondCountMean = 0;
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
// Constructor or initialization function
// https://www.mql5.com/en/docs/basis/function/events
// https://www.mql5.com/en/articles/100
//+------------------------------------------------------------------+
int OnInit()
{
    last = TimeGMT();
    getMicrosecondCountLast = GetMicrosecondCount();
    // Indicator Subwindow Short Name
    IndicatorSetString(INDICATOR_SHORTNAME, iName);
    // Treat Objects
    ObjectDelete(ChartID(), oStats);
    ObjectDelete(ChartID(), oCS);
    ObjectDelete(ChartID(), oCL);
    if(oStatsShow) {
        if(!ObjectCreate(ChartID(), oStats, OBJ_LABEL, ChartWindowFind(ChartID(), iName), 0, 0.0)) {
            ErrorPrint("Object creation with ChartID() " + IntegerToString(ChartID()) + " at window " + IntegerToString(ChartWindowFind(ChartID(), iName)));
        }
        ObjectSetInteger(ChartID(), oStats, OBJPROP_CORNER, lCorner);
        ObjectSetInteger(ChartID(), oStats, OBJPROP_XDISTANCE, oDistX);
        ObjectSetInteger(ChartID(), oStats, OBJPROP_YDISTANCE, oDistY);
        ObjectSetInteger(ChartID(), oStats, OBJPROP_FONTSIZE, lFontSize);
        ObjectSetInteger(ChartID(), oStats, OBJPROP_COLOR, lColor);
        ObjectSetString(ChartID(), oStats, OBJPROP_FONT, lFontName);
        ObjectSetString(ChartID(), oStats, OBJPROP_TEXT, oStats);
    }
    ObjectCreate(ChartID(), oCS, OBJ_LABEL, ChartWindowFind(ChartID(), iName), 0, 0.0);
    ObjectSetInteger(ChartID(), oCS, OBJPROP_CORNER, lCorner);
    ObjectSetInteger(ChartID(), oCS, OBJPROP_XDISTANCE, oDistX);
    ObjectSetInteger(ChartID(), oCS, OBJPROP_YDISTANCE,  (
                         (oStatsShow ? ((lFontSize + oSpacingY) + oDistY) : oDistY)
                     ));
    ObjectSetInteger(ChartID(), oCS, OBJPROP_FONTSIZE, lFontSize);
    ObjectSetInteger(ChartID(), oCS, OBJPROP_COLOR, lColor);
    ObjectSetString(ChartID(), oCS, OBJPROP_FONT, lFontName);
    ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, oCS);
    if(dShowLocal && !dShowSameLine) {
        ObjectCreate(ChartID(), oCL, OBJ_LABEL, ChartWindowFind(ChartID(), iName), 0, 0.0);
        ObjectSetInteger(ChartID(), oCL, OBJPROP_CORNER, lCorner);
        ObjectSetInteger(ChartID(), oCL, OBJPROP_XDISTANCE, oDistX);
        ObjectSetInteger(ChartID(), oCL, OBJPROP_YDISTANCE, (
                             (oStatsShow ? ((2 * (lFontSize + oSpacingY)) + oDistY) : ((lFontSize + oSpacingY) + oDistY))
                         ));
        ObjectSetInteger(ChartID(), oCL, OBJPROP_FONTSIZE, lFontSize);
        ObjectSetInteger(ChartID(), oCL, OBJPROP_COLOR, lColor);
        ObjectSetString(ChartID(), oCL, OBJPROP_FONT, lFontName);
        ObjectSetString(ChartID(), oCL, OBJPROP_TEXT, oCL);
    }
    ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER;
    switch (lCorner) {
    case CORNER_LEFT_UPPER:
        anchor = ANCHOR_LEFT_UPPER;
        break;
    case CORNER_RIGHT_UPPER:
        anchor = ANCHOR_RIGHT_UPPER;
        break;
    case CORNER_LEFT_LOWER:
        anchor = ANCHOR_LEFT_LOWER;
        break;
    case CORNER_RIGHT_LOWER:
        anchor = ANCHOR_RIGHT_LOWER;
        break;
    }
    if(oStatsShow) {
        ObjectSetInteger(ChartID(), oStats, OBJPROP_ANCHOR, anchor);
    }
    ObjectSetInteger(ChartID(), oCS, OBJPROP_ANCHOR, anchor);
    if(dShowLocal && !dShowSameLine) {
        ObjectSetInteger(ChartID(), oCL, OBJPROP_ANCHOR, anchor);
    }
    if(oCSLatencyHP) {
        if(!EventSetMillisecondTimer(1)) {
            ErrorPrint("!EventSetMillisecondTimer(1) failure at subscribing to event timer"); // Create Timer Event in seconds (use EventSetMillisecondTimer for higher precision or HFT/High Frequency Trading)
            return INIT_FAILED;
        }
    } else if(!EventSetTimer(1)) {
        ErrorPrint("!EventSetTimer(1) failure at subscribing to event timer");
        return INIT_FAILED;
    }
    OnTimer(); // Initialize Clock Values
    return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
// Destructor or Deinitialization function
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    ObjectDelete(ChartID(), oStats);
    ObjectDelete(ChartID(), oCS);
    ObjectDelete(ChartID(), oCL);
    EventKillTimer();
    return;
}
//+------------------------------------------------------------------+
// Timer function
//+------------------------------------------------------------------+
void OnTimer()
{
    // Issue: Sometimes IsStopped() is set, but the platform still calls for the OnTimer():
    //  ERROR: ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS, ChartWindowFind(ChartID(), iName), chartSizeXTemp) at "OnTimer", last internal error: 4022 (Mini Charts.mq5)
    //  This check attempts to fix that issue
    if(IsStopped()) {
        return;
    }
    // This is a mess, becuz there are more than 7 booleans and variations of formatting - but it is highly compiler friendly anyway (if the compiler knows about conditional graph optimization)
    if(!dShowLocal && !dShowOffset) {
        ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)));
    } else if(!dShowLocal && dShowOffset) {
        ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)));
    } else if(dShowLocal && dShowSameLine && (dFormat == kTimeSeconds || dFormat == kTimeDateSeconds)) {
        if(!dShowLocalAsUTC && dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeToString((TimeLocal() + dLocalOffset), TIME_SECONDS));
        } else if(dShowLocalAsUTC && dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeToString((TimeGMT() + dLocalOffset), TIME_SECONDS));
        } else if(!dShowLocalAsUTC && !dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_SECONDS) + " " + TimeToString((TimeLocal() + dLocalOffset), EnumToInt(dFormat)));
        } else if(dShowLocalAsUTC && !dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_SECONDS) + " " + TimeToString((TimeGMT() + dLocalOffset), EnumToInt(dFormat)));
        } else if(!dShowLocalAsUTC && dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeLocal() + dLocalOffset), TIME_SECONDS) + " " + TimeGMTOffset((TimeLocal() + dLocalOffset)));
        } else if(dShowLocalAsUTC && dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeGMT() + dLocalOffset), TIME_SECONDS) + " " + TimeGMTOffset((TimeGMT() + dLocalOffset)));
        } else if(!dShowLocalAsUTC && !dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_SECONDS) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeLocal() + dLocalOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeLocal() + dLocalOffset)));
        } else if(dShowLocalAsUTC && !dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_SECONDS) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeGMT() + dLocalOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeGMT() + dLocalOffset)));
        } else {
            ErrorPrint("untreated condition with dShowLocal: \"" + (string) dShowLocal + "\" " + "dShowOffset: \"" + (string) dShowOffset + "\" " + "dShowSameLine: \"" + (string) dShowSameLine + "\" " + "dFormat: \"" + EnumToString(dFormat) + "\" " + "dShowLocalAsUTC: \"" + (string) dShowLocalAsUTC + "\" " + "dUseServer: \"" + (string) dUseServer + "\" " + "\"");
        }
    } else if(dShowLocal && dShowSameLine && (dFormat == kTimeMinutes || dFormat == kTimeDateMinutes)) {
        if(!dShowLocalAsUTC && dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeToString((TimeLocal() + dLocalOffset), TIME_MINUTES));
        } else if(dShowLocalAsUTC && dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeToString((TimeGMT() + dLocalOffset), TIME_MINUTES));
        } else if(!dShowLocalAsUTC && !dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_MINUTES) + " " + TimeToString((TimeLocal() + dLocalOffset), EnumToInt(dFormat)));
        } else if(dShowLocalAsUTC && !dUseServer && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_MINUTES) + " " + TimeToString((TimeGMT() + dLocalOffset), EnumToInt(dFormat)));
        } else if(!dShowLocalAsUTC && dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeLocal() + dLocalOffset), TIME_MINUTES) + " " + TimeGMTOffset((TimeLocal() + dLocalOffset)));
        } else if(dShowLocalAsUTC && dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeGMT() + dLocalOffset), TIME_MINUTES) + " " + TimeGMTOffset((TimeGMT() + dLocalOffset)));
        } else if(!dShowLocalAsUTC && !dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_MINUTES) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeLocal() + dLocalOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeLocal() + dLocalOffset)));
        } else if(dShowLocalAsUTC && !dUseServer && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), TIME_MINUTES) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)) + " " + TimeToString((TimeGMT() + dLocalOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeGMT() + dLocalOffset)));
        } else {
            ErrorPrint("untreated condition with dShowLocal: \"" + (string) dShowLocal + "\" " + "dShowOffset: \"" + (string) dShowOffset + "\" " + "dShowSameLine: \"" + (string) dShowSameLine + "\" " + "dFormat: \"" + EnumToString(dFormat) + "\" " + "dShowLocalAsUTC: \"" + (string) dShowLocalAsUTC + "\" " + "dUseServer: \"" + (string) dUseServer + "\" " + "\"");
        }
    } else if(dShowLocal && !dShowSameLine) {
        if(!dShowLocalAsUTC && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)));
            ObjectSetString(ChartID(), oCL, OBJPROP_TEXT, TimeToString((TimeLocal() + dLocalOffset), EnumToInt(dFormat)));
        } else if(!dShowLocalAsUTC && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)));
            ObjectSetString(ChartID(), oCL, OBJPROP_TEXT, TimeToString((TimeLocal() + dLocalOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeLocal() + dLocalOffset)));
        } else if (dShowLocalAsUTC && !dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)));
            ObjectSetString(ChartID(), oCL, OBJPROP_TEXT, TimeToString((TimeGMT() + dLocalOffset), EnumToInt(dFormat)));
        } else if (dShowLocalAsUTC && dShowOffset) {
            ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, TimeToString((TimeTradeServer() + dServerOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeTradeServer() + dServerOffset)));
            ObjectSetString(ChartID(), oCL, OBJPROP_TEXT, TimeToString((TimeGMT() + dLocalOffset), EnumToInt(dFormat)) + " " + TimeGMTOffset((TimeGMT() + dLocalOffset)));
        } else {
            ErrorPrint("untreated condition with dShowLocal: \"" + (string) dShowLocal + "\" " + "dShowOffset: \"" + (string) dShowOffset + "\" " + "dShowSameLine: \"" + (string) dShowSameLine + "\" " + "dFormat: \"" + EnumToString(dFormat) + "\" " + "dShowLocalAsUTC: \"" + (string) dShowLocalAsUTC + "\" " + "dUseServer: \"" + (string) dUseServer + "\" " + "\"");
        }
    } else {
        ErrorPrint("untreated condition with dShowLocal: \"" + (string) dShowLocal + "\" " + "dShowOffset: \"" + (string) dShowOffset + "\" " + "dShowSameLine: \"" + (string) dShowSameLine + "\" " + "dFormat: \"" + EnumToString(dFormat) + "\" " + "dShowLocalAsUTC: \"" + (string) dShowLocalAsUTC + "\" " + "dUseServer: \"" + (string) dUseServer + "\" " + "\"");
    }
    if(last < TimeGMT() && oStatsShow) { // OnTimer() statsUpdate() optimization, calls once per second, if for some reason the Stats object was missing data
        statsUpdate(); // Has last = TimeGMT();
    }
    if(oCSLatencyAppend) { // Special oCSLatencyAppend, not guaranteed to average correctly if GetMicrosecondCount() returns 0
        // Calculate latency of the last 20 calls to OnTimer() and show as a tooltip and also append to the text
        // The code is highly optimized (atleast the comparisons), that's the reason for > in place of >= - normally there is no reason at all to optimize, but I can't output the code in Assembly to look at, neither this code can be benchmarked usefully
        getMicrosecondCountBuf[getMicrosecondCountI++] = GetMicrosecondCount() - getMicrosecondCountLast;
        if(getMicrosecondCountI > 19) {
            getMicrosecondCountI = 0;
        }
        for(getMicrosecondCountJ = 0, getMicrosecondCountMean = 0; getMicrosecondCountJ < 20 && getMicrosecondCountBuf[getMicrosecondCountJ] > 0; getMicrosecondCountJ++) {
            getMicrosecondCountMean = getMicrosecondCountMean + getMicrosecondCountBuf[getMicrosecondCountJ];
        }
        getMicrosecondCountMean = getMicrosecondCountMean / (getMicrosecondCountJ * 1000);
        ObjectSetString(ChartID(), oCS, OBJPROP_TOOLTIP, ("Last 20 calls latency of OnTimer(): " + IntegerToString(getMicrosecondCountMean) + "ms"));
        ObjectSetString(ChartID(), oCS, OBJPROP_TEXT, (ObjectGetString(ChartID(), oCS, OBJPROP_TEXT) + " " + IntegerToString(getMicrosecondCountMean) + "ms"));
        getMicrosecondCountLast = GetMicrosecondCount();
        // Also lets just append Server Latency and Retransmissions to the tooltip of oStats, for the sake of it
        if(oStatsShow) {
            ObjectSetString(ChartID(), oStats, OBJPROP_TOOLTIP, ("Server Latency: " + IntegerToString(TerminalInfoInteger(TERMINAL_PING_LAST)) + "us" + "/Packet Retransmissions: " + DoubleToString(TerminalInfoDouble(TERMINAL_RETRANSMISSION), 2) + "%"));
        }
    }
    // After OnTimer() returns, the platform does not call ChartRedraw(), so we have to call ChartRedraw()
    ChartRedraw();
    return;
}
//+------------------------------------------------------------------+
// Calculation function
// Fixed Issue: Week Range is not updated on a closed market, when opening the platform from fresh boot, because iLow() and iHigh() return wrong data (0.0) - this is one of the issues with MT5 parallel loading of background chart data (I love parallelism, but only when I have control of it, or atleast know its state).
//  Fixed by: making statsUpdate() independent and called at onTimer()
//+------------------------------------------------------------------+
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(!oStatsShow || (rates_total <= 2)) { // No need to calculate if the data is less than the minimum operational period, or oStatsShow is false - it is returned as 0, because we want the terminal to interpret that we still need to calculate (performance cost is likely unmensurable)
        return 0;
    }
    statsUpdate();
    //ChartRedraw(); // Performance loss is HUGE on every Tick - also, after OnCalculate is returned (such event called only on a Tick, calculation), the platform itself executes a ChartRedraw() and processes the object queue - doing a ChartRedraw() here is just a waste of CPU cycles
    return rates_total; // Calculations are done and valid
}
//+------------------------------------------------------------------+
// Statistical Label Updater
//+------------------------------------------------------------------+
void statsUpdate()
{
    last = TimeGMT();
    ulong m = (ulong) (((iTime(Symbol(), PERIOD_CURRENT, 0) + PeriodSeconds(PERIOD_CURRENT) - TimeCurrent()) < 0) ? 0 : (iTime(Symbol(), PERIOD_CURRENT, 0) + PeriodSeconds(PERIOD_CURRENT) - TimeCurrent()));
    ulong s = m % 60;
    m = (m - s) / 60;
    if(sFormat == kSFormatFirst) {
        ObjectSetString(ChartID(), oStats, OBJPROP_TEXT,
                        "Ampl(" +
                        DoubleToString(iHigh(Symbol(), PERIOD_CURRENT, 1) - iLow(Symbol(), PERIOD_CURRENT, 1), Digits()) +
                        "/" +
                        DoubleToString(iHigh(Symbol(), PERIOD_CURRENT, 0) - iLow(Symbol(), PERIOD_CURRENT, 0), Digits()) +
                        ")/D1(" +
                        DoubleToString((iHigh(Symbol(), PERIOD_D1, 1) - iLow(Symbol(), PERIOD_D1, 1)), Digits()) +
                        "/" +
                        DoubleToString((iHigh(Symbol(), PERIOD_D1, 0) - iLow(Symbol(), PERIOD_D1, 0)), Digits()) +
                        ") Spr(" +
                        IntegerToString(SymbolInfoInteger(Symbol(), SYMBOL_SPREAD)) +
                        (SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) < 10 ? "..." : SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) < 100 ? ".." : SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) < 1000 ? "." : "") +
                        ") " +
                        (((SymbolInfoInteger(Symbol(), SYMBOL_TIME) < (TimeCurrent() - oStatsShowMktClosedTimer)) && oStatsShowMktClosed && TerminalInfoInteger(TERMINAL_CONNECTED)) ? "Mkt Closed" :
                         (oStatsShowMktClosed && !TerminalInfoInteger(TERMINAL_CONNECTED)) ? "Disconnected" : (
                             (m < 10 ? "0" : "") +
                             IntegerToString((m < 0 ? 0 : m)) +
                             ":" +
                             (s < 10 ? "0" : "") +
                             IntegerToString((s < 0 ? 0 : s))
                         ))
                       );
    } else if(sFormat == kSFormatSecond) {
        ObjectSetString(ChartID(), oStats, OBJPROP_TEXT,
                        "Chg(" +
                        DoubleToString((iClose(Symbol(), PERIOD_D1, 2) ? ((iClose(Symbol(), PERIOD_D1, 1) * 100.0 / iClose(Symbol(), PERIOD_D1, 2)) - 100.0) : 0.0), 2) + "%/" + /* Check for Division by Zero, skips calculation if true - unfortunately MQL5 does not follow IEEE 754 Standards, which enforces no error at "zero divide in", such case of 0.0/0.0 should have resulted in NaN and happens because of the platform asynchronous nature of loading different timeframes than the current */
                        DoubleToString((iClose(Symbol(), PERIOD_D1, 1) ? ((iClose(Symbol(), PERIOD_D1, 0) * 100.0 / iClose(Symbol(), PERIOD_D1, 1)) - 100.0) : 0.0), 2) +
                        "%) W" +
                        IntegerToString(weekRange) + "[" + DoubleToString(iLow(Symbol(), PERIOD_W1, iLowest(Symbol(), PERIOD_W1, MODE_LOW, weekRange, 0)), Digits()) +
                        ", " +
                        DoubleToString(iHigh(Symbol(), PERIOD_W1, iHighest(Symbol(), PERIOD_W1, MODE_HIGH, weekRange, 0)), Digits()) +
                        "] Spr(" +
                        IntegerToString(SymbolInfoInteger(Symbol(), SYMBOL_SPREAD)) +
                        (SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) < 10 ? "..." : SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) < 100 ? ".." : SymbolInfoInteger(Symbol(), SYMBOL_SPREAD) < 1000 ? "." : "") +
                        ") " +
                        (((SymbolInfoInteger(Symbol(), SYMBOL_TIME) < (TimeCurrent() - oStatsShowMktClosedTimer)) && oStatsShowMktClosed && TerminalInfoInteger(TERMINAL_CONNECTED)) ? "Mkt Closed" :
                         (oStatsShowMktClosed && !TerminalInfoInteger(TERMINAL_CONNECTED)) ? "Disconnected" : (
                             (m < 10 ? "0" : "") +
                             IntegerToString((m < 0 ? 0 : m)) +
                             ":" +
                             (s < 10 ? "0" : "") +
                             IntegerToString((s < 0 ? 0 : s))
                         ))
                       );
    } else {
        ErrorPrint("not implemented");
    }
    return;
}
//+------------------------------------------------------------------+
// TODO improve function to ignore 1s desync
// Time to Greenwich Mean Time (GMT) Offset formatted output
// string TimeGMTOffset(datetime t)
// Consistency with datetime TimeGMTOffset(void)
// This function returns a formatted string containing the timezone
// offset, for example, +02:00 for UTC +02:00.
// The precision of the returned value depends on how accurate the
// local computer is synchronized to a Network Time Protocol (NTP)
// server, also how accurate the server is in sync to a NTP server,
// and how accurate the local NTP server is in sync with
// server NTP server or related clock sync data.
// Can return wrong Server Offset information if the error is bigger
// than or equal to 1 second between the Server and Local.
//+------------------------------------------------------------------+
string TimeGMTOffset(long t = 0)
{
    string s;
    t = t - TimeGMT();
    if(t < 0) {
        s = "-" + IntegerToString((long) MathFloor(MathAbs(t) / 3600.0), 2, (char) '0') + ":" + IntegerToString((long) ((MathAbs(t) % 3600) / 60), 2, (char) '0');
    } else {
        s = "+" + IntegerToString((long) MathFloor(t / 3600.0), 2, (char) '0') + ":" + IntegerToString((long) ((MathAbs(t) % 3600) / 60), 2, (char) '0');
    }
    return s;
}
//+------------------------------------------------------------------+
// Extra functions, utilities and conversion
//+------------------------------------------------------------------+
int EnumToInt(DateFormat e)
{
    if(e == kTimeSeconds) {
        return TIME_SECONDS;
    } else if(e == kTimeMinutes) {
        return TIME_MINUTES;
    } else if(e == kTimeDateSeconds) {
        return TIME_DATE | TIME_SECONDS;
    } else if(e == kTimeDateMinutes) {
        return TIME_DATE | TIME_MINUTES;
    }
    return -1;
}
//+------------------------------------------------------------------+
// Header Guard #endif
//+------------------------------------------------------------------+
#endif
//+------------------------------------------------------------------+

Comments