diff --git a/Indicator/IndicatorData.h b/Indicator/IndicatorData.h index 4d69e8c41..29fde9ff6 100644 --- a/Indicator/IndicatorData.h +++ b/Indicator/IndicatorData.h @@ -1438,6 +1438,13 @@ class IndicatorData : public IndicatorBase { return false; } + /** + * Fetches historic ticks for a given index (absolute shift) range. + */ + virtual bool FetchHistoryByIndexRange(int _index_from, int _index_to, ARRAY_REF(TickTAB, _out_ticks)) { + return false; + } + /** * Fetches historic ticks for a given start time and minimum number of tick to retrieve. */ diff --git a/Indicator/IndicatorTf.provider.h b/Indicator/IndicatorTf.provider.h index 5197aaa73..63669b218 100644 --- a/Indicator/IndicatorTf.provider.h +++ b/Indicator/IndicatorTf.provider.h @@ -139,9 +139,14 @@ class ItemsHistoryTfCandleProvider : public ItemsHistoryCandleProvider { long _candle_length_ms = (long)spc * 1000; long _ticks_to_ms = _ticks_from_ms + _candle_length_ms - 1; + // We will try to fetch history by two methods. + // 1. By time range if IndicatorTick supports that way. if (!_indi_tick PTR_DEREF FetchHistoryByTimeRange(_ticks_from_ms, _ticks_to_ms, _ticks)) { + // 2. By number of bars if IndicatorTick supports that way. + // if (!_indi_tick PTR_DEREF FetchHistoryByIndexRange(_ticks_from_index, _ticks_to_ms, _ticks))) { // There is no more ticks in the history, giving up. break; + //} } if (ArraySize(_ticks) > 0) { diff --git a/IndicatorLegacy.h b/IndicatorLegacy.h index 471e84676..188d02fd7 100644 --- a/IndicatorLegacy.h +++ b/IndicatorLegacy.h @@ -22,25 +22,67 @@ #ifdef __MQL4__ -#include +#include +#include #include #include #include +#ifndef INDICATOR_LEGACY_VERSION_ACQUIRE_BUFFER +#define INDICATOR_LEGACY_VERSION_ACQUIRE_BUFFER +#endif + +#ifndef INDICATOR_LEGACY_VERSION_RELEASE_BUFFER +#define INDICATOR_LEGACY_VERSION_RELEASE_BUFFER +#endif + #ifdef INDICATOR_LEGACY_VERSION_MT5 +#ifndef INDICATOR_LEGACY_VERSION_SHORT + /** - * Replacement for future OnCalculate(). Currently not used, but could be handy in the future. + * Replacement for future OHLC-based OnCalculate(). */ 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[]) { + // We need to call Platform::Tick() and maybe also IndicatorData::EmitHistory() before. + Platform::OnCalculate(rates_total, prev_calculated); + + INDICATOR_LEGACY_VERSION_ACQUIRE_BUFFER; + int _num_calculated = OnCalculateMT5(rates_total, prev_calculated, time, open, high, low, close, tick_volume, volume, spread); + INDICATOR_LEGACY_VERSION_RELEASE_BUFFER; + + return _num_calculated; +} + +#else + +/** + * Replacement for future price-based OnCalculate(). + */ +int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[]) { + // We need to call Platform::Tick() and maybe also IndicatorData::EmitHistory() before. + Platform::OnCalculate(rates_total, prev_calculated); + + INDICATOR_LEGACY_VERSION_ACQUIRE_BUFFER; + + // NOTE: If compiler sees an error here about parameter conversion then you + // probably must do: + // #define INDICATOR_LEGACY_VERSION_SHORT + // before including IndicatorLegacy.h + int _num_calculated = OnCalculateMT5(rates_total, prev_calculated, begin, price); + + INDICATOR_LEGACY_VERSION_RELEASE_BUFFER; + return _num_calculated; } +#endif + #define OnCalculate OnCalculateMT5 /** diff --git a/Indicators/Tick/Indi_TickMt.mqh b/Indicators/Tick/Indi_TickMt.mqh index 259d6712a..98e33a884 100644 --- a/Indicators/Tick/Indi_TickMt.mqh +++ b/Indicators/Tick/Indi_TickMt.mqh @@ -43,6 +43,12 @@ struct Indi_TickMtParams : IndicatorParams { // MT platform's tick-based indicator. class Indi_TickMt : public IndicatorTick> { + // Caching _to_ms in FetchHistoryByTimeRange() in order to start from last + // shift and don't loop over the same bars again. + long _cache_fetch_history_shift_to_ms; + // Shift to start with if given _to_ms is less that cached _cache_fetch_history_shift_to_ms. + long _cache_fetch_history_shift_shift; + public: Indi_TickMt(Indi_TickMtParams &_p, ENUM_IDATA_SOURCE_TYPE _idstype = IDATA_BUILTIN, IndicatorData *_indi_src = NULL, int _indi_src_mode = 0) @@ -61,7 +67,10 @@ class Indi_TickMt : public IndicatorTick, _out_ticks)) { ArrayResize(_out_ticks, 0); +#ifdef __MQL4__ + // Searching from current bar to older ones. + int _shift; + + if (_to_ms <= _cache_fetch_history_shift_to_ms) { + _shift = _cache_fetch_history_shift_shift; + } else { + _shift = 0; + } + + string _symbol = GetSymbol(); + + while (true) { + double _time = (double)iTime(_symbol, PERIOD_M1, _shift); + + if (_time == 0) { + // Invalid time. + break; + } + + long _time_ms = (long)_time * 1000; + + if (_time_ms > _to_ms) { + // No yet get into valid time range. + ++_shift; + continue; + } + + if (_time_ms < _from_ms) { + // No more ticks. + break; + } + + TickTAB _tick_o(_time_ms, iOpen(_Symbol, PERIOD_M1, _shift)); + TickTAB _tick_h(_time_ms, iHigh(_Symbol, PERIOD_M1, _shift)); + TickTAB _tick_l(_time_ms, iLow(_Symbol, PERIOD_M1, _shift)); + TickTAB _tick_c(_time_ms, iClose(_Symbol, PERIOD_M1, _shift)); + ArrayPushObject(_out_ticks, _tick_o); + ArrayPushObject(_out_ticks, _tick_h); + ArrayPushObject(_out_ticks, _tick_l); + ArrayPushObject(_out_ticks, _tick_c); + ++_shift; + } + + if (_shift != -1) { + _cache_fetch_history_shift_to_ms = _to_ms; + _cache_fetch_history_shift_shift = _shift; + } + + return ArraySize(_out_ticks) != 0; +#else static MqlTick _tmp_ticks[]; ArrayResize(_tmp_ticks, 0); - // There's no history in MQL4. -#ifndef __MQL4__ int _tries = 10; while (_tries > 0) { @@ -154,6 +212,18 @@ class Indi_TickMt : public IndicatorTick, _out_ticks)) { + return false; + } + + /** + * Sends historic entries to listening indicators. May be overriden. + */ + virtual void EmitHistory() {} + void OnTick(int _global_tick_index) override { #ifdef __MQL4__ // Refreshes Ask/Bid constants. diff --git a/Platform.h b/Platform.h index c7db1f1a7..14da7148f 100644 --- a/Platform.h +++ b/Platform.h @@ -56,6 +56,9 @@ class Platform { // Whether to clear passed periods on consecutive Platform::UpdateTime(). static bool time_clear_flags; + // Whether history for all the indicators was emitted. + static bool emitted_history; + // List of added indicators. static DictStruct> indis; @@ -108,6 +111,38 @@ class Platform { ++global_tick_index; } + /** + * Called by indicators' OnCalculate() method in order to prepare history via + * IndicatorData::EmitHistory() and to call Tick() for each OnCalculate() + * call so Tick indicator can emit new tick and Candle indicator can update + * or add new candle data to be used by all indicators added to the platform + * via Platform::Add...(). + */ + static void OnCalculate(const int rates_total, const int prev_calculated) { + if (!emitted_history) { + for (DictStructIterator> _iter = indis.Begin(); _iter.IsValid(); ++_iter) { + EmitHistory(_iter.Value().Ptr()); + } + emitted_history = true; + } + + // We're ready for a tick. + Tick(); + } + + /** + * Emits history for parent indicators in hierarchy and then for the indicator itself. + */ + static void EmitHistory(IndicatorData *_indi) { + IndicatorData *_parent = _indi PTR_DEREF GetDataSource(false); + + if (_parent != nullptr) { + EmitHistory(_parent); + } + + _indi PTR_DEREF EmitHistory(); + } + /** * Returns dictionary of added indicators (keyed by unique id). */ @@ -319,6 +354,7 @@ DateTime Platform::time = 0; unsigned int Platform::time_flags = 0; bool Platform::time_clear_flags = true; int Platform::global_tick_index = 0; +bool Platform::emitted_history = false; DictStruct> Platform::indis; DictStruct> Platform::indis_dflt; @@ -353,3 +389,9 @@ DictStruct> Platform::indis_dflt; } #define TEST_INDICATOR_DEFAULT_BINDINGS(C) TEST_INDICATOR_DEFAULT_BINDINGS_PARAMS(C, ) + +// Auto-initializer for Platform class. +class PlatformAutoInitializer { + public: + PlatformAutoInitializer() { Platform::Init(); } +} _platform_auto_initializer;