408 lines
39 KiB
MQL5
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
|
|
//
|
|
//+------------------------------------------------------------------+
|