Equilibrium/Include/SymbolWatcher.mqh

409 lines
39 KiB
MQL5
Raw Permalink Normal View History

2025-05-30 14:53:03 +02:00
<EFBFBD><EFBFBD>//+------------------------------------------------------------------+
//| 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[]; // !B@C:BC@0 40==KE
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);
}
//+------------------------------------------------------------------+
//| 5@20O 8=8F80;870F8O <0AA82>2 |
//+------------------------------------------------------------------+
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));
}
//+------------------------------------------------------------------+
//| 03@C605B 8 D>@<8@C5< =5>1E>48<>5/8<5NI55AO :>;-2> 40==KE |
//| It loads and we form the necessary / available amount of data |
//+------------------------------------------------------------------+
void CSymbolWatcher::LoadFormationData()
{
int number_bars=100; // >;8G5AB2> ?>43@C605<KE 10@>2
//---
int count_try =0; // !GQBG8: ?>?KB>: :>?8@>20=8O 40==KE
int array_size =0; // 07<5@ <0AA820
datetime server_firstdate =NULL; // @5<O ?5@2>3> 10@0 =0 A5@25@5
datetime series_firstdate =NULL; // @5<O ?5@2>3> 10@0 2 1075 B5@<8=0;0
//--- >;CG8< ?5@2CN 40BC A8<2>;0-?5@8>40 2 1075 B5@<8=0;0
SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE,series_firstdate);
//--- >;CG8< ?5@2CN 40BC A8<2>;0-?5@8>40 =0 A5@25@5
SeriesInfoInteger(this.Symbol,Period(),SERIES_SERVER_FIRSTDATE,server_firstdate);
//--- K2545< A>>1I5=85
// 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="@>F5AA 703@C7:8 8 D>@<8@>20=8O 40==KE: "+
this.Symbol+"("+IntegerToString(s+1)+"/"+IntegerToString(SYMBOLS)+") ... ";
ShowCanvasMessage(message_formation_data);
//--- 03@C78</AD>@<8@C5< 40==K5, 5A;8 @07<5@ <0AA820 <5=LH5, G5< <0:A8<0;L=>5 :>;8G5AB2> 10@>2 2 B5@<8=0;5,
// 0 B0:65 <564C ?5@2>9 40B>9 A5@88 2 B5@<8=0;5 8 ?5@2>9 40B>9 A5@88 =0 A5@25@5
// 1>;LH5 C:070==>3> :>;8G5AB20 10@>2
//--- 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[];
//--- >;CG8< ?5@2CN 40BC A8<2>;0-?5@8>40 2 1075 B5@<8=0;0
SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE,series_firstdate);
//--- 03@C78</A:>?8@C5< 5IQ C:070==>5 :>;8G5AB2> 10@>2
if(CopyTime(this.Symbol,Period(),0,array_size+number_bars,copied_time))
{
//--- A;8 2@5<O ?5@2>3> 10@0 <0AA820 A 2KG5B>< :>;-20 ?>43@C605<KE 10@>2 @0=LH5,
// G5< 2@5<O ?5@2>3> 10@0 =0 3@0D8:5, >AB0=>28< F8:;
if(copied_time[0]-PeriodSeconds()*number_bars<on_calc_time[0])
break;
//--- A;8 @07<5@ <0AA820 =5 C25;8G8;AO, C25;8G8< AGQBG8:
//
if(ArraySize(copied_time)==array_size)
count_try++;
//--- =0G5 ?>;CG8< B5:CI89 @07<5@ <0AA820
else
array_size=ArraySize(copied_time);
//--- A;8 @07<5@ <0AA820 =5 C25;8G8205BAO 2 B5G5=88
// 100 ?>?KB>:, >AB0=>28< F8:;
if(count_try==100)
{
count_try=0;
break;
}
}
//--- 064K5 2000 10@>2 ?@>25@O5< @07<5@K ?>4>:=0
// 8 5A;8 @07<5@ 87<5=8;AO ?>43>=8< ?>4 =53> @07<5@ :0=2K
if(!(array_size%2000))
EventChartChange();
}
}
//+------------------------------------------------------------------+
//| @>25@:0 A>1KB8O 703@C7:8 1>;55 3;C1>:>9 8AB>@88 |
//| Check the event of loading a deeper history |
//+------------------------------------------------------------------+
bool CSymbolWatcher::CheckEventLoadHistory()
{
bool load=false;
//--- A;8 =C6=> >1=>28BL A5@88
if(this.PrevCalculated==0)
{
//--- >;CG8< ?5@2CN 40BC A8<2>;0-?5@8>40
// Get the first date of the period symbol
this.SeriesFirstDate=(datetime)SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE);
//--- A;8 745AL 2 ?5@2K9 @07 (>BACBAB2C5B 7=0G5=85), B>
if(this.SeriesFirstDateLast==NULL)
//--- 0?><=8< ?5@2CN 40BC A8<2>;0-?5@8>40 4;O ?>A;54CNI8E A@02=5=89 A F5;LN >?@545;5=8O 703@C7:8
// 1>;55 3;C1>:>9 8AB>@88
// Remember the first date of the symbol-period for subsequent comparisons to determine the load
// of a deeper history.
this.SeriesFirstDateLast=this.SeriesFirstDate;
}
else
{
//--- >;CG8< ?5@2CN 40BC A8<2>;0-?5@8>40
// Get the first date of the period symbol
this.SeriesFirstDate=(datetime)SeriesInfoInteger(this.Symbol,Period(),SERIES_FIRSTDATE);
//--- A;8 40BK >B;8G0NBAO, B> 5ABL 40B0 2 ?0<OB8 1>;55 ?>74=OO, G5< B0, :>B>@CN ?>;CG8;8 A59G0A, B>
// 7=0G8B 1K;0 703@C7:0 1>;55 3;C1>:>9 8AB>@88
// 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)
{
//--- K2545< A>>1I5=85 2 6C@=0;
Print("(",this.Symbol,",",TimeframeToString(Period()),
") > K;0 703@C65=0/AD>@<8@>20=0 1>;55 3;C1>:0O 8AB>@8O: ",
this.SeriesFirstDateLast," > ",this.SeriesFirstDate);
//--- 0?><=8< 40BC
this.SeriesFirstDateLast=this.SeriesFirstDate;
load=true;
}
}
//--- A;8 1K;0 703@C65=0/AD>@<8@>20=0 1>;55 3;C1>:0O 8AB>@8O, B>
// >B?@028< :><0=4C =0 >1=>2;5=85 3@0D8G5A:8E A5@89 8=48:0B>@0
if(load)
return(false);
//---
return(true);
}
//+------------------------------------------------------------------+
//| ?@545;5=85 2@5<5=8 ?5@2>3> 8AB8==>3> 10@0 4;O >B@8A>2:8 |
//| Determination of the time of the first real bar for rendering |
//+------------------------------------------------------------------+
bool CSymbolWatcher::DetermineBeginForCalculate()
{
datetime time[]; // 0AA82 2@5<5=8 10@>2
int total_period_bars=0; // >;8G5AB2> 10@>2
//--- >;CG8< >1I55 :>;8G5AB2> 10@>2 A8<2>;0
// Get the total number of bars of the symbol
total_period_bars=Bars(symbols_names[s],Period());
//--- !:>?8@C5< <0AA82 2@5<5=8 10@>2. A;8 =5 ?>;CG8;>AL, ?>?@>1C5< 5IQ @07
// 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);
//--- >;CG8< 2@5<O ?5@2>3> 8AB8==>3> 10@0, :>B>@K9 A>>B25BAB2C5B B5:CI5<C B09<D@59<C
// Get the time of the first true bar that corresponds to the current timeframe
this.LimitTime=GetFirstTruePeriodBarTime(time);
//---
return(true);
}
//+------------------------------------------------------------------+
//| 0?>;=O5B 8=48:0B>@=K5 1CD5@K |
//+------------------------------------------------------------------+
void CSymbolWatcher::FillBuffers(int i,datetime const &time[])
{
MqlRates rates[]; // !B@C:BC@0 40==KE
double period_open[]; // &5=0 >B:@KB8O 10@0 2 =0G0;5 ?5@8>40 @0AE>645=8O F5=
datetime period_time[]; // @5<O =0G0;0 ?5@8>40 @0AE>645=8O F5=
int try =100; // >;8G5AB2> ?>?KB>: :>?8@>20=8O
datetime high_tf_time =NULL; // @5<O 10@0 AB0@H53> B09<D@59<0
//--- A;8 =0E>48<AO 2=5 7>=K "8AB8==KE" 10@>2 A8<2>;0, 2K945<
// 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
//
//+------------------------------------------------------------------+