/*
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