Equilibrium/Include/SymbolWatcher.mqh
super.admin dcb7601746 convert
2025-05-30 14:53:03 +02:00

408 lines
39 KiB
MQL5

//+------------------------------------------------------------------+
//| SymbolWatcher.mqh |
//| Copyright 2019, Thomas Schwabhaeuser |
//| schwabhaeuser@icloud.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Thomas Schwabhaeuser"
#property link "schwabhaeuser@icloud.com"
#property version "1.00"
//---
#include "MultiSymbolIndicator.mqh"
//---
#define MEMBER_INDEX(index,member) if(index>=0 && index<ArraySize(this.Rates)) return(this.Rates[index].member); else return(EMPTY_VALUE)
//+------------------------------------------------------------------+
//| Class CSymbolWatcher. |
//| Purpose: Observe rates of selected symbol. |
//| Derives from class CObject. |
//+------------------------------------------------------------------+
class CSymbolWatcher : public CObject
{
private:
string Symbol;
ENUM_DIRECTION Direction;
datetime SeriesFirstDate;
datetime SeriesFirstDateLast;
datetime LimitTime;
MqlRates Rates[];
int RatesTotal;
int PrevCalculated;
void OnInitMembers();
void FillBuffers(int i,datetime const &time[]);
public:
CSymbolWatcher();
~CSymbolWatcher();
//---
string GetSymbol();
ENUM_DIRECTION GetDirection();
//---
int OnInit(string symbol,ENUM_DIRECTION direction);
void OnDeinit(const int reason);
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[]);
int PrepareOnCalculate(double value);
//--- rf. https://www.mql5.com/en/docs/series/timeseries_access
void LoadFormationData(); // SERIES_FIRSTDATE, SERIES_SERVER_FIRSTDATE
// bool CheckAvailableData(); // CopyTime() and CopyBuffer()
// from SERIES_TERMINAL_FIRSTDATE to TimeCurrent()
bool CheckEventLoadHistory(); //
// bool CheckSymbolIsSynchronized(); // SERIES_SYNCHRONIZED, TERMINAL_CONNECTED
bool DetermineBeginForCalculate(); //
//--- methods of access to protected data
double Open(int ind) const { MEMBER_INDEX(ind,open); }
double High(int ind) const { MEMBER_INDEX(ind,high); }
double Low(int ind) const { MEMBER_INDEX(ind,low); }
double CloseBid(int ind) const { MEMBER_INDEX(ind,close); }
int Spread(int ind) const { MEMBER_INDEX(ind,spread); }
datetime Time(int ind) const { MEMBER_INDEX(ind,time); }
long TickVolume(int ind) const { MEMBER_INDEX(ind,tick_volume); }
long RealVolume(int ind) const { MEMBER_INDEX(ind,real_volume); }
//--- methods of access to derived data
double CloseAsk(int ind) const { return(CloseBid(ind)+Spread(ind)); }
double Enter(int ind) const;
double Leave(int ind) const;
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CSymbolWatcher::CSymbolWatcher() : Symbol(""),Direction(WRONG_VALUE),PrevCalculated(0),RatesTotal(0),
SeriesFirstDateLast(NULL),SeriesFirstDate(NULL)
{
Print(__FUNCTION__+": created empy instance");
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CSymbolWatcher::~CSymbolWatcher()
{
Print(__FUNCTION__+": Symbol="+this.Symbol);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
string CSymbolWatcher::GetSymbol(void)
{
return(this.Symbol);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
ENUM_DIRECTION CSymbolWatcher::GetDirection()
{
return(this.Direction);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double CSymbolWatcher::Enter(int ind) const
{
if(this.Direction==BUY) return(this.CloseAsk(ind));
if(this.Direction==SELL) return(this.CloseBid(ind));
return(WRONG_VALUE);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double CSymbolWatcher::Leave(int ind) const
{
if(this.Direction==BUY) return(this.CloseBid(ind));
if(this.Direction==SELL) return(this.CloseAsk(ind));
return(WRONG_VALUE);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int CSymbolWatcher::OnInit(string symbol,ENUM_DIRECTION direction)
{
this.Symbol=symbol;
this.Direction=direction;
Print(__FUNCTION__+": "+
"Direction="+EnumToString(this.Direction)+", "+
"Symbol="+this.Symbol);
//---
OnInitMembers();
//--- ok
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void CSymbolWatcher::OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
//| Indicators need to fill their buffers in OnCalculate() |
//| We are providing the rates of a symbol other than that of |
//| the chart our client was loaded to so it suffices |
//| to fill this.Rates[]. |
//+------------------------------------------------------------------+
int CSymbolWatcher::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[])
{
MqlRates rates[]; // Структура данных
this.RatesTotal=rates_total;
this.PrevCalculated=prev_calculated;
//
//if(CopyRates(this.Symbol,Period(),time[i],1,rates)==1)
// {
// }
//--- detect start position
int start=0;
if(prev_calculated>0) start=prev_calculated-1;
int copied=CopyRates(this.Symbol,Period(),start,rates_total-start,this.Rates);
Print(__FUNCTION__+
"("+rates_total+","+prev_calculated+"): start ="+start+", "+
"Symbol="+this.Symbol);
//--- return value of prev_calculated for next call
return(start+copied+1);
}
//+------------------------------------------------------------------+
//| Первая инициализация массивов |
//+------------------------------------------------------------------+
void CSymbolWatcher::OnInitMembers()
{
LimitTime=NULL;
//ArrayInitialize(this.Rates,EMPTY_RATE); // ArrayInitialize() is only available for elementary data types
//--- let's set the array size for 100 elements and reserve a buffer for another 10 elements
ArrayResize(this.Rates,0);
//ArrayInitialize(this.Time,EMPTY_VALUE);
//ArrayInitialize(this.Open,EMPTY_VALUE);
//ArrayInitialize(this.High,EMPTY_VALUE);
//ArrayInitialize(this.Low,EMPTY_VALUE);
//ArrayInitialize(this.Close,EMPTY_VALUE);
//ArrayInitialize(this.Spread,EMPTY_VALUE);
//ArrayInitialize(this.TickVolume,EMPTY_VALUE);
//ArrayInitialize(this.RealVolume,EMPTY_VALUE);
//ArrayInitialize(this.Color,EMPTY_VALUE);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int CSymbolWatcher::PrepareOnCalculate(double value)
{
//--- ATR_MS.mq5:
// ArrayInitialize(temp_symbol_time[s].time,NULL);
// ArrayInitialize(temp_atr_values[s].value,EMPTY_VALUE);
//---
// ArrayInitialize(buffer_atr[s].data,EMPTY_VALUE);
//--- global:
// struct buffers { double data[]; };
// buffers buffer_atr[SYMBOLS];
//--- Analogue in Equilibrium.mq5:
// double ExtEnterBuffer[];
// double ExtLeaveBuffer[];
// double ExtBidBuffer[];
// double ExtAskBuffer[];
// double ExtMarginBuffer[];
//--- SetPropertiesIndicator():
// for(int s=0; s<SYMBOLS; s++)
// SetIndexBuffer(s,buffer_atr[s].data,INDICATOR_DATA);
for(int i=0;i<ArraySize(this.Rates);i++)
{
this.Rates[i].time = 0;
this.Rates[i].open = value;
this.Rates[i].high = value;
this.Rates[i].low = value;
this.Rates[i].close = value;
this.Rates[i].tick_volume = 0;
this.Rates[i].spread = 0;
this.Rates[i].real_volume = 0;
}
return(ArraySize(this.Rates));
}
//+------------------------------------------------------------------+
//| Загружает и формируем необходимое/имеющееся кол-во данных |
//| It loads and we form the necessary / available amount of data |
//+------------------------------------------------------------------+
void CSymbolWatcher::LoadFormationData()
{
int number_bars=100; // Количество подгружаемых баров
//---
int count_try =0; // Счётчик попыток копирования данных
int array_size =0; // Размер массива
datetime server_firstdate =NULL; // Время первого бара на сервере
datetime series_firstdate =NULL; // Время первого бара в базе терминала
//--- Получим первую дату символа-периода в базе терминала
SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE,series_firstdate);
//--- Получим первую дату символа-периода на сервере
SeriesInfoInteger(this.Symbol,Period(),SERIES_SERVER_FIRSTDATE,server_firstdate);
//--- Выведем сообщение
// FIXME 'message_last' and 'message_formation_data' - undeclared identifier SymbolWatcher.mqh 253 17
// s=number of symbols unknown to symbol specific watcher!
message_last=message_formation_data="Процесс загрузки и формирования данных: "+
this.Symbol+"("+IntegerToString(s+1)+"/"+IntegerToString(SYMBOLS)+") ... ";
ShowCanvasMessage(message_formation_data);
//--- Загрузим/сформируем данные, если размер массива меньше, чем максимальное количество баров в терминале,
// а также между первой датой серии в терминале и первой датой серии на сервере
// больше указанного количества баров
//--- Load / generate data if the size of the array is less than the maximum number of bars in the terminal,
// as well as between the first date of the series in the terminal and the first date of the series
// on the server more than the specified number of bars
while(array_size<on_calc_rates_total &&
series_firstdate-server_firstdate>PeriodSeconds()*number_bars)
{
datetime copied_time[];
//--- Получим первую дату символа-периода в базе терминала
SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE,series_firstdate);
//--- Загрузим/скопируем ещё указанное количество баров
if(CopyTime(this.Symbol,Period(),0,array_size+number_bars,copied_time))
{
//--- Если время первого бара массива с вычетом кол-ва подгружаемых баров раньше,
// чем время первого бара на графике, остановим цикл
if(copied_time[0]-PeriodSeconds()*number_bars<on_calc_time[0])
break;
//--- Если размер массива не увеличился, увеличим счётчик
//
if(ArraySize(copied_time)==array_size)
count_try++;
//--- Иначе получим текущий размер массива
else
array_size=ArraySize(copied_time);
//--- Если размер массива не увеличивается в течении
// 100 попыток, остановим цикл
if(count_try==100)
{
count_try=0;
break;
}
}
//--- Каждые 2000 баров проверяем размеры подокна
// и если размер изменился подгоним под него размер канвы
if(!(array_size%2000))
EventChartChange();
}
}
//+------------------------------------------------------------------+
//| Проверка события загрузки более глубокой истории |
//| Check the event of loading a deeper history |
//+------------------------------------------------------------------+
bool CSymbolWatcher::CheckEventLoadHistory()
{
bool load=false;
//--- Если нужно обновить серии
if(this.PrevCalculated==0)
{
//--- Получим первую дату символа-периода
// Get the first date of the period symbol
this.SeriesFirstDate=(datetime)SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE);
//--- Если здесь в первый раз (отсутствует значение), то
if(this.SeriesFirstDateLast==NULL)
//--- Запомним первую дату символа-периода для последующих сравнений с целью определения загрузки
// более глубокой истории
// Remember the first date of the symbol-period for subsequent comparisons to determine the load
// of a deeper history.
this.SeriesFirstDateLast=this.SeriesFirstDate;
}
else
{
//--- Получим первую дату символа-периода
// Get the first date of the period symbol
this.SeriesFirstDate=(datetime)SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE);
//--- Если даты отличаются, то есть дата в памяти более поздняя, чем та, которую получили сейчас, то
// значит была загрузка более глубокой истории
// If the dates are different, then there is a date in memory later than the one that we received
// now, then it means that there was a deeper loading
if(this.SeriesFirstDateLast>this.SeriesFirstDate)
{
//--- Выведем сообщение в журнал
Print("(",this.Symbol,",",TimeframeToString(Period()),
") > Была загружена/сформирована более глубокая история: ",
this.SeriesFirstDateLast," > ",this.SeriesFirstDate);
//--- Запомним дату
this.SeriesFirstDateLast=this.SeriesFirstDate;
load=true;
}
}
//--- Если была загружена/сформирована более глубокая история, то
// отправим команду на обновление графических серий индикатора
if(load)
return(false);
//---
return(true);
}
//+------------------------------------------------------------------+
//| Определение времени первого истинного бара для отрисовки |
//| Determination of the time of the first real bar for rendering |
//+------------------------------------------------------------------+
bool CSymbolWatcher::DetermineBeginForCalculate()
{
datetime time[]; // Массив времени баров
int total_period_bars=0; // Количество баров
//--- Получим общее количество баров символа
// Get the total number of bars of the symbol
total_period_bars=Bars(symbols_names[s],Period());
//--- Скопируем массив времени баров. Если не получилось, попробуем ещё раз
// Copy the array of time bars. If not, try again.
if(CopyTime(symbols_names[s],Period(),0,total_period_bars,time)<total_period_bars)
return(false);
//--- Получим время первого истинного бара, который соответствует текущему таймфрейму
// Get the time of the first true bar that corresponds to the current timeframe
this.LimitTime=GetFirstTruePeriodBarTime(time);
//---
return(true);
}
//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы |
//+------------------------------------------------------------------+
void CSymbolWatcher::FillBuffers(int i,datetime const &time[])
{
MqlRates rates[]; // Структура данных
double period_open[]; // Цена открытия бара в начале периода расхождения цен
datetime period_time[]; // Время начала периода расхождения цен
int try =100; // Количество попыток копирования
datetime high_tf_time =NULL; // Время бара старшего таймфрейма
//--- Если находимся вне зоны "истинных" баров символа, выйдем
// If we find ourselves outside the zone of 'true' bars of the symbol, we’ll exit
if(time[i]<this.LimitTime)
{
Print(__FUNCTION__+
"("+i+",time[]): time["+i+"]="+time[i]+"<"+this.LimitTime);
return;
}
Print(__FUNCTION__+"("+i+",time[]): ...");
}
//---
// CSymbolWatcher::FillBuffers(100881,time[]): ...
// CSymbolWatcher::FillBuffers(100882,time[]): ...
// CSymbolWatcher::FillBuffers(100883,time[]): ...
// CMultiSymbolIndicator::OnCalculate: filling indicator buffers for i=0 while i<100884...
// OnCalculate(100884,100884,...): delegating to CMultiSymbolIndicator::OnCalculate()
// CMultiSymbolIndicator::OnCalculate(100884,100884,...): limit =100883, updating 3 watchers...
// CSymbolWatcher::OnCalculate(100884,100884): limit =100883, Symbol=EURUSD
// CSymbolWatcher::FillBuffers(100883,time[]): ...
// CSymbolWatcher::OnCalculate(100884,100884): limit =100883, Symbol=EURGBP
// CSymbolWatcher::FillBuffers(100883,time[]): ...
// CSymbolWatcher::OnCalculate(100884,100884): limit =100883, Symbol=GBPUSD
// CSymbolWatcher::FillBuffers(100883,time[]): ...
// CMultiSymbolIndicator::OnCalculate: filling indicator buffers for i=100883 while i<100884...
// CMultiSymbolIndicator::~CMultiSymbolIndicator
// CSymbolWatcher::~CSymbolWatcher: Symbol=EURUSD
// CSymbolWatcher::~CSymbolWatcher: Symbol=EURGBP
// CSymbolWatcher::~CSymbolWatcher: Symbol=GBPUSD
//
//+------------------------------------------------------------------+