diff --git a/RingBuffer/RiBuffDbl.mqh b/RingBuffer/RiBuffDbl.mqh new file mode 100644 index 0000000..04d1118 Binary files /dev/null and b/RingBuffer/RiBuffDbl.mqh differ diff --git a/RingBuffer/RiBuffInt.mqh b/RingBuffer/RiBuffInt.mqh new file mode 100644 index 0000000..616ffed Binary files /dev/null and b/RingBuffer/RiBuffInt.mqh differ diff --git a/RingBuffer/RiEMA.mqh b/RingBuffer/RiEMA.mqh new file mode 100644 index 0000000..bdce896 Binary files /dev/null and b/RingBuffer/RiEMA.mqh differ diff --git a/RingBuffer/RiGauss.mqh b/RingBuffer/RiGauss.mqh new file mode 100644 index 0000000..1b92f3c Binary files /dev/null and b/RingBuffer/RiGauss.mqh differ diff --git a/RingBuffer/RiMACD.mqh b/RingBuffer/RiMACD.mqh new file mode 100644 index 0000000..b82d796 Binary files /dev/null and b/RingBuffer/RiMACD.mqh differ diff --git a/RingBuffer/RiMaxMin.mqh b/RingBuffer/RiMaxMin.mqh new file mode 100644 index 0000000..360a705 Binary files /dev/null and b/RingBuffer/RiMaxMin.mqh differ diff --git a/RingBuffer/RiSMA.mqh b/RingBuffer/RiSMA.mqh new file mode 100644 index 0000000..1b97210 Binary files /dev/null and b/RingBuffer/RiSMA.mqh differ diff --git a/RingBuffer/RiStoch.mqh b/RingBuffer/RiStoch.mqh new file mode 100644 index 0000000..bb752d7 Binary files /dev/null and b/RingBuffer/RiStoch.mqh differ diff --git a/Strings/String.mqh b/Strings/String.mqh new file mode 100644 index 0000000..e1e16ce --- /dev/null +++ b/Strings/String.mqh @@ -0,0 +1,377 @@ +//+------------------------------------------------------------------+ +//| String.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CString. | +//| Appointment: Class-string. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CString : public CObject + { +protected: + string m_string; + +public: + CString(void); + ~CString(void); + //--- methods access + string Str(void) const { return(m_string); }; + uint Len(void) const { return(StringLen(m_string)); }; + void Copy(string ©) const; + void Copy(CString *copy) const; + //--- methods fill + bool Fill(const short character) { return(StringFill(m_string,character)); }; + void Assign(const string str) { m_string=str; }; + void Assign(const CString *str) { m_string=str.Str(); }; + void Append(const string str); + void Append(const CString *str); + uint Insert(const uint pos,const string substring); + uint Insert(const uint pos,const CString *substring); + //--- methods compare + int Compare(const string str) const; + int Compare(const CString *str) const; + int CompareNoCase(const string str) const; + int CompareNoCase(const CString *str) const; + //--- methods prepare + string Left(const uint count) const; + string Right(const uint count) const; + string Mid(const uint pos,const uint count) const; + //--- methods truncation/deletion + int Trim(const string targets); + int TrimLeft(const string targets); + int TrimRight(const string targets); + bool Clear(void) { return(StringInit(m_string)); }; + //--- methods conversion + bool ToUpper(void) { return(StringToUpper(m_string)); }; + bool ToLower(void) { return(StringToLower(m_string)); }; + void Reverse(void); + //--- methods find + int Find(const uint start,const string substring) const; + int FindRev(const string substring) const; + uint Remove(const string substring); + uint Replace(const string substring,const string newstring); + +protected: + virtual int Compare(const CObject *node,const int mode=0) const; + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CString::CString(void) : m_string("") + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CString::~CString(void) + { + } +//+------------------------------------------------------------------+ +//| Copy the string value member to copy | +//+------------------------------------------------------------------+ +void CString::Copy(string ©) const + { + copy=m_string; + } +//+------------------------------------------------------------------+ +//| Copy the string value member to copy | +//+------------------------------------------------------------------+ +void CString::Copy(CString *copy) const + { + copy.Assign(m_string); + } +//+------------------------------------------------------------------+ +//| Add a string to the end | +//+------------------------------------------------------------------+ +void CString::Append(const string str) + { + m_string+=str; + } +//+------------------------------------------------------------------+ +//| Add a string to the end | +//+------------------------------------------------------------------+ +void CString::Append(const CString *str) + { +//--- check + if(!CheckPointer(str)) + return; +//--- + m_string+=str.Str(); + } +//+------------------------------------------------------------------+ +//| Insert a string in specified position | +//+------------------------------------------------------------------+ +uint CString::Insert(const uint pos,const string substring) + { + string tmp=StringSubstr(m_string,0,pos); +//--- + tmp+=substring; + m_string=tmp+StringSubstr(m_string,pos); +//--- result + return(StringLen(m_string)); + } +//+------------------------------------------------------------------+ +//| Insert a string in specified position | +//+------------------------------------------------------------------+ +uint CString::Insert(const uint pos,const CString *substring) + { +//--- check + if(!CheckPointer(substring)) + return(0); +//--- + string tmp=StringSubstr(m_string,0,pos); +//--- + tmp+=substring.Str(); + m_string=tmp+StringSubstr(m_string,pos); +//--- result + return(StringLen(m_string)); + } +//+------------------------------------------------------------------+ +//| Comparison with the string | +//+------------------------------------------------------------------+ +int CString::Compare(const string str) const + { + if(m_stringstr) + return(1); +//--- equal + return(0); + } +//+------------------------------------------------------------------+ +//| Comparison with the string | +//+------------------------------------------------------------------+ +int CString::Compare(const CString *str) const + { +//--- check + if(!CheckPointer(str)) + return(0); +//--- + if(m_stringstr.Str()) + return(1); +//--- equal + return(0); + } +//+------------------------------------------------------------------+ +//| Comparison with the string without case | +//+------------------------------------------------------------------+ +int CString::CompareNoCase(const string str) const + { + string tmp1,tmp2; +//--- + tmp1=m_string; + tmp2=str; + StringToLower(tmp1); + StringToLower(tmp2); +//--- + if(tmp1tmp2) + return(1); +//--- equal + return(0); + } +//+------------------------------------------------------------------+ +//| Comparison with the string without case | +//+------------------------------------------------------------------+ +int CString::CompareNoCase(const CString *str) const + { + string tmp1,tmp2; +//--- check + if(!CheckPointer(str)) + return(0); +//--- + tmp1=m_string; + tmp2=str.Str(); + StringToLower(tmp1); + StringToLower(tmp2); +//--- + if(tmp1tmp2) + return(1); +//--- equal + return(0); + } +//+------------------------------------------------------------------+ +//| Find occurrences of substring from the specified position | +//+------------------------------------------------------------------+ +int CString::Find(const uint start,const string substring) const + { + return(StringFind(m_string,substring,start)); + } +//+------------------------------------------------------------------+ +//| Find last occurrence of substring | +//+------------------------------------------------------------------+ +int CString::FindRev(const string substring) const + { + int result,pos=-1; +//--- + do + { + result=pos; + } + while((pos=StringFind(m_string,substring,pos+1))>=0); +//--- result + return(result); + } +//+------------------------------------------------------------------+ +//| Get a substring consisting of count elements of the left string | +//+------------------------------------------------------------------+ +string CString::Left(const uint count) const + { + return(StringSubstr(m_string,0,count)); + } +//+------------------------------------------------------------------+ +//| Get a substring consisting of count elements of the right string.| +//+------------------------------------------------------------------+ +string CString::Right(const uint count) const + { + return(StringSubstr(m_string,StringLen(m_string)-count,count)); + } +//+------------------------------------------------------------------+ +//| Get a substring consisting of count elements of the pos string | +//+------------------------------------------------------------------+ +string CString::Mid(const uint pos,const uint count) const + { + return(StringSubstr(m_string,pos,count)); + } +//+------------------------------------------------------------------+ +//| Remove from the string, all characters in the begin and | +//| in the end if they arein targets, or space, \t,\n or \r | +//+------------------------------------------------------------------+ +int CString::Trim(const string targets) + { + return(TrimLeft(targets)+TrimRight(targets)); + } +//+------------------------------------------------------------------+ +//| Remove from the string, all characters in the begin if they are | +//| in targets, or space, \t,\n or \r | +//+------------------------------------------------------------------+ +int CString::TrimLeft(const string targets) + { + ushort ch; +//--- + for(int i=0;i=0;i--) + { + ch=StringGetCharacter(m_string,i); + if(ch<=' ') + continue; + for(int j=0;jj;i--,j++) + { + ch=StringGetCharacter(m_string,i); + StringSetCharacter(m_string,i,StringGetCharacter(m_string,j)); + StringSetCharacter(m_string,j,ch); + } + } +//+------------------------------------------------------------------+ +//| Remove all occurrences of the substring | +//+------------------------------------------------------------------+ +uint CString::Remove(const string substring) + { + int result=0,len,pos=-1; + string tmp; +//--- + len=StringLen(substring); + while((pos=StringFind(m_string,substring,pos))>=0) + { + tmp=StringSubstr(m_string,0,pos); + m_string=tmp+StringSubstr(m_string,pos+len); + result++; + } +//--- result + return(result); + } +//+------------------------------------------------------------------+ +//| Replace all occurrences of a substring in the specified string | +//+------------------------------------------------------------------+ +uint CString::Replace(const string substring,const string newstring) + { + int result=0,len,pos=-1; + string tmp; +//--- + len=StringLen(substring); + while((pos=StringFind(m_string,substring,pos))>=0) + { + tmp=StringSubstr(m_string,0,pos)+newstring; + m_string=tmp+StringSubstr(m_string,pos+len); + // to eliminate possible loops + pos+=StringLen(newstring); + result++; + } +//--- result + return(result); + } +//+------------------------------------------------------------------+ +//| Comparison with the string by algorithm | +//+------------------------------------------------------------------+ +int CString::Compare(const CObject *node,const int mode=0) const + { + CString *str=(CString*)node; +//--- check + if(str==NULL) + return(0); +//--- + switch(mode) + { + case 0: return(Compare(str)); + case 1: return(CompareNoCase(str)); + } +//--- equal + return(0); + } +//+------------------------------------------------------------------+ diff --git a/Tools/DateTime.mqh b/Tools/DateTime.mqh new file mode 100644 index 0000000..ffe99ff --- /dev/null +++ b/Tools/DateTime.mqh @@ -0,0 +1,581 @@ +//+------------------------------------------------------------------+ +//| DateTime.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| Structure CDateTime. | +//| Purpose: Working with dates and time. | +//| Extends the MqlDateTime structure. | +//+------------------------------------------------------------------+ +struct CDateTime : public MqlDateTime + { + //--- additional information + string MonthName(const int num) const; + string ShortMonthName(const int num) const; + string DayName(const int num) const; + string ShortDayName(const int num) const; + string MonthName(void) const { return(MonthName(mon)); } + string ShortMonthName(void) const { return(ShortMonthName(mon)); } + string DayName(void) const { return(DayName(day_of_week)); } + string ShortDayName(void) const { return(ShortDayName(day_of_week)); } + int DaysInMonth(void) const; + //--- data access + datetime DateTime(void) { return(StructToTime(this)); } + void DateTime(const datetime value) { TimeToStruct(value,this); } + void DateTime(const MqlDateTime& value) { this=value; } + void Date(const datetime value); + void Date(const MqlDateTime &value); + void Time(const datetime value); + void Time(const MqlDateTime &value); + //--- settings + void Sec(const int value); + void Min(const int value); + void Hour(const int value); + void Day(const int value); + void Mon(const int value); + void Year(const int value); + //--- increments + void SecDec(int delta=1); + void SecInc(int delta=1); + void MinDec(int delta=1); + void MinInc(int delta=1); + void HourDec(int delta=1); + void HourInc(int delta=1); + void DayDec(int delta=1); + void DayInc(int delta=1); + void MonDec(int delta=1); + void MonInc(int delta=1); + void YearDec(int delta=1); + void YearInc(int delta=1); + //--- check + void DayCheck(void); + }; +//+------------------------------------------------------------------+ +//| Gets month name | +//+------------------------------------------------------------------+ +string CDateTime::MonthName(const int num) const + { + switch(num) + { + case 1: return("January"); + case 2: return("February"); + case 3: return("March"); + case 4: return("April"); + case 5: return("May"); + case 6: return("June"); + case 7: return("July"); + case 8: return("August"); + case 9: return("September"); + case 10: return("October"); + case 11: return("November"); + case 12: return("December"); + } +//--- + return("Bad month"); + } +//+------------------------------------------------------------------+ +//| Gets short name of month | +//+------------------------------------------------------------------+ +string CDateTime::ShortMonthName(const int num) const + { + switch(num) + { + case 1: return("jan"); + case 2: return("feb"); + case 3: return("mar"); + case 4: return("apr"); + case 5: return("may"); + case 6: return("jun"); + case 7: return("jul"); + case 8: return("aug"); + case 9: return("sep"); + case 10: return("oct"); + case 11: return("nov"); + case 12: return("dec"); + } +//--- + return("Bad month"); + } +//+------------------------------------------------------------------+ +//| Gets name of week day | +//+------------------------------------------------------------------+ +string CDateTime::DayName(const int num) const + { + switch(num) + { + case 0: return("Sunday"); + case 1: return("Monday"); + case 2: return("Tuesday"); + case 3: return("Wednesday"); + case 4: return("Thursday"); + case 5: return("Friday"); + case 6: return("Saturday"); + } +//--- + return("Bad day of week"); + } +//+------------------------------------------------------------------+ +//| Gets short name of week day | +//+------------------------------------------------------------------+ +string CDateTime::ShortDayName(const int num) const + { + switch(num) + { + case 0: return("Su"); + case 1: return("Mo"); + case 2: return("Tu"); + case 3: return("We"); + case 4: return("Th"); + case 5: return("Fr"); + case 6: return("Sa"); + } +//--- + return("Bad day of week"); + } +//+------------------------------------------------------------------+ +//| Gets number of days in month | +//+------------------------------------------------------------------+ +int CDateTime::DaysInMonth(void) const + { + int leap_year; +//--- + switch(mon) + { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + return(31); + case 2: + leap_year=year; + if(year%100==0) + leap_year/=100; + return((leap_year%4==0)? 29 : 28); + case 4: + case 6: + case 9: + case 11: + return(30); + } +//--- + return(0); + } +//+------------------------------------------------------------------+ +//| Sets date | +//+------------------------------------------------------------------+ +void CDateTime::Date(const datetime value) + { + MqlDateTime dt; +//--- convert to structure + TimeToStruct(value,dt); +//--- set + Date(dt); + } +//+------------------------------------------------------------------+ +//| Sets date | +//+------------------------------------------------------------------+ +void CDateTime::Date(const MqlDateTime &value) + { + day =value.day; + mon =value.mon; + year=value.year; +//--- check if day is correct + DayCheck(); + } +//+------------------------------------------------------------------+ +//| Sets time | +//+------------------------------------------------------------------+ +void CDateTime::Time(const datetime value) + { + MqlDateTime dt; +//--- convert to structure + TimeToStruct(value,dt); +//--- set + Time(dt); + } +//+------------------------------------------------------------------+ +//| Sets time | +//+------------------------------------------------------------------+ +void CDateTime::Time(const MqlDateTime &value) + { + hour=value.hour; + min =value.min; + sec =value.sec; + } +//+------------------------------------------------------------------+ +//| Sets seconds | +//+------------------------------------------------------------------+ +void CDateTime::Sec(const int value) + { +//--- check and set + if(value>=0 && value<60) + sec=value; + } +//+------------------------------------------------------------------+ +//| Sets minutes | +//+------------------------------------------------------------------+ +void CDateTime::Min(const int value) + { +//--- check and set + if(value>=0 && value<60) + min=value; + } +//+------------------------------------------------------------------+ +//| Sets hours | +//+------------------------------------------------------------------+ +void CDateTime::Hour(const int value) + { +//--- check and set + if(value>=0 && value<24) + hour=value; + } +//+------------------------------------------------------------------+ +//| Sets day of month | +//+------------------------------------------------------------------+ +void CDateTime::Day(const int value) + { +//--- check and set + if(value>0 && value<=DaysInMonth()) + { + day=value; + //--- check if day is correct + DayCheck(); + } + } +//+------------------------------------------------------------------+ +//| Sets month | +//+------------------------------------------------------------------+ +void CDateTime::Mon(const int value) + { +//--- check and set + if(value>0 && value<=12) + { + mon=value; + //--- check if day is correct + DayCheck(); + } + } +//+------------------------------------------------------------------+ +//| Sets year | +//+------------------------------------------------------------------+ +void CDateTime::Year(const int value) + { +//--- check and set + if(value>=1970) + { + year=value; + //--- check if day is correct + DayCheck(); + } + } +//+------------------------------------------------------------------+ +//| Subtracts specified number of seconds | +//+------------------------------------------------------------------+ +void CDateTime::SecDec(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + SecInc(-delta); + return; + } +//--- check if subtract from upper number positions + if(delta>60) + { + MinDec(delta/60); + delta%=60; + } + sec-=delta; + if(sec<0) + { + sec+=60; + MinDec(); + } + } +//+------------------------------------------------------------------+ +//| Adds specified number of seconds | +//+------------------------------------------------------------------+ +void CDateTime::SecInc(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + SecDec(-delta); + return; + } +//--- check if add to upper number positions + if(delta>60) + { + MinInc(delta/60); + delta%=60; + } + sec+=delta; + if(sec>=60) + { + sec-=60; + MinInc(); + } + } +//+------------------------------------------------------------------+ +//| Subtracts specified number of minutes | +//+------------------------------------------------------------------+ +void CDateTime::MinDec(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + MinInc(-delta); + return; + } +//--- check if subtract from upper number positions + if(delta>60) + { + HourDec(delta/60); + delta%=60; + } + min-=delta; + if(min<0) + { + min+=60; + HourDec(); + } + } +//+------------------------------------------------------------------+ +//| Adds specified number of minutes | +//+------------------------------------------------------------------+ +void CDateTime::MinInc(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + MinDec(-delta); + return; + } +//--- check if add to upper number positions + if(delta>60) + { + HourInc(delta/60); + delta%=60; + } + min+=delta; + if(min>=60) + { + min-=60; + HourInc(); + } + } +//+------------------------------------------------------------------+ +//| Subtracts specified number of hours | +//+------------------------------------------------------------------+ +void CDateTime::HourDec(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + HourInc(-delta); + return; + } +//--- check if subtract from upper number positions + if(delta>24) + { + DayDec(delta/24); + delta%=24; + } + hour-=delta; + if(hour<0) + { + hour+=24; + DayDec(); + } + } +//+------------------------------------------------------------------+ +//| Adds specified number of hours | +//+------------------------------------------------------------------+ +void CDateTime::HourInc(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + HourDec(-delta); + return; + } +//--- check if add to upper number positions + if(delta>24) + { + DayInc(delta/24); + delta%=24; + } + hour+=delta; + if(hour>=24) + { + hour-=24; + DayInc(); + } + } +//+------------------------------------------------------------------+ +//| Subtracts specified number of days | +//+------------------------------------------------------------------+ +void CDateTime::DayDec(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + DayInc(-delta); + return; + } +//--- uncertain condition, as the number of days in month can differ + while(day<=delta) + { + delta-=day; + MonDec(); + day=DaysInMonth(); + } + day-=delta; +//--- check if day is correct + DayCheck(); + } +//+------------------------------------------------------------------+ +//| Adds specified number of days | +//+------------------------------------------------------------------+ +void CDateTime::DayInc(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + DayDec(-delta); + return; + } +//--- uncertain condition, as the number of days in month can differ + while(DaysInMonth()-day12) + { + YearDec(delta/12); + delta%=12; + } + mon-=delta; + if(mon<=0) + { + mon+=12; + YearDec(); + } +//--- check if day is correct + DayCheck(); + } +//+------------------------------------------------------------------+ +//| Adds specified number of months | +//+------------------------------------------------------------------+ +void CDateTime::MonInc(int delta) + { +//--- if increment is 0 - exit + if(delta==0) + return; +//--- if increment is negative - inverse the operation + if(delta<0) + { + MonDec(-delta); + return; + } +//--- check if add to upper number positions + if(delta>12) + { + YearInc(delta/12); + delta%=12; + } + mon+=delta; + if(mon>12) + { + mon-=12; + YearInc(); + } +//--- check if day is correct + DayCheck(); + } +//+------------------------------------------------------------------+ +//| Subtracts specified number of years | +//+------------------------------------------------------------------+ +void CDateTime::YearDec(int delta) + { +//--- if increment is 0 - exit + if(delta!=0) + { + year-=delta; + //--- check if day is correct + DayCheck(); + } + } +//+------------------------------------------------------------------+ +//| Adds specified number of years | +//+------------------------------------------------------------------+ +void CDateTime::YearInc(int delta) + { +//--- if increment is 0 - exit + if(delta!=0) + { + year+=delta; + //--- check if day is correct + DayCheck(); + } + } +//+------------------------------------------------------------------+ +//| Checks if day number is correct | +//+------------------------------------------------------------------+ +void CDateTime::DayCheck(void) + { + if(day>DaysInMonth()) + day=DaysInMonth(); +//--- this is required to get day of week and day of year + TimeToStruct(StructToTime(this),this); + } +//+------------------------------------------------------------------+ diff --git a/Trade/AccountInfo.mqh b/Trade/AccountInfo.mqh new file mode 100644 index 0000000..8bc9359 --- /dev/null +++ b/Trade/AccountInfo.mqh @@ -0,0 +1,397 @@ +//+------------------------------------------------------------------+ +//| AccountInfo.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CAccountInfo. | +//| Appointment: Class for access to account info. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CAccountInfo : public CObject + { +public: + CAccountInfo(void); + ~CAccountInfo(void); + //--- fast access methods to the integer account propertyes + long Login(void) const; + ENUM_ACCOUNT_TRADE_MODE TradeMode(void) const; + string TradeModeDescription(void) const; + long Leverage(void) const; + ENUM_ACCOUNT_STOPOUT_MODE StopoutMode(void) const; + string StopoutModeDescription(void) const; + ENUM_ACCOUNT_MARGIN_MODE MarginMode(void) const; + string MarginModeDescription(void) const; + bool TradeAllowed(void) const; + bool TradeExpert(void) const; + int LimitOrders(void) const; + //--- fast access methods to the double account propertyes + double Balance(void) const; + double Credit(void) const; + double Profit(void) const; + double Equity(void) const; + double Margin(void) const; + double FreeMargin(void) const; + double MarginLevel(void) const; + double MarginCall(void) const; + double MarginStopOut(void) const; + //--- fast access methods to the string account propertyes + string Name(void) const; + string Server(void) const; + string Currency(void) const; + string Company(void) const; + //--- access methods to the API MQL5 functions + long InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const; + double InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const; + string InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const; + //--- checks + double OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double volume,const double price_open,const double price_close) const; + double MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double volume,const double price) const; + double FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double volume,const double price) const; + double MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double price,const double percent=100) const; + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CAccountInfo::CAccountInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CAccountInfo::~CAccountInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_LOGIN" | +//+------------------------------------------------------------------+ +long CAccountInfo::Login(void) const + { + return(AccountInfoInteger(ACCOUNT_LOGIN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_TRADE_MODE" | +//+------------------------------------------------------------------+ +ENUM_ACCOUNT_TRADE_MODE CAccountInfo::TradeMode(void) const + { + return((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_TRADE_MODE" as string | +//+------------------------------------------------------------------+ +string CAccountInfo::TradeModeDescription(void) const + { + string str; +//--- + switch(TradeMode()) + { + case ACCOUNT_TRADE_MODE_DEMO: + str="Demo trading account"; + break; + case ACCOUNT_TRADE_MODE_CONTEST: + str="Contest trading account"; + break; + case ACCOUNT_TRADE_MODE_REAL: + str="Real trading account"; + break; + default: + str="Unknown trade account"; + } +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_LEVERAGE" | +//+------------------------------------------------------------------+ +long CAccountInfo::Leverage(void) const + { + return(AccountInfoInteger(ACCOUNT_LEVERAGE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_SO_MODE" | +//+------------------------------------------------------------------+ +ENUM_ACCOUNT_STOPOUT_MODE CAccountInfo::StopoutMode(void) const + { + return((ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_SO_MODE" as string | +//+------------------------------------------------------------------+ +string CAccountInfo::StopoutModeDescription(void) const + { + string str; +//--- + switch(StopoutMode()) + { + case ACCOUNT_STOPOUT_MODE_PERCENT: + str="Level is specified in percentage"; + break; + case ACCOUNT_STOPOUT_MODE_MONEY: + str="Level is specified in money"; + break; + default: + str="Unknown stopout mode"; + } +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_MODE" | +//+------------------------------------------------------------------+ +ENUM_ACCOUNT_MARGIN_MODE CAccountInfo::MarginMode(void) const + { + return((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_MODE" as string | +//+------------------------------------------------------------------+ +string CAccountInfo::MarginModeDescription(void) const + { + string str; +//--- + switch(MarginMode()) + { + case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: + str="Netting"; + break; + case ACCOUNT_MARGIN_MODE_EXCHANGE: + str="Exchange"; + break; + case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: + str="Hedging"; + break; + default: + str="Unknown margin mode"; + } +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_TRADE_ALLOWED" | +//+------------------------------------------------------------------+ +bool CAccountInfo::TradeAllowed(void) const + { + return((bool)AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_TRADE_EXPERT" | +//+------------------------------------------------------------------+ +bool CAccountInfo::TradeExpert(void) const + { + return((bool)AccountInfoInteger(ACCOUNT_TRADE_EXPERT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_LIMIT_ORDERS" | +//+------------------------------------------------------------------+ +int CAccountInfo::LimitOrders(void) const + { + return((int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_BALANCE" | +//+------------------------------------------------------------------+ +double CAccountInfo::Balance(void) const + { + return(AccountInfoDouble(ACCOUNT_BALANCE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_CREDIT" | +//+------------------------------------------------------------------+ +double CAccountInfo::Credit(void) const + { + return(AccountInfoDouble(ACCOUNT_CREDIT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_PROFIT" | +//+------------------------------------------------------------------+ +double CAccountInfo::Profit(void) const + { + return(AccountInfoDouble(ACCOUNT_PROFIT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_EQUITY" | +//+------------------------------------------------------------------+ +double CAccountInfo::Equity(void) const + { + return(AccountInfoDouble(ACCOUNT_EQUITY)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN" | +//+------------------------------------------------------------------+ +double CAccountInfo::Margin(void) const + { + return(AccountInfoDouble(ACCOUNT_MARGIN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_FREE" | +//+------------------------------------------------------------------+ +double CAccountInfo::FreeMargin(void) const + { + return(AccountInfoDouble(ACCOUNT_MARGIN_FREE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_LEVEL" | +//+------------------------------------------------------------------+ +double CAccountInfo::MarginLevel(void) const + { + return(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_SO_CALL" | +//+------------------------------------------------------------------+ +double CAccountInfo::MarginCall(void) const + { + return(AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_MARGIN_SO_SO" | +//+------------------------------------------------------------------+ +double CAccountInfo::MarginStopOut(void) const + { + return(AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_NAME" | +//+------------------------------------------------------------------+ +string CAccountInfo::Name(void) const + { + return(AccountInfoString(ACCOUNT_NAME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_SERVER" | +//+------------------------------------------------------------------+ +string CAccountInfo::Server(void) const + { + return(AccountInfoString(ACCOUNT_SERVER)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_CURRENCY" | +//+------------------------------------------------------------------+ +string CAccountInfo::Currency(void) const + { + return(AccountInfoString(ACCOUNT_CURRENCY)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ACCOUNT_COMPANY" | +//+------------------------------------------------------------------+ +string CAccountInfo::Company(void) const + { + return(AccountInfoString(ACCOUNT_COMPANY)); + } +//+------------------------------------------------------------------+ +//| Access functions AccountInfoInteger(...) | +//+------------------------------------------------------------------+ +long CAccountInfo::InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const + { + return(AccountInfoInteger(prop_id)); + } +//+------------------------------------------------------------------+ +//| Access functions AccountInfoDouble(...) | +//+------------------------------------------------------------------+ +double CAccountInfo::InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const + { + return(AccountInfoDouble(prop_id)); + } +//+------------------------------------------------------------------+ +//| Access functions AccountInfoString(...) | +//+------------------------------------------------------------------+ +string CAccountInfo::InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const + { + return(AccountInfoString(prop_id)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderCalcProfit(...). | +//| INPUT: name - symbol name, | +//| trade_operation - trade operation, | +//| volume - volume of the opening position, | +//| price_open - price of the opening position, | +//| price_close - price of the closing position. | +//+------------------------------------------------------------------+ +double CAccountInfo::OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double volume,const double price_open,const double price_close) const + { + double profit=EMPTY_VALUE; +//--- + if(!OrderCalcProfit(trade_operation,symbol,volume,price_open,price_close,profit)) + return(EMPTY_VALUE); +//--- + return(profit); + } +//+------------------------------------------------------------------+ +//| Access functions OrderCalcMargin(...). | +//| INPUT: name - symbol name, | +//| trade_operation - trade operation, | +//| volume - volume of the opening position, | +//| price - price of the opening position. | +//+------------------------------------------------------------------+ +double CAccountInfo::MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double volume,const double price) const + { + double margin=EMPTY_VALUE; +//--- + if(!OrderCalcMargin(trade_operation,symbol,volume,price,margin)) + return(EMPTY_VALUE); +//--- + return(margin); + } +//+------------------------------------------------------------------+ +//| Access functions OrderCalcMargin(...). | +//| INPUT: name - symbol name, | +//| trade_operation - trade operation, | +//| volume - volume of the opening position, | +//| price - price of the opening position. | +//+------------------------------------------------------------------+ +double CAccountInfo::FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double volume,const double price) const + { + return(FreeMargin()-MarginCheck(symbol,trade_operation,volume,price)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderCalcMargin(...). | +//| INPUT: name - symbol name, | +//| trade_operation - trade operation, | +//| price - price of the opening position, | +//| percent - percent of available margin [1-100%]. | +//+------------------------------------------------------------------+ +double CAccountInfo::MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, + const double price,const double percent) const + { + double margin=0.0; +//--- checks + if(symbol=="" || price<=0.0 || percent<1 || percent>100) + { + Print("CAccountInfo::MaxLotCheck invalid parameters"); + return(0.0); + } +//--- calculate margin requirements for 1 lot + if(!OrderCalcMargin(trade_operation,symbol,1.0,price,margin) || margin<0.0) + { + Print("CAccountInfo::MaxLotCheck margin calculation failed"); + return(0.0); + } +//--- + if(margin==0.0) // for pending orders + return(SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX)); +//--- calculate maximum volume + double volume=NormalizeDouble(FreeMargin()*percent/100.0/margin,2); +//--- normalize and check limits + double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); + if(stepvol>0.0) + volume=stepvol*MathFloor(volume/stepvol); +//--- + double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); + if(volumemaxvol) + volume=maxvol; +//--- return volume + return(volume); + } +//+------------------------------------------------------------------+ diff --git a/Trade/DealInfo.mqh b/Trade/DealInfo.mqh new file mode 100644 index 0000000..e72d23c --- /dev/null +++ b/Trade/DealInfo.mqh @@ -0,0 +1,430 @@ +//+------------------------------------------------------------------+ +//| DealInfo.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CDealInfo. | +//| Appointment: Class for access to history deal info. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CDealInfo : public CObject + { +protected: + ulong m_ticket; // ticket of history order + +public: + CDealInfo(void); + ~CDealInfo(void); + //--- methods of access to protected data + void Ticket(const ulong ticket) { m_ticket=ticket; } + ulong Ticket(void) const { return(m_ticket); } + //--- fast access methods to the integer position propertyes + long Order(void) const; + datetime Time(void) const; + ulong TimeMsc(void) const; + ENUM_DEAL_TYPE DealType(void) const; + string TypeDescription(void) const; + ENUM_DEAL_ENTRY Entry(void) const; + string EntryDescription(void) const; + long Magic(void) const; + long PositionId(void) const; + //--- fast access methods to the double position propertyes + double Volume(void) const; + double Price(void) const; + double Commission(void) const; + double Swap(void) const; + double Profit(void) const; + //--- fast access methods to the string position propertyes + string Symbol(void) const; + string Comment(void) const; + string ExternalId(void) const; + //--- access methods to the API MQL5 functions + bool InfoInteger(ENUM_DEAL_PROPERTY_INTEGER prop_id,long &var) const; + bool InfoDouble(ENUM_DEAL_PROPERTY_DOUBLE prop_id,double &var) const; + bool InfoString(ENUM_DEAL_PROPERTY_STRING prop_id,string &var) const; + //--- info methods + string FormatAction(string &str,const uint action) const; + string FormatEntry(string &str,const uint entry) const; + string FormatDeal(string &str) const; + //--- method for select deal + bool SelectByIndex(const int index); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CDealInfo::CDealInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CDealInfo::~CDealInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_ORDER" | +//+------------------------------------------------------------------+ +long CDealInfo::Order(void) const + { + return(HistoryDealGetInteger(m_ticket,DEAL_ORDER)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_TIME" | +//+------------------------------------------------------------------+ +datetime CDealInfo::Time(void) const + { + return((datetime)HistoryDealGetInteger(m_ticket,DEAL_TIME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_TIME_MSC" | +//+------------------------------------------------------------------+ +ulong CDealInfo::TimeMsc(void) const + { + return(HistoryDealGetInteger(m_ticket,DEAL_TIME_MSC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_TYPE" | +//+------------------------------------------------------------------+ +ENUM_DEAL_TYPE CDealInfo::DealType(void) const + { + return((ENUM_DEAL_TYPE)HistoryDealGetInteger(m_ticket,DEAL_TYPE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_TYPE" as string | +//+------------------------------------------------------------------+ +string CDealInfo::TypeDescription(void) const + { + string str; +//--- + switch(DealType()) + { + case DEAL_TYPE_BUY: + str="Buy type"; + break; + case DEAL_TYPE_SELL: + str="Sell type"; + break; + case DEAL_TYPE_BALANCE: + str="Balance type"; + break; + case DEAL_TYPE_CREDIT: + str="Credit type"; + break; + case DEAL_TYPE_CHARGE: + str="Charge type"; + break; + case DEAL_TYPE_CORRECTION: + str="Correction type"; + break; + case DEAL_TYPE_BONUS: + str="Bonus type"; + break; + case DEAL_TYPE_COMMISSION: + str="Commission type"; + break; + case DEAL_TYPE_COMMISSION_DAILY: + str="Daily Commission type"; + break; + case DEAL_TYPE_COMMISSION_MONTHLY: + str="Monthly Commission type"; + break; + case DEAL_TYPE_COMMISSION_AGENT_DAILY: + str="Daily Agent Commission type"; + break; + case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: + str="Monthly Agent Commission type"; + break; + case DEAL_TYPE_INTEREST: + str="Interest Rate type"; + break; + case DEAL_TYPE_BUY_CANCELED: + str="Canceled Buy type"; + break; + case DEAL_TYPE_SELL_CANCELED: + str="Canceled Sell type"; + break; + default: + str="Unknown type"; + } +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_ENTRY" | +//+------------------------------------------------------------------+ +ENUM_DEAL_ENTRY CDealInfo::Entry(void) const + { + return((ENUM_DEAL_ENTRY)HistoryDealGetInteger(m_ticket,DEAL_ENTRY)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_ENTRY" as string | +//+------------------------------------------------------------------+ +string CDealInfo::EntryDescription(void) const + { + string str; +//--- + switch(CDealInfo::Entry()) + { + case DEAL_ENTRY_IN: + str="In entry"; + break; + case DEAL_ENTRY_OUT: + str="Out entry"; + break; + case DEAL_ENTRY_INOUT: + str="InOut entry"; + break; + case DEAL_ENTRY_STATE: + str="Status record"; + break; + case DEAL_ENTRY_OUT_BY: + str="Out By entry"; + break; + default: + str="Unknown entry"; + } +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_MAGIC" | +//+------------------------------------------------------------------+ +long CDealInfo::Magic(void) const + { + return(HistoryDealGetInteger(m_ticket,DEAL_MAGIC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_POSITION_ID" | +//+------------------------------------------------------------------+ +long CDealInfo::PositionId(void) const + { + return(HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_VOLUME" | +//+------------------------------------------------------------------+ +double CDealInfo::Volume(void) const + { + return(HistoryDealGetDouble(m_ticket,DEAL_VOLUME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_PRICE_OPEN" | +//+------------------------------------------------------------------+ +double CDealInfo::Price(void) const + { + return(HistoryDealGetDouble(m_ticket,DEAL_PRICE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_COMMISSION" | +//+------------------------------------------------------------------+ +double CDealInfo::Commission(void) const + { + return(HistoryDealGetDouble(m_ticket,DEAL_COMMISSION)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_SWAP" | +//+------------------------------------------------------------------+ +double CDealInfo::Swap(void) const + { + return(HistoryDealGetDouble(m_ticket,DEAL_SWAP)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_PROFIT" | +//+------------------------------------------------------------------+ +double CDealInfo::Profit(void) const + { + return(HistoryDealGetDouble(m_ticket,DEAL_PROFIT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_SYMBOL" | +//+------------------------------------------------------------------+ +string CDealInfo::Symbol(void) const + { + return(HistoryDealGetString(m_ticket,DEAL_SYMBOL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_COMMENT" | +//+------------------------------------------------------------------+ +string CDealInfo::Comment(void) const + { + return(HistoryDealGetString(m_ticket,DEAL_COMMENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "DEAL_EXTERNAL_ID" | +//+------------------------------------------------------------------+ +string CDealInfo::ExternalId(void) const + { + return(HistoryDealGetString(m_ticket,DEAL_EXTERNAL_ID)); + } +//+------------------------------------------------------------------+ +//| Access functions HistoryDealGetInteger(...) | +//+------------------------------------------------------------------+ +bool CDealInfo::InfoInteger(ENUM_DEAL_PROPERTY_INTEGER prop_id,long &var) const + { + return(HistoryDealGetInteger(m_ticket,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions HistoryDealGetDouble(...) | +//+------------------------------------------------------------------+ +bool CDealInfo::InfoDouble(ENUM_DEAL_PROPERTY_DOUBLE prop_id,double &var) const + { + return(HistoryDealGetDouble(m_ticket,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions HistoryDealGetString(...) | +//+------------------------------------------------------------------+ +bool CDealInfo::InfoString(ENUM_DEAL_PROPERTY_STRING prop_id,string &var) const + { + return(HistoryDealGetString(m_ticket,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Converths the type of a deal to text | +//+------------------------------------------------------------------+ +string CDealInfo::FormatAction(string &str,const uint action) const + { +//--- see the type + switch(action) + { + case DEAL_TYPE_BUY: + str="buy"; + break; + case DEAL_TYPE_SELL: + str="sell"; + break; + case DEAL_TYPE_BALANCE: + str="balance"; + break; + case DEAL_TYPE_CREDIT: + str="credit"; + break; + case DEAL_TYPE_CHARGE: + str="charge"; + break; + case DEAL_TYPE_CORRECTION: + str="correction"; + break; + case DEAL_TYPE_BONUS: + str="bonus"; + break; + case DEAL_TYPE_COMMISSION: + str="commission"; + break; + case DEAL_TYPE_COMMISSION_DAILY: + str="daily commission"; + break; + case DEAL_TYPE_COMMISSION_MONTHLY: + str="monthly commission"; + break; + case DEAL_TYPE_COMMISSION_AGENT_DAILY: + str="daily agent commission"; + break; + case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: + str="monthly agent commission"; + break; + case DEAL_TYPE_INTEREST: + str="interest rate"; + break; + case DEAL_TYPE_BUY_CANCELED: + str="canceled buy"; + break; + case DEAL_TYPE_SELL_CANCELED: + str="canceled sell"; + break; + default: + str="unknown deal type "+(string)action; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the deal direction to text | +//+------------------------------------------------------------------+ +string CDealInfo::FormatEntry(string &str,const uint entry) const + { +//--- see the type + switch(entry) + { + case DEAL_ENTRY_IN: + str="in"; + break; + case DEAL_ENTRY_OUT: + str="out"; + break; + case DEAL_ENTRY_INOUT: + str="in/out"; + break; + case DEAL_ENTRY_OUT_BY: + str="out by"; + break; + default: + str="unknown deal entry "+(string)entry; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the deal parameters to text | +//+------------------------------------------------------------------+ +string CDealInfo::FormatDeal(string &str) const + { + string type; + long tmp_long; +//--- set up + string symbol_name=this.Symbol(); + int digits=_Digits; + if(SymbolInfoInteger(symbol_name,SYMBOL_DIGITS,tmp_long)) + digits=(int)tmp_long; +//--- form the description of the deal + switch(DealType()) + { + //--- Buy-Sell + case DEAL_TYPE_BUY: + case DEAL_TYPE_SELL: + str=StringFormat("#%I64u %s %s %s at %s", + Ticket(), + FormatAction(type,DealType()), + DoubleToString(Volume(),2), + symbol_name, + DoubleToString(Price(),digits)); + break; + + //--- balance operations + case DEAL_TYPE_BALANCE: + case DEAL_TYPE_CREDIT: + case DEAL_TYPE_CHARGE: + case DEAL_TYPE_CORRECTION: + case DEAL_TYPE_BONUS: + case DEAL_TYPE_COMMISSION: + case DEAL_TYPE_COMMISSION_DAILY: + case DEAL_TYPE_COMMISSION_MONTHLY: + case DEAL_TYPE_COMMISSION_AGENT_DAILY: + case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: + case DEAL_TYPE_INTEREST: + str=StringFormat("#%I64u %s %s [%s]", + Ticket(), + FormatAction(type,DealType()), + DoubleToString(Profit(),2), + this.Comment()); + break; + + default: + str="unknown deal type "+(string)DealType(); + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Select a deal on the index | +//+------------------------------------------------------------------+ +bool CDealInfo::SelectByIndex(const int index) + { + ulong ticket=HistoryDealGetTicket(index); + if(ticket==0) + return(false); + Ticket(ticket); +//--- + return(true); + } +//+------------------------------------------------------------------+ diff --git a/Trade/HistoryOrderInfo.mqh b/Trade/HistoryOrderInfo.mqh new file mode 100644 index 0000000..3fb5ea1 --- /dev/null +++ b/Trade/HistoryOrderInfo.mqh @@ -0,0 +1,472 @@ +//+------------------------------------------------------------------+ +//| HistoryOrderInfo.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CHistoryOrderInfo. | +//| Appointment: Class for access to history order info. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CHistoryOrderInfo : public CObject + { +protected: + ulong m_ticket; // ticket of history order +public: + CHistoryOrderInfo(void); + ~CHistoryOrderInfo(void); + //--- methods of access to protected data + void Ticket(const ulong ticket) { m_ticket=ticket; } + ulong Ticket(void) const { return(m_ticket); } + //--- fast access methods to the integer order propertyes + datetime TimeSetup(void) const; + ulong TimeSetupMsc(void) const; + datetime TimeDone(void) const; + ulong TimeDoneMsc(void) const; + ENUM_ORDER_TYPE OrderType(void) const; + string TypeDescription(void) const; + ENUM_ORDER_STATE State(void) const; + string StateDescription(void) const; + datetime TimeExpiration(void) const; + ENUM_ORDER_TYPE_FILLING TypeFilling(void) const; + string TypeFillingDescription(void) const; + ENUM_ORDER_TYPE_TIME TypeTime(void) const; + string TypeTimeDescription(void) const; + long Magic(void) const; + long PositionId(void) const; + long PositionById(void) const; + //--- fast access methods to the double order propertyes + double VolumeInitial(void) const; + double VolumeCurrent(void) const; + double PriceOpen(void) const; + double StopLoss(void) const; + double TakeProfit(void) const; + double PriceCurrent(void) const; + double PriceStopLimit(void) const; + //--- fast access methods to the string order propertyes + string Symbol(void) const; + string Comment(void) const; + string ExternalId(void) const; + //--- access methods to the API MQL5 functions + bool InfoInteger(const ENUM_ORDER_PROPERTY_INTEGER prop_id,long &var) const; + bool InfoDouble(const ENUM_ORDER_PROPERTY_DOUBLE prop_id,double &var) const; + bool InfoString(const ENUM_ORDER_PROPERTY_STRING prop_id,string &var) const; + //--- info methods + string FormatType(string &str,const uint type) const; + string FormatStatus(string &str,const uint status) const; + string FormatTypeFilling(string &str,const uint type) const; + string FormatTypeTime(string &str,const uint type) const; + string FormatOrder(string &str) const; + string FormatPrice(string &str,const double price_order,const double price_trigger,const uint digits) const; + //--- method for select history order + bool SelectByIndex(const int index); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CHistoryOrderInfo::CHistoryOrderInfo(void) : m_ticket(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CHistoryOrderInfo::~CHistoryOrderInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_SETUP" | +//+------------------------------------------------------------------+ +datetime CHistoryOrderInfo::TimeSetup(void) const + { + return((datetime)HistoryOrderGetInteger(m_ticket,ORDER_TIME_SETUP)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_SETUP_MSC" | +//+------------------------------------------------------------------+ +ulong CHistoryOrderInfo::TimeSetupMsc(void) const + { + return(HistoryOrderGetInteger(m_ticket,ORDER_TIME_SETUP_MSC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_DONE" | +//+------------------------------------------------------------------+ +datetime CHistoryOrderInfo::TimeDone(void) const + { + return((datetime)HistoryOrderGetInteger(m_ticket,ORDER_TIME_DONE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_DONE_MSC" | +//+------------------------------------------------------------------+ +ulong CHistoryOrderInfo::TimeDoneMsc(void) const + { + return(HistoryOrderGetInteger(m_ticket,ORDER_TIME_DONE_MSC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE" | +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE CHistoryOrderInfo::OrderType(void) const + { + return((ENUM_ORDER_TYPE)HistoryOrderGetInteger(m_ticket,ORDER_TYPE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE" as string | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::TypeDescription(void) const + { + string str; +//--- + return(FormatType(str,OrderType())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_STATE" | +//+------------------------------------------------------------------+ +ENUM_ORDER_STATE CHistoryOrderInfo::State(void) const + { + return((ENUM_ORDER_STATE)HistoryOrderGetInteger(m_ticket,ORDER_STATE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_STATE" as string | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::StateDescription(void) const + { + string str; +//--- + return(FormatStatus(str,State())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_EXPIRATION" | +//+------------------------------------------------------------------+ +datetime CHistoryOrderInfo::TimeExpiration(void) const + { + return((datetime)HistoryOrderGetInteger(m_ticket,ORDER_TIME_EXPIRATION)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_FILLING" | +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE_FILLING CHistoryOrderInfo::TypeFilling(void) const + { + return((ENUM_ORDER_TYPE_FILLING)HistoryOrderGetInteger(m_ticket,ORDER_TYPE_FILLING)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_FILLING" as string | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::TypeFillingDescription(void) const + { + string str; +//--- + return(FormatTypeFilling(str,TypeFilling())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_TIME" | +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE_TIME CHistoryOrderInfo::TypeTime(void) const + { + return((ENUM_ORDER_TYPE_TIME)HistoryOrderGetInteger(m_ticket,ORDER_TYPE_TIME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_TIME" as string | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::TypeTimeDescription(void) const + { + string str; +//--- + return(FormatTypeTime(str,TypeTime())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_EXPERT" | +//+------------------------------------------------------------------+ +long CHistoryOrderInfo::Magic(void) const + { + return(HistoryOrderGetInteger(m_ticket,ORDER_MAGIC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_POSITION_ID" | +//+------------------------------------------------------------------+ +long CHistoryOrderInfo::PositionId(void) const + { + return(HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_POSITION_BY_ID" | +//+------------------------------------------------------------------+ +long CHistoryOrderInfo::PositionById(void) const + { + return(HistoryOrderGetInteger(m_ticket,ORDER_POSITION_BY_ID)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_VOLUME_INITIAL" | +//+------------------------------------------------------------------+ +double CHistoryOrderInfo::VolumeInitial(void) const + { + return(HistoryOrderGetDouble(m_ticket,ORDER_VOLUME_INITIAL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_VOLUME_CURRENT" | +//+------------------------------------------------------------------+ +double CHistoryOrderInfo::VolumeCurrent(void) const + { + return(HistoryOrderGetDouble(m_ticket,ORDER_VOLUME_CURRENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_PRICE_OPEN" | +//+------------------------------------------------------------------+ +double CHistoryOrderInfo::PriceOpen(void) const + { + return(HistoryOrderGetDouble(m_ticket,ORDER_PRICE_OPEN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_SL" | +//+------------------------------------------------------------------+ +double CHistoryOrderInfo::StopLoss(void) const + { + return(HistoryOrderGetDouble(m_ticket,ORDER_SL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TP" | +//+------------------------------------------------------------------+ +double CHistoryOrderInfo::TakeProfit(void) const + { + return(HistoryOrderGetDouble(m_ticket,ORDER_TP)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_PRICE_CURRENT" | +//+------------------------------------------------------------------+ +double CHistoryOrderInfo::PriceCurrent(void) const + { + return(HistoryOrderGetDouble(m_ticket,ORDER_PRICE_CURRENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_PRICE_STOPLIMIT" | +//+------------------------------------------------------------------+ +double CHistoryOrderInfo::PriceStopLimit(void) const + { + return(HistoryOrderGetDouble(m_ticket,ORDER_PRICE_STOPLIMIT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_SYMBOL" | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::Symbol(void) const + { + return(HistoryOrderGetString(m_ticket,ORDER_SYMBOL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_COMMENT" | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::Comment(void) const + { + return(HistoryOrderGetString(m_ticket,ORDER_COMMENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_EXTERNAL_ID" | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::ExternalId(void) const + { + return(HistoryOrderGetString(m_ticket,ORDER_EXTERNAL_ID)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderGetInteger(...) | +//+------------------------------------------------------------------+ +bool CHistoryOrderInfo::InfoInteger(const ENUM_ORDER_PROPERTY_INTEGER prop_id,long &var) const + { + return(HistoryOrderGetInteger(m_ticket,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderGetDouble(...) | +//+------------------------------------------------------------------+ +bool CHistoryOrderInfo::InfoDouble(const ENUM_ORDER_PROPERTY_DOUBLE prop_id,double &var) const + { + return(HistoryOrderGetDouble(m_ticket,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderGetString(...) | +//+------------------------------------------------------------------+ +bool CHistoryOrderInfo::InfoString(const ENUM_ORDER_PROPERTY_STRING prop_id,string &var) const + { + return(HistoryOrderGetString(m_ticket,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Converts the order type to text | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::FormatType(string &str,const uint type) const + { +//--- see the type + switch(type) + { + case ORDER_TYPE_BUY: + str="buy"; + break; + case ORDER_TYPE_SELL: + str="sell"; + break; + case ORDER_TYPE_BUY_LIMIT: + str="buy limit"; + break; + case ORDER_TYPE_SELL_LIMIT: + str="sell limit"; + break; + case ORDER_TYPE_BUY_STOP: + str="buy stop"; + break; + case ORDER_TYPE_SELL_STOP: + str="sell stop"; + break; + case ORDER_TYPE_BUY_STOP_LIMIT: + str="buy stop limit"; + break; + case ORDER_TYPE_SELL_STOP_LIMIT: + str="sell stop limit"; + break; + case ORDER_TYPE_CLOSE_BY: + str="close by"; + break; + default: + str="unknown order type "+(string)type; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order status to text | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::FormatStatus(string &str,const uint status) const + { +//--- see the type + switch(status) + { + case ORDER_STATE_STARTED: + str="started"; + break; + case ORDER_STATE_PLACED: + str="placed"; + break; + case ORDER_STATE_CANCELED: + str="canceled"; + break; + case ORDER_STATE_PARTIAL: + str="partial"; + break; + case ORDER_STATE_FILLED: + str="filled"; + break; + case ORDER_STATE_REJECTED: + str="rejected"; + break; + case ORDER_STATE_EXPIRED: + str="expired"; + break; + default: + str="unknown order status "+(string)status; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order filling type to text | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::FormatTypeFilling(string &str,const uint type) const + { +//--- see the type + switch(type) + { + case ORDER_FILLING_RETURN: + str="return remainder"; + break; + case ORDER_FILLING_IOC: + str="cancel remainder"; + break; + case ORDER_FILLING_FOK: + str="fill or kill"; + break; + default: + str="unknown type filling "+(string)type; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the type of order by expiration to text | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::FormatTypeTime(string &str,const uint type) const + { +//--- see the type + switch(type) + { + case ORDER_TIME_GTC: + str="gtc"; + break; + case ORDER_TIME_DAY: + str="day"; + break; + case ORDER_TIME_SPECIFIED: + str="specified"; + break; + case ORDER_TIME_SPECIFIED_DAY: + str="specified day"; + break; + default: + str="unknown type time "+(string)type; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order parameters to text | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::FormatOrder(string &str) const + { + string type,price; + long tmp_long; +//--- set up + string symbol_name=this.Symbol(); + int digits=_Digits; + if(SymbolInfoInteger(symbol_name,SYMBOL_DIGITS,tmp_long)) + digits=(int)tmp_long; +//--- form the order description + str=StringFormat("#%I64u %s %s %s", + Ticket(), + FormatType(type,OrderType()), + DoubleToString(VolumeInitial(),2), + symbol_name); +//--- receive the price of the order + FormatPrice(price,PriceOpen(),PriceStopLimit(),digits); +//--- if there is price, write it + if(price!="") + { + str+=" at "; + str+=price; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order prices to text | +//+------------------------------------------------------------------+ +string CHistoryOrderInfo::FormatPrice(string &str,const double price_order,const double price_trigger,const uint digits) const + { + string price,trigger; +//--- Is there its trigger price? + if(price_trigger) + { + price =DoubleToString(price_order,digits); + trigger=DoubleToString(price_trigger,digits); + str =StringFormat("%s (%s)",price,trigger); + } + else + str=DoubleToString(price_order,digits); +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Select a history order on the index | +//+------------------------------------------------------------------+ +bool CHistoryOrderInfo::SelectByIndex(const int index) + { + ulong ticket=HistoryOrderGetTicket(index); + if(ticket==0) + return(false); + Ticket(ticket); +//--- + return(true); + } +//+------------------------------------------------------------------+ diff --git a/Trade/MarketBook.mqh b/Trade/MarketBook.mqh new file mode 100644 index 0000000..059280e Binary files /dev/null and b/Trade/MarketBook.mqh differ diff --git a/Trade/OrderInfo.mqh b/Trade/OrderInfo.mqh new file mode 100644 index 0000000..7c237cf --- /dev/null +++ b/Trade/OrderInfo.mqh @@ -0,0 +1,553 @@ +//+------------------------------------------------------------------+ +//| OrderInfo.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class COrderInfo. | +//| Appointment: Class for access to order info. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class COrderInfo : public CObject + { +protected: + ulong m_ticket; + ENUM_ORDER_TYPE m_type; + ENUM_ORDER_STATE m_state; + datetime m_expiration; + double m_volume_curr; + double m_price_open; + double m_stop_loss; + double m_take_profit; + +public: + COrderInfo(void); + ~COrderInfo(void); + //--- methods of access to protected data + ulong Ticket(void) const { return(m_ticket); } + //--- fast access methods to the integer order propertyes + datetime TimeSetup(void) const; + ulong TimeSetupMsc(void) const; + datetime TimeDone(void) const; + ulong TimeDoneMsc(void) const; + ENUM_ORDER_TYPE OrderType(void) const; + string TypeDescription(void) const; + ENUM_ORDER_STATE State(void) const; + string StateDescription(void) const; + datetime TimeExpiration(void) const; + ENUM_ORDER_TYPE_FILLING TypeFilling(void) const; + string TypeFillingDescription(void) const; + ENUM_ORDER_TYPE_TIME TypeTime(void) const; + string TypeTimeDescription(void) const; + long Magic(void) const; + long PositionId(void) const; + long PositionById(void) const; + //--- fast access methods to the double order propertyes + double VolumeInitial(void) const; + double VolumeCurrent(void) const; + double PriceOpen(void) const; + double StopLoss(void) const; + double TakeProfit(void) const; + double PriceCurrent(void) const; + double PriceStopLimit(void) const; + //--- fast access methods to the string order propertyes + string Symbol(void) const; + string Comment(void) const; + string ExternalId(void) const; + //--- access methods to the API MQL5 functions + bool InfoInteger(const ENUM_ORDER_PROPERTY_INTEGER prop_id,long &var) const; + bool InfoDouble(const ENUM_ORDER_PROPERTY_DOUBLE prop_id,double &var) const; + bool InfoString(const ENUM_ORDER_PROPERTY_STRING prop_id,string &var) const; + //--- info methods + string FormatType(string &str,const uint type) const; + string FormatStatus(string &str,const uint status) const; + string FormatTypeFilling(string &str,const uint type) const; + string FormatTypeTime(string &str,const uint type) const; + string FormatOrder(string &str) const; + string FormatPrice(string &str,const double price_order,const double price_trigger,const uint digits) const; + //--- method for select order + bool Select(void); + bool Select(const ulong ticket); + bool SelectByIndex(const int index); + //--- additional methods + void StoreState(void); + bool CheckState(void); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +COrderInfo::COrderInfo(void) : m_ticket(ULONG_MAX), + m_type(WRONG_VALUE), + m_state(WRONG_VALUE), + m_expiration(0), + m_volume_curr(0.0), + m_price_open(0.0), + m_stop_loss(0.0), + m_take_profit(0.0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +COrderInfo::~COrderInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_SETUP" | +//+------------------------------------------------------------------+ +datetime COrderInfo::TimeSetup(void) const + { + return((datetime)OrderGetInteger(ORDER_TIME_SETUP)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_SETUP_MSC" | +//+------------------------------------------------------------------+ +ulong COrderInfo::TimeSetupMsc(void) const + { + return(OrderGetInteger(ORDER_TIME_SETUP_MSC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_DONE" | +//+------------------------------------------------------------------+ +datetime COrderInfo::TimeDone(void) const + { + return((datetime)OrderGetInteger(ORDER_TIME_DONE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_DONE_MSC" | +//+------------------------------------------------------------------+ +ulong COrderInfo::TimeDoneMsc(void) const + { + return(OrderGetInteger(ORDER_TIME_DONE_MSC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE" | +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE COrderInfo::OrderType(void) const + { + return((ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE" as string | +//+------------------------------------------------------------------+ +string COrderInfo::TypeDescription(void) const + { + string str; +//--- + return(FormatType(str,OrderType())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_STATE" | +//+------------------------------------------------------------------+ +ENUM_ORDER_STATE COrderInfo::State(void) const + { + return((ENUM_ORDER_STATE)OrderGetInteger(ORDER_STATE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_STATE" as string | +//+------------------------------------------------------------------+ +string COrderInfo::StateDescription(void) const + { + string str; +//--- + return(FormatStatus(str,State())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TIME_EXPIRATION" | +//+------------------------------------------------------------------+ +datetime COrderInfo::TimeExpiration(void) const + { + return((datetime)OrderGetInteger(ORDER_TIME_EXPIRATION)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_FILLING" | +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE_FILLING COrderInfo::TypeFilling(void) const + { + return((ENUM_ORDER_TYPE_FILLING)OrderGetInteger(ORDER_TYPE_FILLING)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_FILLING" as string | +//+------------------------------------------------------------------+ +string COrderInfo::TypeFillingDescription(void) const + { + string str; +//--- + return(FormatTypeFilling(str,TypeFilling())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_TIME" | +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE_TIME COrderInfo::TypeTime(void) const + { + return((ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TYPE_TIME" as string | +//+------------------------------------------------------------------+ +string COrderInfo::TypeTimeDescription(void) const + { + string str; +//--- + return(FormatTypeTime(str,TypeFilling())); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_MAGIC" | +//+------------------------------------------------------------------+ +long COrderInfo::Magic(void) const + { + return(OrderGetInteger(ORDER_MAGIC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_POSITION_ID" | +//+------------------------------------------------------------------+ +long COrderInfo::PositionId(void) const + { + return(OrderGetInteger(ORDER_POSITION_ID)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_POSITION_BY_ID" | +//+------------------------------------------------------------------+ +long COrderInfo::PositionById(void) const + { + return(OrderGetInteger(ORDER_POSITION_BY_ID)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_VOLUME_INITIAL" | +//+------------------------------------------------------------------+ +double COrderInfo::VolumeInitial(void) const + { + return(OrderGetDouble(ORDER_VOLUME_INITIAL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_VOLUME_CURRENT" | +//+------------------------------------------------------------------+ +double COrderInfo::VolumeCurrent(void) const + { + return(OrderGetDouble(ORDER_VOLUME_CURRENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_PRICE_OPEN" | +//+------------------------------------------------------------------+ +double COrderInfo::PriceOpen(void) const + { + return(OrderGetDouble(ORDER_PRICE_OPEN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_SL" | +//+------------------------------------------------------------------+ +double COrderInfo::StopLoss(void) const + { + return(OrderGetDouble(ORDER_SL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_TP" | +//+------------------------------------------------------------------+ +double COrderInfo::TakeProfit(void) const + { + return(OrderGetDouble(ORDER_TP)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_PRICE_CURRENT" | +//+------------------------------------------------------------------+ +double COrderInfo::PriceCurrent(void) const + { + return(OrderGetDouble(ORDER_PRICE_CURRENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_PRICE_STOPLIMIT" | +//+------------------------------------------------------------------+ +double COrderInfo::PriceStopLimit(void) const + { + return(OrderGetDouble(ORDER_PRICE_STOPLIMIT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_SYMBOL" | +//+------------------------------------------------------------------+ +string COrderInfo::Symbol(void) const + { + return(OrderGetString(ORDER_SYMBOL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_COMMENT" | +//+------------------------------------------------------------------+ +string COrderInfo::Comment(void) const + { + return(OrderGetString(ORDER_COMMENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "ORDER_EXTERNAL_ID" | +//+------------------------------------------------------------------+ +string COrderInfo::ExternalId(void) const + { + return(OrderGetString(ORDER_EXTERNAL_ID)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderGetInteger(...) | +//+------------------------------------------------------------------+ +bool COrderInfo::InfoInteger(const ENUM_ORDER_PROPERTY_INTEGER prop_id,long &var) const + { + return(OrderGetInteger(prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderGetDouble(...) | +//+------------------------------------------------------------------+ +bool COrderInfo::InfoDouble(const ENUM_ORDER_PROPERTY_DOUBLE prop_id,double &var) const + { + return(OrderGetDouble(prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions OrderGetString(...) | +//+------------------------------------------------------------------+ +bool COrderInfo::InfoString(const ENUM_ORDER_PROPERTY_STRING prop_id,string &var) const + { + return(OrderGetString(prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Converts the order type to text | +//+------------------------------------------------------------------+ +string COrderInfo::FormatType(string &str,const uint type) const + { +//--- see the type + switch(type) + { + case ORDER_TYPE_BUY: + str="buy"; + break; + case ORDER_TYPE_SELL: + str="sell"; + break; + case ORDER_TYPE_BUY_LIMIT: + str="buy limit"; + break; + case ORDER_TYPE_SELL_LIMIT: + str="sell limit"; + break; + case ORDER_TYPE_BUY_STOP: + str="buy stop"; + break; + case ORDER_TYPE_SELL_STOP: + str="sell stop"; + break; + case ORDER_TYPE_BUY_STOP_LIMIT: + str="buy stop limit"; + break; + case ORDER_TYPE_SELL_STOP_LIMIT: + str="sell stop limit"; + break; + case ORDER_TYPE_CLOSE_BY: + str="close by"; + break; + default : + str="unknown order type "+(string)type; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order status to text | +//+------------------------------------------------------------------+ +string COrderInfo::FormatStatus(string &str,const uint status) const + { +//--- see the type + switch(status) + { + case ORDER_STATE_STARTED: + str="started"; + break; + case ORDER_STATE_PLACED: + str="placed"; + break; + case ORDER_STATE_CANCELED: + str="canceled"; + break; + case ORDER_STATE_PARTIAL: + str="partial"; + break; + case ORDER_STATE_FILLED: + str="filled"; + break; + case ORDER_STATE_REJECTED: + str="rejected"; + break; + case ORDER_STATE_EXPIRED: + str="expired"; + break; + case ORDER_STATE_REQUEST_ADD: + str="request adding"; + break; + case ORDER_STATE_REQUEST_MODIFY: + str="request modifying"; + break; + case ORDER_STATE_REQUEST_CANCEL: + str="request cancelling"; + break; + default : + str="unknown order status "+(string)status; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order filling type to text | +//+------------------------------------------------------------------+ +string COrderInfo::FormatTypeFilling(string &str,const uint type) const + { +//--- see the type + switch(type) + { + case ORDER_FILLING_RETURN: + str="return remainder"; + break; + case ORDER_FILLING_IOC: + str="cancel remainder"; + break; + case ORDER_FILLING_FOK: + str="fill or kill"; + break; + default: + str="unknown type filling "+(string)type; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the type of order by expiration to text | +//+------------------------------------------------------------------+ +string COrderInfo::FormatTypeTime(string &str,const uint type) const + { +//--- see the type + switch(type) + { + case ORDER_TIME_GTC: + str="gtc"; + break; + case ORDER_TIME_DAY: + str="day"; + break; + case ORDER_TIME_SPECIFIED: + str="specified"; + break; + case ORDER_TIME_SPECIFIED_DAY: + str="specified day"; + break; + default: + str="unknown type time "+(string)type; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order parameters to text | +//+------------------------------------------------------------------+ +string COrderInfo::FormatOrder(string &str) const + { + string type,price; + long tmp_long; +//--- set up + string symbol_name=this.Symbol(); + int digits=_Digits; + if(SymbolInfoInteger(symbol_name,SYMBOL_DIGITS,tmp_long)) + digits=(int)tmp_long; +//--- form the order description + str=StringFormat("#%I64u %s %s %s", + Ticket(), + FormatType(type,OrderType()), + DoubleToString(VolumeInitial(),2), + symbol_name); +//--- receive the price of the order + FormatPrice(price,PriceOpen(),PriceStopLimit(),digits); +//--- if there is price, write it + if(price!="") + { + str+=" at "; + str+=price; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the order prices to text | +//+------------------------------------------------------------------+ +string COrderInfo::FormatPrice(string &str,const double price_order,const double price_trigger,const uint digits) const + { + string price,trigger; +//--- Is there its trigger price? + if(price_trigger) + { + price =DoubleToString(price_order,digits); + trigger=DoubleToString(price_trigger,digits); + str =StringFormat("%s (%s)",price,trigger); + } + else + str=DoubleToString(price_order,digits); +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Selecting an order to access | +//+------------------------------------------------------------------+ +bool COrderInfo::Select(void) + { + return(OrderSelect(m_ticket)); + } +//+------------------------------------------------------------------+ +//| Selecting an order to access | +//+------------------------------------------------------------------+ +bool COrderInfo::Select(const ulong ticket) + { + if(OrderSelect(ticket)) + { + m_ticket=ticket; + return(true); + } + m_ticket=ULONG_MAX; +//--- + return(false); + } +//+------------------------------------------------------------------+ +//| Select an order by the index | +//+------------------------------------------------------------------+ +bool COrderInfo::SelectByIndex(const int index) + { + ulong ticket=OrderGetTicket(index); + if(ticket==0) + { + m_ticket=ULONG_MAX; + return(false); + } + m_ticket=ticket; +//--- + return(true); + } +//+------------------------------------------------------------------+ +//| Stored order's current state | +//+------------------------------------------------------------------+ +void COrderInfo::StoreState(void) + { + m_type =OrderType(); + m_state =State(); + m_expiration =TimeExpiration(); + m_volume_curr=VolumeCurrent(); + m_price_open =PriceOpen(); + m_stop_loss =StopLoss(); + m_take_profit=TakeProfit(); + } +//+------------------------------------------------------------------+ +//| Check order change | +//+------------------------------------------------------------------+ +bool COrderInfo::CheckState(void) + { + if(m_type==OrderType() && + m_state==State() && + m_expiration==TimeExpiration() && + m_volume_curr==VolumeCurrent() && + m_price_open==PriceOpen() && + m_stop_loss==StopLoss() && + m_take_profit==TakeProfit()) + return(false); +//--- + return(true); + } +//+------------------------------------------------------------------+ diff --git a/Trade/PositionInfo.mqh b/Trade/PositionInfo.mqh new file mode 100644 index 0000000..642c32e --- /dev/null +++ b/Trade/PositionInfo.mqh @@ -0,0 +1,364 @@ +//+------------------------------------------------------------------+ +//| PositionInfo.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CPositionInfo. | +//| Appointment: Class for access to position info. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CPositionInfo : public CObject + { +protected: + ENUM_POSITION_TYPE m_type; + double m_volume; + double m_price; + double m_stop_loss; + double m_take_profit; + +public: + CPositionInfo(void); + ~CPositionInfo(void); + //--- fast access methods to the integer position propertyes + ulong Ticket(void) const; + datetime Time(void) const; + ulong TimeMsc(void) const; + datetime TimeUpdate(void) const; + ulong TimeUpdateMsc(void) const; + ENUM_POSITION_TYPE PositionType(void) const; + string TypeDescription(void) const; + long Magic(void) const; + long Identifier(void) const; + //--- fast access methods to the double position propertyes + double Volume(void) const; + double PriceOpen(void) const; + double StopLoss(void) const; + double TakeProfit(void) const; + double PriceCurrent(void) const; + double Commission(void) const; + double Swap(void) const; + double Profit(void) const; + //--- fast access methods to the string position propertyes + string Symbol(void) const; + string Comment(void) const; + //--- access methods to the API MQL5 functions + bool InfoInteger(const ENUM_POSITION_PROPERTY_INTEGER prop_id,long &var) const; + bool InfoDouble(const ENUM_POSITION_PROPERTY_DOUBLE prop_id,double &var) const; + bool InfoString(const ENUM_POSITION_PROPERTY_STRING prop_id,string &var) const; + //--- info methods + string FormatType(string &str,const uint type) const; + string FormatPosition(string &str) const; + //--- methods for select position + bool Select(const string symbol); + bool SelectByMagic(const string symbol,const ulong magic); + bool SelectByTicket(const ulong ticket); + bool SelectByIndex(const int index); + //--- + void StoreState(void); + bool CheckState(void); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CPositionInfo::CPositionInfo(void) : m_type(WRONG_VALUE), + m_volume(0.0), + m_price(0.0), + m_stop_loss(0.0), + m_take_profit(0.0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CPositionInfo::~CPositionInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TICKET" | +//+------------------------------------------------------------------+ +ulong CPositionInfo::Ticket(void) const + { + return((ulong)PositionGetInteger(POSITION_TICKET)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TIME" | +//+------------------------------------------------------------------+ +datetime CPositionInfo::Time(void) const + { + return((datetime)PositionGetInteger(POSITION_TIME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TIME_MSC" | +//+------------------------------------------------------------------+ +ulong CPositionInfo::TimeMsc(void) const + { + return((ulong)PositionGetInteger(POSITION_TIME_MSC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TIME_UPDATE" | +//+------------------------------------------------------------------+ +datetime CPositionInfo::TimeUpdate(void) const + { + return((datetime)PositionGetInteger(POSITION_TIME_UPDATE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TIME_UPDATE_MSC" | +//+------------------------------------------------------------------+ +ulong CPositionInfo::TimeUpdateMsc(void) const + { + return((ulong)PositionGetInteger(POSITION_TIME_UPDATE_MSC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TYPE" | +//+------------------------------------------------------------------+ +ENUM_POSITION_TYPE CPositionInfo::PositionType(void) const + { + return((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TYPE" as string | +//+------------------------------------------------------------------+ +string CPositionInfo::TypeDescription(void) const + { + string str; +//--- + return(FormatType(str,PositionType())); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_MAGIC" | +//+------------------------------------------------------------------+ +long CPositionInfo::Magic(void) const + { + return(PositionGetInteger(POSITION_MAGIC)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_IDENTIFIER" | +//+------------------------------------------------------------------+ +long CPositionInfo::Identifier(void) const + { + return(PositionGetInteger(POSITION_IDENTIFIER)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_VOLUME" | +//+------------------------------------------------------------------+ +double CPositionInfo::Volume(void) const + { + return(PositionGetDouble(POSITION_VOLUME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_PRICE_OPEN" | +//+------------------------------------------------------------------+ +double CPositionInfo::PriceOpen(void) const + { + return(PositionGetDouble(POSITION_PRICE_OPEN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_SL" | +//+------------------------------------------------------------------+ +double CPositionInfo::StopLoss(void) const + { + return(PositionGetDouble(POSITION_SL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_TP" | +//+------------------------------------------------------------------+ +double CPositionInfo::TakeProfit(void) const + { + return(PositionGetDouble(POSITION_TP)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_PRICE_CURRENT" | +//+------------------------------------------------------------------+ +double CPositionInfo::PriceCurrent(void) const + { + return(PositionGetDouble(POSITION_PRICE_CURRENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_COMMISSION" | +//+------------------------------------------------------------------+ +double CPositionInfo::Commission(void) const + { + return(PositionGetDouble(POSITION_COMMISSION)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_SWAP" | +//+------------------------------------------------------------------+ +double CPositionInfo::Swap(void) const + { + return(PositionGetDouble(POSITION_SWAP)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_PROFIT" | +//+------------------------------------------------------------------+ +double CPositionInfo::Profit(void) const + { + return(PositionGetDouble(POSITION_PROFIT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_SYMBOL" | +//+------------------------------------------------------------------+ +string CPositionInfo::Symbol(void) const + { + return(PositionGetString(POSITION_SYMBOL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "POSITION_COMMENT" | +//+------------------------------------------------------------------+ +string CPositionInfo::Comment(void) const + { + return(PositionGetString(POSITION_COMMENT)); + } +//+------------------------------------------------------------------+ +//| Access functions PositionGetInteger(...) | +//+------------------------------------------------------------------+ +bool CPositionInfo::InfoInteger(const ENUM_POSITION_PROPERTY_INTEGER prop_id,long &var) const + { + return(PositionGetInteger(prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions PositionGetDouble(...) | +//+------------------------------------------------------------------+ +bool CPositionInfo::InfoDouble(const ENUM_POSITION_PROPERTY_DOUBLE prop_id,double &var) const + { + return(PositionGetDouble(prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions PositionGetString(...) | +//+------------------------------------------------------------------+ +bool CPositionInfo::InfoString(const ENUM_POSITION_PROPERTY_STRING prop_id,string &var) const + { + return(PositionGetString(prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Converts the position type to text | +//+------------------------------------------------------------------+ +string CPositionInfo::FormatType(string &str,const uint type) const + { +//--- see the type + switch(type) + { + case POSITION_TYPE_BUY: + str="buy"; + break; + case POSITION_TYPE_SELL: + str="sell"; + break; + default: + str="unknown position type "+(string)type; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Converts the position parameters to text | +//+------------------------------------------------------------------+ +string CPositionInfo::FormatPosition(string &str) const + { + string tmp,type; + long tmp_long; + ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); +//--- set up + string symbol_name=this.Symbol(); + int digits=_Digits; + if(SymbolInfoInteger(symbol_name,SYMBOL_DIGITS,tmp_long)) + digits=(int)tmp_long; +//--- form the position description + if(margin_mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) + str=StringFormat("#%I64u %s %s %s %s", + Ticket(), + FormatType(type,PositionType()), + DoubleToString(Volume(),2), + symbol_name, + DoubleToString(PriceOpen(),digits+3)); + else + str=StringFormat("%s %s %s %s", + FormatType(type,PositionType()), + DoubleToString(Volume(),2), + symbol_name, + DoubleToString(PriceOpen(),digits+3)); +//--- add stops if there are any + double sl=StopLoss(); + double tp=TakeProfit(); + if(sl!=0.0) + { + tmp=StringFormat(" sl: %s",DoubleToString(sl,digits)); + str+=tmp; + } + if(tp!=0.0) + { + tmp=StringFormat(" tp: %s",DoubleToString(tp,digits)); + str+=tmp; + } +//--- return the result + return(str); + } +//+------------------------------------------------------------------+ +//| Access functions PositionSelect(...) | +//+------------------------------------------------------------------+ +bool CPositionInfo::Select(const string symbol) + { + return(PositionSelect(symbol)); + } +//+------------------------------------------------------------------+ +//| Access functions PositionSelect(...) | +//+------------------------------------------------------------------+ +bool CPositionInfo::SelectByMagic(const string symbol,const ulong magic) + { + bool res=false; + uint total=PositionsTotal(); +//--- + for(uint i=0; i0); + } +//+------------------------------------------------------------------+ +//| Stored position's current state | +//+------------------------------------------------------------------+ +void CPositionInfo::StoreState(void) + { + m_type =PositionType(); + m_volume =Volume(); + m_price =PriceOpen(); + m_stop_loss =StopLoss(); + m_take_profit=TakeProfit(); + } +//+------------------------------------------------------------------+ +//| Check position change | +//+------------------------------------------------------------------+ +bool CPositionInfo::CheckState(void) + { + if(m_type==PositionType() && + m_volume==Volume() && + m_price==PriceOpen() && + m_stop_loss==StopLoss() && + m_take_profit==TakeProfit()) + return(false); +//--- + return(true); + } +//+------------------------------------------------------------------+ diff --git a/Trade/SymbolInfo.mqh b/Trade/SymbolInfo.mqh new file mode 100644 index 0000000..1b2da94 --- /dev/null +++ b/Trade/SymbolInfo.mqh @@ -0,0 +1,789 @@ +//+------------------------------------------------------------------+ +//| SymbolInfo.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CSymbolInfo. | +//| Appointment: Class for access to symbol info. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CSymbolInfo : public CObject + { +protected: + string m_name; // symbol name + MqlTick m_tick; // structure of tick; + double m_point; // symbol point + double m_tick_value; // symbol tick value + double m_tick_value_profit; // symbol tick value profit + double m_tick_value_loss; // symbol tick value loss + double m_tick_size; // symbol tick size + double m_contract_size; // symbol contract size + double m_lots_min; // symbol lots min + double m_lots_max; // symbol lots max + double m_lots_step; // symbol lots step + double m_lots_limit; // symbol lots limit + double m_swap_long; // symbol swap long + double m_swap_short; // symbol swap short + int m_digits; // symbol digits + int m_order_mode; // symbol valid orders + ENUM_SYMBOL_TRADE_EXECUTION m_trade_execution; // symbol trade execution + ENUM_SYMBOL_CALC_MODE m_trade_calcmode; // symbol trade calcmode + ENUM_SYMBOL_TRADE_MODE m_trade_mode; // symbol trade mode + ENUM_SYMBOL_SWAP_MODE m_swap_mode; // symbol swap mode + ENUM_DAY_OF_WEEK m_swap3; // symbol swap3 + double m_margin_initial; // symbol margin initial + double m_margin_maintenance; // symbol margin maintenance + bool m_margin_hedged_use_leg; // calculate hedged margin using larger leg + double m_margin_hedged; // symbol margin hedged + int m_trade_time_flags; // symbol trade time flags + int m_trade_fill_flags; // symbol trade fill flags + +public: + CSymbolInfo(void); + ~CSymbolInfo(void); + //--- methods of access to protected data + string Name(void) const { return(m_name); } + bool Name(const string name); + bool Refresh(void); + bool RefreshRates(void); + //--- fast access methods to the integer symbol propertyes + bool Select(void) const; + bool Select(const bool select); + bool IsSynchronized(void) const; + //--- volumes + ulong Volume(void) const { return(m_tick.volume); } + ulong VolumeHigh(void) const; + ulong VolumeLow(void) const; + //--- miscellaneous + datetime Time(void) const { return(m_tick.time); } + int Spread(void) const; + bool SpreadFloat(void) const; + int TicksBookDepth(void) const; + //--- trade levels + int StopsLevel(void) const; + int FreezeLevel(void) const; + //--- fast access methods to the double symbol propertyes + //--- bid parameters + double Bid(void) const { return(m_tick.bid); } + double BidHigh(void) const; + double BidLow(void) const; + //--- ask parameters + double Ask(void) const { return(m_tick.ask); } + double AskHigh(void) const; + double AskLow(void) const; + //--- last parameters + double Last(void) const { return(m_tick.last); } + double LastHigh(void) const; + double LastLow(void) const; + //--- fast access methods to the mix symbol propertyes + int OrderMode(void) const { return(m_order_mode); } + //--- terms of trade + ENUM_SYMBOL_CALC_MODE TradeCalcMode(void) const { return(m_trade_calcmode); } + string TradeCalcModeDescription(void) const; + ENUM_SYMBOL_TRADE_MODE TradeMode(void) const { return(m_trade_mode); } + string TradeModeDescription(void) const; + //--- execution terms of trade + ENUM_SYMBOL_TRADE_EXECUTION TradeExecution(void) const { return(m_trade_execution); } + string TradeExecutionDescription(void) const; + //--- swap terms of trade + ENUM_SYMBOL_SWAP_MODE SwapMode(void) const { return(m_swap_mode); } + string SwapModeDescription(void) const; + ENUM_DAY_OF_WEEK SwapRollover3days(void) const { return(m_swap3); } + string SwapRollover3daysDescription(void) const; + //--- dates for futures + datetime StartTime(void) const; + datetime ExpirationTime(void) const; + //--- margin parameters + double MarginInitial(void) const { return(m_margin_initial); } + double MarginMaintenance(void) const { return(m_margin_maintenance); } + bool MarginHedgedUseLeg(void) const { return(m_margin_hedged_use_leg); } + double MarginHedged(void) const { return(m_margin_hedged); } + //--- left for backward compatibility + double MarginLong(void) const { return(0.0); } + double MarginShort(void) const { return(0.0); } + double MarginLimit(void) const { return(0.0); } + double MarginStop(void) const { return(0.0); } + double MarginStopLimit(void) const { return(0.0); } + //--- trade flags parameters + int TradeTimeFlags(void) const { return(m_trade_time_flags); } + int TradeFillFlags(void) const { return(m_trade_fill_flags); } + //--- tick parameters + int Digits(void) const { return(m_digits); } + double Point(void) const { return(m_point); } + double TickValue(void) const { return(m_tick_value); } + double TickValueProfit(void) const { return(m_tick_value_profit); } + double TickValueLoss(void) const { return(m_tick_value_loss); } + double TickSize(void) const { return(m_tick_size); } + //--- lots parameters + double ContractSize(void) const { return(m_contract_size); } + double LotsMin(void) const { return(m_lots_min); } + double LotsMax(void) const { return(m_lots_max); } + double LotsStep(void) const { return(m_lots_step); } + double LotsLimit(void) const { return(m_lots_limit); } + //--- swaps + double SwapLong(void) const { return(m_swap_long); } + double SwapShort(void) const { return(m_swap_short); } + //--- fast access methods to the string symbol propertyes + string CurrencyBase(void) const; + string CurrencyProfit(void) const; + string CurrencyMargin(void) const; + string Bank(void) const; + string Description(void) const; + string Path(void) const; + //--- session information + long SessionDeals(void) const; + long SessionBuyOrders(void) const; + long SessionSellOrders(void) const; + double SessionTurnover(void) const; + double SessionInterest(void) const; + double SessionBuyOrdersVolume(void) const; + double SessionSellOrdersVolume(void) const; + double SessionOpen(void) const; + double SessionClose(void) const; + double SessionAW(void) const; + double SessionPriceSettlement(void) const; + double SessionPriceLimitMin(void) const; + double SessionPriceLimitMax(void) const; + //--- access methods to the API MQL5 functions + bool InfoInteger(const ENUM_SYMBOL_INFO_INTEGER prop_id,long& var) const; + bool InfoDouble(const ENUM_SYMBOL_INFO_DOUBLE prop_id,double& var) const; + bool InfoString(const ENUM_SYMBOL_INFO_STRING prop_id,string& var) const; + bool InfoMarginRate(const ENUM_ORDER_TYPE order_type,double& initial_margin_rate,double& maintenance_margin_rate) const; + //--- service methods + double NormalizePrice(const double price) const; + bool CheckMarketWatch(void); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CSymbolInfo::CSymbolInfo(void) : m_name(NULL), + m_point(0.0), + m_tick_value(0.0), + m_tick_value_profit(0.0), + m_tick_value_loss(0.0), + m_tick_size(0.0), + m_contract_size(0.0), + m_lots_min(0.0), + m_lots_max(0.0), + m_lots_step(0.0), + m_swap_long(0.0), + m_swap_short(0.0), + m_digits(0), + m_order_mode(0), + m_trade_execution(0), + m_trade_calcmode(0), + m_trade_mode(0), + m_swap_mode(0), + m_swap3(0), + m_margin_initial(0.0), + m_margin_maintenance(0.0), + m_margin_hedged_use_leg(false), + m_margin_hedged(0.0), + m_trade_time_flags(0), + m_trade_fill_flags(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CSymbolInfo::~CSymbolInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Set name | +//+------------------------------------------------------------------+ +bool CSymbolInfo::Name(const string name) + { + string symbol_name=StringLen(name)>0 ? name : _Symbol; +//--- check previous set name + if(m_name!=symbol_name) + { + m_name=symbol_name; + //--- + if(!CheckMarketWatch()) + return(false); + //--- + if(!Refresh()) + { + m_name=""; + Print(__FUNCTION__+": invalid data of symbol '"+symbol_name+"'"); + return(false); + } + } +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Refresh cached data | +//+------------------------------------------------------------------+ +bool CSymbolInfo::Refresh(void) + { + long tmp_long=0; +//--- + if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short)) + return(false); + if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp_long)) + return(false); + m_digits=(int)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp_long)) + return(false); + m_order_mode=(int)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp_long)) + return(false); + m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp_long)) + return(false); + m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp_long)) + return(false); + m_trade_mode=(ENUM_SYMBOL_TRADE_MODE)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_MODE,tmp_long)) + return(false); + m_swap_mode=(ENUM_SYMBOL_SWAP_MODE)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_ROLLOVER3DAYS,tmp_long)) + return(false); + m_swap3=(ENUM_DAY_OF_WEEK)tmp_long; + if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_INITIAL,m_margin_initial)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_MAINTENANCE,m_margin_maintenance)) + return(false); + if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_HEDGED,m_margin_hedged)) + return(false); + if(!SymbolInfoInteger(m_name,SYMBOL_MARGIN_HEDGED_USE_LEG,tmp_long)) + return(false); + m_margin_hedged_use_leg=(bool)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_MODE,tmp_long)) + return(false); + m_trade_time_flags=(int)tmp_long; + if(!SymbolInfoInteger(m_name,SYMBOL_FILLING_MODE,tmp_long)) + return(false); + m_trade_fill_flags=(int)tmp_long; +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Refresh cached data | +//+------------------------------------------------------------------+ +bool CSymbolInfo::RefreshRates(void) + { + return(SymbolInfoTick(m_name,m_tick)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SELECT" | +//+------------------------------------------------------------------+ +bool CSymbolInfo::Select(void) const + { + return((bool)SymbolInfoInteger(m_name,SYMBOL_SELECT)); + } +//+------------------------------------------------------------------+ +//| Set the property value "SYMBOL_SELECT" | +//+------------------------------------------------------------------+ +bool CSymbolInfo::Select(const bool select) + { + return(SymbolSelect(m_name,select)); + } +//+------------------------------------------------------------------+ +//| Check synchronize symbol | +//+------------------------------------------------------------------+ +bool CSymbolInfo::IsSynchronized(void) const + { + return(SymbolIsSynchronized(m_name)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_VOLUMEHIGH" | +//+------------------------------------------------------------------+ +ulong CSymbolInfo::VolumeHigh(void) const + { + return(SymbolInfoInteger(m_name,SYMBOL_VOLUMEHIGH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_VOLUMELOW" | +//+------------------------------------------------------------------+ +ulong CSymbolInfo::VolumeLow(void) const + { + return(SymbolInfoInteger(m_name,SYMBOL_VOLUMELOW)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SPREAD" | +//+------------------------------------------------------------------+ +int CSymbolInfo::Spread(void) const + { + return((int)SymbolInfoInteger(m_name,SYMBOL_SPREAD)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SPREAD_FLOAT" | +//+------------------------------------------------------------------+ +bool CSymbolInfo::SpreadFloat(void) const + { + return((bool)SymbolInfoInteger(m_name,SYMBOL_SPREAD_FLOAT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_TICKS_BOOKDEPTH" | +//+------------------------------------------------------------------+ +int CSymbolInfo::TicksBookDepth(void) const + { + return((int)SymbolInfoInteger(m_name,SYMBOL_TICKS_BOOKDEPTH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_TRADE_STOPS_LEVEL" | +//+------------------------------------------------------------------+ +int CSymbolInfo::StopsLevel(void) const + { + return((int)SymbolInfoInteger(m_name,SYMBOL_TRADE_STOPS_LEVEL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_TRADE_FREEZE_LEVEL" | +//+------------------------------------------------------------------+ +int CSymbolInfo::FreezeLevel(void) const + { + return((int)SymbolInfoInteger(m_name,SYMBOL_TRADE_FREEZE_LEVEL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_BIDHIGH" | +//+------------------------------------------------------------------+ +double CSymbolInfo::BidHigh(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_BIDHIGH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_BIDLOW" | +//+------------------------------------------------------------------+ +double CSymbolInfo::BidLow(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_BIDLOW)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_ASKHIGH" | +//+------------------------------------------------------------------+ +double CSymbolInfo::AskHigh(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_ASKHIGH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_ASKLOW" | +//+------------------------------------------------------------------+ +double CSymbolInfo::AskLow(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_ASKLOW)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_LASTHIGH" | +//+------------------------------------------------------------------+ +double CSymbolInfo::LastHigh(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_LASTHIGH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_LASTLOW" | +//+------------------------------------------------------------------+ +double CSymbolInfo::LastLow(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_LASTLOW)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_TRADE_CALC_MODE" as string | +//+------------------------------------------------------------------+ +string CSymbolInfo::TradeCalcModeDescription(void) const + { + string str; +//--- + switch(m_trade_calcmode) + { + case SYMBOL_CALC_MODE_FOREX: + str="Calculation of profit and margin for Forex"; + break; + case SYMBOL_CALC_MODE_CFD: + str="Calculation of collateral and earnings for CFD"; + break; + case SYMBOL_CALC_MODE_FUTURES: + str="Calculation of collateral and profits for futures"; + break; + case SYMBOL_CALC_MODE_CFDINDEX: + str="Calculation of collateral and earnings for CFD on indices"; + break; + case SYMBOL_CALC_MODE_CFDLEVERAGE: + str="Calculation of collateral and earnings for the CFD when trading with leverage"; + break; + case SYMBOL_CALC_MODE_EXCH_STOCKS: + str="Calculation for exchange stocks"; + break; + case SYMBOL_CALC_MODE_EXCH_FUTURES: + str="Calculation for exchange futures"; + break; + case SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS: + str="Calculation for FORTS futures"; + break; + default: + str="Unknown calculation mode"; + } +//--- result + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_TRADE_MODE" as string | +//+------------------------------------------------------------------+ +string CSymbolInfo::TradeModeDescription(void) const + { + string str; +//--- + switch(m_trade_mode) + { + case SYMBOL_TRADE_MODE_DISABLED: + str="Disabled"; + break; + case SYMBOL_TRADE_MODE_LONGONLY: + str="Long only"; + break; + case SYMBOL_TRADE_MODE_SHORTONLY: + str="Short only"; + break; + case SYMBOL_TRADE_MODE_CLOSEONLY: + str="Close only"; + break; + case SYMBOL_TRADE_MODE_FULL: + str="Full access"; + break; + default: + str="Unknown trade mode"; + } +//--- result + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_TRADE_EXEMODE" as string | +//+------------------------------------------------------------------+ +string CSymbolInfo::TradeExecutionDescription(void) const + { + string str; +//--- + switch(m_trade_execution) + { + case SYMBOL_TRADE_EXECUTION_REQUEST: + str="Trading on request"; + break; + case SYMBOL_TRADE_EXECUTION_INSTANT: + str="Trading on live streaming prices"; + break; + case SYMBOL_TRADE_EXECUTION_MARKET: + str="Execution of orders on the market"; + break; + case SYMBOL_TRADE_EXECUTION_EXCHANGE: + str="Exchange execution"; + break; + default: + str="Unknown trade execution"; + } +//--- result + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SWAP_MODE" as string | +//+------------------------------------------------------------------+ +string CSymbolInfo::SwapModeDescription(void) const + { + string str; +//--- + switch(m_swap_mode) + { + case SYMBOL_SWAP_MODE_DISABLED: + str="No swaps"; + break; + case SYMBOL_SWAP_MODE_POINTS: + str="Swaps are calculated in points"; + break; + case SYMBOL_SWAP_MODE_CURRENCY_SYMBOL: + str="Swaps are calculated in base currency"; + break; + case SYMBOL_SWAP_MODE_CURRENCY_MARGIN: + str="Swaps are calculated in margin currency"; + break; + case SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT: + str="Swaps are calculated in deposit currency"; + break; + case SYMBOL_SWAP_MODE_INTEREST_CURRENT: + str="Swaps are calculated as annual interest using the current price"; + break; + case SYMBOL_SWAP_MODE_INTEREST_OPEN: + str="Swaps are calculated as annual interest using the open price"; + break; + case SYMBOL_SWAP_MODE_REOPEN_CURRENT: + str="Swaps are charged by reopening positions at the close price"; + break; + case SYMBOL_SWAP_MODE_REOPEN_BID: + str="Swaps are charged by reopening positions at the Bid price"; + break; + default: + str="Unknown swap mode"; + } +//--- result + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SWAP_ROLLOVER3DAYS" as string | +//+------------------------------------------------------------------+ +string CSymbolInfo::SwapRollover3daysDescription(void) const + { + string str; +//--- + switch(m_swap3) + { + case SUNDAY: + str="Sunday"; + break; + case MONDAY: + str="Monday"; + break; + case TUESDAY: + str="Tuesday"; + break; + case WEDNESDAY: + str="Wednesday"; + break; + case THURSDAY: + str="Thursday"; + break; + case FRIDAY: + str="Friday"; + break; + case SATURDAY: + str="Saturday"; + break; + default: + str="Unknown"; + } +//--- result + return(str); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_START_TIME" | +//+------------------------------------------------------------------+ +datetime CSymbolInfo::StartTime(void) const + { + return((datetime)SymbolInfoInteger(m_name,SYMBOL_START_TIME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_EXPIRATION_TIME" | +//+------------------------------------------------------------------+ +datetime CSymbolInfo::ExpirationTime(void) const + { + return((datetime)SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_TIME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_CURRENCY_BASE" | +//+------------------------------------------------------------------+ +string CSymbolInfo::CurrencyBase(void) const + { + return(SymbolInfoString(m_name,SYMBOL_CURRENCY_BASE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_CURRENCY_PROFIT" | +//+------------------------------------------------------------------+ +string CSymbolInfo::CurrencyProfit(void) const + { + return(SymbolInfoString(m_name,SYMBOL_CURRENCY_PROFIT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_CURRENCY_MARGIN" | +//+------------------------------------------------------------------+ +string CSymbolInfo::CurrencyMargin(void) const + { + return(SymbolInfoString(m_name,SYMBOL_CURRENCY_MARGIN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_BANK" | +//+------------------------------------------------------------------+ +string CSymbolInfo::Bank(void) const + { + return(SymbolInfoString(m_name,SYMBOL_BANK)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_DESCRIPTION" | +//+------------------------------------------------------------------+ +string CSymbolInfo::Description(void) const + { + return(SymbolInfoString(m_name,SYMBOL_DESCRIPTION)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_PATH" | +//+------------------------------------------------------------------+ +string CSymbolInfo::Path(void) const + { + return(SymbolInfoString(m_name,SYMBOL_PATH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_DEALS" | +//+------------------------------------------------------------------+ +long CSymbolInfo::SessionDeals(void) const + { + return(SymbolInfoInteger(m_name,SYMBOL_SESSION_DEALS)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_BUY_ORDERS" | +//+------------------------------------------------------------------+ +long CSymbolInfo::SessionBuyOrders(void) const + { + return(SymbolInfoInteger(m_name,SYMBOL_SESSION_BUY_ORDERS)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_SELL_ORDERS" | +//+------------------------------------------------------------------+ +long CSymbolInfo::SessionSellOrders(void) const + { + return(SymbolInfoInteger(m_name,SYMBOL_SESSION_SELL_ORDERS)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_TURNOVER" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionTurnover(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_TURNOVER)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_INTEREST" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionInterest(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_INTEREST)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_BUY_ORDERS_VOLUME" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionBuyOrdersVolume(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_BUY_ORDERS_VOLUME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_SELL_ORDERS_VOLUME" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionSellOrdersVolume(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_SELL_ORDERS_VOLUME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_OPEN" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionOpen(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_OPEN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_CLOSE" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionClose(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_CLOSE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_AW" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionAW(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_AW)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_PRICE_SETTLEMENT" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionPriceSettlement(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_PRICE_SETTLEMENT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_PRICE_LIMIT_MIN" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionPriceLimitMin(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_PRICE_LIMIT_MIN)); + } +//+------------------------------------------------------------------+ +//| Get the property value "SYMBOL_SESSION_PRICE_LIMIT_MAX" | +//+------------------------------------------------------------------+ +double CSymbolInfo::SessionPriceLimitMax(void) const + { + return(SymbolInfoDouble(m_name,SYMBOL_SESSION_PRICE_LIMIT_MAX)); + } +//+------------------------------------------------------------------+ +//| Access functions SymbolInfoInteger(...) | +//+------------------------------------------------------------------+ +bool CSymbolInfo::InfoInteger(const ENUM_SYMBOL_INFO_INTEGER prop_id,long &var) const + { + return(SymbolInfoInteger(m_name,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions SymbolInfoDouble(...) | +//+------------------------------------------------------------------+ +bool CSymbolInfo::InfoDouble(const ENUM_SYMBOL_INFO_DOUBLE prop_id,double &var) const + { + return(SymbolInfoDouble(m_name,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions SymbolInfoString(...) | +//+------------------------------------------------------------------+ +bool CSymbolInfo::InfoString(const ENUM_SYMBOL_INFO_STRING prop_id,string &var) const + { + return(SymbolInfoString(m_name,prop_id,var)); + } +//+------------------------------------------------------------------+ +//| Access functions SymbolInfoMarginRate(...) | +//+------------------------------------------------------------------+ +bool CSymbolInfo::InfoMarginRate(const ENUM_ORDER_TYPE order_type,double& initial_margin_rate,double& maintenance_margin_rate) const + { + return(SymbolInfoMarginRate(m_name,order_type,initial_margin_rate,maintenance_margin_rate)); + } +//+------------------------------------------------------------------+ +//| Normalize price | +//+------------------------------------------------------------------+ +double CSymbolInfo::NormalizePrice(const double price) const + { + if(m_tick_size!=0) + return(NormalizeDouble(MathRound(price/m_tick_size)*m_tick_size,m_digits)); +//--- + return(NormalizeDouble(price,m_digits)); + } +//+------------------------------------------------------------------+ +//| Checks if symbol is selected in the MarketWatch | +//| and adds symbol to the MarketWatch, if necessary | +//+------------------------------------------------------------------+ +bool CSymbolInfo::CheckMarketWatch(void) + { +//--- check if symbol is selected in the MarketWatch + if(!Select()) + { + if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOL) + { + printf(__FUNCTION__+": Unknown symbol '%s'",m_name); + return(false); + } + if(!Select(true)) + { + printf(__FUNCTION__+": Error adding symbol %d",GetLastError()); + return(false); + } + } +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ diff --git a/Trade/TerminalInfo.mqh b/Trade/TerminalInfo.mqh new file mode 100644 index 0000000..8f6c7be --- /dev/null +++ b/Trade/TerminalInfo.mqh @@ -0,0 +1,225 @@ +//+------------------------------------------------------------------+ +//| TerminalInfo.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CTerminalInfo. | +//| Appointment: Class for access to terminal info. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CTerminalInfo : public CObject + { +public: + CTerminalInfo(void); + ~CTerminalInfo(void); + //--- fast access methods to the integer terminal propertyes + int Build(void) const; + bool IsConnected(void) const; + bool IsDLLsAllowed(void) const; + bool IsTradeAllowed(void) const; + bool IsEmailEnabled(void) const; + bool IsFtpEnabled(void) const; + int MaxBars(void) const; + int CodePage(void) const; + int CPUCores(void) const; + int MemoryPhysical(void) const; + int MemoryTotal(void) const; + int MemoryAvailable(void) const; + int MemoryUsed(void) const; + bool IsX64(void) const; + int OpenCLSupport(void) const; + int DiskSpace(void) const; + //--- fast access methods to the string terminal propertyes + string Language(void) const; + string Name(void) const; + string Company(void) const; + string Path(void) const; + string DataPath(void) const; + string CommonDataPath(void) const; + //--- access methods to the API MQL5 functions + long InfoInteger(const ENUM_TERMINAL_INFO_INTEGER prop_id) const; + string InfoString(const ENUM_TERMINAL_INFO_STRING prop_id) const; + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CTerminalInfo::CTerminalInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CTerminalInfo::~CTerminalInfo(void) + { + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_BUILD" | +//+------------------------------------------------------------------+ +int CTerminalInfo::Build(void) const + { + return((int)TerminalInfoInteger(TERMINAL_BUILD)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_CONNECTED" | +//+------------------------------------------------------------------+ +bool CTerminalInfo::IsConnected(void) const + { + return((bool)TerminalInfoInteger(TERMINAL_CONNECTED)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_DLLS_ALLOWED" | +//+------------------------------------------------------------------+ +bool CTerminalInfo::IsDLLsAllowed(void) const + { + return((bool)TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_TRADE_ALLOWED" | +//+------------------------------------------------------------------+ +bool CTerminalInfo::IsTradeAllowed(void) const + { + return((bool)TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_EMAIL_ENABLED" | +//+------------------------------------------------------------------+ +bool CTerminalInfo::IsEmailEnabled(void) const + { + return((bool)TerminalInfoInteger(TERMINAL_EMAIL_ENABLED)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_FTP_ENABLED" | +//+------------------------------------------------------------------+ +bool CTerminalInfo::IsFtpEnabled(void) const + { + return((bool)TerminalInfoInteger(TERMINAL_FTP_ENABLED)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_MAXBARS" | +//+------------------------------------------------------------------+ +int CTerminalInfo::MaxBars(void) const + { + return((int)TerminalInfoInteger(TERMINAL_MAXBARS)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_CODEPAGE" | +//+------------------------------------------------------------------+ +int CTerminalInfo::CodePage(void) const + { + return((int)TerminalInfoInteger(TERMINAL_CODEPAGE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_CPU_CORES" | +//+------------------------------------------------------------------+ +int CTerminalInfo::CPUCores(void) const + { + return((int)TerminalInfoInteger(TERMINAL_CPU_CORES)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_MEMORY_PHYSICAL" | +//+------------------------------------------------------------------+ +int CTerminalInfo::MemoryPhysical(void) const + { + return((int)TerminalInfoInteger(TERMINAL_MEMORY_PHYSICAL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_MEMORY_TOTAL" | +//+------------------------------------------------------------------+ +int CTerminalInfo::MemoryTotal(void) const + { + return((int)TerminalInfoInteger(TERMINAL_MEMORY_TOTAL)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_MEMORY_AVAILABLE" | +//+------------------------------------------------------------------+ +int CTerminalInfo::MemoryAvailable(void) const + { + return((int)TerminalInfoInteger(TERMINAL_MEMORY_AVAILABLE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_MEMORY_USED" | +//+------------------------------------------------------------------+ +int CTerminalInfo::MemoryUsed(void) const + { + return((int)TerminalInfoInteger(TERMINAL_MEMORY_USED)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_X64" | +//+------------------------------------------------------------------+ +bool CTerminalInfo::IsX64(void) const + { + return((bool)TerminalInfoInteger(TERMINAL_X64)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_OPENCL_SUPPORT" | +//+------------------------------------------------------------------+ +int CTerminalInfo::OpenCLSupport(void) const + { + return((int)TerminalInfoInteger(TERMINAL_OPENCL_SUPPORT)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_DISK_SPACE" | +//+------------------------------------------------------------------+ +int CTerminalInfo::DiskSpace(void) const + { + return((int)TerminalInfoInteger(TERMINAL_DISK_SPACE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_LANGUAGE" | +//+------------------------------------------------------------------+ +string CTerminalInfo::Language(void) const + { + return(TerminalInfoString(TERMINAL_LANGUAGE)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_NAME" | +//+------------------------------------------------------------------+ +string CTerminalInfo::Name(void) const + { + return(TerminalInfoString(TERMINAL_NAME)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_COMPANY" | +//+------------------------------------------------------------------+ +string CTerminalInfo::Company(void) const + { + return(TerminalInfoString(TERMINAL_COMPANY)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_PATH" | +//+------------------------------------------------------------------+ +string CTerminalInfo::Path(void) const + { + return(TerminalInfoString(TERMINAL_PATH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_DATA_PATH" | +//+------------------------------------------------------------------+ +string CTerminalInfo::DataPath(void) const + { + return(TerminalInfoString(TERMINAL_DATA_PATH)); + } +//+------------------------------------------------------------------+ +//| Get the property value "TERMINAL_COMMONDATA_PATH" | +//+------------------------------------------------------------------+ +string CTerminalInfo::CommonDataPath(void) const + { + return(TerminalInfoString(TERMINAL_COMMONDATA_PATH)); + } +//+------------------------------------------------------------------+ +//| Access functions AccountInfoInteger(...) | +//+------------------------------------------------------------------+ +long CTerminalInfo::InfoInteger(const ENUM_TERMINAL_INFO_INTEGER prop_id) const + { + return(TerminalInfoInteger(prop_id)); + } +//+------------------------------------------------------------------+ +//| Access functions AccountInfoString(...) | +//+------------------------------------------------------------------+ +string CTerminalInfo::InfoString(const ENUM_TERMINAL_INFO_STRING prop_id) const + { + return(TerminalInfoString(prop_id)); + } +//+------------------------------------------------------------------+ diff --git a/Trade/Trade.mqh b/Trade/Trade.mqh new file mode 100644 index 0000000..6ca6897 --- /dev/null +++ b/Trade/Trade.mqh @@ -0,0 +1,1708 @@ +//+------------------------------------------------------------------+ +//| Trade.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#include +#include "OrderInfo.mqh" +#include "HistoryOrderInfo.mqh" +#include "PositionInfo.mqh" +#include "DealInfo.mqh" +//+------------------------------------------------------------------+ +//| enumerations | +//+------------------------------------------------------------------+ +enum ENUM_LOG_LEVELS + { + LOG_LEVEL_NO =0, + LOG_LEVEL_ERRORS=1, + LOG_LEVEL_ALL =2 + }; +//+------------------------------------------------------------------+ +//| Class CTrade. | +//| Appointment: Class trade operations. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CTrade : public CObject + { +protected: + MqlTradeRequest m_request; // request data + MqlTradeResult m_result; // result data + MqlTradeCheckResult m_check_result; // result check data + bool m_async_mode; // trade mode + ulong m_magic; // expert magic number + ulong m_deviation; // deviation default + ENUM_ORDER_TYPE_FILLING m_type_filling; + ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; + //--- + ENUM_LOG_LEVELS m_log_level; + +public: + CTrade(void); + ~CTrade(void); + //--- methods of access to protected data + void LogLevel(const ENUM_LOG_LEVELS log_level) { m_log_level=log_level; } + void Request(MqlTradeRequest &request) const; + ENUM_TRADE_REQUEST_ACTIONS RequestAction(void) const { return(m_request.action); } + string RequestActionDescription(void) const; + ulong RequestMagic(void) const { return(m_request.magic); } + ulong RequestOrder(void) const { return(m_request.order); } + ulong RequestPosition(void) const { return(m_request.position); } + ulong RequestPositionBy(void) const { return(m_request.position_by); } + string RequestSymbol(void) const { return(m_request.symbol); } + double RequestVolume(void) const { return(m_request.volume); } + double RequestPrice(void) const { return(m_request.price); } + double RequestStopLimit(void) const { return(m_request.stoplimit); } + double RequestSL(void) const { return(m_request.sl); } + double RequestTP(void) const { return(m_request.tp); } + ulong RequestDeviation(void) const { return(m_request.deviation); } + ENUM_ORDER_TYPE RequestType(void) const { return(m_request.type); } + string RequestTypeDescription(void) const; + ENUM_ORDER_TYPE_FILLING RequestTypeFilling(void) const { return(m_request.type_filling); } + string RequestTypeFillingDescription(void) const; + ENUM_ORDER_TYPE_TIME RequestTypeTime(void) const { return(m_request.type_time); } + string RequestTypeTimeDescription(void) const; + datetime RequestExpiration(void) const { return(m_request.expiration); } + string RequestComment(void) const { return(m_request.comment); } + //--- + void Result(MqlTradeResult &result) const; + uint ResultRetcode(void) const { return(m_result.retcode); } + string ResultRetcodeDescription(void) const; + int ResultRetcodeExternal(void) const { return(m_result.retcode_external); } + ulong ResultDeal(void) const { return(m_result.deal); } + ulong ResultOrder(void) const { return(m_result.order); } + double ResultVolume(void) const { return(m_result.volume); } + double ResultPrice(void) const { return(m_result.price); } + double ResultBid(void) const { return(m_result.bid); } + double ResultAsk(void) const { return(m_result.ask); } + string ResultComment(void) const { return(m_result.comment); } + //--- + void CheckResult(MqlTradeCheckResult &check_result) const; + uint CheckResultRetcode(void) const { return(m_check_result.retcode); } + string CheckResultRetcodeDescription(void) const; + double CheckResultBalance(void) const { return(m_check_result.balance); } + double CheckResultEquity(void) const { return(m_check_result.equity); } + double CheckResultProfit(void) const { return(m_check_result.profit); } + double CheckResultMargin(void) const { return(m_check_result.margin); } + double CheckResultMarginFree(void) const { return(m_check_result.margin_free); } + double CheckResultMarginLevel(void) const { return(m_check_result.margin_level); } + string CheckResultComment(void) const { return(m_check_result.comment); } + //--- trade methods + void SetAsyncMode(const bool mode) { m_async_mode=mode; } + void SetExpertMagicNumber(const ulong magic) { m_magic=magic; } + void SetDeviationInPoints(const ulong deviation) { m_deviation=deviation; } + void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING filling) { m_type_filling=filling; } + bool SetTypeFillingBySymbol(const string symbol); + void SetMarginMode(void) { m_margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); } + //--- methods for working with positions + bool PositionOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume, + const double price,const double sl,const double tp,const string comment=""); + bool PositionModify(const string symbol,const double sl,const double tp); + bool PositionModify(const ulong ticket,const double sl,const double tp); + bool PositionClose(const string symbol,const ulong deviation=ULONG_MAX); + bool PositionClose(const ulong ticket,const ulong deviation=ULONG_MAX); + bool PositionCloseBy(const ulong ticket,const ulong ticket_by); + bool PositionClosePartial(const string symbol,const double volume,const ulong deviation=ULONG_MAX); + bool PositionClosePartial(const ulong ticket,const double volume,const ulong deviation=ULONG_MAX); + //--- methods for working with pending orders + bool OrderOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume, + const double limit_price,const double price,const double sl,const double tp, + ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0, + const string comment=""); + bool OrderModify(const ulong ticket,const double price,const double sl,const double tp, + const ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const double stoplimit=0.0); + bool OrderDelete(const ulong ticket); + //--- additions methods + bool Buy(const double volume,const string symbol=NULL,double price=0.0,const double sl=0.0,const double tp=0.0,const string comment=""); + bool Sell(const double volume,const string symbol=NULL,double price=0.0,const double sl=0.0,const double tp=0.0,const string comment=""); + bool BuyLimit(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0, + const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment=""); + bool BuyStop(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0, + const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment=""); + bool SellLimit(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0, + const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment=""); + bool SellStop(const double volume,const double price,const string symbol=NULL,const double sl=0.0,const double tp=0.0, + const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0,const string comment=""); + //--- method check + virtual double CheckVolume(const string symbol,double volume,double price,ENUM_ORDER_TYPE order_type); + virtual bool OrderCheck(const MqlTradeRequest &request,MqlTradeCheckResult &check_result); + virtual bool OrderSend(const MqlTradeRequest &request,MqlTradeResult &result); + //--- info methods + void PrintRequest(void) const; + void PrintResult(void) const; + //--- positions + string FormatPositionType(string &str,const uint type) const; + //--- orders + string FormatOrderType(string &str,const uint type) const; + string FormatOrderStatus(string &str,const uint status) const; + string FormatOrderTypeFilling(string &str,const uint type) const; + string FormatOrderTypeTime(string &str,const uint type) const; + string FormatOrderPrice(string &str,const double price_order,const double price_trigger,const uint digits) const; + //--- trade request + string FormatRequest(string &str,const MqlTradeRequest &request) const; + string FormatRequestResult(string &str,const MqlTradeRequest &request,const MqlTradeResult &result) const; + +protected: + bool FillingCheck(const string symbol); + bool ExpirationCheck(const string symbol); + bool OrderTypeCheck(const string symbol); + void ClearStructures(void); + bool IsStopped(const string function); + bool IsHedging(void) const { return(m_margin_mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); } + //--- position select depending on netting or hedging + bool SelectPosition(const string symbol); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CTrade::CTrade(void) : m_async_mode(false), + m_magic(0), + m_deviation(10), + m_type_filling(ORDER_FILLING_FOK), + m_log_level(LOG_LEVEL_ERRORS) + { + SetMarginMode(); +//--- initialize protected data + ClearStructures(); +//--- check programm mode + if(MQL5InfoInteger(MQL5_TESTING)) + m_log_level=LOG_LEVEL_ALL; + if(MQL5InfoInteger(MQL5_OPTIMIZATION)) + m_log_level=LOG_LEVEL_NO; + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CTrade::~CTrade(void) + { + } +//+------------------------------------------------------------------+ +//| Get the request structure | +//+------------------------------------------------------------------+ +void CTrade::Request(MqlTradeRequest &request) const + { + request.action =m_request.action; + request.magic =m_request.magic; + request.order =m_request.order; + request.symbol =m_request.symbol; + request.volume =m_request.volume; + request.price =m_request.price; + request.stoplimit =m_request.stoplimit; + request.sl =m_request.sl; + request.tp =m_request.tp; + request.deviation =m_request.deviation; + request.type =m_request.type; + request.type_filling=m_request.type_filling; + request.type_time =m_request.type_time; + request.expiration =m_request.expiration; + request.comment =m_request.comment; + request.position =m_request.position; + request.position_by =m_request.position_by; + } +//+------------------------------------------------------------------+ +//| Get the trade action as string | +//+------------------------------------------------------------------+ +string CTrade::RequestActionDescription(void) const + { + string str; +//--- + FormatRequest(str,m_request); +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the order type as string | +//+------------------------------------------------------------------+ +string CTrade::RequestTypeDescription(void) const + { + string str; +//--- + FormatOrderType(str,(uint)m_request.order); +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the order type filling as string | +//+------------------------------------------------------------------+ +string CTrade::RequestTypeFillingDescription(void) const + { + string str; +//--- + FormatOrderTypeFilling(str,m_request.type_filling); +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the order type time as string | +//+------------------------------------------------------------------+ +string CTrade::RequestTypeTimeDescription(void) const + { + string str; +//--- + FormatOrderTypeTime(str,m_request.type_time); +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the result structure | +//+------------------------------------------------------------------+ +void CTrade::Result(MqlTradeResult &result) const + { + result.retcode =m_result.retcode; + result.deal =m_result.deal; + result.order =m_result.order; + result.volume =m_result.volume; + result.price =m_result.price; + result.bid =m_result.bid; + result.ask =m_result.ask; + result.comment =m_result.comment; + result.request_id=m_result.request_id; + result.retcode_external=m_result.retcode_external; + } +//+------------------------------------------------------------------+ +//| Get the retcode value as string | +//+------------------------------------------------------------------+ +string CTrade::ResultRetcodeDescription(void) const + { + string str; +//--- + FormatRequestResult(str,m_request,m_result); +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Get the check result structure | +//+------------------------------------------------------------------+ +void CTrade::CheckResult(MqlTradeCheckResult &check_result) const + { +//--- copy structure + check_result.retcode =m_check_result.retcode; + check_result.balance =m_check_result.balance; + check_result.equity =m_check_result.equity; + check_result.profit =m_check_result.profit; + check_result.margin =m_check_result.margin; + check_result.margin_free =m_check_result.margin_free; + check_result.margin_level=m_check_result.margin_level; + check_result.comment =m_check_result.comment; + } +//+------------------------------------------------------------------+ +//| Get the check retcode value as string | +//+------------------------------------------------------------------+ +string CTrade::CheckResultRetcodeDescription(void) const + { + string str; + MqlTradeResult result; +//--- + result.retcode=m_check_result.retcode; + FormatRequestResult(str,m_request,result); +//--- + return(str); + } +//+------------------------------------------------------------------+ +//| Open position | +//+------------------------------------------------------------------+ +bool CTrade::PositionOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume, + const double price,const double sl,const double tp,const string comment) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- clean + ClearStructures(); +//--- check + if(order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL) + { + m_result.retcode=TRADE_RETCODE_INVALID; + m_result.comment="Invalid order type"; + return(false); + } +//--- setting request + m_request.action =TRADE_ACTION_DEAL; + m_request.symbol =symbol; + m_request.magic =m_magic; + m_request.volume =volume; + m_request.type =order_type; + m_request.price =price; + m_request.sl =sl; + m_request.tp =tp; + m_request.deviation=m_deviation; +//--- check order type + if(!OrderTypeCheck(symbol)) + return(false); +//--- check filling + if(!FillingCheck(symbol)) + return(false); + m_request.comment=comment; +//--- action and return the result + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Modify specified opened position | +//+------------------------------------------------------------------+ +bool CTrade::PositionModify(const string symbol,const double sl,const double tp) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- check position existence + if(!SelectPosition(symbol)) + return(false); +//--- clean + ClearStructures(); +//--- setting request + m_request.action =TRADE_ACTION_SLTP; + m_request.symbol =symbol; + m_request.magic =m_magic; + m_request.sl =sl; + m_request.tp =tp; + m_request.position=PositionGetInteger(POSITION_TICKET); +//--- action and return the result + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Modify specified opened position | +//+------------------------------------------------------------------+ +bool CTrade::PositionModify(const ulong ticket,const double sl,const double tp) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- check position existence + if(!PositionSelectByTicket(ticket)) + return(false); +//--- clean + ClearStructures(); +//--- setting request + m_request.action =TRADE_ACTION_SLTP; + m_request.position=ticket; + m_request.symbol =PositionGetString(POSITION_SYMBOL); + m_request.magic =m_magic; + m_request.sl =sl; + m_request.tp =tp; +//--- action and return the result + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Close specified opened position | +//+------------------------------------------------------------------+ +bool CTrade::PositionClose(const string symbol,const ulong deviation) + { + bool partial_close=false; + int retry_count =10; + uint retcode =TRADE_RETCODE_REJECT; +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- clean + ClearStructures(); +//--- check filling + if(!FillingCheck(symbol)) + return(false); + do + { + //--- check + if(SelectPosition(symbol)) + { + if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) + { + //--- prepare request for close BUY position + m_request.type =ORDER_TYPE_SELL; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID); + } + else + { + //--- prepare request for close SELL position + m_request.type =ORDER_TYPE_BUY; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK); + } + } + else + { + //--- position not found + m_result.retcode=retcode; + return(false); + } + //--- setting request + m_request.action =TRADE_ACTION_DEAL; + m_request.symbol =symbol; + m_request.volume =PositionGetDouble(POSITION_VOLUME); + m_request.magic =m_magic; + m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation; + m_request.position =PositionGetInteger(POSITION_TICKET); + //--- check volume + double max_volume=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX); + if(m_request.volume>max_volume) + { + m_request.volume=max_volume; + partial_close=true; + } + else + partial_close=false; + //--- hedging? just send order + if(IsHedging()) + return(OrderSend(m_request,m_result)); + //--- order send + if(!OrderSend(m_request,m_result)) + { + if(--retry_count!=0) + continue; + if(retcode==TRADE_RETCODE_DONE_PARTIAL) + m_result.retcode=retcode; + return(false); + } + //--- WARNING. If position volume exceeds the maximum volume allowed for deal, + //--- and when the asynchronous trade mode is on, for safety reasons, position is closed not completely, + //--- but partially. It is decreased by the maximum volume allowed for deal. + if(m_async_mode) + break; + retcode=TRADE_RETCODE_DONE_PARTIAL; + if(partial_close) + Sleep(1000); + } + while(partial_close); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Close specified opened position | +//+------------------------------------------------------------------+ +bool CTrade::PositionClose(const ulong ticket,const ulong deviation) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- check position existence + if(!PositionSelectByTicket(ticket)) + return(false); + string symbol=PositionGetString(POSITION_SYMBOL); +//--- clean + ClearStructures(); +//--- check filling + if(!FillingCheck(symbol)) + return(false); +//--- check + if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) + { + //--- prepare request for close BUY position + m_request.type =ORDER_TYPE_SELL; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID); + } + else + { + //--- prepare request for close SELL position + m_request.type =ORDER_TYPE_BUY; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK); + } +//--- setting request + m_request.action =TRADE_ACTION_DEAL; + m_request.position =ticket; + m_request.symbol =symbol; + m_request.volume =PositionGetDouble(POSITION_VOLUME); + m_request.magic =m_magic; + m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation; +//--- close position + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Close one position by other | +//+------------------------------------------------------------------+ +bool CTrade::PositionCloseBy(const ulong ticket,const ulong ticket_by) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- check hedging mode + if(!IsHedging()) + return(false); +//--- check position existence + if(!PositionSelectByTicket(ticket)) + return(false); + string symbol=PositionGetString(POSITION_SYMBOL); + ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if(!PositionSelectByTicket(ticket_by)) + return(false); + string symbol_by=PositionGetString(POSITION_SYMBOL); + ENUM_POSITION_TYPE type_by=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); +//--- check positions + if(type==type_by) + return(false); + if(symbol!=symbol_by) + return(false); +//--- clean + ClearStructures(); +//--- check filling + if(!FillingCheck(symbol)) + return(false); +//--- setting request + m_request.action =TRADE_ACTION_CLOSE_BY; + m_request.position =ticket; + m_request.position_by=ticket_by; + m_request.magic =m_magic; +//--- close position + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Partial close specified opened position (for hedging mode only) | +//+------------------------------------------------------------------+ +bool CTrade::PositionClosePartial(const string symbol,const double volume,const ulong deviation) + { + uint retcode=TRADE_RETCODE_REJECT; +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- for hedging mode only + if(!IsHedging()) + return(false); +//--- clean + ClearStructures(); +//--- check filling + if(!FillingCheck(symbol)) + return(false); +//--- check + if(SelectPosition(symbol)) + { + if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) + { + //--- prepare request for close BUY position + m_request.type =ORDER_TYPE_SELL; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID); + } + else + { + //--- prepare request for close SELL position + m_request.type =ORDER_TYPE_BUY; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK); + } + } + else + { + //--- position not found + m_result.retcode=retcode; + return(false); + } +//--- check volume + double position_volume=PositionGetDouble(POSITION_VOLUME); + if(position_volume>volume) + position_volume=volume; +//--- setting request + m_request.action =TRADE_ACTION_DEAL; + m_request.symbol =symbol; + m_request.volume =position_volume; + m_request.magic =m_magic; + m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation; + m_request.position =PositionGetInteger(POSITION_TICKET); +//--- hedging? just send order + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Partial close specified opened position (for hedging mode only) | +//+------------------------------------------------------------------+ +bool CTrade::PositionClosePartial(const ulong ticket,const double volume,const ulong deviation) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- for hedging mode only + if(!IsHedging()) + return(false); +//--- check position existence + if(!PositionSelectByTicket(ticket)) + return(false); + string symbol=PositionGetString(POSITION_SYMBOL); +//--- clean + ClearStructures(); +//--- check filling + if(!FillingCheck(symbol)) + return(false); +//--- check + if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) + { + //--- prepare request for close BUY position + m_request.type =ORDER_TYPE_SELL; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_BID); + } + else + { + //--- prepare request for close SELL position + m_request.type =ORDER_TYPE_BUY; + m_request.price=SymbolInfoDouble(symbol,SYMBOL_ASK); + } +//--- check volume + double position_volume=PositionGetDouble(POSITION_VOLUME); + if(position_volume>volume) + position_volume=volume; +//--- setting request + m_request.action =TRADE_ACTION_DEAL; + m_request.position =ticket; + m_request.symbol =symbol; + m_request.volume =position_volume; + m_request.magic =m_magic; + m_request.deviation=(deviation==ULONG_MAX) ? m_deviation : deviation; +//--- close position + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Installation pending order | +//+------------------------------------------------------------------+ +bool CTrade::OrderOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume,const double limit_price, + const double price,const double sl,const double tp, + ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const string comment) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- clean + ClearStructures(); +//--- check filling + if(!FillingCheck(symbol)) + return(false); +//--- check order type + if(order_type==ORDER_TYPE_BUY || order_type==ORDER_TYPE_SELL) + { + m_result.retcode=TRADE_RETCODE_INVALID; + m_result.comment="Invalid order type"; + return(false); + } +//--- check order expiration + if(type_time==ORDER_TIME_GTC && expiration==0) + { + int exp=(int)SymbolInfoInteger(symbol,SYMBOL_EXPIRATION_MODE); + if((exp&SYMBOL_EXPIRATION_GTC)!=SYMBOL_EXPIRATION_GTC) + { + //--- if you place order for an unlimited time and if placing of such orders is prohibited + //--- try to place order with expiration at the end of the day + if((exp&SYMBOL_EXPIRATION_DAY)!=SYMBOL_EXPIRATION_DAY) + { + //--- if even this is not possible - error + Print(__FUNCTION__,": Error: Unable to place order without explicitly specified expiration time"); + m_result.retcode=TRADE_RETCODE_INVALID_EXPIRATION; + m_result.comment="Invalid expiration type"; + return(false); + } + type_time=ORDER_TIME_DAY; + } + } +//--- setting request + m_request.action =TRADE_ACTION_PENDING; + m_request.symbol =symbol; + m_request.magic =m_magic; + m_request.volume =volume; + m_request.type =order_type; + m_request.stoplimit =limit_price; + m_request.price =price; + m_request.sl =sl; + m_request.tp =tp; + m_request.type_time =type_time; + m_request.expiration =expiration; +//--- check order type + if(!OrderTypeCheck(symbol)) + return(false); +//--- check filling + if(!FillingCheck(symbol)) + { + m_result.retcode=TRADE_RETCODE_INVALID_FILL; + Print(__FUNCTION__+": Invalid filling type"); + return(false); + } +//--- check expiration + if(!ExpirationCheck(symbol)) + { + m_result.retcode=TRADE_RETCODE_INVALID_EXPIRATION; + Print(__FUNCTION__+": Invalid expiration type"); + return(false); + } + m_request.comment=comment; +//--- action and return the result + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Modify specified pending order | +//+------------------------------------------------------------------+ +bool CTrade::OrderModify(const ulong ticket,const double price,const double sl,const double tp, + const ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const double stoplimit) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- check order existence + if(!OrderSelect(ticket)) + return(false); +//--- clean + ClearStructures(); +//--- setting request + m_request.symbol =OrderGetString(ORDER_SYMBOL); + m_request.action =TRADE_ACTION_MODIFY; + m_request.magic =m_magic; + m_request.order =ticket; + m_request.price =price; + m_request.stoplimit =stoplimit; + m_request.sl =sl; + m_request.tp =tp; + m_request.type_time =type_time; + m_request.expiration =expiration; +//--- action and return the result + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Delete specified pending order | +//+------------------------------------------------------------------+ +bool CTrade::OrderDelete(const ulong ticket) + { +//--- check stopped + if(IsStopped(__FUNCTION__)) + return(false); +//--- clean + ClearStructures(); +//--- setting request + m_request.action =TRADE_ACTION_REMOVE; + m_request.magic =m_magic; + m_request.order =ticket; +//--- action and return the result + return(OrderSend(m_request,m_result)); + } +//+------------------------------------------------------------------+ +//| Output full information of request to log | +//+------------------------------------------------------------------+ +void CTrade::PrintRequest(void) const + { + if(m_log_level0.0) + volume=stepvol*(MathFloor(lots/stepvol)-1); + //--- + double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); + if(volumeLOG_LEVEL_ERRORS) + PrintFormat(__FUNCTION__+": %s [%s]",FormatRequest(action,request),FormatRequestResult(fmt,request,result)); + } + else + { + if(m_log_level>LOG_LEVEL_NO) + PrintFormat(__FUNCTION__+": %s [%s]",FormatRequest(action,request),FormatRequestResult(fmt,request,result)); + } +//--- return the result + return(res); + } +//+------------------------------------------------------------------+ +//| Position select depending on netting or hedging | +//+------------------------------------------------------------------+ +bool CTrade::SelectPosition(const string symbol) + { + bool res=false; +//--- + if(IsHedging()) + { + uint total=PositionsTotal(); + for(uint i=0; i +#define CLASS_LAYOUT 999 + +#ifdef LAYOUT_BOX_DEBUG +#define COLOR_BOX_BORDER clrRed +#else +#define COLOR_BOX_BORDER clrNONE +#endif +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +enum LAYOUT_STYLE +{ + LAYOUT_STYLE_VERTICAL, + LAYOUT_STYLE_HORIZONTAL +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +enum VERTICAL_ALIGN +{ + VERTICAL_ALIGN_CENTER, + VERTICAL_ALIGN_CENTER_NOSIDES, + VERTICAL_ALIGN_TOP, + VERTICAL_ALIGN_BOTTOM +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +enum HORIZONTAL_ALIGN +{ + HORIZONTAL_ALIGN_CENTER, + HORIZONTAL_ALIGN_CENTER_NOSIDES, + HORIZONTAL_ALIGN_LEFT, + HORIZONTAL_ALIGN_RIGHT +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +class CBox: public CWndClient +{ + protected: + LAYOUT_STYLE m_layout_style; + VERTICAL_ALIGN m_vertical_align; + HORIZONTAL_ALIGN m_horizontal_align; + CSize m_min_size; + int m_controls_total; + int m_padding_top; + int m_padding_bottom; + int m_padding_left; + int m_padding_right; + int m_total_x; + int m_total_y; + + public: + CBox(); + ~CBox(); + virtual int Type() const + { + return CLASS_LAYOUT; + } + virtual bool Create(const long chart, const string name, const int subwin, + const int x1, const int y1, const int x2, const int y2); + virtual bool Pack(); + void LayoutStyle(LAYOUT_STYLE style) + { + m_layout_style = style; + } + LAYOUT_STYLE LayoutStyle() const + { + return (m_layout_style); + } + void HorizontalAlign(const HORIZONTAL_ALIGN align) + { + m_horizontal_align = align; + } + HORIZONTAL_ALIGN HorizontalAlign() const + { + return (m_horizontal_align); + } + void VerticalAlign(const VERTICAL_ALIGN align) + { + m_vertical_align = align; + } + VERTICAL_ALIGN VerticalAlign() const + { + return (m_vertical_align); + } + void Padding(const int top, const int bottom, const int left, const int right); + void Padding(const int padding); + void PaddingTop(const int padding) + { + m_padding_top = padding; + } + int PaddingTop() const + { + return (m_padding_top); + } + void PaddingRight(const int padding) + { + m_padding_right = padding; + } + int PaddingRight() const + { + return (m_padding_right); + } + void PaddingBottom(const int padding) + { + m_padding_bottom = padding; + } + int PaddingBottom() const + { + return (m_padding_bottom); + } + void PaddingLeft(const int padding) + { + m_padding_left = padding; + } + int PaddingLeft() const + { + return (m_padding_left); + } + CSize GetMinSize() const + { + CSize sz; + sz.cx = m_min_size.cx + m_padding_left + m_padding_right; + sz.cy = m_min_size.cy + m_padding_top + m_padding_bottom; + return sz; + } + + protected: + virtual void CheckControlSize(CWnd *control); + virtual void GetTotalControlsSize(void); + virtual bool GetSpace(int &x_space, int &y_space); + virtual bool Render(void); + virtual void Shift(CWnd *control, int &x, int &y, const int x_space, const int y_space); +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CBox::CBox(): + m_layout_style(LAYOUT_STYLE_HORIZONTAL), + m_vertical_align(VERTICAL_ALIGN_CENTER), + m_horizontal_align(HORIZONTAL_ALIGN_CENTER), + m_controls_total(0), + m_padding_top(2), + m_padding_bottom(2), + m_padding_left(2), + m_padding_right(2), + m_total_x(0), + m_total_y(0) + +{ + m_min_size.cx = 0; + m_min_size.cy = 0; +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CBox::~CBox() +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CBox::Create(const long chart, const string name, const int subwin, + const int x1, const int y1, const int x2, const int y2) +{ + if(!CWndContainer::Create(chart, name, subwin, x1, y1, x2, y2)) + return (false); + if(!CreateBack()) + return (false); + if(!ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG)) + return (false); + if(!ColorBorder(COLOR_BOX_BORDER)) + return (false); + return (true); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CBox::Pack(void) +{ + GetTotalControlsSize(); + return (Render()); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CBox::CheckControlSize(CWnd *control) +{ + bool adjust = false; + CSize size = Size(); + CSize control_size = control.Size(); + if(control_size.cx > size.cx - (m_padding_left + m_padding_right)) + { + control_size.cx = size.cx - (m_padding_left + m_padding_right); + adjust = true; + } + if(control_size.cy > size.cy - (m_padding_top + m_padding_bottom)) + { + control_size.cy = size.cy - (m_padding_top + m_padding_bottom); + adjust = true; + } + if(adjust) + control.Size(control_size.cx, control_size.cy); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CBox::GetTotalControlsSize(void) +{ + m_total_x = 0; + m_total_y = 0; + m_controls_total = 0; + m_min_size.cx = 0; + m_min_size.cy = 0; + int total = ControlsTotal(); + + + for(int i = 0; i < total; i++) + { + CWnd *control = Control(i); + if(control == NULL) continue; + if(control == &m_background) continue; + CheckControlSize(control); + if(control.Type() == CLASS_LAYOUT) + { + ((CBox *)control).GetTotalControlsSize(); + } + + CSize control_size = control.Size(); + if(m_min_size.cx < control_size.cx) + m_min_size.cx = control_size.cx; + if(m_min_size.cy < control_size.cy) + m_min_size.cy = control_size.cy; + if(m_layout_style == LAYOUT_STYLE_HORIZONTAL) m_total_x += control_size.cx; + else m_total_x = MathMax(m_min_size.cx, m_total_x); + if(m_layout_style == LAYOUT_STYLE_VERTICAL) m_total_y += control_size.cy; + else m_total_y = MathMax(m_min_size.cy, m_total_y); + m_controls_total++; + } + + CSize size = Size(); + + if(m_total_x > size.cx && m_layout_style == LAYOUT_STYLE_HORIZONTAL) + { + size.cx = m_total_x; + } + if(m_total_y > size.cy && m_layout_style == LAYOUT_STYLE_VERTICAL) // shrink + { + size.cy = m_total_y; + } + + Size(size); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CBox::GetSpace(int &x_space, int &y_space) +{ + if(m_controls_total == 0) + return (true); + if(m_controls_total == 1) + { + if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES) + m_horizontal_align = HORIZONTAL_ALIGN_CENTER; + if(m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES) + m_vertical_align = VERTICAL_ALIGN_CENTER; + } + CSize size = Size(); + + int x_space_total = 0; + int y_space_total = 0; + if(m_layout_style == LAYOUT_STYLE_HORIZONTAL) + { + x_space_total = size.cx - (m_total_x + m_padding_left + m_padding_right); + y_space_total = size.cy - (m_min_size.cy + m_padding_top + m_padding_bottom); + + if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES) + x_space = x_space_total / (m_controls_total - 1); + else if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER) + x_space = x_space_total / (m_controls_total + 1); + else + x_space = x_space_total / m_controls_total; + + if(m_vertical_align == VERTICAL_ALIGN_CENTER || m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES) + y_space = y_space_total / 2; + else + y_space = y_space_total; + } + else if(m_layout_style == LAYOUT_STYLE_VERTICAL) + { + x_space_total = size.cx - (m_min_size.cx + m_padding_left + m_padding_right); + y_space_total = size.cy - (m_total_y + m_padding_top + m_padding_bottom); + + if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER || m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES) + x_space = x_space_total / 2; + else + x_space = x_space_total; + + if(m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES) + y_space = y_space_total / (m_controls_total - 1); + else if(m_vertical_align == VERTICAL_ALIGN_CENTER) + y_space = y_space_total / (m_controls_total + 1); + else + y_space = y_space_total / m_controls_total; + } + else + return (false); + + if(x_space < 0) x_space = 0; + if(y_space < 0) y_space = 0; + + return (true); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CBox::Shift(CWnd *control, int &x, int &y, const int x_space, const int y_space) +{ + if(m_layout_style == LAYOUT_STYLE_HORIZONTAL) + x += x_space + control.Width(); + else if(m_layout_style == LAYOUT_STYLE_VERTICAL) + y += y_space + control.Height(); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CBox::Render(void) +{ + int x_space = 0, y_space = 0; + if(!GetSpace(x_space, y_space)) + return (false); + int x = Left() + m_padding_left + + ((m_horizontal_align == HORIZONTAL_ALIGN_LEFT || m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES) ? 0 : x_space); + int y = Top() + m_padding_top + + ((m_vertical_align == VERTICAL_ALIGN_TOP || m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES) ? 0 : y_space); + for(int j = 0; j < ControlsTotal(); j++) + { + CWnd *control = Control(j); + if(control == NULL) + continue; + if(control == GetPointer(m_background)) + continue; + control.Move(x, y); + if(control.Type() == CLASS_LAYOUT) + { + CBox *container = control; + container.Pack(); + } + if(j < ControlsTotal() - 1) + Shift(GetPointer(control), x, y, x_space, y_space); + } + return (true); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CBox::Padding(const int top, const int right, const int bottom, const int left) +{ + m_padding_top = top; + m_padding_right = right; + m_padding_bottom = bottom; + m_padding_left = left; +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CBox::Padding(const int padding) +{ + m_padding_top = padding; + m_padding_right = padding; + m_padding_bottom = padding; + m_padding_left = padding; +} +//+------------------------------------------------------------------+ diff --git a/test/Layouts/ComboBoxResizable.mqh b/test/Layouts/ComboBoxResizable.mqh new file mode 100644 index 0000000..500647c --- /dev/null +++ b/test/Layouts/ComboBoxResizable.mqh @@ -0,0 +1,66 @@ +//+------------------------------------------------------------------+ +//| ComboBoxResizable.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//+------------------------------------------------------------------+ + +#include + +class ComboBoxResizable: public CComboBox +{ + public: + virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override; + + virtual bool OnResize(void) override + { + m_edit.Width(Width()); + + int x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_COMBO_BUTTON_X_OFF); + int y1 = (Height() - CONTROLS_BUTTON_SIZE) / 2; + m_drop.Move(Left() + x1, Top() + y1); + + m_list.Width(Width()); + + return CWndContainer::OnResize(); + } + + virtual bool OnClickButton(void) override + { + // this is a hack to trigger resizing of elements in the list + // we need it because standard ListView is incorrectly coded in such a way + // that elements are resized only if vscroll is present + bool vs = m_list.VScrolled(); + if(m_drop.Pressed()) + { + m_list.VScrolled(true); + } + bool b = CComboBox::OnClickButton(); + m_list.VScrolled(vs); + return b; + } + + virtual bool Enable(void) override + { + m_edit.Show(); + m_drop.Show(); + return CComboBox::Enable(); + } + + virtual bool Disable(void) override + { + m_edit.Hide(); + m_drop.Hide(); + return CComboBox::Disable(); + } +}; + +#define EXIT_ON_DISABLED \ + if(!IsEnabled()) \ + { \ + return false; \ + } + +EVENT_MAP_BEGIN(ComboBoxResizable) + EXIT_ON_DISABLED + ON_EVENT(ON_CLICK, m_drop, OnClickButton) +EVENT_MAP_END(CComboBox) diff --git a/test/Layouts/Grid.mqh b/test/Layouts/Grid.mqh new file mode 100644 index 0000000..5a209e8 --- /dev/null +++ b/test/Layouts/Grid.mqh @@ -0,0 +1,153 @@ +//+------------------------------------------------------------------+ +//| Grid.mqh | +//| Enrico Lambino | +//| www.mql5.com/en/users/iceron| +//+------------------------------------------------------------------+ +#property copyright "Enrico Lambino" +#property link "http://www.mql5.com" +#property strict +#include "Box.mqh" +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +class CGrid: public CBox +{ + protected: + int m_cols; + int m_rows; + int m_hgap; + int m_vgap; + CSize m_cell_size; + + public: + CGrid(); + CGrid(int rows, int cols, int hgap = 0, int vgap = 0); + ~CGrid(); + virtual int Type() const + { + return CLASS_LAYOUT; + } + virtual bool Init(int rows, int cols, int hgap = 0, int vgap = 0); + virtual bool Create(const long chart, const string name, const int subwin, + const int x1, const int y1, const int x2, const int y2); + virtual int Columns() + { + return (m_cols); + } + virtual void Columns(int cols) + { + m_cols = cols; + } + virtual int Rows() + { + return (m_rows); + } + virtual void Rows(int rows) + { + m_rows = rows; + } + virtual int HGap() + { + return (m_hgap); + } + virtual void HGap(int gap) + { + m_hgap = gap; + } + virtual int VGap() + { + return (m_vgap); + } + virtual void VGap(int gap) + { + m_vgap = gap; + } + virtual bool Pack(); + + protected: + virtual void CheckControlSize(CWnd *control); +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGrid::CGrid() +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGrid::CGrid(int rows, int cols, int hgap = 0, int vgap = 0) +{ + Init(rows, cols, hgap, vgap); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGrid::~CGrid() +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGrid::Init(int rows, int cols, int hgap = 0, int vgap = 0) +{ + Columns(cols); + Rows(rows); + HGap(hgap); + VGap(vgap); + return (true); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGrid::Create(const long chart, const string name, const int subwin, + const int x1, const int y1, const int x2, const int y2) +{ + return (CBox::Create(chart, name, subwin, x1, y1, x2, y2)); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGrid::Pack() +{ + CSize size = Size(); + m_cell_size.cx = (size.cx - ((m_cols + 1) * m_hgap)) / m_cols; + m_cell_size.cy = (size.cy - ((m_rows + 1) * m_vgap)) / m_rows; + int x = Left(), y = Top(); + int cnt = 0; + for(int i = 0; i < ControlsTotal(); i++) + { + CWnd *control = Control(i); + if(control == NULL) + continue; + if(control == GetPointer(m_background)) + continue; + if(cnt == 0 || Right() - (x + m_cell_size.cx) < m_cell_size.cx + m_hgap) + { + if(cnt == 0) + y += m_vgap; + else + y += m_vgap + m_cell_size.cy; + x = Left() + m_hgap; + } + else + x += m_cell_size.cx + m_hgap; + CheckControlSize(control); + control.Move(x, y); + if(control.Type() == CLASS_LAYOUT) + { + CBox *container = control; + container.Pack(); + } + cnt++; + } + return (true); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGrid::CheckControlSize(CWnd *control) +{ + control.Size(m_cell_size.cx, m_cell_size.cy); +} +//+------------------------------------------------------------------+ diff --git a/test/Layouts/GridTk.mqh b/test/Layouts/GridTk.mqh new file mode 100644 index 0000000..a9348f7 --- /dev/null +++ b/test/Layouts/GridTk.mqh @@ -0,0 +1,154 @@ +//+------------------------------------------------------------------+ +//| GridTk.mqh | +//| Enrico Lambino | +//| www.mql5.com/en/users/iceron| +//+------------------------------------------------------------------+ +#property copyright "Enrico Lambino" +#property link "http://www.mql5.com" +#property strict +#include "Grid.mqh" +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +class CGridConstraints: public CObject +{ + protected: + CWnd *m_control; + int m_row; + int m_col; + int m_rowspan; + int m_colspan; + + public: + CGridConstraints(CWnd *control, int row, int column, int rowspan = 1, int colspan = 1); + ~CGridConstraints(); + CWnd *Control() + { + return (m_control); + } + int Row() + { + return (m_row); + } + int Column() + { + return (m_col); + } + int RowSpan() + { + return (m_rowspan); + } + int ColSpan() + { + return (m_colspan); + } +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGridConstraints::CGridConstraints(CWnd *control, int row, int column, int rowspan = 1, int colspan = 1) +{ + m_control = control; + m_row = MathMax(0, row); + m_col = MathMax(0, column); + m_rowspan = MathMax(1, rowspan); + m_colspan = MathMax(1, colspan); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGridConstraints::~CGridConstraints() +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +class CGridTk: public CGrid +{ + protected: + CArrayObj m_constraints; + + public: + CGridTk(); + ~CGridTk(); + bool Grid(CWnd *control, int row, int column, int rowspan, int colspan); + bool Pack(); + CGridConstraints *GetGridConstraints(CWnd *control); +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGridTk::CGridTk(void) +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGridTk::~CGridTk(void) +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGridTk::Grid(CWnd *control, int row, int column, int rowspan = 1, int colspan = 1) +{ + CGridConstraints *constraints = new CGridConstraints(control, row, column, rowspan, colspan); + if(!CheckPointer(constraints)) + return (false); + if(!m_constraints.Add(constraints)) + return (false); + return (Add(control)); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGridTk::Pack() +{ + CGrid::Pack(); + CSize size = Size(); + m_cell_size.cx = (size.cx - (m_cols + 1) * m_hgap) / m_cols; + m_cell_size.cy = (size.cy - (m_rows + 1) * m_vgap) / m_rows; + for(int i = 0; i < ControlsTotal(); i++) + { + int x = Left(), y = Top(); + CWnd *control = Control(i); + if(control == NULL) + continue; + if(control == GetPointer(m_background)) + continue; + CGridConstraints *constraints = GetGridConstraints(control); + if(constraints == NULL) + continue; + int column = constraints.Column(); + int row = constraints.Row(); + x += (column * m_cell_size.cx) + ((column + 1) * m_hgap); + y += (row * m_cell_size.cy) + ((row + 1) * m_vgap); + int colspan = constraints.ColSpan(); + int rowspan = constraints.RowSpan(); + control.Size(colspan * m_cell_size.cx + ((colspan - 1) * m_hgap), rowspan * m_cell_size.cy + ((rowspan - 1) * m_vgap)); + control.Move(x, y); + if(control.Type() == CLASS_LAYOUT) + { + CBox *container = control; + container.Pack(); + } + } + return (true); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGridConstraints *CGridTk::GetGridConstraints(CWnd *control) +{ + for(int i = 0; i < m_constraints.Total(); i++) + { + CGridConstraints *constraints = m_constraints.At(i); + CWnd *ctrl = constraints.Control(); + if(ctrl == NULL) + continue; + if(ctrl == control) + return (constraints); + } + return (NULL); +} +//+------------------------------------------------------------------+ diff --git a/test/Layouts/MaximizableAppDialog.mqh b/test/Layouts/MaximizableAppDialog.mqh new file mode 100644 index 0000000..d41c47b --- /dev/null +++ b/test/Layouts/MaximizableAppDialog.mqh @@ -0,0 +1,337 @@ +//+------------------------------------------------------------------+ +//| MaximizableAppDialog.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//+------------------------------------------------------------------+ + +#include +#include + +#resource "res\\Expand2.bmp" +#resource "res\\size6.bmp" +#resource "res\\size10.bmp" + +class MaximizableAppDialog: public CAppDialog +{ + protected: + CBmpButton m_button_truemax; + CBmpButton m_button_size; + bool m_maximized; + CRect m_max_rect; + CSize m_size_limit; + bool m_sizing; + + // window maximization + virtual bool CreateButtonMinMax(void) override; + virtual void OnClickButtonMinMax(void) override; + virtual void OnClickButtonTrueMax(void); + virtual void OnClickButtonSizeFixMe(void); + virtual void Expand(void); + virtual void Restore(void); + + virtual void Minimize(void) override; + + // window resizing + bool CreateButtonSize(void); + bool OnDialogSizeStart(void); + virtual bool OnDialogDragStart(void) override; + virtual bool OnDialogDragProcess(void) override; + virtual bool OnDialogDragEnd(void) override; + + virtual void SelfAdjustment(const bool minimized = false) = 0; + + public: + MaximizableAppDialog(): m_maximized(false), m_sizing(false) {} + virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) override; + virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override; + + virtual bool OnChartChange(const long &lparam, const double &dparam, const string &sparam); + + void ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); + + void SetSizeLimit(const CSize &limit) { m_size_limit = limit; } + CSize GetSizeLimit() { return m_size_limit; } +}; + +void MaximizableAppDialog::ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + if(id == CHARTEVENT_CHART_CHANGE) + { + if(OnChartChange(lparam, dparam, sparam)) return; + } + CAppDialog::ChartEvent(id, lparam, dparam, sparam); +} + +EVENT_MAP_BEGIN(MaximizableAppDialog) + ON_EVENT(ON_CLICK, m_button_truemax, OnClickButtonTrueMax) + ON_EVENT(ON_CLICK, m_button_size, OnClickButtonSizeFixMe) + ON_EVENT(ON_DRAG_START, m_button_size, OnDialogSizeStart) + ON_EVENT_PTR(ON_DRAG_PROCESS, m_drag_object, OnDialogDragProcess) + ON_EVENT_PTR(ON_DRAG_END, m_drag_object, OnDialogDragEnd) +EVENT_MAP_END(CAppDialog) + +bool MaximizableAppDialog::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) +{ + // 1 * CONTROLS_BORDER_WIDTH - stays here, because the standard control library minimizes window + // when it's height is 1 pixel smaller than the entire chart height + m_max_rect.SetBound(0, + 0, + (int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) - 0 * CONTROLS_BORDER_WIDTH, + (int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS) - 1 * CONTROLS_BORDER_WIDTH); + if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)) return false; + if(!CreateButtonSize()) return false; + m_size_limit.cx = x2 - x1; + m_size_limit.cy = y2 - y1; + if(m_size_limit.cx >= m_max_rect.Width() || m_size_limit.cy >= m_max_rect.Height()) + { + m_size_limit.cx = m_min_rect.Width() * 3; + m_size_limit.cy = m_min_rect.Height() * 7; + } + + return true; +} + +bool MaximizableAppDialog::CreateButtonMinMax(void) override +{ + if(!CAppDialog::CreateButtonMinMax()) return false; + + // add maximization button + int off = (m_panel_flag) ? 0 : 2 * CONTROLS_BORDER_WIDTH; + + int x1 = Width() - off - 3 * (CONTROLS_BUTTON_SIZE + CONTROLS_DIALOG_BUTTON_OFF); + int y1 = off + CONTROLS_DIALOG_BUTTON_OFF; + int x2 = x1 + CONTROLS_BUTTON_SIZE; + int y2 = y1 + CONTROLS_BUTTON_SIZE; + + if(!m_button_truemax.Create(m_chart_id, m_name + "TrueMax", m_subwin, x1, y1, x2, y2)) return false; + if(!m_button_truemax.BmpNames("::res\\Expand2.bmp", "::res\\Restore.bmp")) return false; + if(!CWndContainer::Add(m_button_truemax)) return false; + + m_button_truemax.Locking(true); + m_button_truemax.Alignment(WND_ALIGN_RIGHT, 0, 0, off + 2 * CONTROLS_BUTTON_SIZE + 2 * CONTROLS_DIALOG_BUTTON_OFF, 0); + + CaptionAlignment(WND_ALIGN_WIDTH, off, 0, off + 3 * (CONTROLS_BUTTON_SIZE + CONTROLS_DIALOG_BUTTON_OFF), 0); + + return true; +} + +bool MaximizableAppDialog::CreateButtonSize(void) +{ + int off = (m_panel_flag) ? 0 : 2 * CONTROLS_BORDER_WIDTH; + + int x1 = Width() - CONTROLS_BUTTON_SIZE + 1; + int y1 = Height() - CONTROLS_BUTTON_SIZE + 1; + int x2 = x1 + CONTROLS_BUTTON_SIZE - 1; + int y2 = y1 + CONTROLS_BUTTON_SIZE - 1; + + if(!m_button_size.Create(m_chart_id, m_name + "Size", m_subwin, x1, y1, x2, y2)) return false; + if(!m_button_size.BmpNames("::res\\size6.bmp", "::res\\size10.bmp")) return false; + if(!CWndContainer::Add(m_button_size)) return false; + m_button_size.Alignment(WND_ALIGN_RIGHT|WND_ALIGN_BOTTOM, 0, 0, 0, 0); + m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG); + + return true; +} + +void MaximizableAppDialog::OnClickButtonTrueMax(void) +{ + if(m_button_truemax.Pressed()) + Expand(); + else + Restore(); + + SubwinOff(); +} + +// This is a hack. It's required because in minimized state sizing button somehow "overlaps" +// the close button and intercepts clicks on it (which prevents exit from minimized app). +// This happens despite the fact that the sizing button is hidden, disabled and assigned +// with minimal Z-order (checked out, then removed). +// Looks like a bug in the standard control library, specifically: +// In CWnd::OnMouseEvent there must be a line: +// +// if(!IS_ENABLED || !IS_VISIBLE) return false; +// +// but it's not there, so invisible, disabled and even background objects are processed +// in the same manner as all other objects. Specifically in CWndContainer::OnMouseEvent +// there is a reverse loop through all objects (it does _not_ respect Z-order anyhow). + +void MaximizableAppDialog::OnClickButtonSizeFixMe(void) +{ + if(m_minimized) + { + Destroy(); + } +} + +void MaximizableAppDialog::Expand(void) +{ + m_maximized = true; + m_minimized = false; + m_button_minmax.Pressed(false); + Rebound(m_max_rect); + m_button_size.Hide(); + m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE); + m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG); + if(!m_panel_flag) + { + m_caption.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG); + } + + ClientAreaVisible(true); + SelfAdjustment(); +} + +//+------------------------------------------------------------------+ +//| Restore dialog window | +//+------------------------------------------------------------------+ +void MaximizableAppDialog::Restore(void) +{ + m_maximized = false; + m_minimized = false; + m_button_minmax.Pressed(false); + m_button_size.Show(); + m_button_size.StateFlagsSet(WND_STATE_FLAG_ENABLE); + m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG); + CAppDialog::Maximize(); + if(!m_panel_flag) + { + m_caption.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG); + } + SelfAdjustment(); +} + +void MaximizableAppDialog::Minimize() +{ + CAppDialog::Minimize(); + m_button_size.Hide(); + m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE); + m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG); +} + +bool MaximizableAppDialog::OnChartChange(const long &lparam, const double &dparam, const string &sparam) +{ + m_max_rect.SetBound(0, 0, + (int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) - 0 * CONTROLS_BORDER_WIDTH, + (int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS) - 1 * CONTROLS_BORDER_WIDTH); + if(m_maximized) + { + if(m_rect.Width() != m_max_rect.Width() || m_rect.Height() != m_max_rect.Height()) + { + Rebound(m_max_rect); + SelfAdjustment(); + m_chart.Redraw(); + } + return true; + } + return false; +} + +void MaximizableAppDialog::OnClickButtonMinMax(void) +{ + CAppDialog::OnClickButtonMinMax(); + m_button_truemax.Pressed(false); + m_maximized = false; + if(m_minimized) + { + m_button_size.Hide(); + m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE); + m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG); + } + else + { + m_button_size.Show(); + m_button_size.StateFlagsSet(WND_STATE_FLAG_ENABLE); + m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG); + } + if(!m_panel_flag) + { + m_caption.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG); + } + SelfAdjustment(m_minimized); +} + +bool MaximizableAppDialog::OnDialogSizeStart(void) +{ + if(m_drag_object == NULL) + { + m_drag_object = new CDragWnd; + if(m_drag_object == NULL) return false; + } + int x1 = m_button_size.Left() - CONTROLS_DRAG_SPACING; + int y1 = m_button_size.Top() - CONTROLS_DRAG_SPACING; + int x2 = m_button_size.Right() + CONTROLS_DRAG_SPACING; + int y2 = m_button_size.Bottom() + CONTROLS_DRAG_SPACING; + + m_drag_object.Create(m_chart_id, "", m_subwin, x1, y1, x2, y2); + m_drag_object.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG); + + CChart chart; + chart.Attach(m_chart_id); + m_drag_object.Limits(-CONTROLS_DRAG_SPACING, -CONTROLS_DRAG_SPACING, + chart.WidthInPixels() + CONTROLS_DRAG_SPACING, + chart.HeightInPixels(m_subwin) + CONTROLS_DRAG_SPACING); + chart.Detach(); + + m_drag_object.MouseX(m_button_size.MouseX()); + m_drag_object.MouseY(m_button_size.MouseY()); + m_drag_object.MouseFlags(m_button_size.MouseFlags()); + + m_sizing = true; + + return true; +} + +bool MaximizableAppDialog::OnDialogDragStart(void) +{ + if(m_maximized) return false; + + return CAppDialog::OnDialogDragStart(); +} +//+------------------------------------------------------------------+ +//| Continue dragging the dialog box | +//+------------------------------------------------------------------+ +bool MaximizableAppDialog::OnDialogDragProcess(void) +{ + if(!m_sizing) return CDialog::OnDialogDragProcess(); + + if(m_drag_object == NULL) return false; + + int x = m_drag_object.Right() - Right() - CONTROLS_DRAG_SPACING; + int y = m_drag_object.Bottom() - Bottom() - CONTROLS_DRAG_SPACING; + + // resize dialog + CRect r = Rect(); + r.right += x; + r.bottom += y; + + if(r.Width() < m_size_limit.cx) r.right = r.left + m_size_limit.cx; + if(r.Height() < m_size_limit.cy) r.bottom = r.top + m_size_limit.cy; + + Rebound(r); + + SelfAdjustment(); + + return true; +} +//+------------------------------------------------------------------+ +//| End dragging the dialog box | +//+------------------------------------------------------------------+ +bool MaximizableAppDialog::OnDialogDragEnd(void) +{ + if(!m_sizing) return CDialog::OnDialogDragEnd(); + + if(m_drag_object != NULL) + { + m_button_size.MouseFlags(m_drag_object.MouseFlags()); + delete m_drag_object; + m_drag_object = NULL; + } + + m_norm_rect.SetBound(m_rect); + m_sizing = false; + + SelfAdjustment(); + + return true; +} diff --git a/test/Layouts/SpinEditResizable.mqh b/test/Layouts/SpinEditResizable.mqh new file mode 100644 index 0000000..e8a1f63 --- /dev/null +++ b/test/Layouts/SpinEditResizable.mqh @@ -0,0 +1,27 @@ +//+------------------------------------------------------------------+ +//| SpinEditResizable.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//+------------------------------------------------------------------+ + +#include + +class SpinEditResizable: public CSpinEdit +{ + public: + virtual bool OnResize(void) override + { + m_edit.Width(Width()); + m_edit.Height(Height()); + + int x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_SPIN_BUTTON_X_OFF); + int y1 = (Height() - 2 * CONTROLS_SPIN_BUTTON_SIZE) / 2; + m_inc.Move(Left() + x1, Top() + y1); + + x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_SPIN_BUTTON_X_OFF); + y1 = (Height() - 2 * CONTROLS_SPIN_BUTTON_SIZE) / 2 + CONTROLS_SPIN_BUTTON_SIZE; + m_dec.Move(Left() + x1, Top() + y1); + + return CWndContainer::OnResize(); + } +}; \ No newline at end of file diff --git a/test/Layouts/res/expand2.bmp b/test/Layouts/res/expand2.bmp new file mode 100644 index 0000000..40da9f2 Binary files /dev/null and b/test/Layouts/res/expand2.bmp differ diff --git a/test/Layouts/res/size10.bmp b/test/Layouts/res/size10.bmp new file mode 100644 index 0000000..b54a419 Binary files /dev/null and b/test/Layouts/res/size10.bmp differ diff --git a/test/Layouts/res/size6.bmp b/test/Layouts/res/size6.bmp new file mode 100644 index 0000000..3c6d810 Binary files /dev/null and b/test/Layouts/res/size6.bmp differ diff --git a/test/MT4Orders.mqh b/test/MT4Orders.mqh new file mode 100644 index 0000000..c75f292 --- /dev/null +++ b/test/MT4Orders.mqh @@ -0,0 +1 @@ +// MQL4&5-code // Ïàðàëëåëüíîå èñïîëüçîâàíèå MT4 è MT5 îðäåðíûõ ñèñòåì. // https://www.mql5.com/ru/code/16006 // Äàííûé mqh-ôàéë ïîñëå ñîîòâåòñòâóþùåãî #include ïîçâîëÿåò ðàáîòàòü ñ îðäåðàìè â MQL5 (MT5-Hedge) òî÷íî òàê æå, êàê â MQL4. // Ò.å. îðäåðíàÿ ÿçûêîâàÿ ñèñòåìà (ÎßÑ) ñòàíîâèòñÿ èäåíòè÷íîé MQL4. Ïðè ýòîì ñîõðàíÿåòñÿ âîçìîæíîñòü ÏÀÐÀËËÅËÜÍÎ èñïîëüçîâàòü // MQL5-îðäåðíóþ ñèñòåìó.  ÷àñòíîñòè, ñòàíäàðòíàÿ MQL5-áèáëèîòåêà áóäåò ïðîäîëæàòü ïîëíîöåííî ðàáîòàòü. // Âûáîð ìåæäó îðäåðíûìè ñèñòåìàìè äåëàòü íå íóæíî. Èñïîëüçóéòå èõ ïàðàëëåëüíî! // Ïðè ïåðåâîäå MQL4 -> MQL5 îðäåðíóþ ñèñòåìó òðîãàòü ÑÎÂÑÅÌ íå òðåáóåòñÿ. // Äîñòàòî÷íî âíåñòè òîëüêî îäíó ñòðî÷êó â íà÷àëå (åñëè èñõîäíèê ñïîñîáåí êîìïèëèðîâàòüñÿ â MT4 ïðè #property strict): // #include // åñëè åñòü #include , âñòàâèòü ýòó ñòðî÷êó ÏÎÑËÅ // Àíàëîãè÷íî äåéñòâóÿ (äîáàâëåíèå îäíîé ñòðîêè) â ñâîèõ MQL5-êîäàõ, ìîæíî äîáàâèòü ê MT5-ÎßÑ åùå è MT4-ÎßÑ, ëèáî ïîëíîñòüþ çàìåíèòü åå. // Àâòîð ñîçäàâàë òàêóþ âîçìîæíîñòü äëÿ ñåáÿ, ïîýòîìó íàìåðåííî íå ïðîâîäèë ïîäîáíóþ æå èäåþ ïåðåõîäà "îäíîé ñòðîêîé" // äëÿ òàéìñåðèé, ãðàôè÷åñêèõ îáúåêòîâ, èíäèêàòîðîâ è ò.ä. // Äàííàÿ ðàáîòà çàòðàãèâàåò ÒÎËÜÊÎ îðäåðíóþ ñèñòåìó. // Âîïðîñ âîçìîæíîñòè ñîçäàíèÿ ïîäîáíîé ïîëíîé áèáëèîòåêè, êîãäà MQL4-êîä ìîæåò ðàáîòàòü â MT5 ÁÅÇ ÈÇÌÅÍÅÍÈÉ, íå ðåøàëñÿ. // ×òî íå ðåàëèçîâàíî: // CloseBy-ìîìåíòû - ïîêà áûëî íå äî ýòîãî. Âîçìîæíî, â áóäóùåì, êîãäà ïîíàäîáèòñÿ. // Îïðåäåëåíèå TP è SL çàêðûòûõ ïîçèöèé - íà äàííûé ìîìåíò (build 1470) MQL5 ýòîãî äåëàòü íå óìååò. // Ó÷åò DEAL_ENTRY_INOUT è DEAL_ENTRY_OUT_BY ñäåëîê. // Îñîáåííîñòè: //  MT4 OrderSelect â ðåæèìå SELECT_BY_TICKET âûáèðàåò òèêåò íåçàâèñèìî îò MODE_TRADES/MODE_HISTORY, // ò.ê. "Íîìåð òèêåòà ÿâëÿåòñÿ óíèêàëüíûì èäåíòèôèêàòîðîì îðäåðà". //  MT5 íîìåð òèêåòà ÍÅ óíèêàëåí, // ïîýòîìó OrderSelect â ðåæèìå SELECT_BY_TICKET èìååò ñëåäóþùèå ïðèîðèòåòû âûáîðà ïðè ñîâïàäàþùèõ òèêåòàõ: // MODE_TRADES: ñóùåñòâóþùàÿ ïîçèöèÿ > ñóùåñòâóþùèé îðäåð > ñäåëêà > îòìåíåííûé îðäåð // MODE_HISTORY: ñäåëêà > îòìåíåííûé îðäåð > ñóùåñòâóþùàÿ ïîçèöèÿ > ñóùåñòâóþùèé îðäåð // // Ñîîòâåòñòâåííî, OrderSelect â ðåæèìå SELECT_BY_TICKET â MT5 â ðåäêèõ ñëó÷àÿõ (â òåñòåðå) ìîæåò âûáðàòü íå òî, ÷òî çàäóìûâàëîñü â MT4. // // Åñëè âûçâàòü OrdersTotal() ñî âõîäíûì ïàðàìåòðîì, òî âîçâðàùàåìîå çíà÷åíèå áóäåò ñîîòâåòñòâîâàòü MT5-âàðèàíòó. // Ñïèñîê èçìåíåíèé: // 03.08.2016: // Ðåëèç - ïèñàëñÿ è ïðîâåðÿëñÿ òîëüêî íà îôôëàéí-òåñòåðå. // 29.09.2016: // Add: âîçìîæíîñòü ðàáîòû íà áèðæå (SYMBOL_TRADE_EXECUTION_EXCHANGE). Ó÷èòûâàéòå, ÷òî áèðæà - Netto(íå Hedge)-mode. // Add: Òðåáîâàíèå "åñëè åñòü #include , âñòàâèòü ýòó ñòðî÷êó ÏÎÑËÅ" // çàìåíåíî íà "åñëè åñòü #include , âñòàâèòü ýòó ñòðî÷êó ÏÎÑËÅ". // Fix: OrderSend ìàðêåò-îðäåðîâ âîçâðàùàåò òèêåò ïîçèöèè, à íå ñäåëêè. // 13.11.2016: // Add: Ïîëíàÿ ñèíõðîíèçàöèÿ OrderSend, OrderModify, OrderClose, OrderDelete ñ òîðãîâûì îêðóæåíèåì (ðåàë-òàéì è èñòîðèÿ) - êàê â MT4. // Ìàêñèìàëüíîå âðåìÿ ñèíõðîíèçàöèè ìîæíî çàäàòü ÷åðåç MT4ORDERS::OrderSend_MaxPause â ìêñ. Ñðåäíåå âðåìÿ ñèíõðîíèçàöèè â MT5 ~1 ìñ. // Ïî-óìîë÷àíèþ ìàêñèìàëüíîå âðåìÿ ñèíõðîíèçàöèè ðàâíî îäíîé ñåêóíäå. MT4ORDERS::OrderSend_MaxPause = 0 - îòñóòñòâèå ñèíõðîíèçàöèè. // Add: Ïîñêîëüêó ïàðàìåòð SlipPage (OrderSend, OrderClose) âëèÿåò íà èñïîëíåíèå ìàðêåò-îðäåðîâ òîëüêî â Instant-ðåæèìå, // òî ÷åðåç íåãî òåïåðü ïðè æåëàíèè ìîæíî çàäàâàòü òèï èñïîëíåíèÿ ïî îñòàòêó - ENUM_ORDER_TYPE_FILLING: // ORDER_FILLING_FOK, ORDER_FILLING_IOC èëè ORDER_FILLING_RETURN. //  ñëó÷àå îøèáî÷íîãî çàäàíèÿ èëè íå ïîääåðæêè ñèìâîëîì çàäàííîãî òèïà èñïîëíåíèÿ àâòîìàòè÷åñêè áóäåò âûáðàí ðàáî÷èé ðåæèì. // Ïðèìåðû: // OrderSend(Symb, Type, Lots, Price, ORDER_FILLING_FOK, SL, TP) - îòïðàâèòü ñîîòâåòñòâóþùèé îðäåð ñ òèïîì èñïîëíåíèÿ ORDER_FILLING_FOK // OrderSend(Symb, Type, Lots, Price, ORDER_FILLING_IOC, SL, TP) - îòïðàâèòü ñîîòâåòñòâóþùèé îðäåð ñ òèïîì èñïîëíåíèÿ ORDER_FILLING_IOC // OrderClose(Ticket, Lots, Price, ORDER_FILLING_RETURN) - îòïðàâèòü ñîîòâåòñòâóþùèé ìàðêåò-îðäåð ñ òèïîì èñïîëíåíèÿ ORDER_FILLING_RETURN // Add: OrdersHistoryTotal() è OrderSelect(Pos, SELECT_BY_POS, MODE_HISTORY) çàêåøèðîâàíû - ðàáîòàþò ìàêñèìàëüíî áûñòðî. //  áèáëèîòåêå íå îñòàëîñü ìåäëåííûõ ðåàëèçàöèé. // 08.02.2017: // Add: Ïåðåìåííûå MT4ORDERS::LastTradeRequest è MT4ORDERS::LastTradeResult ñîäåðæàò ñîîòâåòñòâóþùèå äàííûå MT5-OrderSend. // 14.06.2017: // Add: Âêëþ÷åíà èçíà÷àëüíî çàëîæåííàÿ ðåàëèçàöèÿ îïðåäåëåíèÿ SL/TP çàêðûòûõ ïîçèöèé (çàêðûòûõ ÷åðåç OrderClose). // Add: MagicNumber òåïåðü èìååò òèï long - 8 áàéò (ðàíüøå áûë int - 4 áàéòà). // Add: Åñëè â OrderSend, OrderClose èëè OrderModify öâåòîâîé âõîäíîé ïàðàìåòð (ñàìûé ïîñëåäíèé) çàäàòü ðàâíûì INT_MAX, òî áóäåò ñôîðìèðîâàí // ñîîòâåòñòâóþùèé òîðãîâûé MT5-çàïðîñ (MT4ORDERS::LastTradeRequest), íî îòïðàâëåí îí ÍÅ áóäåò. Âìåñòî ýòîãî áóäåò ïðîâåäåíà åãî MT5-ïðîâåðêà, // ðåçóëüòàò êîòîðîé ñòàíåò äîñòóïåí â MT4ORDERS::LastTradeCheckResult. //  ñëó÷àå óñïåøíîé ïðîâåðêè OrderModify è OrderClose âåðíóò true, èíà÷å - false. // OrderSend âåðíåò 0 â ñëó÷àå óñïåõà, èíà÷å - -1. // // Åñëè æå ñîîòâåòñòâóþùèé öâåòîâîé âõîäíîé ïàðàìåòð çàäàòü ðàíûì INT_MIN, òî ÒÎËÜÊÎ â ñëó÷àå óñïåøíîé MT5-ïðîâåðêè ñôîðìèðîâàííîãî // òîðãîâîãî çàïðîñà(êàê â ñëó÷àå ñ INT_MAX) îí ÁÓÄÅÒ îòïðàâëåí. // Add: Äîáàâëåíû àñèíõðîííûå àíàëîãè MQL4-òîðãîâûì ôóíêöèÿì: OrderSendAsync, OrderModifyAsync, OrderCloseAsync, OrderDeleteAsync. // Âîçâðàùàþò ñîîòâåòñòâóþùèé Result.request_id â ñëó÷àå óäà÷è, èíà÷å - 0. // 03.08.2017: // Add: Äîáàâëåíà OrderCloseBy. // Add: Óñêîðåíà ðàáîòà OrderSelect â MODE_TRADES-ðåæèìå. Òåïåðü åñòü âîçìîæíîñòü ïîëó÷àòü äàííûå âûáðàííîãî îðäåðà ÷åðåç // ñîîòâåòñòâóþùèå MT4-Order-ôóíêöèè, äàæå åñëè MT5-ïîçèöèÿ/îðäåð(íå â èñòîðèè) âûáðàíû íå ÷åðåç MT4Orders. // Íàïðèìåð, ÷åðåç MT5-PositionSelect*-ôóíêöèè èëè MT5-OrderSelect. // Add: Äîáàâëåíû OrderOpenPriceRequest() è OrderClosePriceRequest() - âîçðàùàþò öåíó òîðãîâîãî çàïðîñà ïðè îòêðûòèè/çàêðûòèè ïîçèöèè. // Ñ ïîìîùüþ äàííûõ ôóíêöèé âîçìîæíî âû÷èñëÿòü ñîîòâåòñòâóþùèå ïðîñêàëüçûâàíèÿ îðäåðîâ. // 26.08.2017: // Add: Äîáàâëåíû OrderOpenTimeMsc() è OrderCloseTimeMsc() - ñîîòâåòñòâóþùåå âðåìÿ â ìèëëèñåêóíäàõ. // Fix: Ðàíüøå âñå òîðãîâûå òèêåòû èìåëè òèï int, êàê â MT4. Èç-çà ïîÿâëåíèÿ ñëó÷àåâ âûõîäà çà ïðåäåëû int-òèïà â MT5, òèï òèêåòîâ èçìåíåí íà long. // Ñîîòâåòñòâåííî, OrderTicket è OrderSend âîçâðàùàþò long-çíà÷åíèÿ. Ðåæèì âîçâðàòà òîãî æå òèïà, ÷òî è â MT4 (int), âêëþ÷àåòñÿ ÷åðåç // ïðîïèñûâàíèå ñëåäóþùåé ñòðîêè ïåðåä #include // #define MT4_TICKET_TYPE // Îáÿçûâàåì OrderSend è OrderTicket âîçâðàùàòü çíà÷åíèå òàêîãî æå òèïà, êàê â MT4 - int. // 03.09.2017: // Add: Äîáàâëåíû OrderTicketOpen() - òèêåò MT5-ñäåëêè îòêðûòèÿ ïîçèöèè // OrderOpenReason() - ïðè÷èíà ïðîâåäåíèÿ MT5-ñäåëêè îòêðûòèÿ (ïðè÷èíà îòêðûòèÿ ïîçèöèè) // OrderCloseReason() - ïðè÷èíà ïðîâåäåíèÿ MT5-ñäåëêè çàêðûòèÿ (ïðè÷èíà çàêðûòèÿ ïîçèöèè) // 14.09.2017: // Fix: Òåïåðü áèáëèîòåêà íå âèäèò òåêóùèå MT5-îðäåðà, êîòîðûå íå èìåþò ñîñòîÿíèå ORDER_STATE_PLACED. // ×òîáû áèáëèîòåêà âèäåëà âñå îòêðûòûå MT5-îðäåðà íóæíî ÄÎ áèáëèîòåêè ïðîïèñàòü ñòðîêó // // #define MT4ORDERS_SELECTFILTER_OFF // Îáÿçûâàåì MT4Orders.mqh âèäåòü âñå òåêóùèå MT5-îðäåðà // 16.10.2017: // Fix: OrdersHistoryTotal() ðåàãèðóåò íà ñìåíó íîìåðà òîðãîâîãî ñ÷åòà âî âðåìÿ âûïîëíåíèÿ. // 13.02.2018 // Add: Äîáàâëåíî ëîãèðîâàíèå îøèáî÷íîãî âûïîëíåíèÿ MT5-OrderSend. // Fix: Òåïåðü "íåâèäèìû" òîëüêî çàêðûâàþùèå MT5-îðäåðà (SL/TP/SO, partial/full close). // Fix: Ìåõàíèçì îïðåäåëåíèÿ SL/TP çàêðûòûõ ïîçèöèé ïîñëå OrderClose ñêîððåêòèðîâàí - ðàáîòàåò, åñëè ïîçâîëÿåò StopLevel. // 15.02.2018 // Fix: Ïðîâåðêà ñèíõðîíèçàöèè MT5-OrderSend òåïåðü ó÷èòûâàåò âîçìîæíûå îñîáåííîñòè ðåàëèçàöèè ECN/STP. // 06.03.2018 // Add: Äîáàâëåíû TICKET_TYPE è MAGIC_TYPE, ÷òîáû ìîæíî áûëî ïèñàòü åäèíûé êðîññïëàòôîðìåííûé êîä // áåç ïðåäóïðåæäåíèé êîìïèëÿòîðîâ (âêëþ÷àÿ strict-ðåæèì MQL4). // 30.05.2018 // Add: Óñêîðåíà ðàáîòà ñ èñòîðèåé òîðãîâëè, âûáðàíà çîëîòàÿ ñåðåäèíà ðåàëèçàöèé ìåæäó ïðîèçâîäèòåëüíîñòüþ è // ïîòðåáëåíèåì ïàìÿòè - âàæíî äëÿ VPS. Èñïîëüçóåòñÿ ñòàíäàðòíàÿ Generic-áèáëèîòåêà. // Åñëè íå õî÷åòñÿ èñïîëüçîâàòü Generic-áèáëèîòåêó, òî äîñòóïåí ñòàðûé ðåæèì ðàáîòû ñ èñòîðèåé. // Äëÿ ýòîãî íóæíî ÄÎ MT4Orders-áèáëèîòåêè ïðîïèñàòü ñòðîêó // // #define MT4ORDERS_FASTHISTORY_OFF // Âûêëþ÷àåì áûñòðóþ ðåàëèçàöèþ èñòîðèè òîðãîâëè - íå èñïîëüçóåì Generic-áèáëèîòåêó. // 02.11.2018 // Fix: Öåíà îòêðûòèÿ MT4-ïîçèöèè äî åå ñðàáàòûâàíèÿ òåïåðü íå ìîæåò áûòü íóëåâîé. // Fix: Ó÷òåíû ðåäêèå îñîáåííîñòè èñïîëíåíèÿ íåêîòîðûõ òîðãîâûõ ñåðâåðîâ. // 26.11.2018 // Fix: Ìýäæèê è êîììåíòàðèé çàêðûòîé MT4-ïîçèöèè: ïðèîðèòåò ó ñîîòâåòñòâóþùèõ ïîëåé îòêðûâàþùèõ ñäåëîê âûøå, ÷åì ó çàêðûâàþùèõ. // Fix: Ó÷èòûâàåòñÿ ðåäêîå èçìåíåíèå MT5-OrdersTotal è MT5-PositionsTotal âî âðåìÿ ðàñ÷åòà MT4-OrdersTotal è MT4-OrderSelect. // Fix: Îðäåðà, êîòîðûå îòêðûëè ïîçèöèþ, íî íå óñïåëè óäàëèòüñÿ MT5, áèáëèîòåêîé áîëüøå íå ó÷èòûâàþòñÿ. // 17.01.2019 // Fix: Èñïðàâëåíà äîñàäíàÿ îøèáêà ïðè âûáîðå îòëîæåííûõ îðäåðîâ. // 08.02.2019 // Add: Êîììåíòàðèé ïîçèöèè ñîõðàíÿåòñÿ ïðè ÷àñòè÷íîì çàêðûòèè ÷åðåç OrderClose. // Åñëè íóæíî ñìåíèòü êîììåíòàðèé îòêðûòîé ïîçèöèè ïðè ÷àñòè÷íîì çàêðûòèè, â OrderClose ìîæíî åãî çàäàòü. // 20.02.2019 // Fix:  ñëó÷àå îòñóòñòâèÿ MT5-îðäåðà îò ñóùåñòâóþùåé MT5-ñäåëêè áèáëèîòåêà áóäåò îæèäàòü ñèíõðîíèçàöèè èñòîðèè.  ñëó÷àå íåóäà÷è ñîîáùèò îá ýòîì. #ifdef __MQL5__ #ifndef __MT4ORDERS__ #define __MT4ORDERS__ #define MT4ORDERS_SLTP_OLD // Âêëþ÷åíèå ñòàðîãî ìåõàíèçìà îïðåäåëåíèÿ SL/TP çàêðûòûõ ïîçèöèé ÷åðåç OrderClose #ifdef MT4_TICKET_TYPE #define TICKET_TYPE int #define MAGIC_TYPE int #undef MT4_TICKET_TYPE #else // MT4_TICKET_TYPE #define TICKET_TYPE long #define MAGIC_TYPE long #endif // MT4_TICKET_TYPE struct MT4_ORDER { long Ticket; int Type; long TicketOpen; double Lots; string Symbol; string Comment; double OpenPriceRequest; double OpenPrice; long OpenTimeMsc; datetime OpenTime; ENUM_DEAL_REASON OpenReason; double StopLoss; double TakeProfit; double ClosePriceRequest; double ClosePrice; long CloseTimeMsc; datetime CloseTime; ENUM_DEAL_REASON CloseReason; ENUM_ORDER_STATE State; datetime Expiration; long MagicNumber; double Profit; double Commission; double Swap; #define POSITION_SELECT (-1) #define ORDER_SELECT (-2) static const MT4_ORDER GetPositionData( void ) { MT4_ORDER Res = {0}; Res.Ticket = ::PositionGetInteger(POSITION_TICKET); Res.Type = (int)::PositionGetInteger(POSITION_TYPE); Res.Lots = ::PositionGetDouble(POSITION_VOLUME); Res.Symbol = ::PositionGetString(POSITION_SYMBOL); // Res.Comment = NULL; // MT4ORDERS::CheckPositionCommissionComment(); Res.OpenPrice = ::PositionGetDouble(POSITION_PRICE_OPEN); Res.OpenTime = (datetime)::PositionGetInteger(POSITION_TIME); Res.StopLoss = ::PositionGetDouble(POSITION_SL); Res.TakeProfit = ::PositionGetDouble(POSITION_TP); Res.ClosePrice = ::PositionGetDouble(POSITION_PRICE_CURRENT); Res.CloseTime = 0; Res.Expiration = 0; Res.MagicNumber = ::PositionGetInteger(POSITION_MAGIC); Res.Profit = ::PositionGetDouble(POSITION_PROFIT); Res.Swap = ::PositionGetDouble(POSITION_SWAP); // Res.Commission = UNKNOWN_COMMISSION; // MT4ORDERS::CheckPositionCommissionComment(); return(Res); } static const MT4_ORDER GetOrderData( void ) { MT4_ORDER Res = {0}; Res.Ticket = ::OrderGetInteger(ORDER_TICKET); Res.Type = (int)::OrderGetInteger(ORDER_TYPE); Res.Lots = ::OrderGetDouble(ORDER_VOLUME_CURRENT); Res.Symbol = ::OrderGetString(ORDER_SYMBOL); Res.Comment = ::OrderGetString(ORDER_COMMENT); Res.OpenPrice = ::OrderGetDouble(ORDER_PRICE_OPEN); Res.OpenTime = (datetime)::OrderGetInteger(ORDER_TIME_SETUP); Res.StopLoss = ::OrderGetDouble(ORDER_SL); Res.TakeProfit = ::OrderGetDouble(ORDER_TP); Res.ClosePrice = ::OrderGetDouble(ORDER_PRICE_CURRENT); Res.CloseTime = 0; // (datetime)::OrderGetInteger(ORDER_TIME_DONE) Res.Expiration = (datetime)::OrderGetInteger(ORDER_TIME_EXPIRATION); Res.MagicNumber = ::OrderGetInteger(ORDER_MAGIC); Res.Profit = 0; Res.Commission = 0; Res.Swap = 0; if (!Res.OpenPrice) Res.OpenPrice = Res.ClosePrice; return(Res); } string ToString( void ) const { static const string Types[] = {"buy", "sell", "buy limit", "sell limit", "buy stop", "sell stop", "balance"}; const int digits = (int)::SymbolInfoInteger(this.Symbol, SYMBOL_DIGITS); MT4_ORDER TmpOrder = {0}; if (this.Ticket == POSITION_SELECT) { TmpOrder = MT4_ORDER::GetPositionData(); TmpOrder.Comment = this.Comment; TmpOrder.Commission = this.Commission; } else if (this.Ticket == ORDER_SELECT) TmpOrder = MT4_ORDER::GetOrderData(); return(((this.Ticket == POSITION_SELECT) || (this.Ticket == ORDER_SELECT)) ? TmpOrder.ToString() : ("#" + (string)this.Ticket + " " + (string)this.OpenTime + " " + ((this.Type < ::ArraySize(Types)) ? Types[this.Type] : "unknown") + " " + ::DoubleToString(this.Lots, 2) + " " + this.Symbol + " " + ::DoubleToString(this.OpenPrice, digits) + " " + ::DoubleToString(this.StopLoss, digits) + " " + ::DoubleToString(this.TakeProfit, digits) + " " + ((this.CloseTime > 0) ? ((string)this.CloseTime + " ") : "") + ::DoubleToString(this.ClosePrice, digits) + " " + ::DoubleToString(this.Commission, 2) + " " + ::DoubleToString(this.Swap, 2) + " " + ::DoubleToString(this.Profit, 2) + " " + ((this.Comment == "") ? "" : (this.Comment + " ")) + (string)this.MagicNumber + (((this.Expiration > 0) ? (" expiration " + (string)this.Expiration): "")))); } }; #define RESERVE_SIZE 1000 #define DAY (24 * 3600) #define HISTORY_PAUSE (MT4HISTORY::IsTester ? 0 : 5) #define END_TIME D'31.12.3000 23:59:59' #define THOUSAND 1000 #define LASTTIME(A) \ if (Time##A >= LastTimeMsc) \ { \ const datetime TmpTime = (datetime)(Time##A / THOUSAND); \ \ if (TmpTime > this.LastTime) \ { \ this.LastTotalOrders = 0; \ this.LastTotalDeals = 0; \ \ this.LastTime = TmpTime; \ LastTimeMsc = this.LastTime * THOUSAND; \ } \ \ this.LastTotal##A##s++; \ } #ifndef MT4ORDERS_FASTHISTORY_OFF #include #endif // MT4ORDERS_FASTHISTORY_OFF class MT4HISTORY { private: static const bool MT4HISTORY::IsTester; // static long MT4HISTORY::AccountNumber; #ifndef MT4ORDERS_FASTHISTORY_OFF CHashMap DealsIn; #endif // MT4ORDERS_FASTHISTORY_OFF long Tickets[]; uint Amount; datetime LastTime; int LastTotalDeals; int LastTotalOrders; datetime LastInitTime; bool RefreshHistory( void ) { bool Res = false; const datetime LastTimeCurrent = ::TimeCurrent(); if (!MT4HISTORY::IsTester && ((LastTimeCurrent >= this.LastInitTime + DAY)/* || (MT4HISTORY::AccountNumber != ::AccountInfoInteger(ACCOUNT_LOGIN))*/)) { // MT4HISTORY::AccountNumber = ::AccountInfoInteger(ACCOUNT_LOGIN); this.LastTime = 0; this.LastTotalOrders = 0; this.LastTotalDeals = 0; this.Amount = 0; ::ArrayResize(this.Tickets, this.Amount, RESERVE_SIZE); this.LastInitTime = LastTimeCurrent; #ifndef MT4ORDERS_FASTHISTORY_OFF this.DealsIn.Clear(); #endif // MT4ORDERS_FASTHISTORY_OFF } const datetime LastTimeCurrentLeft = LastTimeCurrent - HISTORY_PAUSE; if (::HistorySelect(this.LastTime, END_TIME)) // https://www.mql5.com/ru/forum/285631/page79#comment_9884935 { const int TotalOrders = ::HistoryOrdersTotal(); const int TotalDeals = ::HistoryDealsTotal(); Res = ((TotalOrders > this.LastTotalOrders) || (TotalDeals > this.LastTotalDeals)); if (Res) { int iOrder = this.LastTotalOrders; int iDeal = this.LastTotalDeals; ulong TicketOrder = 0; ulong TicketDeal = 0; long TimeOrder = (iOrder < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = ::HistoryOrderGetTicket(iOrder)), ORDER_TIME_DONE_MSC) : LONG_MAX; long TimeDeal = (iDeal < TotalDeals) ? ::HistoryDealGetInteger((TicketDeal = ::HistoryDealGetTicket(iDeal)), DEAL_TIME_MSC) : LONG_MAX; if (this.LastTime < LastTimeCurrentLeft) { this.LastTotalOrders = 0; this.LastTotalDeals = 0; this.LastTime = LastTimeCurrentLeft; } long LastTimeMsc = this.LastTime * THOUSAND; while ((iDeal < TotalDeals) || (iOrder < TotalOrders)) if (TimeOrder < TimeDeal) { LASTTIME(Order) if (MT4HISTORY::IsMT4Order(TicketOrder)) { this.Amount = ::ArrayResize(this.Tickets, this.Amount + 1, RESERVE_SIZE); this.Tickets[this.Amount - 1] = -(long)TicketOrder; } iOrder++; TimeOrder = (iOrder < TotalOrders) ? ::HistoryOrderGetInteger((TicketOrder = ::HistoryOrderGetTicket(iOrder)), ORDER_TIME_DONE_MSC) : LONG_MAX; } else { LASTTIME(Deal) if (MT4HISTORY::IsMT4Deal(TicketDeal)) { this.Amount = ::ArrayResize(this.Tickets, this.Amount + 1, RESERVE_SIZE); this.Tickets[this.Amount - 1] = (long)TicketDeal; } #ifndef MT4ORDERS_FASTHISTORY_OFF else if ((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(TicketDeal, DEAL_ENTRY) == DEAL_ENTRY_IN) this.DealsIn.Add(::HistoryDealGetInteger(TicketDeal, DEAL_POSITION_ID), TicketDeal); #endif // MT4ORDERS_FASTHISTORY_OFF iDeal++; TimeDeal = (iDeal < TotalDeals) ? ::HistoryDealGetInteger((TicketDeal = ::HistoryDealGetTicket(iDeal)), DEAL_TIME_MSC) : LONG_MAX; } } else if (LastTimeCurrentLeft > this.LastTime) { this.LastTime = LastTimeCurrentLeft; this.LastTotalOrders = 0; this.LastTotalDeals = 0; } } return(Res); } public: static bool IsMT4Deal( const ulong &Ticket ) { const ENUM_DEAL_TYPE DealType = (ENUM_DEAL_TYPE)::HistoryDealGetInteger(Ticket, DEAL_TYPE); const ENUM_DEAL_ENTRY DealEntry = (ENUM_DEAL_ENTRY)::HistoryDealGetInteger(Ticket, DEAL_ENTRY); return(((DealType != DEAL_TYPE_BUY) && (DealType != DEAL_TYPE_SELL)) || // íå òîðãîâàÿ ñäåëêà ((DealEntry == DEAL_ENTRY_OUT) || (DealEntry == DEAL_ENTRY_OUT_BY))); // òîðãîâàÿ } static bool IsMT4Order( const ulong &Ticket ) { // Åñëè îòëîæåííûé îðäåð èñïîëíèëñÿ, åãî ORDER_POSITION_ID çàïîëíÿåòñÿ. // https://www.mql5.com/ru/forum/170952/page70#comment_6543162 // https://www.mql5.com/ru/forum/93352/page19#comment_6646726 // ×òî äåëàòü â ñèòóàöèè, êîãäà ëèìèòíûé îðäåð áûë ÷àñòè÷íî èñïîëíåí, à çàòåì óäàëåí? return(/*(::HistoryOrderGetDouble(Ticket, ORDER_VOLUME_CURRENT) > 0) ||*/ !::HistoryOrderGetInteger(Ticket, ORDER_POSITION_ID)); } MT4HISTORY( void ) : Amount(::ArrayResize(this.Tickets, 0, RESERVE_SIZE)), LastTime(0), LastTotalDeals(0), LastTotalOrders(0), LastInitTime(0) { // this.RefreshHistory(); // Åñëè èñòîðèÿ íå èñïîëüçóåòñÿ, íåçà÷åì çàáèâàòü ðåñóðñû. } ulong GetPositionDealIn( const ulong PositionIdentifier = -1 ) // 0 - íåëüçÿ, ò.ê. áàëàíñîâàÿ ñäåëêà òåñòåðà èìååò íîëü { ulong Ticket = 0; if (PositionIdentifier == -1) { const ulong MyPositionIdentifier = ::PositionGetInteger(POSITION_IDENTIFIER); #ifndef MT4ORDERS_FASTHISTORY_OFF if (!this.DealsIn.TryGetValue(MyPositionIdentifier, Ticket)) #endif // MT4ORDERS_FASTHISTORY_OFF { const datetime PosTime = (datetime)::PositionGetInteger(POSITION_TIME); if (::HistorySelect(PosTime, PosTime)) { const int Total = ::HistoryDealsTotal(); for (int i = 0; i < Total; i++) { const ulong TicketDeal = ::HistoryDealGetTicket(i); if ((::HistoryDealGetInteger(TicketDeal, DEAL_POSITION_ID) == MyPositionIdentifier) /*&& ((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(TicketDeal, DEAL_ENTRY) == DEAL_ENTRY_IN) */) // Ïåðâîå óïîìèíàíèå è òàê áóäåò DEAL_ENTRY_IN { Ticket = TicketDeal; #ifndef MT4ORDERS_FASTHISTORY_OFF this.DealsIn.Add(MyPositionIdentifier, Ticket); #endif // MT4ORDERS_FASTHISTORY_OFF break; } } } } } else if (PositionIdentifier && // PositionIdentifier áàëàíñîâûõ ñäåëîê ðàâåí íóëþ #ifndef MT4ORDERS_FASTHISTORY_OFF !this.DealsIn.TryGetValue(PositionIdentifier, Ticket) && #endif // MT4ORDERS_FASTHISTORY_OFF ::HistorySelectByPosition(PositionIdentifier) && (::HistoryDealsTotal() > 1)) // Ïî÷åìó > 1, à íå > 0 ?! { Ticket = ::HistoryDealGetTicket(0); // Ïåðâîå óïîìèíàíèå è òàê áóäåò DEAL_ENTRY_IN /* const int Total = ::HistoryDealsTotal(); for (int i = 0; i < Total; i++) { const ulong TicketDeal = ::HistoryDealGetTicket(i); if (TicketDeal > 0) if ((ENUM_DEAL_ENTRY)::HistoryDealGetInteger(TicketDeal, DEAL_ENTRY) == DEAL_ENTRY_IN) { Ticket = TicketDeal; break; } } */ #ifndef MT4ORDERS_FASTHISTORY_OFF this.DealsIn.Add(PositionIdentifier, Ticket); #endif // MT4ORDERS_FASTHISTORY_OFF } return(Ticket); } int GetAmount( void ) { this.RefreshHistory(); return((int)this.Amount); } long operator []( const uint &Pos ) { long Res = 0; if ((Pos >= this.Amount)/* || (!MT4HISTORY::IsTester && (MT4HISTORY::AccountNumber != ::AccountInfoInteger(ACCOUNT_LOGIN)))*/) { this.RefreshHistory(); if (Pos < this.Amount) Res = this.Tickets[Pos]; } else Res = this.Tickets[Pos]; return(Res); } }; static const bool MT4HISTORY::IsTester = ::MQLInfoInteger(MQL_TESTER); // static long MT4HISTORY::AccountNumber = ::AccountInfoInteger(ACCOUNT_LOGIN); #undef LASTTIME #undef THOUSAND #undef END_TIME #undef HISTORY_PAUSE #undef DAY #undef RESERVE_SIZE #define OP_BUY ORDER_TYPE_BUY #define OP_SELL ORDER_TYPE_SELL #define OP_BUYLIMIT ORDER_TYPE_BUY_LIMIT #define OP_SELLLIMIT ORDER_TYPE_SELL_LIMIT #define OP_BUYSTOP ORDER_TYPE_BUY_STOP #define OP_SELLSTOP ORDER_TYPE_SELL_STOP #define OP_BALANCE 6 #define SELECT_BY_POS 0 #define SELECT_BY_TICKET 1 #define MODE_TRADES 0 #define MODE_HISTORY 1 class MT4ORDERS { private: static MT4_ORDER Order; static MT4HISTORY History; static const bool MT4ORDERS::IsTester; static const bool MT4ORDERS::IsHedging; static int OrderSendBug; static bool HistorySelectOrder( const ulong &Ticket ) { return((::HistoryOrderGetInteger(Ticket, ORDER_TICKET) == Ticket) || ::HistoryOrderSelect(Ticket)); } static bool HistorySelectDeal( const ulong &Ticket ) { return((::HistoryDealGetInteger(Ticket, DEAL_TICKET) == Ticket) || ::HistoryDealSelect(Ticket)); } #define UNKNOWN_COMMISSION DBL_MIN #define UNKNOWN_REQUEST_PRICE DBL_MIN #define UNKNOWN_TICKET 0 // #define UNKNOWN_REASON (-1) static bool CheckNewTicket( void ) { static long PrevPosTimeUpdate = 0; static long PrevPosTicket = 0; const long PosTimeUpdate = ::PositionGetInteger(POSITION_TIME_UPDATE_MSC); const long PosTicket = ::PositionGetInteger(POSITION_TICKET); // Íà ñëó÷àé, åñëè ïîëüçîâàòåëü ñäåëàë âûáîð ïîçèöèè íå ÷åðåç MT4Orders // Ïåðåãðóæàòü MQL5-PositionSelect* è MQL5-OrderSelect íåðåçîííî. // Ýòîé ïðîâåðêè äîñòàòî÷íî, ò.ê. íåñêîëüêî èçìåíåíèé ïîçèöèè + PositionSelect â îäíó ìèëëèñåêóíäó âîçìîæíî òîëüêî â òåñòåðå const bool Res = ((PosTimeUpdate != PrevPosTimeUpdate) || (PosTicket != PrevPosTicket)); if (Res) { MT4ORDERS::GetPositionData(); PrevPosTimeUpdate = PosTimeUpdate; PrevPosTicket = PosTicket; } return(Res); } static bool CheckPositionTicketOpen( void ) { if ((MT4ORDERS::Order.TicketOpen == UNKNOWN_TICKET) || MT4ORDERS::CheckNewTicket()) MT4ORDERS::Order.TicketOpen = (long)MT4ORDERS::History.GetPositionDealIn(); // Âñå èç-çà ýòîé î÷åíü äîðîãîé ôóíêöèè return(true); } static bool CheckPositionCommissionComment( void ) { if ((MT4ORDERS::Order.Commission == UNKNOWN_COMMISSION) || MT4ORDERS::CheckNewTicket()) { MT4ORDERS::Order.Commission = ::PositionGetDouble(POSITION_COMMISSION); // Âñåãäà íîëü MT4ORDERS::Order.Comment = ::PositionGetString(POSITION_COMMENT); if (!MT4ORDERS::Order.Commission || (MT4ORDERS::Order.Comment == "")) { MT4ORDERS::CheckPositionTicketOpen(); const ulong Ticket = MT4ORDERS::Order.TicketOpen; if ((Ticket > 0) && MT4ORDERS::HistorySelectDeal(Ticket)) { if (!MT4ORDERS::Order.Commission) { const double LotsIn = ::HistoryDealGetDouble(Ticket, DEAL_VOLUME); if (LotsIn > 0) MT4ORDERS::Order.Commission = ::HistoryDealGetDouble(Ticket, DEAL_COMMISSION) * ::PositionGetDouble(POSITION_VOLUME) / LotsIn; } if (MT4ORDERS::Order.Comment == "") MT4ORDERS::Order.Comment = ::HistoryDealGetString(Ticket, DEAL_COMMENT); } } } return(true); } /* static bool CheckPositionOpenReason( void ) { if ((MT4ORDERS::Order.OpenReason == UNKNOWN_REASON) || MT4ORDERS::CheckNewTicket()) { MT4ORDERS::CheckPositionTicketOpen(); const ulong Ticket = MT4ORDERS::Order.TicketOpen; if ((Ticket > 0) && (MT4ORDERS::IsTester || MT4ORDERS::HistorySelectDeal(Ticket))) MT4ORDERS::Order.OpenReason = (ENUM_DEAL_REASON)::HistoryDealGetInteger(Ticket, DEAL_REASON); } return(true); } */ static bool CheckPositionOpenPriceRequest( void ) { const long PosTicket = ::PositionGetInteger(POSITION_TICKET); if (((MT4ORDERS::Order.OpenPriceRequest == UNKNOWN_REQUEST_PRICE) || MT4ORDERS::CheckNewTicket()) && !(MT4ORDERS::Order.OpenPriceRequest = (::HistoryOrderSelect(PosTicket) && (MT4ORDERS::IsTester || (::PositionGetInteger(POSITION_TIME_MSC) == ::HistoryOrderGetInteger(PosTicket, ORDER_TIME_DONE_MSC)))) // À íóæíà ëè ýòà ïðîâåðêà? ? ::HistoryOrderGetDouble(PosTicket, ORDER_PRICE_OPEN) : ::PositionGetDouble(POSITION_PRICE_OPEN))) MT4ORDERS::Order.OpenPriceRequest = ::PositionGetDouble(POSITION_PRICE_OPEN); // Íà ñëó÷àé, åñëè öåíà îðäåðà íóëåâàÿ return(true); } static void GetPositionData( void ) { MT4ORDERS::Order.Ticket = POSITION_SELECT; MT4ORDERS::Order.Commission = UNKNOWN_COMMISSION; // MT4ORDERS::CheckPositionCommissionComment(); MT4ORDERS::Order.OpenPriceRequest = UNKNOWN_REQUEST_PRICE; // MT4ORDERS::CheckPositionOpenPriceRequest() MT4ORDERS::Order.TicketOpen = UNKNOWN_TICKET; // MT4ORDERS::Order.OpenReason = UNKNOWN_REASON; return; } // #undef UNKNOWN_REASON #undef UNKNOWN_TICKET #undef UNKNOWN_REQUEST_PRICE #undef UNKNOWN_COMMISSION static void GetOrderData( void ) { MT4ORDERS::Order.Ticket = ORDER_SELECT; return; } static void GetHistoryOrderData( const ulong Ticket ) { MT4ORDERS::Order.Ticket = ::HistoryOrderGetInteger(Ticket, ORDER_TICKET); MT4ORDERS::Order.Type = (int)::HistoryOrderGetInteger(Ticket, ORDER_TYPE); MT4ORDERS::Order.TicketOpen = MT4ORDERS::Order.Ticket; MT4ORDERS::Order.Lots = ::HistoryOrderGetDouble(Ticket, ORDER_VOLUME_CURRENT); if (!MT4ORDERS::Order.Lots) MT4ORDERS::Order.Lots = ::HistoryOrderGetDouble(Ticket, ORDER_VOLUME_INITIAL); MT4ORDERS::Order.Symbol = ::HistoryOrderGetString(Ticket, ORDER_SYMBOL); MT4ORDERS::Order.Comment = ::HistoryOrderGetString(Ticket, ORDER_COMMENT); MT4ORDERS::Order.OpenTimeMsc = ::HistoryOrderGetInteger(Ticket, ORDER_TIME_SETUP_MSC); MT4ORDERS::Order.OpenTime = (datetime)(MT4ORDERS::Order.OpenTimeMsc / 1000); MT4ORDERS::Order.OpenPrice = ::HistoryOrderGetDouble(Ticket, ORDER_PRICE_OPEN); MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; MT4ORDERS::Order.OpenReason = (ENUM_DEAL_REASON)::HistoryOrderGetInteger(Ticket, ORDER_REASON); MT4ORDERS::Order.StopLoss = ::HistoryOrderGetDouble(Ticket, ORDER_SL); MT4ORDERS::Order.TakeProfit = ::HistoryOrderGetDouble(Ticket, ORDER_TP); MT4ORDERS::Order.CloseTimeMsc = ::HistoryOrderGetInteger(Ticket, ORDER_TIME_DONE_MSC); MT4ORDERS::Order.CloseTime = (datetime)(MT4ORDERS::Order.CloseTimeMsc / 1000); MT4ORDERS::Order.ClosePrice = ::HistoryOrderGetDouble(Ticket, ORDER_PRICE_CURRENT); MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; MT4ORDERS::Order.CloseReason = MT4ORDERS::Order.OpenReason; MT4ORDERS::Order.State = (ENUM_ORDER_STATE)::HistoryOrderGetInteger(Ticket, ORDER_STATE); MT4ORDERS::Order.Expiration = (datetime)::HistoryOrderGetInteger(Ticket, ORDER_TIME_EXPIRATION); MT4ORDERS::Order.MagicNumber = ::HistoryOrderGetInteger(Ticket, ORDER_MAGIC); MT4ORDERS::Order.Profit = 0; MT4ORDERS::Order.Commission = 0; MT4ORDERS::Order.Swap = 0; return; } #define WHILE(A) while ((!(Res = (A))) && MT4ORDERS::Waiting()) #define TOSTR(A) #A + " = " + (string)(A) + "\n" #define TOSTR2(A) #A + " = " + EnumToString(A) + " (" + (string)(A) + ")\n" static void GetHistoryPositionData( const ulong Ticket ) { MT4ORDERS::Order.Ticket = (long)Ticket; MT4ORDERS::Order.Type = (int)::HistoryDealGetInteger(Ticket, DEAL_TYPE); if ((MT4ORDERS::Order.Type > OP_SELL)) MT4ORDERS::Order.Type += (OP_BALANCE - OP_SELL - 1); else MT4ORDERS::Order.Type = 1 - MT4ORDERS::Order.Type; MT4ORDERS::Order.Lots = ::HistoryDealGetDouble(Ticket, DEAL_VOLUME); MT4ORDERS::Order.Symbol = ::HistoryDealGetString(Ticket, DEAL_SYMBOL); MT4ORDERS::Order.Comment = ::HistoryDealGetString(Ticket, DEAL_COMMENT); MT4ORDERS::Order.CloseTimeMsc = ::HistoryDealGetInteger(Ticket, DEAL_TIME_MSC); MT4ORDERS::Order.CloseTime = (datetime)(MT4ORDERS::Order.CloseTimeMsc / 1000); // (datetime)::HistoryDealGetInteger(Ticket, DEAL_TIME); MT4ORDERS::Order.ClosePrice = ::HistoryDealGetDouble(Ticket, DEAL_PRICE); MT4ORDERS::Order.CloseReason = (ENUM_DEAL_REASON)::HistoryDealGetInteger(Ticket, DEAL_REASON);; MT4ORDERS::Order.Expiration = 0; MT4ORDERS::Order.MagicNumber = ::HistoryDealGetInteger(Ticket, DEAL_MAGIC); MT4ORDERS::Order.Profit = ::HistoryDealGetDouble(Ticket, DEAL_PROFIT); MT4ORDERS::Order.Commission = ::HistoryDealGetDouble(Ticket, DEAL_COMMISSION); MT4ORDERS::Order.Swap = ::HistoryDealGetDouble(Ticket, DEAL_SWAP); #ifndef MT4ORDERS_SLTP_OLD MT4ORDERS::Order.StopLoss = ::HistoryDealGetDouble(Ticket, DEAL_SL); MT4ORDERS::Order.TakeProfit = ::HistoryDealGetDouble(Ticket, DEAL_TP); #else // MT4ORDERS_SLTP_OLD MT4ORDERS::Order.StopLoss = 0; MT4ORDERS::Order.TakeProfit = 0; #endif // MT4ORDERS_SLTP_OLD const ulong OrderTicket = ::HistoryDealGetInteger(Ticket, DEAL_ORDER); const ulong PosTicket = ::HistoryDealGetInteger(Ticket, DEAL_POSITION_ID); const ulong OpenTicket = (OrderTicket > 0) ? MT4ORDERS::History.GetPositionDealIn(PosTicket) : 0; if (OpenTicket > 0) { const ENUM_DEAL_REASON Reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(Ticket, DEAL_REASON); const ENUM_DEAL_ENTRY DealEntry = (ENUM_DEAL_ENTRY)::HistoryDealGetInteger(Ticket, DEAL_ENTRY); // Èñòîðèÿ (OpenTicket è OrderTicket) ïîäãðóæåíà, áëàãîäàðÿ GetPositionDealIn, - HistorySelectByPosition #ifdef MT4ORDERS_FASTHISTORY_OFF const bool Res = true; #else // MT4ORDERS_FASTHISTORY_OFF // ×àñòè÷íîå èñïîëíåíèå ïîðîäèò íóæíûé îðäåð - https://www.mql5.com/ru/forum/227423/page2#comment_6543129 bool Res = MT4ORDERS::IsTester ? MT4ORDERS::HistorySelectOrder(OrderTicket) : MT4ORDERS::Waiting(true); if (!Res) WHILE(MT4ORDERS::HistorySelectOrder(OrderTicket)) // https://www.mql5.com/ru/forum/304239#comment_10710403 ; if (MT4ORDERS::HistorySelectDeal(OpenTicket)) // Îáÿçàòåëüíî ñðàáîòàåò, ò.ê. OpenTicket ãàðàíòèðîâàííî â èñòîðèè. #endif // MT4ORDERS_FASTHISTORY_OFF { MT4ORDERS::Order.TicketOpen = (long)OpenTicket; MT4ORDERS::Order.OpenReason = Reason; MT4ORDERS::Order.OpenPrice = ::HistoryDealGetDouble(OpenTicket, DEAL_PRICE); MT4ORDERS::Order.OpenTimeMsc = ::HistoryDealGetInteger(OpenTicket, DEAL_TIME_MSC); MT4ORDERS::Order.OpenTime = (datetime)(MT4ORDERS::Order.OpenTimeMsc / 1000); const double OpenLots = ::HistoryDealGetDouble(OpenTicket, DEAL_VOLUME); if (OpenLots > 0) MT4ORDERS::Order.Commission += ::HistoryDealGetDouble(OpenTicket, DEAL_COMMISSION) * MT4ORDERS::Order.Lots / OpenLots; // if (!MT4ORDERS::Order.MagicNumber) // Ìýäæèê çàêðûòîé ïîçèöèè âñåãäà äîëæåí áûòü ðàâåí ìýäæèêó îòêðûâàþùåé ñäåëêè. const long Magic = ::HistoryDealGetInteger(OpenTicket, DEAL_MAGIC); if (Magic) MT4ORDERS::Order.MagicNumber = Magic; // if (MT4ORDERS::Order.Comment == "") // Êîììåíòàðèé çàêðûòîé ïîçèöèè âñåãäà äîëæåí áûòü ðàâåí êîììåíòàðèþ îòêðûâàþùåé ñäåëêè. const string StrComment = ::HistoryDealGetString(OpenTicket, DEAL_COMMENT); if (StrComment != "") MT4ORDERS::Order.Comment = StrComment; if (Res) { #ifdef MT4ORDERS_SLTP_OLD // Ïåðåâåðíóòî - íå îøèáêà: ñì. OrderClose. MT4ORDERS::Order.StopLoss = ::HistoryOrderGetDouble(OrderTicket, (Reason == DEAL_REASON_SL) ? ORDER_PRICE_OPEN : ORDER_TP); MT4ORDERS::Order.TakeProfit = ::HistoryOrderGetDouble(OrderTicket, (Reason == DEAL_REASON_TP) ? ORDER_PRICE_OPEN : ORDER_SL); #endif // MT4ORDERS_SLTP_OLD MT4ORDERS::Order.State = (ENUM_ORDER_STATE)::HistoryOrderGetInteger(OrderTicket, ORDER_STATE); if (!(MT4ORDERS::Order.ClosePriceRequest = (DealEntry == DEAL_ENTRY_OUT_BY) ? MT4ORDERS::Order.ClosePrice : ::HistoryOrderGetDouble(OrderTicket, ORDER_PRICE_OPEN))) MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; if (!(MT4ORDERS::Order.OpenPriceRequest = (MT4ORDERS::HistorySelectOrder(PosTicket) && // À íóæíà ëè ýòà ïðîâåðêà? (MT4ORDERS::IsTester || (::HistoryDealGetInteger(OpenTicket, DEAL_TIME_MSC) == ::HistoryOrderGetInteger(PosTicket, ORDER_TIME_DONE_MSC)))) ? ::HistoryOrderGetDouble(PosTicket, ORDER_PRICE_OPEN) : MT4ORDERS::Order.OpenPrice)) MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; } else { MT4ORDERS::Order.State = ORDER_STATE_FILLED; MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; } } if (!Res) { ::Alert("HistoryOrderSelect(" + (string)OrderTicket + ") - BUG! MT4ORDERS - not Sync with History!"); ::Alert("Please send the logs to the author - https://www.mql5.com/en/users/fxsaber"); ::Print(TOSTR(::AccountInfoString(ACCOUNT_SERVER)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_CONNECTED)) + TOSTR(::TerminalInfoInteger(TERMINAL_PING_LAST)) + TOSTR(::TerminalInfoDouble(TERMINAL_RETRANSMISSION)) + TOSTR(::TerminalInfoInteger(TERMINAL_BUILD)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_X64)) + TOSTR(MT4ORDERS::IsHedging) + TOSTR(Res) + TOSTR(MT4ORDERS::OrderSendBug) + TOSTR(Ticket) + TOSTR(OrderTicket) + TOSTR(OpenTicket) + TOSTR(PosTicket) + TOSTR(MT4ORDERS::HistorySelectOrder(OrderTicket))); } } else { MT4ORDERS::Order.TicketOpen = MT4ORDERS::Order.Ticket; MT4ORDERS::Order.OpenPrice = MT4ORDERS::Order.ClosePrice; // ::HistoryDealGetDouble(Ticket, DEAL_PRICE); MT4ORDERS::Order.OpenTimeMsc = MT4ORDERS::Order.CloseTimeMsc; MT4ORDERS::Order.OpenTime = MT4ORDERS::Order.CloseTime; // (datetime)::HistoryDealGetInteger(Ticket, DEAL_TIME); MT4ORDERS::Order.OpenReason = MT4ORDERS::Order.CloseReason; MT4ORDERS::Order.State = ORDER_STATE_FILLED; MT4ORDERS::Order.ClosePriceRequest = MT4ORDERS::Order.ClosePrice; MT4ORDERS::Order.OpenPriceRequest = MT4ORDERS::Order.OpenPrice; } return; } static bool Waiting( const bool FlagInit = false ) { static ulong StartTime = 0; const bool Res = FlagInit ? false : (::GetMicrosecondCount() - StartTime < MT4ORDERS::OrderSend_MaxPause); if (FlagInit) { StartTime = ::GetMicrosecondCount(); MT4ORDERS::OrderSendBug = 0; } else if (Res) { // ::Sleep(0); // https://www.mql5.com/ru/forum/170952/page100#comment_8750511 MT4ORDERS::OrderSendBug++; } return(Res); } static bool EqualPrices( const double Price1, const double &Price2, const int &digits) { return(!::NormalizeDouble(Price1 - Price2, digits)); } static bool HistoryDealSelect( MqlTradeResult &Result ) { // Çàìåíèòü HistorySelectByPosition íà HistorySelect(PosTime, PosTime) if (!Result.deal && Result.order && ::HistorySelectByPosition(::HistoryOrderGetInteger(Result.order, ORDER_POSITION_ID))) for (int i = ::HistoryDealsTotal() - 1; i >= 0; i--) { const ulong DealTicket = ::HistoryDealGetTicket(i); if (Result.order == ::HistoryDealGetInteger(DealTicket, DEAL_ORDER)) { Result.deal = DealTicket; Result.price = ::HistoryDealGetDouble(DealTicket, DEAL_PRICE); break; } } return(::HistoryDealSelect(Result.deal)); } /* #define MT4ORDERS_BENCHMARK Alert(MT4ORDERS::LastTradeRequest.symbol + " " + \ (string)MT4ORDERS::LastTradeResult.order + " " + \ MT4ORDERS::LastTradeResult.comment); \ Print(ToString(MT4ORDERS::LastTradeRequest) + \ ToString(MT4ORDERS::LastTradeResult)); */ #define TMP_MT4ORDERS_BENCHMARK(A) \ static ulong Max##A = 0; \ \ if (Interval##A > Max##A) \ { \ MT4ORDERS_BENCHMARK \ \ Max##A = Interval##A; \ } static void OrderSend_Benchmark( const ulong &Interval1, const ulong &Interval2 ) { #ifdef MT4ORDERS_BENCHMARK TMP_MT4ORDERS_BENCHMARK(1) TMP_MT4ORDERS_BENCHMARK(2) #endif // MT4ORDERS_BENCHMARK return; } #undef TMP_MT4ORDERS_BENCHMARK static string ToString( const MqlTradeRequest &Request ) { return(TOSTR2(Request.action) + TOSTR(Request.magic) + TOSTR(Request.order) + TOSTR(Request.symbol) + TOSTR(Request.volume) + TOSTR(Request.price) + TOSTR(Request.stoplimit) + TOSTR(Request.sl) + TOSTR(Request.tp) + TOSTR(Request.deviation) + TOSTR2(Request.type) + TOSTR2(Request.type_filling) + TOSTR2(Request.type_time) + TOSTR(Request.expiration) + TOSTR(Request.comment) + TOSTR(Request.position) + TOSTR(Request.position_by)); } static string ToString( const MqlTradeResult &Result ) { return(TOSTR(Result.retcode) + TOSTR(Result.deal) + TOSTR(Result.order) + TOSTR(Result.volume) + TOSTR(Result.price) + TOSTR(Result.bid) + TOSTR(Result.ask) + TOSTR(Result.comment) + TOSTR(Result.request_id) + TOSTR(Result.retcode_external)); } static bool OrderSend( const MqlTradeRequest &Request, MqlTradeResult &Result ) { const ulong StartTime1 = MT4ORDERS::IsTester ? 0 : ::GetMicrosecondCount(); bool Res = ::OrderSend(Request, Result); const ulong Interval1 = MT4ORDERS::IsTester ? 0 : (::GetMicrosecondCount() - StartTime1); const ulong StartTime2 = MT4ORDERS::IsTester ? 0 : ::GetMicrosecondCount(); if (Res && !MT4ORDERS::IsTester && (Result.retcode < TRADE_RETCODE_ERROR) && (MT4ORDERS::OrderSend_MaxPause > 0)) { Res = (Result.retcode == TRADE_RETCODE_DONE); MT4ORDERS::Waiting(true); // TRADE_ACTION_CLOSE_BY îòñóòñòâóåò â ïåðå÷íå ïðîâåðîê if (Request.action == TRADE_ACTION_DEAL) { if (!Result.deal) { WHILE(::OrderSelect(Result.order) || ::HistoryOrderSelect(Result.order)) ; if (!Res) ::Print(TOSTR(::OrderSelect(Result.order)) + TOSTR(::HistoryOrderSelect(Result.order))); else if (::OrderSelect(Result.order) && !(Res = ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PLACED) || ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PARTIAL))) ::Print(TOSTR(::OrderSelect(Result.order)) + TOSTR2((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE))); } // Åñëè ïîñëå ÷àñòè÷íîãî èñïîëíåíèÿ îñòàâøàÿñÿ ÷àñòü îñòàëàñü âèñåòü - false. if (Res) { const bool ResultDeal = (!Result.deal) && (!MT4ORDERS::OrderSendBug); if (MT4ORDERS::OrderSendBug && (!Result.deal)) ::Print("Before ::HistoryOrderSelect(Result.order):\n" + TOSTR(MT4ORDERS::OrderSendBug) + TOSTR(Result.deal)); WHILE(::HistoryOrderSelect(Result.order)) ; // Åñëè ðàíåå íå áûëî OrderSend-áàãà è áûë Result.deal == 0 if (ResultDeal) MT4ORDERS::OrderSendBug = 0; if (!Res) ::Print(TOSTR(::HistoryOrderSelect(Result.order))); // Åñëè èñòîðè÷åñêèé îðäåð íå èñïîëíèëñÿ (îòêëîíèëè) - false else if (!(Res = ((ENUM_ORDER_STATE)::HistoryOrderGetInteger(Result.order, ORDER_STATE) == ORDER_STATE_FILLED) || ((ENUM_ORDER_STATE)::HistoryOrderGetInteger(Result.order, ORDER_STATE) == ORDER_STATE_PARTIAL))) ::Print(TOSTR2((ENUM_ORDER_STATE)::HistoryOrderGetInteger(Result.order, ORDER_STATE))); } if (Res) { const bool ResultDeal = (!Result.deal) && (!MT4ORDERS::OrderSendBug); if (MT4ORDERS::OrderSendBug && (!Result.deal)) ::Print("Before MT4ORDERS::HistoryDealSelect(Result):\n" + TOSTR(MT4ORDERS::OrderSendBug) + TOSTR(Result.deal)); WHILE(MT4ORDERS::HistoryDealSelect(Result)) ; // Åñëè ðàíåå íå áûëî OrderSend-áàãà è áûë Result.deal == 0 if (ResultDeal) MT4ORDERS::OrderSendBug = 0; if (!Res) ::Print(TOSTR(MT4ORDERS::HistoryDealSelect(Result))); } } else if (Request.action == TRADE_ACTION_PENDING) { if (Res) { WHILE(::OrderSelect(Result.order)) ; if (!Res) ::Print(TOSTR(::OrderSelect(Result.order))); else if (!(Res = ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PLACED) || ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) == ORDER_STATE_PARTIAL))) ::Print(TOSTR2((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE))); } else { WHILE(::HistoryOrderSelect(Result.order)) ; ::Print(TOSTR(::HistoryOrderSelect(Result.order))); Res = false; } } else if (Request.action == TRADE_ACTION_SLTP) { if (Res) { const int digits = (int)::SymbolInfoInteger(Request.symbol, SYMBOL_DIGITS); bool EqualSL = false; bool EqualTP = false; do if (Request.position ? ::PositionSelectByTicket(Request.position) : ::PositionSelect(Request.symbol)) { EqualSL = MT4ORDERS::EqualPrices(::PositionGetDouble(POSITION_SL), Request.sl, digits); EqualTP = MT4ORDERS::EqualPrices(::PositionGetDouble(POSITION_TP), Request.tp, digits); } WHILE(EqualSL && EqualTP); if (!Res) ::Print(TOSTR(::PositionGetDouble(POSITION_SL)) + TOSTR(::PositionGetDouble(POSITION_TP)) + TOSTR(EqualSL) + TOSTR(EqualTP) + TOSTR(Request.position ? ::PositionSelectByTicket(Request.position) : ::PositionSelect(Request.symbol))); } } else if (Request.action == TRADE_ACTION_MODIFY) { if (Res) { const int digits = (int)::SymbolInfoInteger(Request.symbol, SYMBOL_DIGITS); bool EqualSL = false; bool EqualTP = false; bool EqualPrice = false; do if (::OrderSelect(Result.order) && ((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE) != ORDER_STATE_REQUEST_MODIFY)) { EqualSL = MT4ORDERS::EqualPrices(::OrderGetDouble(ORDER_SL), Request.sl, digits); EqualTP = MT4ORDERS::EqualPrices(::OrderGetDouble(ORDER_TP), Request.tp, digits); EqualPrice = MT4ORDERS::EqualPrices(::OrderGetDouble(ORDER_PRICE_OPEN), Request.price, digits); } WHILE((EqualSL && EqualTP && EqualPrice)); if (!Res) ::Print(TOSTR(::OrderGetDouble(ORDER_SL)) + TOSTR(Request.sl)+ TOSTR(::OrderGetDouble(ORDER_TP)) + TOSTR(Request.tp) + TOSTR(::OrderGetDouble(ORDER_PRICE_OPEN)) + TOSTR(Request.price) + TOSTR(EqualSL) + TOSTR(EqualTP) + TOSTR(EqualPrice) + TOSTR(::OrderSelect(Result.order)) + TOSTR2((ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE))); } } else if (Request.action == TRADE_ACTION_REMOVE) { if (Res) WHILE(::HistoryOrderSelect(Result.order)) ; if (!Res) ::Print(TOSTR(::HistoryOrderSelect(Result.order))); } const ulong Interval2 = ::GetMicrosecondCount() - StartTime2; Result.comment += " " + ::DoubleToString(Interval1 / 1000.0, 3) + " + " + ::DoubleToString(Interval2 / 1000.0, 3) + " (" + (string)MT4ORDERS::OrderSendBug + ") ms."; if (!Res || MT4ORDERS::OrderSendBug) { ::Alert(Res ? "OrderSend(" + (string)Result.order + ") - BUG!" : "MT4ORDERS - not Sync with History!"); ::Alert("Please send the logs to the author - https://www.mql5.com/en/users/fxsaber"); ::Print(TOSTR(::AccountInfoString(ACCOUNT_SERVER)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_CONNECTED)) + TOSTR(::TerminalInfoInteger(TERMINAL_PING_LAST)) + TOSTR(::TerminalInfoDouble(TERMINAL_RETRANSMISSION)) + TOSTR(::TerminalInfoInteger(TERMINAL_BUILD)) + TOSTR((bool)::TerminalInfoInteger(TERMINAL_X64)) + TOSTR(MT4ORDERS::IsHedging) + TOSTR(Res) + TOSTR(MT4ORDERS::OrderSendBug) + MT4ORDERS::ToString(Request) + MT4ORDERS::ToString(Result)); } else MT4ORDERS::OrderSend_Benchmark(Interval1, Interval2); } else if (!MT4ORDERS::IsTester) { Result.comment += " " + ::DoubleToString(Interval1 / 1000.0, 3) + " ms"; ::Print(MT4ORDERS::ToString(Request) + MT4ORDERS::ToString(Result)); // ExpertRemove(); } return(Res); } #undef TOSTR2 #undef TOSTR #undef WHILE static ENUM_DAY_OF_WEEK GetDayOfWeek( const datetime &time ) { MqlDateTime sTime = {0}; ::TimeToStruct(time, sTime); return((ENUM_DAY_OF_WEEK)sTime.day_of_week); } static bool SessionTrade( const string &Symb ) { datetime TimeNow = ::TimeCurrent(); const ENUM_DAY_OF_WEEK DayOfWeek = MT4ORDERS::GetDayOfWeek(TimeNow); TimeNow %= 24 * 60 * 60; bool Res = false; datetime From, To; for (int i = 0; (!Res) && ::SymbolInfoSessionTrade(Symb, DayOfWeek, i, From, To); i++) Res = ((From <= TimeNow) && (TimeNow < To)); return(Res); } static bool SymbolTrade( const string &Symb ) { MqlTick Tick; return(::SymbolInfoTick(Symb, Tick) ? (Tick.bid && Tick.ask && MT4ORDERS::SessionTrade(Symb) /* && ((ENUM_SYMBOL_TRADE_MODE)::SymbolInfoInteger(Symb, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_FULL) */) : false); } static bool CorrectResult( void ) { ::ZeroMemory(MT4ORDERS::LastTradeResult); MT4ORDERS::LastTradeResult.retcode = MT4ORDERS::LastTradeCheckResult.retcode; MT4ORDERS::LastTradeResult.comment = MT4ORDERS::LastTradeCheckResult.comment; return(false); } static bool NewOrderCheck( void ) { return((::OrderCheck(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeCheckResult) && (MT4ORDERS::IsTester || MT4ORDERS::SymbolTrade(MT4ORDERS::LastTradeRequest.symbol))) || (!MT4ORDERS::IsTester && MT4ORDERS::CorrectResult())); } static bool NewOrderSend( const int &Check ) { return((Check == INT_MAX) ? MT4ORDERS::NewOrderCheck() : (((Check != INT_MIN) || MT4ORDERS::NewOrderCheck()) && MT4ORDERS::OrderSend(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeResult) ? MT4ORDERS::LastTradeResult.retcode < TRADE_RETCODE_ERROR : false)); } static bool ModifyPosition( const long &Ticket, MqlTradeRequest &Request ) { const bool Res = ::PositionSelectByTicket(Ticket); if (Res) { Request.action = TRADE_ACTION_SLTP; Request.position = Ticket; Request.symbol = ::PositionGetString(POSITION_SYMBOL); // óêàçàíèÿ îäíîãî òèêåòà íå äîñòàòî÷íî! } return(Res); } static ENUM_ORDER_TYPE_FILLING GetFilling( const string &Symb, const uint Type = ORDER_FILLING_FOK ) { static ENUM_ORDER_TYPE_FILLING Res = ORDER_FILLING_FOK; static string LastSymb = NULL; static uint LastType = ORDER_FILLING_FOK; const bool SymbFlag = (LastSymb != Symb); if (SymbFlag || (LastType != Type)) // Ìîæíî íåìíîãî óñêîðèòü, ïîìåíÿâ î÷åðåäíîñòü ïðîâåðêè óñëîâèÿ. { LastType = Type; if (SymbFlag) LastSymb = Symb; const ENUM_SYMBOL_TRADE_EXECUTION ExeMode = (ENUM_SYMBOL_TRADE_EXECUTION)::SymbolInfoInteger(Symb, SYMBOL_TRADE_EXEMODE); const int FillingMode = (int)::SymbolInfoInteger(Symb, SYMBOL_FILLING_MODE); Res = (!FillingMode || (Type >= ORDER_FILLING_RETURN) || ((FillingMode & (Type + 1)) != Type + 1)) ? (((ExeMode == SYMBOL_TRADE_EXECUTION_EXCHANGE) || (ExeMode == SYMBOL_TRADE_EXECUTION_INSTANT)) ? ORDER_FILLING_RETURN : ((FillingMode == SYMBOL_FILLING_IOC) ? ORDER_FILLING_IOC : ORDER_FILLING_FOK)) : (ENUM_ORDER_TYPE_FILLING)Type; } return(Res); } static ENUM_ORDER_TYPE_TIME GetExpirationType( const string &Symb, uint Expiration = ORDER_TIME_GTC ) { static ENUM_ORDER_TYPE_TIME Res = ORDER_TIME_GTC; static string LastSymb = NULL; static uint LastExpiration = ORDER_TIME_GTC; const bool SymbFlag = (LastSymb != Symb); if ((LastExpiration != Expiration) || SymbFlag) { LastExpiration = Expiration; if (SymbFlag) LastSymb = Symb; const int ExpirationMode = (int)::SymbolInfoInteger(Symb, SYMBOL_EXPIRATION_MODE); if ((Expiration > ORDER_TIME_SPECIFIED_DAY) || (!((ExpirationMode >> Expiration) & 1))) { if ((Expiration < ORDER_TIME_SPECIFIED) || (ExpirationMode < SYMBOL_EXPIRATION_SPECIFIED)) Expiration = ORDER_TIME_GTC; else if (Expiration > ORDER_TIME_DAY) Expiration = ORDER_TIME_SPECIFIED; uint i = 1 << Expiration; while ((Expiration <= ORDER_TIME_SPECIFIED_DAY) && ((ExpirationMode & i) != i)) { i <<= 1; Expiration++; } } Res = (ENUM_ORDER_TYPE_TIME)Expiration; } return(Res); } static bool ModifyOrder( const long &Ticket, const double &Price, const datetime &Expiration, MqlTradeRequest &Request ) { const bool Res = ::OrderSelect(Ticket); if (Res) { Request.action = TRADE_ACTION_MODIFY; Request.order = Ticket; Request.price = Price; Request.symbol = ::OrderGetString(ORDER_SYMBOL); // https://www.mql5.com/ru/forum/1111/page1817#comment_4087275 // Request.type_filling = (ENUM_ORDER_TYPE_FILLING)::OrderGetInteger(ORDER_TYPE_FILLING); Request.type_filling = MT4ORDERS::GetFilling(Request.symbol); Request.type_time = MT4ORDERS::GetExpirationType(Request.symbol, (uint)Expiration); if (Expiration > ORDER_TIME_DAY) Request.expiration = Expiration; } return(Res); } static bool SelectByPosHistory( const int Index ) { const long Ticket = MT4ORDERS::History[Index]; const bool Res = (Ticket > 0) ? ::HistoryDealSelect(Ticket) : ((Ticket < 0) ? ::HistoryOrderSelect(-Ticket) : false); if (Res) { if (Ticket > 0) MT4ORDERS::GetHistoryPositionData(Ticket); else MT4ORDERS::GetHistoryOrderData(-Ticket); } return(Res); } // https://www.mql5.com/ru/forum/227960#comment_6603506 static bool OrderVisible( void ) { /* const ENUM_ORDER_STATE OrderState = (ENUM_ORDER_STATE)::OrderGetInteger(ORDER_STATE); return((OrderState == ORDER_STATE_PLACED) || (OrderState == ORDER_STATE_PARTIAL)); */ bool Res = !::OrderGetInteger(ORDER_POSITION_ID); if (Res) { const long Ticket = ::PositionGetInteger(POSITION_TICKET); if (::PositionSelectByTicket(::OrderGetInteger(ORDER_TICKET))) // Îðäåð è åãî ïîçèöèÿ ìîãóò áûòü îäíîâðåìåííî - äàííîå óñëîâèå ïîìîæåò òîëüêî íà Hedge-ñ÷åòàõ { if (Ticket && (::PositionGetInteger(POSITION_TICKET) != Ticket)) ::PositionSelectByTicket(Ticket); Res = false; } } return(Res); } static ulong OrderGetTicket( const int Index ) { ulong Res; int PrevTotal; const long PrevTicket = ::OrderGetInteger(ORDER_TICKET); do { Res = 0; PrevTotal = ::OrdersTotal(); if ((Index >= 0) && (Index < PrevTotal)) { int Count = 0; for (int i = 0; i < PrevTotal; i++) { const int Total = ::OrdersTotal(); // Âî âðåìÿ ïåðåáîðà ìîæåò èçìåíèòüñÿ êîëè÷åñòâî îðäåðîâ if (Total != PrevTotal) { PrevTotal = Total; Count = 0; i = -1; } else { const ulong Ticket = ::OrderGetTicket(i); if (Ticket && MT4ORDERS::OrderVisible()) { if (Count == Index) { Res = Ticket; break; } Count++; } } } // Ïðè íåóäà÷å âûáèðàåì òîò îðäåð, ÷òî áûë âûáðàí ðàíåå. if (!Res && PrevTicket && (::OrderGetInteger(ORDER_TICKET) != PrevTicket)) const bool AntiWarning = ::OrderSelect(PrevTicket); } } while (PrevTotal != ::OrdersTotal()); // Âî âðåìÿ ïåðåáîðà ìîæåò èçìåíèòüñÿ êîëè÷åñòâî îðäåðîâ return(Res); } // Ñ îäíèì è òåì æå òèêåòîì ïðèîðèòåò âûáîðà ïîçèöèè âûøå îðäåðà static bool SelectByPos( const int Index ) { const int Total = ::PositionsTotal(); const bool Flag = (Index < Total); const bool Res = (Flag) ? ::PositionGetTicket(Index) : #ifdef MT4ORDERS_SELECTFILTER_OFF ::OrderGetTicket(Index - Total); #else // MT4ORDERS_SELECTFILTER_OFF (MT4ORDERS::IsTester ? ::OrderGetTicket(Index - Total) : MT4ORDERS::OrderGetTicket(Index - Total)); #endif //MT4ORDERS_SELECTFILTER_OFF if (Res) { if (Flag) MT4ORDERS::GetPositionData(); else MT4ORDERS::GetOrderData(); } return(Res); } static bool SelectByHistoryTicket( const long &Ticket ) { bool Res = ::HistoryDealSelect(Ticket) ? MT4HISTORY::IsMT4Deal(Ticket) : false; if (Res) MT4ORDERS::GetHistoryPositionData(Ticket); else { Res = ::HistoryOrderSelect(Ticket) ? MT4HISTORY::IsMT4Order(Ticket) : false; if (Res) MT4ORDERS::GetHistoryOrderData(Ticket); } return(Res); } static bool SelectByExistingTicket( const long &Ticket ) { bool Res = true; if (::PositionSelectByTicket(Ticket)) MT4ORDERS::GetPositionData(); else if (::OrderSelect(Ticket)) MT4ORDERS::GetOrderData(); else Res = false; return(Res); } // Ñ îäíèì è òåì æå òèêåòîì ïðèîðèòåòû âûáîðà: // MODE_TRADES: ñóùåñòâóþùàÿ ïîçèöèÿ > ñóùåñòâóþùèé îðäåð > ñäåëêà > îòìåíåííûé îðäåð // MODE_HISTORY: ñäåëêà > îòìåíåííûé îðäåð > ñóùåñòâóþùàÿ ïîçèöèÿ > ñóùåñòâóþùèé îðäåð static bool SelectByTicket( const long &Ticket, const int &Pool ) { return((Pool == MODE_TRADES) ? (MT4ORDERS::SelectByExistingTicket(Ticket) ? true : MT4ORDERS::SelectByHistoryTicket(Ticket)) : (MT4ORDERS::SelectByHistoryTicket(Ticket) ? true : MT4ORDERS::SelectByExistingTicket(Ticket))); } #ifdef MT4ORDERS_SLTP_OLD static void CheckPrices( double &MinPrice, double &MaxPrice, const double Min, const double Max ) { if (MinPrice && (MinPrice >= Min)) MinPrice = 0; if (MaxPrice && (MaxPrice <= Max)) MaxPrice = 0; return; } #endif // MT4ORDERS_SLTP_OLD static int OrdersTotal( void ) { int Res = 0; const long PrevTicket = ::OrderGetInteger(ORDER_TICKET); int PrevTotal; do { PrevTotal = ::OrdersTotal(); for (int i = PrevTotal - 1; i >= 0; i--) { const int Total = ::OrdersTotal(); // Âî âðåìÿ ïåðåáîðà ìîæåò èçìåíèòüñÿ êîëè÷åñòâî îðäåðîâ if (Total != PrevTotal) { PrevTotal = Total; Res = 0; i = PrevTotal; } else if (::OrderGetTicket(i) && MT4ORDERS::OrderVisible()) Res++; } } while (PrevTotal != ::OrdersTotal()); // Âî âðåìÿ ïåðåáîðà ìîæåò èçìåíèòüñÿ êîëè÷åñòâî îðäåðîâ if (PrevTicket && (::OrderGetInteger(ORDER_TICKET) != PrevTicket)) const bool AntiWarning = ::OrderSelect(PrevTicket); return(Res); } public: static uint OrderSend_MaxPause; // ìàêñèìàëüíîå âðåìÿ íà ñèíõðîíèçàöèþ â ìêñ. static MqlTradeResult LastTradeResult; static MqlTradeRequest LastTradeRequest; static MqlTradeCheckResult LastTradeCheckResult; static bool MT4OrderSelect( const long &Index, const int &Select, const int &Pool ) { return((Select == SELECT_BY_POS) ? ((Pool == MODE_TRADES) ? MT4ORDERS::SelectByPos((int)Index) : MT4ORDERS::SelectByPosHistory((int)Index)) : MT4ORDERS::SelectByTicket(Index, Pool)); } // Òàêàÿ "ïåðåãðóçêà" ïîçâîëÿåò èñïîëüçîâàòüñÿ ñîâìåñòíî è MT5-âàðèàíò OrderSelect static bool MT4OrderSelect( const ulong &Ticket ) { return(::OrderSelect(Ticket)); } static int MT4OrdersTotal( void ) { #ifdef MT4ORDERS_SELECTFILTER_OFF return(::OrdersTotal() + + ::PositionsTotal()); #else // MT4ORDERS_SELECTFILTER_OFF int Res; if (MT4ORDERS::IsTester) return(::OrdersTotal() + + ::PositionsTotal()); else { int PrevTotal; do { PrevTotal = ::PositionsTotal(); Res = MT4ORDERS::OrdersTotal() + PrevTotal; } while (PrevTotal != ::PositionsTotal()); // Îòñëåæèâàåì òîëüêî èçìåíåíèå ïîçèöèé, ò.ê. îðäåðà îòñëåæèâàþòñÿ â MT4ORDERS::OrdersTotal() } return(Res); // https://www.mql5.com/ru/forum/290673#comment_9493241 #endif //MT4ORDERS_SELECTFILTER_OFF } // Òàêàÿ "ïåðåãðóçêà" ïîçâîëÿåò èñïîëüçîâàòüñÿ ñîâìåñòíî è MT5-âàðèàíò OrdersTotal static int MT4OrdersTotal( const bool ) { return(::OrdersTotal()); } static int MT4OrdersHistoryTotal( void ) { return(MT4ORDERS::History.GetAmount()); } static long MT4OrderSend( const string &Symb, const int &Type, const double &dVolume, const double &Price, const int &SlipPage, const double &SL, const double &TP, const string &comment, const MAGIC_TYPE &magic, const datetime &dExpiration, const color &arrow_color ) { ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = (((Type == OP_BUY) || (Type == OP_SELL)) ? TRADE_ACTION_DEAL : TRADE_ACTION_PENDING); MT4ORDERS::LastTradeRequest.magic = magic; MT4ORDERS::LastTradeRequest.symbol = ((Symb == NULL) ? ::Symbol() : Symb); MT4ORDERS::LastTradeRequest.volume = dVolume; MT4ORDERS::LastTradeRequest.price = Price; MT4ORDERS::LastTradeRequest.tp = TP; MT4ORDERS::LastTradeRequest.sl = SL; MT4ORDERS::LastTradeRequest.deviation = SlipPage; MT4ORDERS::LastTradeRequest.type = (ENUM_ORDER_TYPE)Type; MT4ORDERS::LastTradeRequest.type_filling = MT4ORDERS::GetFilling(MT4ORDERS::LastTradeRequest.symbol, (uint)MT4ORDERS::LastTradeRequest.deviation); if (MT4ORDERS::LastTradeRequest.action == TRADE_ACTION_PENDING) { MT4ORDERS::LastTradeRequest.type_time = MT4ORDERS::GetExpirationType(MT4ORDERS::LastTradeRequest.symbol, (uint)dExpiration); if (dExpiration > ORDER_TIME_DAY) MT4ORDERS::LastTradeRequest.expiration = dExpiration; } if (comment != NULL) MT4ORDERS::LastTradeRequest.comment = comment; return((arrow_color == INT_MAX) ? (MT4ORDERS::NewOrderCheck() ? 0 : -1) : ((((int)arrow_color != INT_MIN) || MT4ORDERS::NewOrderCheck()) && MT4ORDERS::OrderSend(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeResult) ? (MT4ORDERS::IsHedging ? (long)MT4ORDERS::LastTradeResult.order : // PositionID == Result.order - îñîáåííîñòü MT5-Hedge ((MT4ORDERS::LastTradeRequest.action == TRADE_ACTION_DEAL) ? (MT4ORDERS::IsTester ? (::PositionSelect(MT4ORDERS::LastTradeRequest.symbol) ? PositionGetInteger(POSITION_TICKET) : 0) : // HistoryDealSelect â MT4ORDERS::OrderSend ::HistoryDealGetInteger(MT4ORDERS::LastTradeResult.deal, DEAL_POSITION_ID)) : (long)MT4ORDERS::LastTradeResult.order)) : -1)); } static bool MT4OrderModify( const long &Ticket, const double &Price, const double &SL, const double &TP, const datetime &Expiration, const color &Arrow_Color ) { ::ZeroMemory(MT4ORDERS::LastTradeRequest); // Ó÷èòûâàåòñÿ ñëó÷àé, êîãäà ïðèñóòñòâóþò îðäåð è ïîçèöèÿ ñ îäíèì è òåì æå òèêåòîì bool Res = ((Ticket != MT4ORDERS::Order.Ticket) || (MT4ORDERS::Order.Ticket <= OP_SELL)) ? (MT4ORDERS::ModifyPosition(Ticket, MT4ORDERS::LastTradeRequest) ? true : MT4ORDERS::ModifyOrder(Ticket, Price, Expiration, MT4ORDERS::LastTradeRequest)) : (MT4ORDERS::ModifyOrder(Ticket, Price, Expiration, MT4ORDERS::LastTradeRequest) ? true : MT4ORDERS::ModifyPosition(Ticket, MT4ORDERS::LastTradeRequest)); // if (Res) // Èãíîðèðóåì ïðîâåðêó - åñòü OrderCheck { MT4ORDERS::LastTradeRequest.tp = TP; MT4ORDERS::LastTradeRequest.sl = SL; Res = MT4ORDERS::NewOrderSend(Arrow_Color); } return(Res); } static bool MT4OrderClose( const long &Ticket, const double &dLots, const double &Price, const int &SlipPage, const color &Arrow_Color, const string &comment ) { // Åñòü MT4ORDERS::LastTradeRequest è MT4ORDERS::LastTradeResult, ïîýòîìó íà ðåçóëüòàò íå âëèÿåò, íî íóæíî äëÿ PositionGetString íèæå ::PositionSelectByTicket(Ticket); ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = TRADE_ACTION_DEAL; MT4ORDERS::LastTradeRequest.position = Ticket; MT4ORDERS::LastTradeRequest.symbol = ::PositionGetString(POSITION_SYMBOL); // Ñîõðàíÿåì êîììåíòàðèé ïðè ÷àñòè÷íîì çàêðûòèè ïîçèöèè // if (dLots < ::PositionGetDouble(POSITION_VOLUME)) MT4ORDERS::LastTradeRequest.comment = (comment == NULL) ? ::PositionGetString(POSITION_COMMENT) : comment; // Ïðàâèëüíî ëè íå çàäàâàòü ìýäæèê ïðè çàêðûòèè? -Ïðàâèëüíî! MT4ORDERS::LastTradeRequest.volume = dLots; MT4ORDERS::LastTradeRequest.price = Price; #ifdef MT4ORDERS_SLTP_OLD // Íóæíî äëÿ îïðåäåëåíèÿ SL/TP-óðîâíåé ó çàêðûòîé ïîçèöèè. Ïåðåâåðíóòî - íå îøèáêà // SYMBOL_SESSION_PRICE_LIMIT_MIN è SYMBOL_SESSION_PRICE_LIMIT_MAX ïðîâåðÿòü íå òðåáóåòñÿ, ò.ê. èñõîäíûå SL/TP óæå óñòàíîâëåíû MT4ORDERS::LastTradeRequest.tp = ::PositionGetDouble(POSITION_SL); MT4ORDERS::LastTradeRequest.sl = ::PositionGetDouble(POSITION_TP); if (MT4ORDERS::LastTradeRequest.tp || MT4ORDERS::LastTradeRequest.sl) { const double StopLevel = ::SymbolInfoInteger(MT4ORDERS::LastTradeRequest.symbol, SYMBOL_TRADE_STOPS_LEVEL) * ::SymbolInfoDouble(MT4ORDERS::LastTradeRequest.symbol, SYMBOL_POINT); const bool FlagBuy = (::PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY); const double CurrentPrice = SymbolInfoDouble(MT4ORDERS::LastTradeRequest.symbol, FlagBuy ? SYMBOL_ASK : SYMBOL_BID); if (CurrentPrice) { if (FlagBuy) MT4ORDERS::CheckPrices(MT4ORDERS::LastTradeRequest.tp, MT4ORDERS::LastTradeRequest.sl, CurrentPrice - StopLevel, CurrentPrice + StopLevel); else MT4ORDERS::CheckPrices(MT4ORDERS::LastTradeRequest.sl, MT4ORDERS::LastTradeRequest.tp, CurrentPrice - StopLevel, CurrentPrice + StopLevel); } else { MT4ORDERS::LastTradeRequest.tp = 0; MT4ORDERS::LastTradeRequest.sl = 0; } } #endif // MT4ORDERS_SLTP_OLD MT4ORDERS::LastTradeRequest.deviation = SlipPage; MT4ORDERS::LastTradeRequest.type = (ENUM_ORDER_TYPE)(1 - ::PositionGetInteger(POSITION_TYPE)); MT4ORDERS::LastTradeRequest.type_filling = MT4ORDERS::GetFilling(MT4ORDERS::LastTradeRequest.symbol, (uint)MT4ORDERS::LastTradeRequest.deviation); return(MT4ORDERS::NewOrderSend(Arrow_Color)); } static bool MT4OrderCloseBy( const long &Ticket, const long &Opposite, const color &Arrow_Color ) { ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = TRADE_ACTION_CLOSE_BY; MT4ORDERS::LastTradeRequest.position = Ticket; MT4ORDERS::LastTradeRequest.position_by = Opposite; if ((!MT4ORDERS::IsTester) && ::PositionSelectByTicket(Ticket)) // íóæåí äëÿ MT4ORDERS::SymbolTrade() MT4ORDERS::LastTradeRequest.symbol = ::PositionGetString(POSITION_SYMBOL); return(MT4ORDERS::NewOrderSend(Arrow_Color)); } static bool MT4OrderDelete( const long &Ticket, const color &Arrow_Color ) { // bool Res = ::OrderSelect(Ticket); // Íàäî ëè ýòî, êîãäà íóæíû MT4ORDERS::LastTradeRequest è MT4ORDERS::LastTradeResult ? ::ZeroMemory(MT4ORDERS::LastTradeRequest); MT4ORDERS::LastTradeRequest.action = TRADE_ACTION_REMOVE; MT4ORDERS::LastTradeRequest.order = Ticket; if ((!MT4ORDERS::IsTester) && ::OrderSelect(Ticket)) // íóæåí äëÿ MT4ORDERS::SymbolTrade() MT4ORDERS::LastTradeRequest.symbol = ::OrderGetString(ORDER_SYMBOL); return(MT4ORDERS::NewOrderSend(Arrow_Color)); } #define MT4_ORDERFUNCTION(NAME,T,A,B,C) \ static T MT4Order##NAME( void ) \ { \ return(POSITION_ORDER((T)(A), (T)(B), MT4ORDERS::Order.NAME, C)); \ } #define POSITION_ORDER(A,B,C,D) (((MT4ORDERS::Order.Ticket == POSITION_SELECT) && (D)) ? (A) : ((MT4ORDERS::Order.Ticket == ORDER_SELECT) ? (B) : (C))) MT4_ORDERFUNCTION(Ticket, long, ::PositionGetInteger(POSITION_TICKET), ::OrderGetInteger(ORDER_TICKET), true) MT4_ORDERFUNCTION(Type, int, ::PositionGetInteger(POSITION_TYPE), ::OrderGetInteger(ORDER_TYPE), true) MT4_ORDERFUNCTION(Lots, double, ::PositionGetDouble(POSITION_VOLUME), ::OrderGetDouble(ORDER_VOLUME_CURRENT), true) MT4_ORDERFUNCTION(OpenPrice, double, ::PositionGetDouble(POSITION_PRICE_OPEN), (::OrderGetDouble(ORDER_PRICE_OPEN) ? ::OrderGetDouble(ORDER_PRICE_OPEN) : ::OrderGetDouble(ORDER_PRICE_CURRENT)), true) MT4_ORDERFUNCTION(OpenTimeMsc, long, ::PositionGetInteger(POSITION_TIME_MSC), ::OrderGetInteger(ORDER_TIME_SETUP_MSC), true) MT4_ORDERFUNCTION(OpenTime, datetime, ::PositionGetInteger(POSITION_TIME), ::OrderGetInteger(ORDER_TIME_SETUP), true) MT4_ORDERFUNCTION(StopLoss, double, ::PositionGetDouble(POSITION_SL), ::OrderGetDouble(ORDER_SL), true) MT4_ORDERFUNCTION(TakeProfit, double, ::PositionGetDouble(POSITION_TP), ::OrderGetDouble(ORDER_TP), true) MT4_ORDERFUNCTION(ClosePrice, double, ::PositionGetDouble(POSITION_PRICE_CURRENT), ::OrderGetDouble(ORDER_PRICE_CURRENT), true) MT4_ORDERFUNCTION(CloseTimeMsc, long, 0, 0, true) MT4_ORDERFUNCTION(CloseTime, datetime, 0, 0, true) MT4_ORDERFUNCTION(Expiration, datetime, 0, ::OrderGetInteger(ORDER_TIME_EXPIRATION), true) MT4_ORDERFUNCTION(MagicNumber, long, ::PositionGetInteger(POSITION_MAGIC), ::OrderGetInteger(ORDER_MAGIC), true) MT4_ORDERFUNCTION(Profit, double, ::PositionGetDouble(POSITION_PROFIT), 0, true) MT4_ORDERFUNCTION(Swap, double, ::PositionGetDouble(POSITION_SWAP), 0, true) MT4_ORDERFUNCTION(Symbol, string, ::PositionGetString(POSITION_SYMBOL), ::OrderGetString(ORDER_SYMBOL), true) MT4_ORDERFUNCTION(Comment, string, MT4ORDERS::Order.Comment, ::OrderGetString(ORDER_COMMENT), MT4ORDERS::CheckPositionCommissionComment()) MT4_ORDERFUNCTION(Commission, double, MT4ORDERS::Order.Commission, 0, MT4ORDERS::CheckPositionCommissionComment()) MT4_ORDERFUNCTION(OpenPriceRequest, double, MT4ORDERS::Order.OpenPriceRequest, ::OrderGetDouble(ORDER_PRICE_OPEN), MT4ORDERS::CheckPositionOpenPriceRequest()) MT4_ORDERFUNCTION(ClosePriceRequest, double, ::PositionGetDouble(POSITION_PRICE_CURRENT), ::OrderGetDouble(ORDER_PRICE_CURRENT), true) MT4_ORDERFUNCTION(TicketOpen, long, MT4ORDERS::Order.TicketOpen, ::OrderGetInteger(ORDER_TICKET), MT4ORDERS::CheckPositionTicketOpen()) // MT4_ORDERFUNCTION(OpenReason, ENUM_DEAL_REASON, MT4ORDERS::Order.OpenReason, ::OrderGetInteger(ORDER_REASON), MT4ORDERS::CheckPositionOpenReason()) MT4_ORDERFUNCTION(OpenReason, ENUM_DEAL_REASON, ::PositionGetInteger(POSITION_REASON), ::OrderGetInteger(ORDER_REASON), true) MT4_ORDERFUNCTION(CloseReason, ENUM_DEAL_REASON, 0, ::OrderGetInteger(ORDER_REASON), true) #undef POSITION_ORDER #undef MT4_ORDERFUNCTION static void MT4OrderPrint( void ) { if (MT4ORDERS::Order.Ticket == POSITION_SELECT) MT4ORDERS::CheckPositionCommissionComment(); ::Print(MT4ORDERS::Order.ToString()); return; } #undef ORDER_SELECT #undef POSITION_SELECT }; // #define OrderToString MT4ORDERS::MT4OrderToString static MT4_ORDER MT4ORDERS::Order = {0}; static MT4HISTORY MT4ORDERS::History; static const bool MT4ORDERS::IsTester = ::MQLInfoInteger(MQL_TESTER); // Åñëè ïåðåêëþ÷èòü ñ÷åò, ýòî çíà÷åíèå ó ñîâåòíèêîâ âñå ðàâíî ïåðåñ÷èòàåòñÿ // https://www.mql5.com/ru/forum/170952/page61#comment_6132824 static const bool MT4ORDERS::IsHedging = ((ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); static int MT4ORDERS::OrderSendBug = 0; static uint MT4ORDERS::OrderSend_MaxPause = 1000000; // ìàêñèìàëüíîå âðåìÿ íà ñèíõðîíèçàöèþ â ìêñ. static MqlTradeResult MT4ORDERS::LastTradeResult = {0}; static MqlTradeRequest MT4ORDERS::LastTradeRequest = {0}; static MqlTradeCheckResult MT4ORDERS::LastTradeCheckResult = {0}; bool OrderClose( const long Ticket, const double dLots, const double Price, const int SlipPage, const color Arrow_Color = clrNONE, const string comment = NULL ) { return(MT4ORDERS::MT4OrderClose(Ticket, dLots, Price, SlipPage, Arrow_Color, comment)); } bool OrderModify( const long Ticket, const double Price, const double SL, const double TP, const datetime Expiration, const color Arrow_Color = clrNONE ) { return(MT4ORDERS::MT4OrderModify(Ticket, Price, SL, TP, Expiration, Arrow_Color)); } bool OrderCloseBy( const long Ticket, const long Opposite, const color Arrow_Color = clrNONE ) { return(MT4ORDERS::MT4OrderCloseBy(Ticket, Opposite, Arrow_Color)); } bool OrderDelete( const long Ticket, const color Arrow_Color = clrNONE ) { return(MT4ORDERS::MT4OrderDelete(Ticket, Arrow_Color)); } void OrderPrint( void ) { MT4ORDERS::MT4OrderPrint(); return; } #define MT4_ORDERGLOBALFUNCTION(NAME,T) \ T Order##NAME( void ) \ { \ return((T)MT4ORDERS::MT4Order##NAME()); \ } MT4_ORDERGLOBALFUNCTION(sHistoryTotal, int) MT4_ORDERGLOBALFUNCTION(Ticket, TICKET_TYPE) MT4_ORDERGLOBALFUNCTION(Type, int) MT4_ORDERGLOBALFUNCTION(Lots, double) MT4_ORDERGLOBALFUNCTION(OpenPrice, double) MT4_ORDERGLOBALFUNCTION(OpenTimeMsc, long) MT4_ORDERGLOBALFUNCTION(OpenTime, datetime) MT4_ORDERGLOBALFUNCTION(StopLoss, double) MT4_ORDERGLOBALFUNCTION(TakeProfit, double) MT4_ORDERGLOBALFUNCTION(ClosePrice, double) MT4_ORDERGLOBALFUNCTION(CloseTimeMsc, long) MT4_ORDERGLOBALFUNCTION(CloseTime, datetime) MT4_ORDERGLOBALFUNCTION(Expiration, datetime) MT4_ORDERGLOBALFUNCTION(MagicNumber, MAGIC_TYPE) MT4_ORDERGLOBALFUNCTION(Profit, double) MT4_ORDERGLOBALFUNCTION(Commission, double) MT4_ORDERGLOBALFUNCTION(Swap, double) MT4_ORDERGLOBALFUNCTION(Symbol, string) MT4_ORDERGLOBALFUNCTION(Comment, string) MT4_ORDERGLOBALFUNCTION(OpenPriceRequest, double) MT4_ORDERGLOBALFUNCTION(ClosePriceRequest, double) MT4_ORDERGLOBALFUNCTION(TicketOpen, long) MT4_ORDERGLOBALFUNCTION(OpenReason, ENUM_DEAL_REASON) MT4_ORDERGLOBALFUNCTION(CloseReason, ENUM_DEAL_REASON) #undef MT4_ORDERGLOBALFUNCTION // Ïåðåãðóæåííûå ñòàíäàðòíûå ôóíêöèè #define OrdersTotal MT4ORDERS::MT4OrdersTotal // ÏÎÑËÅ Expert/Expert.mqh - èäåò âûçîâ MT5-OrdersTotal() bool OrderSelect( const long Index, const int Select, const int Pool = MODE_TRADES ) { return(MT4ORDERS::MT4OrderSelect(Index, Select, Pool)); } TICKET_TYPE OrderSend( const string Symb, const int Type, const double dVolume, const double Price, const int SlipPage, const double SL, const double TP, const string comment = NULL, const MAGIC_TYPE magic = 0, const datetime dExpiration = 0, color arrow_color = clrNONE ) { return((TICKET_TYPE)MT4ORDERS::MT4OrderSend(Symb, Type, dVolume, Price, SlipPage, SL, TP, comment, magic, dExpiration, arrow_color)); } #define RETURN_ASYNC(A) return((A) && ::OrderSendAsync(MT4ORDERS::LastTradeRequest, MT4ORDERS::LastTradeResult) && \ (MT4ORDERS::LastTradeResult.retcode == TRADE_RETCODE_PLACED) ? MT4ORDERS::LastTradeResult.request_id : 0); uint OrderCloseAsync( const long Ticket, const double dLots, const double Price, const int SlipPage, const color Arrow_Color = clrNONE ) { RETURN_ASYNC(OrderClose(Ticket, dLots, Price, SlipPage, INT_MAX)) } uint OrderModifyAsync( const long Ticket, const double Price, const double SL, const double TP, const datetime Expiration, const color Arrow_Color = clrNONE ) { RETURN_ASYNC(OrderModify(Ticket, Price, SL, TP, Expiration, INT_MAX)) } uint OrderDeleteAsync( const long Ticket, const color Arrow_Color = clrNONE ) { RETURN_ASYNC(OrderDelete(Ticket, INT_MAX)) } uint OrderSendAsync( const string Symb, const int Type, const double dVolume, const double Price, const int SlipPage, const double SL, const double TP, const string comment = NULL, const MAGIC_TYPE magic = 0, const datetime dExpiration = 0, color arrow_color = clrNONE ) { RETURN_ASYNC(!OrderSend(Symb, Type, dVolume, Price, SlipPage, SL, TP, comment, magic, dExpiration, INT_MAX)) } #undef RETURN_ASYNC #undef MT4ORDERS_SLTP_OLD // #undef TICKET_TYPE #endif // __MT4ORDERS__ #else // __MQL5__ #define TICKET_TYPE int #define MAGIC_TYPE int #endif // __MQL5__ \ No newline at end of file diff --git a/test/Marketeer/CSVReader.mqh b/test/Marketeer/CSVReader.mqh new file mode 100644 index 0000000..d685beb --- /dev/null +++ b/test/Marketeer/CSVReader.mqh @@ -0,0 +1,112 @@ +//+------------------------------------------------------------------+ +//| CSVReader.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/ru/articles/5913 | +//+------------------------------------------------------------------+ + +#include +#include + + +input GroupSettings SCV_Settings; // C S V S E T T I N G S + +input string CSVDelimiter = ";" /*mql5 signals use ';' instead of ','*/; // · Delimiter + + +class CSVConverter +{ + private: + class File + { + int file; + + public: + File(const string name, const int flags, const short delimiter) + { + file = FileOpen(name, flags, delimiter, CP_UTF8); + } + + File(const string name, const int flags) + { + file = FileOpen(name, flags); + } + + bool isOpened() + { + return (file != INVALID_HANDLE); + } + + int handle() + { + return file; + } + + ~File() + { + if(file != INVALID_HANDLE) FileClose(file); + } + }; + + public: + static IndexMap *ReadCSV(const string inputFileName) + { + // history.csv - 13 columns, positions.csv - 10 columns + int columns = StringFind(inputFileName, ".history.csv") > 0 ? 13 : (StringFind(inputFileName, ".positions.csv") > 0 ? 10 : 0); + if(columns == 0) + { + Print("Supported files: *.history.csv and .positions.csv"); + return NULL; + } + Print("Reading csv-file ", inputFileName); + uchar delimiter = (uchar)CSVDelimiter[0]; + File f(inputFileName, FILE_READ|FILE_TXT|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE, delimiter); + if(!f.isOpened()) + { + Alert("Can't read file " + inputFileName); + return NULL; + } + int file = f.handle(); + + bool headerLine = true; + IndexMap *data = new IndexMap(); + string headers[]; + uint count = 0; + + while(!FileIsEnding(file)) + { + string stLine = ""; + string stParts[]; + + stLine = FileReadString(file); + + int nParts = StringSplit(stLine, delimiter, stParts); + if(nParts != columns) + { + Print("File " + inputFileName + " contains " + (string)nParts + " columns (" + (string)(columns) + "+ required)"); + Print("Line: ", stLine); + return NULL; + } + + if(headerLine) + { + headerLine = false; + ArrayCopy(headers, stParts); + continue; + } + + IndexMap *row = new IndexMap(); + + for(int i = 0; i < nParts; i++) + { + row.setValue((string)i + "." + headers[i], stParts[i]); + } + + data.add((string)count, row); + ++count; + + } + + return data; + } + +}; \ No newline at end of file diff --git a/test/Marketeer/CSVcolumns.mqh b/test/Marketeer/CSVcolumns.mqh new file mode 100644 index 0000000..9e19672 --- /dev/null +++ b/test/Marketeer/CSVcolumns.mqh @@ -0,0 +1,11 @@ +#define CSV_COLUMN_TIME1 0 +#define CSV_COLUMN_TYPE 1 +#define CSV_COLUMN_VOLUME 2 +#define CSV_COLUMN_SYMBOL 3 +#define CSV_COLUMN_PRICE1 4 +#define CSV_COLUMN_TIME2 5 // 7 +#define CSV_COLUMN_PRICE2 6 // 8 +#define CSV_COLUMN_COMMISSION 7 // 9 +#define CSV_COLUMN_SWAP 8 // 10 +#define CSV_COLUMN_PROFIT 9 // 11 +#define CSV_COLUMN_COMMENT 12 diff --git a/test/Marketeer/Converter.mqh b/test/Marketeer/Converter.mqh new file mode 100644 index 0000000..cd44aed --- /dev/null +++ b/test/Marketeer/Converter.mqh @@ -0,0 +1,24 @@ +template +class Converter +{ + private: + union _L2D + { + T1 L; + T2 D; + } + L2D; + + public: + T2 operator[](const T1 L) + { + L2D.L = L; + return L2D.D; + } + + T1 operator[](const T2 D) + { + L2D.D = D; + return L2D.L; + } +}; diff --git a/test/Marketeer/GroupSettings.mqh b/test/Marketeer/GroupSettings.mqh new file mode 100644 index 0000000..b340aea --- /dev/null +++ b/test/Marketeer/GroupSettings.mqh @@ -0,0 +1 @@ +enum GroupSettings {}; diff --git a/test/Marketeer/HTMLcolumns.mqh b/test/Marketeer/HTMLcolumns.mqh new file mode 100644 index 0000000..c1e3924 --- /dev/null +++ b/test/Marketeer/HTMLcolumns.mqh @@ -0,0 +1,15 @@ +#define COLUMNS_COUNT 13 + +#define COLUMN_TIME 0 +#define COLUMN_DEAL 1 +#define COLUMN_SYMBOL 2 +#define COLUMN_TYPE 3 +#define COLUMN_DIRECTION 4 +#define COLUMN_VOLUME 5 +#define COLUMN_PRICE 6 +#define COLUMN_ORDER 7 +#define COLUMN_COMISSION 8 +#define COLUMN_SWAP 9 +#define COLUMN_PROFIT 10 +#define COLUMN_BALANCE 11 +#define COLUMN_COMMENT 12 diff --git a/test/Marketeer/IndexMap.mqh b/test/Marketeer/IndexMap.mqh new file mode 100644 index 0000000..f714ede --- /dev/null +++ b/test/Marketeer/IndexMap.mqh @@ -0,0 +1,401 @@ +//+------------------------------------------------------------------+ +//| IndexMapT.mqh | +//| https://www.mql5.com/ru/articles/5706/ | +//+------------------------------------------------------------------+ + +#define EMPTY ((int)EMPTY_VALUE) + +#ifdef HASHMAP_WARNING +#define NULL_PLACEHOLDER "n/a" +#else +#define NULL_PLACEHOLDER "" +#endif + +class Object +{ + public: + + virtual string asString() const + { + return(__FUNCSIG__); + } + + virtual string asCSVString() const + { + return(__FUNCSIG__); + } + + virtual string getTypeName() const = 0; +}; + +/** + * Base container for indexed map. Can contain plain types and pointers. + */ +class Container: public Object +{ + protected: + enum datatype + { + null, + s, + d, + t, + i, + o, + u + }; + + datatype type; + + public: + datatype getType() const + { + return type; + } + + // helper method to access plain type values from the base class + template + R get() const; + + // helper method to access object pointers from the base class + template + Object *getObject() const; +}; + +/** + * Variable type data for indexed map; plain types only. + */ +template +class TypeContainer: public Container +{ + private: + int digits; + int flags; + + protected: + T v; + + TypeContainer() + { + digits = _Digits; + flags = TIME_DATE | TIME_MINUTES; + } + + public: + TypeContainer(T _v, int precision = INT_MIN, int timeflags = TIME_DATE | TIME_MINUTES) + { + v = _v; + digits = precision == INT_MIN ? _Digits : precision; + flags = timeflags; + + if(typename(T) == "string") + { + type = datatype::s; + } + else + if(typename(T) == "double" || typename(T) == "float") + { + type = datatype::d; + } + else + if(typename(T) == "datetime") + { + type = datatype::t; + } + else + if(typename(T) == "char" || typename(T) == "short" || typename(T) == "int" || typename(T) == "long") + { + type = datatype::i; + } + else + { + type = datatype::u; + } + } + + virtual T getValue() const + { + return v; + } + + // represent data as a string, convert if necessary + virtual string asString() const + { + switch(type) + { + case datatype::s: return (string)v; + case datatype::d: return DoubleToString((double)v, digits); + case datatype::t: return TimeToString((datetime)v, flags); + case datatype::i: return IntegerToString((long)v); + default: return (string)v; + } + } + + virtual string asCSVString() const + { + return asString(); + } + + virtual string getTypeName() const override + { + return typename(this); + } +}; + +/** + * Object pointer types for indexed map. + * Pointer will be deleted automatically. + */ +template +class ObjectContainer: public Container +{ + protected: + Object *o; + + public: + ObjectContainer(T _v) + { + o = _v; + type = datatype::o; + } + + ~ObjectContainer() + { + if(CheckPointer(o) == POINTER_DYNAMIC) + { + delete(o); + } + } + + T getObject() const + { + return o; + } + + virtual string asString() const override + { + return o.asString(); + } + + virtual string asCSVString() const + { + return o.asCSVString(); + } + + virtual string getTypeName() const override + { + return typename(this); + } +}; + + +template +R Container::get() const +{ + const TypeContainer *ptr = dynamic_cast *>(&this); + if(ptr != NULL) return (R)ptr.getValue(); + return (R)NULL; +} + +template // R is supposed to be an object pointer +Object *Container::getObject() const +{ + const ObjectContainer *obj = dynamic_cast *>(&this); + if(obj != NULL) return obj.getObject(); + return NULL; +} + + +/** + * Indexed map with random access by key and index. + */ +template +class IndexMapT: public Container // Object +{ + private: + T keys[]; + Container *values[]; + int count; + string id; + uchar delimiter; + + public: + IndexMapT(): count(0), delimiter(',') {} + IndexMapT(string obj): id (obj), count(0), delimiter(',') {} + IndexMapT(uchar d): count(0), delimiter(d) {} + + ~IndexMapT() + { + reset(); + } + + virtual string getTypeName() const override + { + return typename(this); + } + + void add(const T key, Container *value) + { + ArrayResize(keys, count + 1); + ArrayResize(values, count + 1); + keys[count] = key; + values[count] = value; + count++; + } + + void reset() + { + for(int i = 0; i < count; i++) + { + if(CheckPointer(values[i]) == POINTER_DYNAMIC) + { + delete(values[i]); + } + } + ArrayResize(keys, 0); + ArrayResize(values, 0); + count = 0; + } + + bool isKeyExisting(const T key) const + { + return (getIndex(key) != EMPTY); + } + + int getIndex(const T key) const + { + for(int i = 0; i < count; i++) + { + if(keys[i] == key) return(i); + } + #ifdef HASHMAP_VERBOSE + Print(__FUNCSIG__, ": no key=", key); + #endif + return EMPTY; + } + + Container *operator[](const int index) const + { + if(index < 0 || index >= count) + { + #ifdef HASHMAP_VERBOSE + Print(__FUNCSIG__, ": index=", index); + #endif + return(NULL); + } + return(GetPointer(values[index])); + } + + Container *operator[](const T key) const + { + for(int i = 0; i < count; i++) + { + if(keys[i] == key) return(GetPointer(values[i])); + } + #ifdef HASHMAP_VERBOSE + Print(__FUNCSIG__, ": no key=", key); + #endif + return(NULL); + } + + T getKey(const int index) const + { + if(index < 0 || index >= count) + { + Print(__FUNCSIG__, ": index=", index); + } + return(keys[index]); + } + + template + void setValue(const T key, R value) + { + // NB: implementation specific + // in HTML every attribute can occur only once in a tag, + // all successive assignments are ignored + if(!isKeyExisting(key)) + { + set(key, new TypeContainer(value)); + } + } + + void set(const T key, Container *value) + { + int index = getIndex(key); + if(index != EMPTY) + { + #ifdef HASHMAP_WARNING + Print(__FUNCSIG__, ": overwritten key=", key, ", old value=", (values[index] != NULL ? values[index].asString() : "null"), ", new value=", (value != NULL ? value.asString() : "null")); + #endif + values[index] = value; + } + else + { + add(key, value); + } + } + + void set(const T key) + { + int index = getIndex(key); + if(index == EMPTY) + { + add(key, NULL); + } + } + + int getSize() const + { + return(count); + } + + virtual string asString() const + { + string result = ""; + for(int i = 0; i < count; i++) + { + result += (string)keys[i] + "=" + (CheckPointer(values[i]) == POINTER_INVALID ? NULL_PLACEHOLDER : values[i].asString()) + ";"; + } + return result; + } + + virtual string asCSVString() const + { + string result = ""; + string d = CharToString(delimiter); + for(int i = 0; i < count; i++) + { + string v = NULL_PLACEHOLDER; + + if(CheckPointer(values[i]) != POINTER_INVALID) + { + v = values[i].asCSVString(); + StringReplace(v, d, ""); + } + + if(i < count - 1) + { + result += v + d; + } + else + { + result += v; + } + } + return result; + } +}; + +class IndexMap: public IndexMapT +{ + public: + IndexMap(): IndexMapT() {} + IndexMap(string obj): IndexMapT(obj) {} + IndexMap(uchar d): IndexMapT(d) {} + string get(const string key) + { + Container *c = this[key]; + if(c == NULL) return NULL; + return c.get(); + } +}; \ No newline at end of file diff --git a/test/Marketeer/RubbArray.mqh b/test/Marketeer/RubbArray.mqh new file mode 100644 index 0000000..dc926ac --- /dev/null +++ b/test/Marketeer/RubbArray.mqh @@ -0,0 +1,195 @@ +//+------------------------------------------------------------------+ +//| RubbArray.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//| https://www.mql5.com/ru/articles/5638 | +//+------------------------------------------------------------------+ + +template +interface Clonable +{ + T clone(); +}; + +template +class BaseArray +{ + protected: + T data[]; + + public: + virtual ~BaseArray() + { + clear(); + } + + virtual void clear() + { + ArrayResize(data, 0); + } + + T operator[](int i) const + { + return get(i); + } + + T get(int i) const + { + if(i < 0 || i >= ArraySize(data)) + { + Print("Array size=", ArraySize(data), ", index=", i); + return NULL; + } + return data[i]; + } + + T top() const + { + if(ArraySize(data) == 0) + { + Print("Array size=0"); + return NULL; + } + return data[ArraySize(data) - 1]; + } + + T peek() const + { + return top(); + } + + BaseArray *add(T d) + { + int n = ArraySize(data); + ArrayResize(data, n + 1); + data[n] = d; + return &this; + } + + BaseArray *operator<<(T d) + { + return add(d); + } + + BaseArray *operator<<(const BaseArray *x) + { + for(int i = 0; i < x.size(); i++) + { + Clonable *clone = dynamic_cast *>(x[i]); + if(clone != NULL) + { + add(clone.clone()); + } + else + { + add(x[i]); + } + } + return &this; + } + + BaseArray *push(T d) + { + return add(d); + } + + void operator=(const BaseArray &d) + { + int i, n = d.size(); + ArrayResize(data, n); + for(i = 0; i < n; i++) + { + data[i] = d[i]; + } + } + + T operator>>(int i) + { + T d = this[i]; + if(d == NULL) return NULL; + int n = ArraySize(data) - 1; + if(i < n) + { + ArrayCopy(data, data, i, i + 1); + } + ArrayResize(data, n); + return d; + } + + T pop() + { + int _size = ArraySize(data) - 1; + T d = this[_size]; + ArrayResize(data, _size); + return d; + } + + int size() const + { + return ArraySize(data); + } + + + string toString() const + { + static string formats[4][2] = {{"double", "%f"}, {"long", "%i"}, {"string", "%s"}, {"int", "%i"}}; + string fmt = "%x"; + for(int k = 0; k < ArrayRange(formats, 0); k++) + { + if(typename(T) == formats[k][0]) + { + fmt = formats[k][1]; + break; + } + } + + int i, n = ArraySize(data); + string s; + for(i = 0; i < n; i++) + { + s += StringFormat(fmt, data[i]) + ","; + } + return (s); + } + + +}; + +template +class RubbArray: public BaseArray +{ + public: + RubbArray() + { + } + + ~RubbArray() + { + clear(); + } + + virtual void clear() override + { + int i, n = ArraySize(data); + for(i = 0; i < n; i++) + { + if(CheckPointer(data[i]) == POINTER_DYNAMIC) delete data[i]; + } + ArrayResize(data, 0); + } + + T replace(const int i, T v) + { + int n = ArraySize(data); + if(i < n) + { + if(CheckPointer(data[i]) == POINTER_DYNAMIC) delete data[i]; + data[i] = v; + } + return v; + } + +}; + +#define List RubbArray +#define Stack RubbArray diff --git a/test/Marketeer/TimeMT4.mqh b/test/Marketeer/TimeMT4.mqh new file mode 100644 index 0000000..5d0241e --- /dev/null +++ b/test/Marketeer/TimeMT4.mqh @@ -0,0 +1,43 @@ +//#define DATETIME_PLACEHOLDER ((datetime)0xFFFFFFFFFFFFFFFF) + +class DateTime +{ + private: + MqlDateTime mdtstruct; + + public: + DateTime(){TimeToStruct(0, mdtstruct);} + DateTime *assign(datetime dt) {TimeToStruct(dt, mdtstruct); return &this;} + int __TimeDayOfWeek() {return mdtstruct.day_of_week;} + int __TimeDayOfYear() {return mdtstruct.day_of_year;} + int __TimeYear() {return mdtstruct.year;} + int __TimeMonth() {return mdtstruct.mon;} + int __TimeDay() {return mdtstruct.day;} + int __TimeHour() {return mdtstruct.hour;} + int __TimeMinute() {return mdtstruct.min;} + int __TimeSeconds() {return mdtstruct.sec;} +}; + +DateTime _DateTime; + +#define TimeDayOfWeek(T) _DateTime.assign(T).__TimeDayOfWeek() +#define TimeYear(T) _DateTime.assign(T).__TimeYear() +#define TimeMonth(T) _DateTime.assign(T).__TimeMonth() +#define TimeDay(T) _DateTime.assign(T).__TimeDay() +#define TimeHour(T) _DateTime.assign(T).__TimeHour() +#define TimeMinute(T) _DateTime.assign(T).__TimeMinute() +#define TimeSeconds(T) _DateTime.assign(T).__TimeSeconds() + +#define _TimeYear _DateTime.__TimeYear +#define _TimeMonth _DateTime.__TimeMonth +#define _TimeDay _DateTime.__TimeDay +#define _TimeHour _DateTime.__TimeHour +#define _TimeMinute _DateTime.__TimeMinute +#define _TimeSeconds _DateTime.__TimeSeconds + +#define Year _DateTime.assign(TimeCurrent()).__TimeYear +#define Month _DateTime.assign(TimeCurrent()).__TimeMonth +#define Day _DateTime.assign(TimeCurrent()).__TimeDay +#define Hour _DateTime.assign(TimeCurrent()).__TimeHour +#define Minute _DateTime.assign(TimeCurrent()).__TimeMinute +#define Seconds _DateTime.assign(TimeCurrent()).__TimeSeconds diff --git a/test/Marketeer/WebDataExtractor.mqh b/test/Marketeer/WebDataExtractor.mqh new file mode 100644 index 0000000..34925de --- /dev/null +++ b/test/Marketeer/WebDataExtractor.mqh @@ -0,0 +1,1628 @@ +//+------------------------------------------------------------------+ +//| WebDataExtractor.mqh | +//| Copyright (c) 2015-2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//| https://www.mql5.com/ru/articles/5706/ | +//| https://www.mql5.com/ru/articles/5913/ | +//| HTML to CSV converter | +//+------------------------------------------------------------------+ + +//#define HASHMAP_WARNING // HASHMAP_VERBOSE +#include +#include + + +input GroupSettings HTMLR_Settings; // H T M L R E P O R T S E T T I N G S + +input string RowSelector = "tr:has-n-children(13)[bgcolor^=\"#F\"]"; // · RowSelector +input string ColumnSettingsFile = "ReportHistoryDeals.cfg.csv"; // · ColumnSettingsFile +input string SubstitutionSettingsFile = ""; // · SubstitutionSettingsFile + +input bool LogDomElements = false; // · LogDomElements +input bool LogDomWarnings = false; // · LogDomWarnings +input bool LogSelectedElements = false; // · LogSelectedElements + + +#define LOG_MASK_DOM 1 +#define LOG_MASK_DOM_WARNING 2 +#define LOG_MASK_SELECTOR 4 +#define LOG_MASK_MAX 4 +#define LOG_MASK_ALL ((LOG_MASK_MAX << 1) - 1) + +ushort combinators[] = +{ + ' ', '+', '>', '~' +}; + +string empty_tags[] = +{ + #include +}; + +/* + + CSS selectors: + 1 * any element + 1 .intro class="intro" + 1 #id1 id="id1" + 1 p p tag + 1 div p p tag somewhere inside div tag + 2 div > p p tag with parent div tag + 2 div + p p tag as a sibling immediately after div tag, common parent + 3 div ~ p p tag as a sibling somewhere after div tag (less strict than +), common parent + 2 [target] a tag with target attribute + 2 [target=_blank] a tag with attribute target="_blank" + 2- [title~=flower] a tag with attribute title containing "space"-separated word "flower" somewhere + 3 [title*=flower] a tag with attribute title containing "flower" sequence somewhere (less strict than ~) + 2- [data-value|="foo"] a tag whose attribute value has this "foo" in a dash-separated list somewhere + 3 [data-value^="foo"] attribute value starts with this + 3 [data-value$="foo"] attribute value ends with this + + 2 p:first-child a tag

that is the first child of its parent + 3 p:last-child a tag

that is the last child of its parent + 3 p:nth-child(n) + 3 p:nth-last-child(n) + 3- p:not(selector) + + http://www.w3.org/TR/CSS2/selector.html + A simple selector is either a type selector or universal selector followed immediately by + zero or more attribute selectors, ID selectors, or pseudo-classes, in any order. + + The simple selector matches if all of its components match. + + A selector is a chain of one or more simple selectors separated by combinators. + + It always begins with a type selector or a universal selector. No other type selector or universal selector is allowed in the sequence. + + Combinators are: white space, ">", and "+" (and "~" css3) + +*/ + +enum StateBit +{ + blank, + insideTagOpen, + insideTagClose, + insideComment, + insideScript +}; + +enum AttrBit +{ + name, + value +}; + +class AttributesParser +{ + private: + AttrBit state; + int offset; + int cursor; + int length; + string key; + + void skipWhiteSpace(const string &data) + { + int n = StringLen(data); + while(offset < n && StringGetCharacter(data, offset) <= 32) + { + offset++; + cursor++; + } + } + + public: + AttributesParser(): state(AttrBit::name), offset(0), cursor(0), key(""){} + + void parseAll(const string &data, IndexMap &attributes) + { + length = StringLen(data); + while(parse(data, attributes)); + + if(offset < length) + { + string ending = StringSubstr(data, cursor); + StringTrimRight(ending); + StringTrimLeft(ending); + if(StringLen(ending) > 0) + { + attributes.set(ending); + } + else if(key != "") + { + attributes.set(key); + } + } + else if(key != "") + { + attributes.set(key); + } + } + + + bool parse(const string &data, IndexMap &attributes) + { + skipWhiteSpace(data); + + int x = 0; + if(state == AttrBit::name) + { + while(offset < length) + { + x = StringGetCharacter(data, offset); + if(x == '=' || (x <= 32)) break; + offset++; + } + + string attr = StringSubstr(data, cursor, offset - cursor); + if(StringLen(attr) > 0) + { + key = attr; + } + + if(x == '=') + { + state = AttrBit::value; + offset++; + cursor = offset; + } + // else - attribute without value + + return(offset < length); + } + else + if(state == AttrBit::value) + { + if(key == "") + { + Print("Wrong state::value with empty key"); + } + ushort c = StringGetCharacter(data, offset); + if(c == '"' || c == '\'') + { + offset++; + cursor++; + while(offset < length) + { + if(StringGetCharacter(data, offset) == c) break; + offset++; + } + + attributes.setValue(key, StringSubstr(data, cursor, offset - cursor)); + key = ""; + + state = AttrBit::name; + offset++; + cursor = offset; + return(offset < length); + } + else + if(c > 32) + { + while(offset < length) + { + if(StringGetCharacter(data, offset) == ' ') break; + offset++; + } + + attributes.setValue(key, StringSubstr(data, cursor, offset - cursor)); + key = ""; + + state = AttrBit::name; + offset++; + cursor = offset; + return(offset < length); + + } + else + if(c <= 32) // empty value, 'attr=' + { + while(offset < length) + { + if(StringGetCharacter(data, offset) > ' ') break; + offset++; + } + + attributes.set(key); + key = ""; + + state = AttrBit::name; + cursor = offset; + return(offset < length); + } + } + return(false); + } +}; + +class SubSelector +{ + enum PseudoClassModifier + { + none, + firstChild, + lastChild, + nthChild, + nthLastChild, + hasNthChildren + }; + + public: + ushort type; + string value; + PseudoClassModifier modifier; + string param; + + SubSelector(ushort t, string v): type(t), value(v), modifier(PseudoClassModifier::none) {} + + SubSelector(ushort t, string v, PseudoClassModifier m): type(t), value(v), modifier(m) {} + + SubSelector(ushort t, string v, PseudoClassModifier m, string p): type(t), value(v), modifier(m), param(p) {} +}; + + +class SubSelectorArray +{ + private: + SubSelector *selectors[]; + IndexMap mod; + + static TypeContainer first; + static TypeContainer last; + static TypeContainer nth; + static TypeContainer nthLast; + static TypeContainer hasN; + + void init() + { + mod.add(":first-child", &first); + mod.add(":last-child", &last); + mod.add(":nth-child", &nth); + mod.add(":nth-last-child", &nthLast); + mod.add(":has-n-children", &hasN); + } + + void createFromString(const string &selector) + { + ushort p = 0; // previous/pending type + int ppos = 0; + int i, n = StringLen(selector); + bool quotes = false; + for(i = 0; i < n; i++) + { + ushort t = StringGetCharacter(selector, i); + if(t == '"') + { + quotes = !quotes; + } + + if(quotes) continue; + + if(t == '.' || t == '#' || t == '[' || t == ']') + { + string v = StringSubstr(selector, ppos, i - ppos); + if(i == 0) v = "*"; + if(p == '[' && StringLen(v) > 0 && StringGetCharacter(v, StringLen(v) - 1) == ']') + { + v = StringSubstr(v, 0, StringLen(v) - 1); + } + add(p, v); + p = t; + if(p == ']') p = 0; + ppos = i + 1; + } + } + + if(ppos < n) + { + string v = StringSubstr(selector, ppos, n - ppos); + if(p == '[' && StringLen(v) > 0 && StringGetCharacter(v, StringLen(v) - 1) == ']') + { + v = StringSubstr(v, 0, StringLen(v) - 1); + } + add(p, v); + } + } + + public: + + SubSelectorArray() + { + init(); + } + + SubSelectorArray(const string selector) + { + init(); + createFromString(selector); + } + + int size() const + { + return ArraySize(selectors); + } + + SubSelector *operator[](int i) const + { + return selectors[i]; + } + + void add(const ushort t, string v) + { + int n = ArraySize(selectors); + ArrayResize(selectors, n + 1); + + PseudoClassModifier m = PseudoClassModifier::none; + string param; + + for(int j = 0; j < mod.getSize(); j++) + { + int p = StringFind(v, mod.getKey(j)); + if(p > -1) + { + if(p + StringLen(mod.getKey(j)) < StringLen(v)) + { + param = StringSubstr(v, p + StringLen(mod.getKey(j))); + if(StringGetCharacter(param, 0) == '(' && StringGetCharacter(param, StringLen(param) - 1) == ')') + { + param = StringSubstr(param, 1, StringLen(param) - 2); + } + else + { + param = ""; + } + } + + m = mod[j].get(); + v = StringSubstr(v, 0, p); + + break; + } + } + + if(t == '[' && m == PseudoClassModifier::none) + { + AttributesParser p; + IndexMap attr; + p.parseAll(v, attr); + + // attributes are selected one by one: element[attr1=value][attr2=value] + // the map should contain only 1 valid pair at a time + if(attr.getSize() > 0) + { + param = attr.getKey(0); + v = attr[0] != NULL ? attr[0].get() : ""; + } + } + + if(StringLen(param) == 0) + { + selectors[n] = new SubSelector(t, v, m); + } + else + { + selectors[n] = new SubSelector(t, v, m, param); + } + } + + ~SubSelectorArray() + { + int i, n = ArraySize(selectors); + for(i = 0; i < n; i++) + { + delete(selectors[i]); + } + } +}; + +TypeContainer SubSelectorArray::first(PseudoClassModifier::firstChild); +TypeContainer SubSelectorArray::last(PseudoClassModifier::lastChild); +TypeContainer SubSelectorArray::nth(PseudoClassModifier::nthChild); +TypeContainer SubSelectorArray::nthLast(PseudoClassModifier::nthLastChild); +TypeContainer SubSelectorArray::hasN(PseudoClassModifier::hasNthChildren); + +class DomIterator; + +class DomElement +{ + private: + string name; + string content; + IndexMap attributes; + + DomElement *parent; + + int level; + + protected: + bool childrenOwner; + DomElement *children[]; + + void clear() + { + int i, n = ArraySize(children); + for(i = 0; i < n; i++) + { + delete children[i]; + } + ArrayResize(children, 0); + } + + bool isCombinator(ushort c) + { + for(int i = 0; i < ArraySize(combinators); i++) + { + if(combinators[i] == c) return(true); + } + return(false); + } + + public: + DomElement(): parent(NULL), childrenOwner(true) {} + DomElement(const string n): parent(NULL), childrenOwner(true) + { + name = n; + } + + DomElement(const string n, const string text): parent(NULL), childrenOwner(true) + { + name = n; + content = text; + } + + DomElement(DomElement *p, const string &n, const string text = ""): childrenOwner(true) + { + p.addChild(&this); + parent = p; + level = p.level + 1; + name = n; + if(text != "") content = text; + } + + ~DomElement() + { + if(childrenOwner) + { + clear(); + } + } + + void addChild(DomElement *child) + { + int n = ArraySize(children); + ArrayResize(children, n + 1); + children[n] = child; + } + + int getChildrenCount() const + { + return ArraySize(children); + } + + DomElement *getChild(const int i) const + { + if(i >= 0 && i < ArraySize(children)) + { + return children[i]; + } + return NULL; + } + + void addChildren(DomElement *p) + { + if(CheckPointer(p) == POINTER_DYNAMIC) + { + for(int i = 0; i < p.getChildrenCount(); i++) + { + addChild(p.getChild(i)); + } + } + } + + int getChildIndex(DomElement *e) const + { + for(int i = 0; i < ArraySize(children); i++) + { + if(children[i] == e) return(i); + } + return(-1); + } + + void setName(string n) + { + name = n; + } + + void setText(string t) + { + content = t; + } + + DomElement *getParent() const + { + return parent; + } + + string getName() const + { + return name; + } + + string getText() const + { + string text; + for(int i = 0; i < ArraySize(children); i++) + { + text += children[i].getText(); + } + return content + text; + } + + int getLevel() const + { + return level; + } + + void print(bool full = true) + { + PrintFormat("%" + IntegerToString(level) + "c %s, %s", ' ', name, attributes.asString()); + if(full) + { + int i, n = ArraySize(children); + for(i = 0; i < n; i++) + { + children[i].print(); + } + } + } + + void printWithContent(bool full = true) + { + PrintFormat("%" + IntegerToString(level) + "c %s, %s, %s", ' ', name, content, attributes.asString()); + if(full) + { + int i, n = ArraySize(children); + for(i = 0; i < n; i++) + { + children[i].printWithContent(); + } + } + } + + void printShort() + { + string id = ""; + Container *pid = attributes["id"]; + if(pid != NULL) id = pid.get(); + PrintFormat("%" + IntegerToString(level) + "c %d %s %s", ' ', level, name, id); + } + + void printShortWithChildren() + { + string id = ""; + Container *pid = attributes["id"]; + if(pid != NULL) id = pid.get(); + PrintFormat("%" + IntegerToString(level) + "c %d %s %s", ' ', level, name, id); + int i, n = ArraySize(children); + for(i = 0; i < n; i++) + { + children[i].printShort(); + } + } + + void parseAttributes(const string &data) + { + AttributesParser p; + p.parseAll(data, attributes); + } + + void setAttribute(const string id, Container *value) + { + attributes.set(id, value); + } + + bool hasAttribute(const string key) const + { + return (attributes.isKeyExisting(key)); + } + + string getAttribute(const string key) const + { + Container *c = attributes[key]; + if(c != NULL) return c.get(); + return ""; + } + + string getAttribute(const int index) const + { + Container *c = attributes[index]; + if(c != NULL) return c.get(); + return ""; + } + + bool match(const SubSelectorArray *u) + { + bool matched = true; + int i, n = u.size(); + for(i = 0; i < n && matched; i++) + { + if(u[i].type == 0) // tag name + { + if(u[i].value == "*") + { + // any tag + } + else + if(StringLen(u[i].value) > 0 && StringCompare(name, u[i].value) != 0) + { + matched = false; + } + else + if(u[i].modifier == PseudoClassModifier::firstChild) + { + if(parent != NULL && parent.getChildIndex(&this) != 0) + { + matched = false; + } + } + else + if(u[i].modifier == PseudoClassModifier::lastChild) + { + if(parent != NULL && parent.getChildIndex(&this) != parent.getChildrenCount() - 1) + { + matched = false; + } + } + else + if(u[i].modifier == PseudoClassModifier::nthChild) + { + int x = (int)StringToInteger(u[i].param); + if(parent != NULL && parent.getChildIndex(&this) != x - 1) // children are counted starting from 1 + { + matched = false; + } + } + else + if(u[i].modifier == PseudoClassModifier::nthLastChild) + { + int x = (int)StringToInteger(u[i].param); + if(parent != NULL && parent.getChildrenCount() - parent.getChildIndex(&this) - 1 != x - 1) + { + matched = false; + } + } + else + if(u[i].modifier == PseudoClassModifier::hasNthChildren) + { + int x = (int)StringToInteger(u[i].param); + if(getChildrenCount() != x) + { + matched = false; + } + } + } + else + if(u[i].type == '.') // class + { + if(attributes.isKeyExisting("class")) + { + Container *c = attributes["class"]; + if(c == NULL || StringFind(" " + c.get() + " ", " " + u[i].value + " ") == -1) + { + matched = false; + } + } + else + { + matched = false; + } + } + else + if(u[i].type == '#') // id + { + if(attributes.isKeyExisting("id")) + { + Container *c = attributes["id"]; + if(c == NULL || StringCompare(c.get(), u[i].value) != 0) + { + matched = false; + } + } + else + { + matched = false; + } + } + else + if(u[i].type == '[') // attributes + { + string key = u[i].param; + string v = u[i].value; + + ushort suffix = StringGetCharacter(key, StringLen(key) - 1); + + if(suffix == '*' || suffix == '^' || suffix == '$') // contains, starts with, or ends with + { + key = StringSubstr(key, 0, StringLen(key) - 1); + } + else + { + suffix = 0; + } + + if(hasAttribute(key) && attributes[key] != NULL) + { + if(StringLen(v) > 0) + { + if(suffix == 0) + { + if(key == "class") + { + matched &= (StringFind(" " + attributes[key].get() + " ", " " + v + " ") > -1); + } + else + { + matched &= (StringCompare(v, attributes[key].get()) == 0); + } + } + else + if(suffix == '*') + { + matched &= (StringFind(attributes[key].get(), v) != -1); + } + else + if(suffix == '^') + { + matched &= (StringFind(attributes[key].get(), v) == 0); + } + else + if(suffix == '$') + { + string x = attributes[key].get(); + if(StringLen(x) > StringLen(v)) + { + matched &= (StringFind(x, v, StringLen(x) - StringLen(v)) == StringLen(v)); + } + } + } + } + else + { + matched = false; + } + } + } + + return matched; + } + + bool find(const ushort op, const SubSelectorArray *selectors, DomIterator *output) + { + bool found = false; + int i, n; + if(op == ' ' || op == '>' || op == '/') + { + n = ArraySize(children); + for(i = 0; i < n; i++) + { + if(children[i].match(selectors)) + { + if(op == '/') + { + found = true; + output.addChild(GetPointer(children[i])); + } + else + if(op == ' ') + { + DomElement *p = &this; + while(p != NULL) + { + if(output.getChildIndex(p) != -1) + { + found = true; + output.addChild(GetPointer(children[i])); + break; + } + p = p.parent; + } + } + else // op == '>' + { + if(output.getChildIndex(&this) != -1) + { + found = true; + output.addChild(GetPointer(children[i])); + } + } + } + + children[i].find(op, selectors, output); + } + } + else + if(op == '+' || op == '~') + { + if(CheckPointer(parent) == POINTER_DYNAMIC) + { + if(output.getChildIndex(&this) != -1) + { + int q = parent.getChildIndex(&this); + if(q != -1) + { + n = (op == '+') ? (q + 2) : parent.getChildrenCount(); + if(n > parent.getChildrenCount()) n = parent.getChildrenCount(); + for(i = q + 1; i < n; i++) + { + DomElement *m = parent.getChild(i); + if(CheckPointer(m) != POINTER_DYNAMIC) + { + Print("bad:", name, " i=", i, " q=", q, " n=", n); + } + if(m.match(selectors)) + { + found = true; + output.addChild(m); + } + } + } + else + { + Print("Error: can't find 'this' element"); + } + } + } + else + { + if(name != "root") + { + Print("No parent for: ", name); + } + } + for(i = 0; i < ArraySize(children); i++) + { + found = children[i].find(op, selectors, output) || found; + } + } + else + { + Print("Error: unknown combinator:", ShortToString(op)); + } + return found; + } + + + DomIterator *querySelect(const string q) + { + DomIterator *result = new DomIterator(); + + if(q == ".") + { + result.addChild(&this); // root + return result; + } + + int cursor = 0; // where selector string started + int i, n = StringLen(q); + ushort p = 0; // previous character + ushort a = 0; // next/pending operator + ushort b = '/'; // current operator, root notation from the start + string selector = "*"; // current simple selector, 'any' by default + int index = 0; // position in the resulting array of objects + + + for(i = 0; i < n; i++) + { + ushort c = StringGetCharacter(q, i); + if(isCombinator(c)) + { + a = c; + if(!isCombinator(p)) + { + selector = StringSubstr(q, cursor, i - cursor); + } + else + { + // suppress blanks around other combinators + a = MathMax(c, p); + } + cursor = i + 1; + } + else + { + if(isCombinator(p)) + { + index = result.getChildrenCount(); + + SubSelectorArray selectors(selector); + find(b, &selectors, result); + b = a; + + // now we can delete outdated results in positions up to 'index' + result.removeFirst(index); + } + } + p = c; + } + + if(cursor < i) + { + selector = StringSubstr(q, cursor, i - cursor); + + index = result.getChildrenCount(); + + SubSelectorArray selectors(selector); + find(b, &selectors, result); + result.removeFirst(index); + } + + return result; + } + + IndexMap *tableSelect(const string rowSelector, const string &headers[], const string &columSelectors[], const string &dataSelectors[], const IndexMap *subst = NULL, const bool numericKeys = false) + { + if(ArraySize(columSelectors) != ArraySize(dataSelectors)) return NULL; + + int n = ArraySize(columSelectors); + + DomIterator *r = querySelect(rowSelector); + if((HtmlParser::isDebug() & LOG_MASK_SELECTOR) != 0) + { + r.printAllWithContent(); + } + + IndexMap *data = new IndexMap('\n'); + int counter = 0; + + r.rewind(); + while(r.hasNext()) + { + DomElement *e = r.next(); + if((HtmlParser::isDebug() & LOG_MASK_SELECTOR) != 0) + { + Print("row N" + (string)counter); + } + + string id = IntegerToString(counter); + if(!numericKeys) // we can store IDs of found elements in results, optionally + { + if(e.hasAttribute("id")) id += "-" + e.getAttribute("id"); + } + + IndexMap *row = new IndexMap(); + + for(int i = 0; i < n; i++) + { + if(StringLen(columSelectors[i])) + { + DomIterator *d = e.querySelect(columSelectors[i]); + if((HtmlParser::isDebug() & LOG_MASK_SELECTOR) != 0) + { + d.printAllWithContent(); + } + + string value; + + if(d.getChildrenCount() > 0) + { + if(d.getChildrenCount() > 1) + { + if((HtmlParser::isDebug() & LOG_MASK_SELECTOR) != 0) + { + Print("Too many elements selected:", d.getChildrenCount()); + } + } + + if(dataSelectors[i] == "") + { + value = d[0].getText(); + } + else + { + value = d[0].getAttribute(dataSelectors[i]); + } + + if(CheckPointer(subst) != POINTER_INVALID) + { + if(subst.isKeyExisting(columSelectors[i])) + { + IndexMap *rules = dynamic_cast(subst[columSelectors[i]]); + + if(rules != NULL) + { + for(int j = 0; j < rules.getSize(); j++) + { + StringReplace(value, rules.getKey(j), (rules[j] != NULL ? rules[j].asString() : "")); + } + } + } + } + + StringTrimLeft(value); + StringTrimRight(value); + + if(numericKeys) + { + row.setValue(IntegerToString(i), value); + } + else + { + row.setValue(headers[i]/*columSelectors[i] + dataSelectors[i]*/, value); + } + } + else // field not found + { + if(numericKeys) + { + row.set(IntegerToString(i)); + } + else + { + row.set(columSelectors[i] + dataSelectors[i]); + } + } + delete d; + } + else // constant data + { + if(numericKeys) + { + row.setValue(IntegerToString(i), dataSelectors[i]); + } + else + { + row.setValue(dataSelectors[i], dataSelectors[i]); + } + + } + } + if(row.getSize() > 0) + { + data.set(id, row); + counter++; + } + else + { + delete row; + } + } + + delete r; + + return data; + } +}; + +class DomIterator: public DomElement +{ + private: + int cursor; + + public: + DomIterator() + { + childrenOwner = false; + } + + bool hasNext() + { + return(cursor < ArraySize(children)); + } + + DomElement *next() + { + if(hasNext()) + { + return children[cursor++]; + } + return NULL; + } + + DomElement *operator[](int index) + { + if(index >= 0 && index < ArraySize(children)) + { + return children[index]; + } + return NULL; + } + + void rewind() + { + cursor = 0; + } + + void removeFirst(const int n) + { + int m = ArraySize(children); + if(n < m) + { + ArrayCopy(children, children, 0, n); + ArrayResize(children, m - n); + } + else + if(n == m) + { + ArrayResize(children, 0); + } + } + + void printAll() + { + rewind(); + while(hasNext()) + { + DomElement *e = next(); + e.print(false); + } + } + + void printAllWithContent() + { + rewind(); + while(hasNext()) + { + DomElement *e = next(); + e.printWithContent(false); + } + } + +}; + + +class HtmlParser +{ + private: + const string TAG_OPEN_START; + const string TAG_OPEN_STOP; + + const string TAG_OPENCLOSE_STOP; + + const string TAG_CLOSE_START; + const string TAG_CLOSE_STOP; + + const string COMMENT_START; + const string COMMENT_STOP; + + const string SCRIPT_STOP; + + + StateBit state; + + DomElement *root; + DomElement *cursor; + int offset; + + IndexMap empties; + + static int debugLevel; + + public: + HtmlParser(): + TAG_OPEN_START("<"), + TAG_OPEN_STOP(">"), + TAG_OPENCLOSE_STOP("/>"), + TAG_CLOSE_START(""), + COMMENT_START(""), + SCRIPT_STOP("/script>"), + state(blank) + { + for(int i = 0; i < ArraySize(empty_tags); i++) + { + empties.set(empty_tags[i]); + } + } + + ~HtmlParser() + { + if(root != NULL) + { + delete root; + } + } + + DomElement *parse(const string &html) + { + if(root != NULL) + { + delete root; + } + root = new DomElement("root"); + cursor = root; + offset = 0; + + while(processText(html)); + + return root; + } + + bool processText(const string &html) + { + int p; + if(state == blank) + { + p = StringFind(html, "<", offset); + if(p == -1) // no more tags + { + return(false); + } + else if(p > 0) + { + if(p > offset) + { + string text = StringSubstr(html, offset, p - offset); + StringTrimLeft(text); + StringTrimRight(text); + StringReplace(text, " ", ""); + if(StringLen(text) > 0) + { + cursor.setText(text); + } + } + } + + offset = p; + + if(IsString(html, COMMENT_START)) state = insideComment; + else + if(IsString(html, TAG_CLOSE_START)) state = insideTagClose; + else + if(IsString(html, TAG_OPEN_START)) state = insideTagOpen; + + return(true); + } + else + if(state == insideTagOpen) + { + offset++; + int pspace = StringFind(html, " ", offset); + int pright = StringFind(html, ">", offset); + p = MathMin(pspace, pright); + if(p == -1) + { + p = MathMax(pspace, pright); + } + + if(p == -1 || pright == -1) // no tag closing + { + return(false); + } + + if(pspace > pright) + { + pspace = -1; // outer space + } + + bool selfclose = false; + if(IsString(html, TAG_OPENCLOSE_STOP, pright - StringLen(TAG_OPENCLOSE_STOP) + 1)) + { + selfclose = true; + if(p == pright) p--; + pright--; + } + + string name = StringSubstr(html, offset, p - offset); + + StringToLower(name); + StringTrimRight(name); + DomElement *e = new DomElement(cursor, name); + + if(pspace != -1) + { + string txt; + if(pright - pspace > 1) + { + txt = StringSubstr(html, pspace + 1, pright - (pspace + 1)); + e.parseAttributes(txt); + } + } + + if((HtmlParser::isDebug() & LOG_MASK_DOM) != 0) + { + e.print(false); + } + + bool softSelfClose = false; + if(!selfclose) + { + if(empties.isKeyExisting(name)) + { + selfclose = true; + softSelfClose = true; + } + } + + pright++; + if(!selfclose) + { + cursor = e; + } + else + { + if(!softSelfClose) pright++; + } + + offset = pright; + + if((name == "script") && !selfclose) + { + state = insideScript; + } + else + { + state = blank; + } + + return(true); + + } + else + if(state == insideTagClose) + { + offset += StringLen(TAG_CLOSE_START); + p = StringFind(html, ">", offset); + if(p == -1) + { + return(false); + } + + string tag = StringSubstr(html, offset, p - offset); + StringToLower(tag); + + DomElement *rewind = cursor; + + while(StringCompare(cursor.getName(), tag) != 0) + { + string previous = cursor.getName(); + cursor = cursor.getParent(); + if(cursor == NULL) + { + // orphan closing tag + cursor = rewind; + state = blank; + offset = p + 1; + return(true); + } + + if((isDebug() & LOG_MASK_DOM_WARNING) != 0) + { + Print("Misplaced /", tag, ">. Go from ", previous, " up to ", cursor.getName(), " ", cursor.getLevel()); + } + } + + cursor = cursor.getParent(); + if(cursor == NULL) return(false); + + state = blank; + offset = p + 1; + + return(true); + } + else + if(state == insideComment) + { + offset += StringLen(COMMENT_START); + p = StringFind(html, COMMENT_STOP, offset); + if(p == -1) + { + return(false); + } + + offset = p + StringLen(COMMENT_STOP); + state = blank; + + return(true); + } + else + if(state == insideScript) + { + p = StringFind(html, SCRIPT_STOP, offset); + if(p == -1) + { + return(false); + } + + offset = p + StringLen(SCRIPT_STOP); + state = blank; + + cursor = cursor.getParent(); + if(cursor == NULL) return(false); + + return(true); + } + return(false); + } + + bool IsString(const string &html, const string x, int subset = -1) + { + if(subset == -1) subset = offset; + return(StringSubstr(html, subset, StringLen(x)) == x); + } + + static void enableDebug(int level) + { + debugLevel = level; + } + + static int isDebug() + { + return debugLevel; + } + +}; + + +int HtmlParser::debugLevel = 0; + +class HTMLConverter +{ + protected: + static bool loadColumnConfig(string &columnSelectors[], string &dataSelectors[], string &headers[]) + { + Print("Reading column configuration ", ColumnSettingsFile); + int h = FileOpen(ColumnSettingsFile, FILE_READ|FILE_ANSI|FILE_TXT|FILE_SHARE_READ|FILE_SHARE_WRITE, '|', CP_UTF8); + if(h == -1) + { + Print("Error reading file '", ColumnSettingsFile, "': ", GetLastError()); + return false; + } + + int n = 0; + bool headerRead = false; + while(!FileIsEnding(h)) + { + string stParts[]; + string stLine = FileReadString(h); + int nParts = StringSplit(stLine, ',', stParts); + if(nParts == 3) + { + if(!headerRead) + { + headerRead = true; + continue; + } + ArrayResize(columnSelectors, n + 1); + ArrayResize(dataSelectors, n + 1); + ArrayResize(headers, n + 1); + headers[n] = stParts[0]; + columnSelectors[n] = stParts[1]; + dataSelectors[n] = stParts[2]; + // Print("Column ", (n + 1), ": '", stParts[0], "', selector: '", stParts[1], "', locator: '", stParts[2], "'"); + n++; + } + } + + FileClose(h); + return true; + } + + static bool loadSubstConfig(int &rulesColumns[], string &rulesHave[], string &rulesWant[]) + { + Print("Reading column configuration ", SubstitutionSettingsFile); + int h = FileOpen(SubstitutionSettingsFile, FILE_READ|FILE_ANSI|FILE_TXT|FILE_SHARE_READ|FILE_SHARE_WRITE, '|', CP_UTF8); + if(h == -1) + { + Print("Error reading file '", SubstitutionSettingsFile, "': ", GetLastError()); + return false; + } + + int n = 0; + bool headerRead = false; + while(!FileIsEnding(h)) + { + string stParts[]; + string stLine = FileReadString(h); + int nParts = StringSplit(stLine, ',', stParts); + if(nParts == 3) + { + if(!headerRead) + { + headerRead = true; + continue; + } + ArrayResize(rulesColumns, n + 1); + ArrayResize(rulesHave, n + 1); + ArrayResize(rulesWant, n + 1); + rulesColumns[n] = (int)StringToInteger(stParts[0]) - 1; + if(rulesColumns[n] < 0) + { + Print("Invalid column number ", rulesColumns[n], " changed to 0"); + rulesColumns[n] = 0; + } + rulesHave[n] = stParts[1]; + rulesWant[n] = stParts[2]; + // Print("Rule ", (n + 1), " for column ", (rulesColumns[n] + 1), ": find '", stParts[1], "', replace with: '", stParts[2], "'"); + n++; + } + } + + FileClose(h); + return true; + } + + public: + static IndexMap *convertReport2Map(const string URL, const bool columnNames = false) + { + if(URL == "") + { + Print("Parameter URL can not be empty"); + return NULL; + } + + if(RowSelector == "") + { + Print("Enter at least one of parameters: RowSelector, SaveName, TestQuery"); + return NULL; + } + + HtmlParser p; + + string xml; + + Print("Reading html-file ", URL); + int h = FileOpen(URL, FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_ANSI, 0, CP_UTF8); + if(h == INVALID_HANDLE) + { + Print("Error reading file '", URL, "': ", GetLastError()); + return NULL; + } + StringInit(xml, (int)FileSize(h)); + while(!FileIsEnding(h)) + { + xml += FileReadString(h) + "\n"; + } + // xml = FileReadString(h, (int)FileSize(h)); - has 4095 bytes limit in binary files! + FileClose(h); + + int logLevel = 0; + if(LogDomElements) logLevel |= LOG_MASK_DOM; + if(LogDomWarnings) logLevel |= LOG_MASK_DOM_WARNING; + if(LogSelectedElements) logLevel |= LOG_MASK_SELECTOR; + + if(logLevel != 0) + { + p.enableDebug(logLevel); + } + DomElement *document = p.parse(xml); + + Print("Row selector: '", RowSelector, "'"); + + + string columnSelectors[]; + string dataSelectors[]; + string headers[]; + + if(!loadColumnConfig(columnSelectors, dataSelectors, headers)) return NULL; + + IndexMap subst; + int rulesColumns[]; + string rulesHave[]; + string rulesWant[]; + + if(SubstitutionSettingsFile != "") + { + if(!loadSubstConfig(rulesColumns, rulesHave, rulesWant)) return NULL; + } + + for(int i = 0; i < ArraySize(rulesHave); i++) + { + string key = columnSelectors[rulesColumns[i]]; + + if(!subst.isKeyExisting(key)) + { + subst.add(key, new IndexMap("id" + IntegerToString(i))); + } + + IndexMap *hm = (IndexMap *)subst[key]; + + hm.setValue(rulesHave[i], rulesWant[i]); + } + + IndexMap *data = document.tableSelect(RowSelector, headers, columnSelectors, dataSelectors, &subst, !columnNames); + + return data; + } +}; diff --git a/test/Marketeer/empty_strings.h b/test/Marketeer/empty_strings.h new file mode 100644 index 0000000..9f6fcf9 --- /dev/null +++ b/test/Marketeer/empty_strings.h @@ -0,0 +1,20 @@ +// header +"isindex", +"base", +"meta", +"link", +"nextid", +"range", +// elsewhere +"img", +"br", +"hr", +"frame", +"wbr", +"basefont", +"spacer", +"area", +"param", +"keygen", +"col", +"limittext" \ No newline at end of file diff --git a/test/OLAP/CSVcube.mqh b/test/OLAP/CSVcube.mqh new file mode 100644 index 0000000..72b8454 --- /dev/null +++ b/test/OLAP/CSVcube.mqh @@ -0,0 +1,151 @@ +//+------------------------------------------------------------------+ +//| CSVcube.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//| Online Analytical Processing of trading hypercubes | +//| https://www.mql5.com/ru/articles/6602 | +//| https://www.mql5.com/ru/articles/6603 | +//+------------------------------------------------------------------+ + +#include +#include + +template +class CSVTradeRecord: public T // TradeRecord +{ + public: + CSVTradeRecord(const double balance, const string symbol, const IndexMap *row) + { + const int add = row.getSize() == 13 ? 2 : 0; + set(FIELD_NUMBER, counter); + set(FIELD_TICKET, counter++); + set(FIELD_SYMBOL, symbols.add(symbol)); + string t = row[CSV_COLUMN_TYPE].get(); + StringToLower(t); + const int _type = t == "buy" ? +1 : (t == "sell" ? -1 : 0); + set(FIELD_TYPE, _type == +1 ? OP_BUY : (_type == -1 ? OP_SELL : OP_BALANCE)); + datetime time1 = StringToTime(row[CSV_COLUMN_TIME1].get()) + TimeShift; + datetime time2 = StringToTime(row[CSV_COLUMN_TIME2 + add].get()) + TimeShift; + set(FIELD_DATETIME1, time1); + set(FIELD_DATETIME2, time2); + set(FIELD_DURATION, time2 - time1); + double price1 = StringToDouble(row[CSV_COLUMN_PRICE1].get()); + double price2 = StringToDouble(row[CSV_COLUMN_PRICE2 + add].get()); + set(FIELD_PRICE1, price1); + set(FIELD_PRICE2, price2); + set(FIELD_MAGIC, 0); + magics.add(0); + set(FIELD_LOT, StringToDouble(row[CSV_COLUMN_VOLUME].get())); + t = row[CSV_COLUMN_PROFIT + add].get(); + StringReplace(t, " ", ""); + const double profit = StringToDouble(t); + set(FIELD_PROFIT_AMOUNT, profit); + set(FIELD_PROFIT_PERCENT, (profit / balance)); + set(FIELD_PROFIT_POINT, (_type * (price2 - price1) / SymbolInfoDouble(symbol, SYMBOL_POINT))); + set(FIELD_COMMISSION, StringToDouble(row[CSV_COLUMN_COMMISSION + add].get())); + set(FIELD_SWAP, StringToDouble(row[CSV_COLUMN_SWAP + add].get())); + + fillCustomFields(); + } + +}; + +template +class CSVReportAdapter: public DataAdapter +{ + private: + RubbArray *> trades; + + int cursor; + int size; + double balance; + IndexMap *data; + + void reset() + { + size = 0; + cursor = 0; + balance = 0; + if(CheckPointer(data) == POINTER_DYNAMIC) delete data; + } + + public: + CSVReportAdapter() + { + reset(); + TradeRecord::reset(); + } + + ~CSVReportAdapter() + { + if(CheckPointer(data) == POINTER_DYNAMIC) delete data; + } + + bool load(const string file) + { + reset(); + data = CSVConverter::ReadCSV(file); + if(data != NULL) + { + size = generate(); + Print(data.getSize(), " records transferred to ", size, " trades"); + } + return data != NULL; + } + + virtual int reservedSize() override + { + return size; + } + + virtual Record *getNext() override + { + if(cursor < size) + { + return trades[cursor++]; + } + return NULL; + } + + protected: + int generate() + { + int count = 0; + balance = 0; + for(int i = data.getSize() - 1; i >= 0; --i) // csv-files have reverse chronological order + { + IndexMap *row = data[i]; + const int add = row.getSize() == 13 ? 2 : 0; + string s = row[CSV_COLUMN_SYMBOL].get(); + StringTrimLeft(s); + if(StringLen(s) > 0) + { + if(balance == 0) + { + Print("Zero balance, 10000 emulated"); + balance = 10000; + } + + string real = TradeRecord::realsymbol(s); + if(real == NULL) continue; + + trades << new CSVTradeRecord(balance, real, row); + ++count; + } + else + { + string type = row[CSV_COLUMN_TYPE].get(); + StringToLower(type); + if(type == "balance") + { + string t = row[CSV_COLUMN_PROFIT + add].get(); + StringReplace(t, " ", ""); + balance += StringToDouble(t); + } + } + + } + + return count; + } +}; diff --git a/test/OLAP/HTMLcube.mqh b/test/OLAP/HTMLcube.mqh new file mode 100644 index 0000000..3fa3a76 --- /dev/null +++ b/test/OLAP/HTMLcube.mqh @@ -0,0 +1,350 @@ +//+------------------------------------------------------------------+ +//| HTMLcube.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//| Online Analytical Processing of trading hypercubes | +//| https://www.mql5.com/ru/articles/6602 | +//| https://www.mql5.com/ru/articles/6603 | +//+------------------------------------------------------------------+ + +#include + +input GroupSettings Common_Settings; // G E N E R A L S E T T I N G S + +input string ReportFile = ""; // · ReportFile +input string Prefix = ""; // · Prefix +input string Suffix = ""; // · Suffix +input int TimeShift = 0; // · TimeShift + + +#include +#include +#include + + +template +class HTMLTradeRecord: public T // TradeRecord +{ + public: + HTMLTradeRecord( + const double balance, + const long ticket, + const string symbol, + const int type, + const datetime time1, + const datetime time2, + const double price1, + const double price2, + const double lot, + const double profit, + const double commission, + const double swap) + { + set(FIELD_NUMBER, counter++); + set(FIELD_TICKET, ticket); + set(FIELD_SYMBOL, symbols.add(symbol)); + set(FIELD_TYPE, type); + set(FIELD_DATETIME1, time1); + set(FIELD_DATETIME2, time2); + set(FIELD_DURATION, time2 - time1); + set(FIELD_PRICE1, (float)price1); + set(FIELD_PRICE2, (float)price2); + set(FIELD_MAGIC, 0); + magics.add(0); + set(FIELD_LOT, (float)lot); + set(FIELD_PROFIT_AMOUNT, (float)profit); + set(FIELD_PROFIT_PERCENT, (float)(profit / balance)); + set(FIELD_PROFIT_POINT, (float)((type == OP_BUY ? +1 : -1) * (price2 - price1) / SymbolInfoDouble(symbol, SYMBOL_POINT))); + set(FIELD_COMMISSION, (float)commission); + set(FIELD_SWAP, (float)swap); + + fillCustomFields(); // calls implementation from T + } + +}; + +template +class HTMLReportAdapter: public DataAdapter +{ + private: + + class Deal // if MQL5 could respect private access specifier for classes, + { // Trades will be unreachable from outer world, so it would be fine to have + public: // fields made public for direct access from Processor only + datetime time; + double price; + int type; // +1 - buy, -1 - sell + int direction; // +1 - in, -1 - out, 0 - in/out + double volume; + double profit; + long deal; + long order; + string comment; + string symbol; + double commission; + double swap; + + public: + Deal(const IndexMap *row) // this is MT5 deal + { + time = StringToTime(row[COLUMN_TIME].get()) + TimeShift; + price = StringToDouble(row[COLUMN_PRICE].get()); + string t = row[COLUMN_TYPE].get(); + type = t == "buy" ? +1 : (t == "sell" ? -1 : 0); + t = row[COLUMN_DIRECTION].get(); + direction = 0; + if(StringFind(t, "in") > -1) ++direction; + if(StringFind(t, "out") > -1) --direction; + volume = StringToDouble(row[COLUMN_VOLUME].get()); + t = row[COLUMN_PROFIT].get(); + StringReplace(t, " ", ""); + profit = StringToDouble(t); + deal = StringToInteger(row[COLUMN_DEAL].get()); + order = StringToInteger(row[COLUMN_ORDER].get()); + comment = row[COLUMN_COMMENT].get(); + symbol = row[COLUMN_SYMBOL].get(); + commission = StringToDouble(row[COLUMN_COMISSION].get()); + swap = StringToDouble(row[COLUMN_SWAP].get()); + } + + bool isIn() const + { + return direction >= 0; + } + + bool isOut() const + { + return direction <= 0; + } + + bool isOpposite(const Deal *t) const + { + return type * t.type < 0; + } + + bool isActive() const + { + return volume > 0; + } + + int op_type() const + { + if(type == +1) return OP_BUY; + else if(type == -1) return OP_SELL; + return OP_BALANCE; + } + }; + + RubbArray array; + RubbArray queue; + + + int size; + int cursor; + double balance; + IndexMap *data; + + RubbArray *> trades; + + + protected: + int generate() + { + array.clear(); + balance = 0; + for(int i = 0; i < data.getSize(); ++i) + { + IndexMap *row = data[i]; + if(CheckPointer(row) == POINTER_INVALID || row.getSize() != COLUMNS_COUNT) return 0; // something is broken + string s = row[COLUMN_SYMBOL].get(); + StringTrimLeft(s); + if(StringLen(s) > 0) + { + array << new Deal(row); + } + else if(row[COLUMN_TYPE].get() == "balance") + { + string t = row[COLUMN_PROFIT].get(); + StringReplace(t, " ", ""); + balance += StringToDouble(t); + } + } + + if(balance == 0) balance = 10000; // default, if missing + + int count = 0; + // abstract: + // if direction <= 0 + // collect all Trades from the queue which have direction >= 0 and opposite type + // if this volume is greater than collected volumes + // reduce volume in this Deal by the total volume of collected Trades + // else if collected volumes are greater than this volume + // reduce volume in matched Trades in a loop until all volume of this Deal is exhausted + // create object-lines from all affected Trades to this Deal + // 'delete' all affected Trades with zero volume from queue + // if volume == 0, 'delete' this Deal (disactivate) + // if direction >= 0 push the new Deal object to the queue + + for(int i = 0; i < array.size(); ++i) + { + Deal *current = array[i]; + + if(!current.isActive()) continue; + + string real = TradeRecord::realsymbol(current.symbol); + if(real == NULL) continue; + + if(current.isOut()) + { + // first try to find exact match + for(int j = 0; j < queue.size(); ++j) + { + if(queue[j].isIn() && queue[j].isOpposite(current) && queue[j].volume == current.volume && queue[j].symbol == current.symbol) + { + trades << new HTMLTradeRecord( + balance, + queue[j].deal, + real, // current.symbol, + queue[j].op_type(), + queue[j].time, + current.time, + queue[j].price, + current.price, + current.volume, + current.profit, + queue[j].commission + current.commission, + current.swap); + balance += current.profit; + + current.volume = 0; + queue >> j; // remove from queue + ++count; + break; + } + } + + if(!current.isActive()) continue; + + // second try to perform partial close + for(int j = 0; j < queue.size(); ++j) + { + if(queue[j].isIn() && queue[j].isOpposite(current) && queue[j].symbol == current.symbol) + { + if(current.volume >= queue[j].volume) + { + double fraction = queue[j].volume / current.volume; + + trades << new HTMLTradeRecord( + balance, + queue[j].deal, + real, // current.symbol, + queue[j].op_type(), + queue[j].time, + current.time, + queue[j].price, + current.price, + queue[j].volume, + current.profit * fraction, + queue[j].commission + current.commission * fraction, + current.swap * fraction); + balance += current.profit * fraction; + + current.volume -= queue[j].volume; + queue[j].volume = 0; + ++count; + } + else + { + double fraction = current.volume / queue[j].volume; + + trades << new HTMLTradeRecord( + balance, + queue[j].deal, + real, // current.symbol, + queue[j].op_type(), + queue[j].time, + current.time, + queue[j].price, + current.price, + current.volume, + queue[j].profit * fraction, // should be 0 + queue[j].commission * fraction + current.commission, + current.swap); + balance += queue[j].profit * fraction; + + queue[j].volume -= current.volume; + current.volume = 0; + ++count; + break; + } + } + } + + // purge all inactive from queue + for(int j = queue.size() - 1; j >= 0; --j) + { + if(!queue[j].isActive()) + { + queue >> j; + } + } + } + + if(current.isActive()) // is _still_ active + { + if(current.isIn()) + { + queue << current; + } + } + } + + return count; + } + + void reset() + { + cursor = 0; + balance = 0; + if(CheckPointer(data) == POINTER_DYNAMIC) delete data; + } + + public: + HTMLReportAdapter() + { + reset(); + TradeRecord::reset(); + } + + ~HTMLReportAdapter() + { + if(CheckPointer(data) == POINTER_DYNAMIC) delete data; + ((BaseArray *)&queue).clear(); + } + + bool load(const string file) + { + reset(); + data = HTMLConverter::convertReport2Map(file, true); + if(data != NULL) + { + size = generate(); + Print(data.getSize(), " deals transferred to ", size, " trades"); + } + return data != NULL; + } + + virtual int reservedSize() override + { + return size; + } + + virtual Record *getNext() override + { + if(cursor < size) + { + return trades[cursor++]; + } + return NULL; + } +}; diff --git a/test/OLAP/OLAPcore.mqh b/test/OLAP/OLAPcore.mqh new file mode 100644 index 0000000..fff3afe --- /dev/null +++ b/test/OLAP/OLAPcore.mqh @@ -0,0 +1,241 @@ +//+------------------------------------------------------------------+ +//| OLAPcore.mqh | +//| Copyright © 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//| Online Analytical Processing of trading hypercubes | +//| https://www.mql5.com/ru/articles/6602 | +//| https://www.mql5.com/ru/articles/6603 | +//+------------------------------------------------------------------+ + +#include +#include +#include + + +class DaysRangeSelector: public DateTimeSelector +{ + protected: + int granulatity; + + public: + DaysRangeSelector(const int n): DateTimeSelector(FIELD_DURATION, 7), granulatity(n) + { + _typename = typename(this); + } + + virtual int getRange() const + { + return granulatity; + } + + virtual bool select(const Record *r, int &index) const + { + double d = r.get(selector); + int days = (int)(d / (60 * 60 * 24)); + index = MathMin(days, granulatity - 1); + return true; + } + + virtual string getLabel(const int index) const + { + return index < granulatity - 1 ? ((index < 10 ? " ": "") + (string)index + "D") : ((string)index + "D+"); + } +}; + + +class OLAPWrapper +{ + protected: + Selector *createSelector(const SELECTORS selector, const TRADE_RECORD_FIELDS field) + { + switch(selector) + { + case SELECTOR_TYPE: + return new TypeSelector(); + case SELECTOR_SYMBOL: + return new SymbolSelector(); + case SELECTOR_SERIAL: + return new SerialNumberSelector(); + case SELECTOR_MAGIC: + return new MagicSelector(); + case SELECTOR_PROFITABLE: + return new ProfitableSelector(); + case SELECTOR_DURATION: + return new DaysRangeSelector(15); // up to 14 days + case SELECTOR_WEEKDAY: + return field != FIELD_NONE ? new WeekDaySelector(field) : NULL; + case SELECTOR_DAYHOUR: + return field != FIELD_NONE ? new DayHourSelector(field) : NULL; + case SELECTOR_HOURMINUTE: + return field != FIELD_NONE ? new DayHourSelector(field) : NULL; + case SELECTOR_SCALAR: + return field != FIELD_NONE ? new TradeSelector(field) : NULL; + case SELECTOR_QUANTS: + return field != FIELD_NONE ? new QuantizationSelector(field) : NULL; + } + return NULL; + } + + public: + void process( + const SELECTORS &selectorArray[], const TRADE_RECORD_FIELDS &selectorField[], + const AGGREGATORS AggregatorType, const TRADE_RECORD_FIELDS AggregatorField, Display &display, + const SORT_BY SortBy = SORT_BY_NONE, + const double Filter1value1 = 0, const double Filter1value2 = 0) + { + int selectorCount = 0; + for(int i = 0; i < MathMin(ArraySize(selectorArray), 3); i++) + { + selectorCount += selectorArray[i] != SELECTOR_NONE; + } + + if(selectorCount == 0) + { + Alert("No selectors. Setup at least one of them."); + return; + } + + // filter section not used yet >>> + SELECTORS Filter1 = SELECTOR_NONE; + TRADE_RECORD_FIELDS Filter1Field = FIELD_NONE; + + if(ArraySize(selectorArray) > 3) + { + Filter1 = selectorArray[3]; + } + + if(ArraySize(selectorField) > 3) + { + Filter1Field = selectorField[3]; + } + // <<< filter section not used + + HistoryDataAdapter history; + HTMLReportAdapter report; + CSVReportAdapter external; + + DataAdapter *adapter = &history; + + if(ReportFile != "") + { + if(StringFind(ReportFile, ".htm") > 0 && report.load(ReportFile)) + { + adapter = &report; + } + else + if(StringFind(ReportFile, ".csv") > 0 && external.load(ReportFile)) + { + adapter = &external; + } + else + { + Alert("Unknown file format: ", ReportFile); + return; + } + } + else + { + Print("Analyzing account history"); + } + + Analyst *analyst; + + Selector *selectors[]; + ArrayResize(selectors, selectorCount); + + for(int i = 0; i < selectorCount; i++) + { + selectors[i] = createSelector(selectorArray[i], selectorField[i]); + if(selectors[i] == NULL) + { + Print("Selector ", i, " is empty. Setup selectors successively (don't leave a hole in-between), specify a field when required"); + return; + } + } + + // filter section not used yet >>> + Filter *filters[]; + if(Filter1 != SELECTOR_NONE) + { + ArrayResize(filters, 1); + Selector *filterSelector = createSelector(Filter1, Filter1Field); + if(Filter1value1 != Filter1value2) + { + filters[0] = new FilterRange(filterSelector, Filter1value1, Filter1value2); + } + else + { + filters[0] = new Filter(filterSelector, Filter1value1); + } + } + // <<< filter section not used + + Aggregator *aggregator; + + // MQL does not support a 'class info' metaclass. + // Otherwise we could use an array of classes instead of the switch + switch(AggregatorType) + { + case AGGREGATOR_SUM: + aggregator = new SumAggregator(AggregatorField, selectors, filters); + break; + case AGGREGATOR_AVERAGE: + aggregator = new AverageAggregator(AggregatorField, selectors, filters); + break; + case AGGREGATOR_MAX: + aggregator = new MaxAggregator(AggregatorField, selectors, filters); + break; + case AGGREGATOR_MIN: + aggregator = new MinAggregator(AggregatorField, selectors, filters); + break; + case AGGREGATOR_COUNT: + aggregator = new CountAggregator(AggregatorField, selectors, filters); + break; + case AGGREGATOR_PROFITFACTOR: + aggregator = new ProfitFactorAggregator(AggregatorField, selectors, filters); + break; + case AGGREGATOR_PROGRESSIVE: + aggregator = new ProgressiveTotalAggregator(AggregatorField, selectors, filters); + break; + case AGGREGATOR_IDENTITY: + aggregator = new IdentityAggregator(AggregatorField, selectors, filters); + break; + } + + analyst = new Analyst(adapter, aggregator, display); + + analyst.acquireData(); + + Print("Symbol number: ", TradeRecord::getSymbolCount()); + for(int i = 0; i < TradeRecord::getSymbolCount(); i++) + { + Print(i, "] ", TradeRecord::getSymbol(i)); + } + + Print("Magic number: ", TradeRecord::getMagicCount()); + for(int i = 0; i < TradeRecord::getMagicCount(); i++) + { + Print(i, "] ", TradeRecord::getMagic(i)); + } + + Print("Filters: ", aggregator.getFilterTitles()); + + Print("Selectors: ", selectorCount); + + analyst.build(); + analyst.display(SortBy, AggregatorType == AGGREGATOR_IDENTITY); + + delete analyst; + delete aggregator; + for(int i = 0; i < selectorCount; i++) + { + delete selectors[i]; + } + for(int i = 0; i < ArraySize(filters); i++) + { + delete filters[i].getSelector(); + delete filters[i]; + } + } + +}; diff --git a/test/OLAP/OLAPcube.mqh b/test/OLAP/OLAPcube.mqh new file mode 100644 index 0000000..7b4b5ec --- /dev/null +++ b/test/OLAP/OLAPcube.mqh @@ -0,0 +1,1698 @@ +//+------------------------------------------------------------------+ +//| OLAPcube.mqh | +//| Copyright © 2016-2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//| Online Analytical Processing of trading hypercubes | +//| https://www.mql5.com/ru/articles/6602 | +//| https://www.mql5.com/ru/articles/6603 | +//+------------------------------------------------------------------+ + +#define MT4ORDERS_FASTHISTORY_OFF +#include + +#include +#include +#include +#include + +#ifndef OP_BALANCE +#define OP_BALANCE 6 +#endif + +enum SELECTORS +{ + SELECTOR_NONE, // none + SELECTOR_TYPE, // type + SELECTOR_SYMBOL, // symbol + SELECTOR_SERIAL, // ordinal + SELECTOR_MAGIC, // magic + SELECTOR_PROFITABLE, // profitable + /* custom selector (see demo) */ + SELECTOR_DURATION, // duration in days + /* all the next require a field as parameter */ + SELECTOR_WEEKDAY, // day-of-week(datetime field) + SELECTOR_DAYHOUR, // hour-of-day(datetime field) + SELECTOR_HOURMINUTE, // minute-of-hour(datetime field) + SELECTOR_SCALAR, // scalar(field) + SELECTOR_QUANTS // quants(field) +}; + +enum AGGREGATORS +{ + AGGREGATOR_SUM, // SUM + AGGREGATOR_AVERAGE, // AVERAGE + AGGREGATOR_MAX, // MAX + AGGREGATOR_MIN, // MIN + AGGREGATOR_COUNT, // COUNT + AGGREGATOR_PROFITFACTOR, // PROFIT FACTOR + AGGREGATOR_PROGRESSIVE, // PROGRESSIVE TOTAL + AGGREGATOR_IDENTITY // IDENTITY +}; + +enum DATA_TYPES +{ + DATA_TYPE_NONE, + DATA_TYPE_NUMBER = 'd', + DATA_TYPE_INTEGER = 'i', + DATA_TYPE_TIME = 't', + DATA_TYPE_STRING = 's' +}; + +class Record +{ + private: + double data[]; + + public: + Record(const int length) + { + ArrayResize(data, length); + ArrayInitialize(data, 0); + } + + void set(const int index, double value) + { + data[index] = value; + } + + double get(const int index) const + { + return data[index]; + } + + virtual string legend(const int index) const + { + return NULL; + } + + virtual char datatype(const int index) const + { + return 0; + } + + virtual void fillCustomFields() {/* does nothing */}; +}; + +// single pass data reader +class DataAdapter +{ + public: + virtual Record *getNext() = 0; + virtual int reservedSize() = 0; +}; + +template +int EnumToArray(E dummy, int &values[], const int start = INT_MIN, const int stop = INT_MAX) +{ + string t = typename(E) + "::"; + int length = StringLen(t); + + ArrayResize(values, 0); + int count = 0; + + for(int i = start; i < stop && !IsStopped(); i++) + { + E e = (E)i; + if(StringCompare(StringSubstr(EnumToString(e), 0, length), t) != 0) + { + ArrayResize(values, count + 1); + values[count++] = i; + } + } + return count; +} + +// MT4 and MT5 hedge +enum TRADE_RECORD_FIELDS +{ + FIELD_NONE, // none + FIELD_NUMBER, // serial number + FIELD_TICKET, // ticket + FIELD_SYMBOL, // symbol + FIELD_TYPE, // type (OP_BUY/OP_SELL) + FIELD_DATETIME1, // open datetime + FIELD_DATETIME2, // close datetime + FIELD_DURATION, // duration + FIELD_PRICE1, // open price + FIELD_PRICE2, // close price + FIELD_MAGIC, // magic number + FIELD_LOT, // lot + FIELD_PROFIT_AMOUNT, // profit amount + FIELD_PROFIT_PERCENT,// profit percent + FIELD_PROFIT_POINT, // profit points + FIELD_COMMISSION, // commission + FIELD_SWAP, // swap + FIELD_CUSTOM1, // custom 1 + FIELD_CUSTOM2 // custom 2 +}; + + +template +class Selector +{ + protected: + E selector; + string _typename; + + public: + Selector(const E field): selector(field) + { + _typename = typename(this); + } + + virtual void prepare(const Record *r) + { + // no action by default + } + + // returns index of cell to store values from the record + virtual bool select(const Record *r, int &index) const = 0; + + virtual int getRange() const = 0; + virtual double getMin() const = 0; + virtual double getMax() const = 0; + + virtual E getField() const + { + return selector; + } + + virtual string getLabel(const int index) const = 0; + + virtual string getTitle() const + { + return _typename + "(" + EnumToString(selector) + ")"; + } +}; + + +class TradeSelector: public Selector +{ + public: + TradeSelector(const TRADE_RECORD_FIELDS field): Selector(field) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const override + { + index = 0; + return true; + } + + virtual int getRange() const override + { + return 1; // this is a scalar by default, returns 1 value + } + + virtual double getMin() const override + { + return 0; + } + + virtual double getMax() const override + { + return (double)(getRange() - 1); + } + + virtual string getLabel(const int index) const override + { + return EnumToString(selector) + "[" + (string)index + "]"; // "scalar" + } + /* + virtual string format(const double value) const override + { + if(selector == FIELD_DATETIME1 || selector == FIELD_DATETIME2) + { + return TimeToString(value); + } + } + */ +}; + +class TypeSelector: public TradeSelector +{ + public: + TypeSelector(): TradeSelector(FIELD_TYPE) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const + { + index = (int)r.get(selector); + return index >= getMin() && index <= getMax(); + } + + virtual int getRange() const + { + return 2; // OP_BUY, OP_SELL + } + + virtual double getMin() const + { + return OP_BUY; + } + + virtual double getMax() const + { + return OP_SELL; + } + + virtual string getLabel(const int index) const + { + const static string types[2] = {"buy", "sell"}; + return types[index]; + } +}; + +template +class DateTimeSelector: public TradeSelector +{ + protected: + int granularity; + + public: + DateTimeSelector(const E field, const int size): TradeSelector(field), granularity(size) + { + _typename = typename(this); + } + + virtual int getRange() const + { + return granularity; + } +}; + + +class WeekDaySelector: public DateTimeSelector +{ + public: + WeekDaySelector(const TRADE_RECORD_FIELDS f): DateTimeSelector(f, 7) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const + { + double d = r.get(selector); + datetime t = (datetime)d; + index = TimeDayOfWeek(t); + return true; + } + + virtual string getLabel(const int index) const + { + static string days[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + return days[index]; + } +}; + +class DayHourSelector: public DateTimeSelector +{ + public: + DayHourSelector(const TRADE_RECORD_FIELDS f): DateTimeSelector(f, 24) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const + { + double d = r.get(selector); + datetime t = (datetime)d; + index = TimeHour(t); + return true; + } + + virtual string getLabel(const int index) const + { + return (string)index; + } +}; + +class HourMinuteSelector: public DateTimeSelector +{ + public: + HourMinuteSelector(const TRADE_RECORD_FIELDS f): DateTimeSelector(f, 60) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const + { + double d = r.get(selector); + datetime t = (datetime)d; + index = TimeMinute(t); + return true; + } + + virtual string getLabel(const int index) const + { + return (string)index; + } +}; + + +class SymbolSelector: public TradeSelector +{ + public: + SymbolSelector(): TradeSelector(FIELD_SYMBOL) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const override + { + index = (int)r.get(selector); // symbols are stored as indices in vocabulary + return (index >= 0); + } + + virtual int getRange() const override + { + return TradeRecord::getSymbolCount(); + } + + virtual string getLabel(const int index) const override + { + return TradeRecord::getSymbol(index); + } +}; + +template +class Vocabulary +{ + protected: + T index[]; + + public: + int get(const T &text) const + { + int n = ArraySize(index); + for(int i = 0; i < n; i++) + { + if(index[i] == text) return i; + } + return -(n + 1); + } + + int add(const T text) + { + int n = get(text); + if(n < 0) + { + n = -n; + ArrayResize(index, n); + index[n - 1] = text; + return n - 1; + } + return n; + } + + int size() const + { + return ArraySize(index); + } + + T operator[](const int position) const + { + return index[position]; + } + + void clear() + { + ArrayResize(index, 0); + } + +}; + +class QuantizationSelector: public TradeSelector +{ + protected: + Vocabulary quants; + + public: + QuantizationSelector(const TRADE_RECORD_FIELDS field): TradeSelector(field) + { + _typename = typename(this); + } + + virtual void prepare(const Record *r) override + { + double value = r.get(selector); + quants.add(value); + } + + virtual bool select(const Record *r, int &index) const override + { + double value = r.get(selector); + index = quants.get(value); + return (index >= 0); + } + + virtual int getRange() const override + { + return quants.size(); + } + + virtual string getLabel(const int index) const override + { + return (string)(float)quants[index]; + } +}; + + +class SerialNumberSelector: public TradeSelector +{ + public: + SerialNumberSelector(): TradeSelector(FIELD_NUMBER) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const override + { + index = (int)r.get(selector); + return true; + } + + virtual int getRange() const override + { + return TradeRecord::getRecordCount(); + } + + virtual string getLabel(const int index) const override + { + return (string)(index); + } +}; + +class MagicSelector: public TradeSelector +{ + public: + MagicSelector(): TradeSelector(FIELD_MAGIC) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const override + { + index = TradeRecord::getMagicIndex((int)r.get(selector)); + return true; + } + + virtual int getRange() const override + { + return TradeRecord::getMagicCount(); + } +}; + +class ProfitableSelector: public TradeSelector +{ + public: + ProfitableSelector(): TradeSelector(FIELD_PROFIT_AMOUNT) + { + _typename = typename(this); + } + + virtual bool select(const Record *r, int &index) const override + { + index = (r.get(selector) > 0) ? 1 : 0; + return true; + } + + virtual int getRange() const override + { + return 2; // 0(false) - loss, 1(true) - profit + } + + virtual string getLabel(const int index) const override + { + return index ? "profit" : "loss"; + } +}; + + + +template +class Filter +{ + protected: + Selector *selector; + double filter; + + public: + Filter(Selector &s, const double value): selector(&s), filter(value) + { + } + + virtual bool matches(const Record *r) const + { + int index; + if(selector.select(r, index)) + { + if(index == (int)filter) return true; + } + return false; + } + + Selector *getSelector() const + { + return selector; + } + + virtual string getTitle() const + { + return selector.getTitle() + "[" + (string)filter + "]"; + } +}; + +template +class FilterRange: public Filter +{ + protected: + double filterMax; + + public: + FilterRange(Selector &s, const double valueMin, const double valueMax): Filter(&s, valueMin), filterMax(valueMax) + { + } + + virtual bool matches(const Record *r) const override + { + int index; + if(selector.select(r, index)) + { + if(index >= filter && index <= filterMax) return true; + } + return false; + } + + virtual string getTitle() const override + { + return selector.getTitle() + "[" + (string)filter + ".." + (string)filterMax + "]"; + } +}; + +enum SORT_BY // applicable only for 1-dimensional cubes +{ + SORT_BY_NONE, // none + SORT_BY_VALUE_ASCENDING, // value (ascending) + SORT_BY_VALUE_DESCENDING, // value (descending) + SORT_BY_LABEL_ASCENDING, // label (ascending) + SORT_BY_LABEL_DESCENDING // label (descending) +}; + +#define SORT_ASCENDING(A) (((A) & 1) != 0) +#define SORT_VALUE(A) ((A) < 3) + + +class MetaCube +{ + protected: + int dimensions[]; + int offsets[]; + double totals[]; + string _typename; + + public: + int getDimension() const + { + return ArraySize(dimensions); + } + + int getDimensionRange(const int n) const + { + return dimensions[n]; + } + + int getCubeSize() const + { + return ArraySize(totals); + } + + virtual double getValue(const int &indices[]) const = 0; + virtual string getMetaCubeTitle() const = 0; + virtual string getDimensionTitle(const int d) const = 0; + virtual string getDimensionIndexLabel(const int d, const int index) const = 0; + virtual string getFilterTitles() const = 0; + virtual bool getVector(const int dimension, const int &consts[], PairArray *&result, const SORT_BY sortby = SORT_BY_NONE) const = 0; + //virtual string getDimensionFormat(const int d) const = 0; +}; + +template +class Aggregator: public MetaCube +{ + protected: + const E field; + const int selectorCount; + const Selector *selectors[]; + const int filterCount; + const Filter *filters[]; + + virtual int mixIndex(const int &k[]) const + { + int result = 0; + for(int i = 0; i < selectorCount; i++) + { + result += k[i] * offsets[i]; + } + return result; + } + + virtual bool decode(const int _input, int &k[]) const + { + int index = _input; + ArrayResize(k, selectorCount); + ArrayInitialize(k, 0); + for(int i = selectorCount - 1; i >= 0; i--) + { + k[i] = index / offsets[i]; + index -= k[i] * offsets[i]; + } + + if(index != 0) + { + Print("Bad index decode: ", _input); + ArrayPrint(k); + return false; + } + return true; + } + + virtual bool filter(const Record *data) const + { + int q = 0; + for(; q < filterCount; q++) + { + if(!filters[q].matches(data)) + { + break; + } + } + + if(q < filterCount) return false; + + return true; + } + + + public: + Aggregator(const E f, const Selector *&s[], const Filter *&t[]): field(f), selectorCount(ArraySize(s)), filterCount(ArraySize(t)) + { + ArrayResize(selectors, selectorCount); + for(int i = 0; i < selectorCount; i++) + { + selectors[i] = s[i]; + } + ArrayResize(filters, filterCount); + for(int i = 0; i < filterCount; i++) + { + filters[i] = t[i]; + } + _typename = typename(this); + } + + virtual void setSelectorBounds(const int length = 0) + { + ArrayResize(dimensions, selectorCount); + int total = 1; + for(int i = 0; i < selectorCount; i++) + { + dimensions[i] = selectors[i].getRange(); + total *= dimensions[i]; + } + ArrayResize(totals, total); + ArrayInitialize(totals, 0); + + ArrayResize(offsets, selectorCount); + offsets[0] = 1; + for(int i = 1; i < selectorCount; i++) + { + offsets[i] = dimensions[i - 1] * offsets[i - 1]; // 1, X, Y*X + } + } + + virtual void prepareSelectors(const Record *&data[]) + { + int n = ArraySize(data); + for(int i = 0; i < n; i++) + { + for(int j = 0; j < selectorCount; j++) + { + Selector *s = (Selector *)selectors[j]; + s.prepare(data[i]); + } + } + } + + // build an array with number of dimensions equal to number of selectors + virtual void calculate(const Record *&data[]) + { + int k[]; + ArrayResize(k, selectorCount); + int n = ArraySize(data); + for(int i = 0; i < n; i++) + { + if(!filter(data[i])) continue; + + int j = 0; + for(; j < selectorCount; j++) + { + int d; + if(!selectors[j].select(data[i], d)) // record successfully mapped to a cell of selector? + { + break; // skip it, if not + } + k[j] = d; // save index in j-th dimension in array + } + if(j == selectorCount) // all coordinates are resolved + { + update(mixIndex(k), data[i].get(field)); // apply maths/stats + } + } + } + + double getValue(const int &indices[]) const override + { + return totals[mixIndex(indices)]; + } + + virtual string getMetaCubeTitle() const override + { + return _typename + " " + EnumToString(field); + } + + virtual string getDimensionTitle(const int d) const override + { + if(d >= ArraySize(selectors)) return "n/a"; + return selectors[d].getTitle(); + } + + /* + virtual string getDimensionFormat(const int d) const override + { + if(d >= ArraySize(selectors)) return NULL; + return selectors[d].format(); // there's no such thing like datetime format + } + */ + + virtual string getDimensionIndexLabel(const int d, const int index) const override + { + if(d >= ArraySize(selectors)) return "n/a"; + return selectors[d].getLabel(index); + } + + virtual string getFilterTitles() const override + { + string titles = ""; + for(int i = 0; i < ArraySize(filters); i++) + { + titles += filters[i].getTitle() + ";"; + } + if(titles == "") titles = "no"; + return titles; + } + + virtual void update(const int index, const double value) = 0; + + virtual bool getVector(const int dimension, const int &consts[], PairArray *&result, const SORT_BY sortby = SORT_BY_NONE) const + { + const int n = getDimension(); + if(dimension >= n || n < 0) return false; + + result = new PairArray; + + int indices[]; + ArrayResize(indices, n); + + if(sortby != SORT_BY_NONE) + { + result.compareBy((SORT_ASCENDING(sortby) ? (Comparator *)(new Greater()) : (Comparator *)(new Lesser()))); + } + else + { + result.compareBy(NULL); + } + + int m = getDimensionRange(dimension); + result.allocate(m); + int count = 0; + for(int i = 0; i < m; i++) + { + ArrayCopy(indices, consts); + indices[dimension] = i; + + double v = getValue(indices); + if(SORT_VALUE(sortby)) + { + result.insert(count, v, getDimensionIndexLabel(dimension, i)); + } + else + { + result.insert(count, getDimensionIndexLabel(dimension, i), v); + } + count++; + } + + result.allocate(count); + + return true; + } + +}; + +template +class IdentityAggregator: public Aggregator +{ + private: + int size; + + protected: + virtual int mixIndex(const int &k[/*0 - record number, 1 - field number*/]) const override + { + int result = 0; + for(int i = 0; i < size; i++) + { + result += k[i] * offsets[i]; + } + return result; + } + + virtual bool decode(const int _input, int &k[]) const override + { + int index = _input; + ArrayResize(k, size); + ArrayInitialize(k, 0); + for(int i = 1; i >= 0; i--) + { + k[i] = index / offsets[i]; + index -= k[i] * offsets[i]; + } + + if(index != 0) + { + Print("Bad index decode: ", _input); + ArrayPrint(k); + return false; + } + return true; + } + + public: + IdentityAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void setSelectorBounds(const int length = 0) override + { + size = 1 + (selectorCount > 1); + ArrayResize(dimensions, size); + int total = length * selectorCount; + dimensions[0] = length; + if(selectorCount > 1) dimensions[1] = selectorCount; + ArrayResize(totals, total); + ArrayInitialize(totals, 0); + + ArrayResize(offsets, size); + offsets[0] = 1; + if(selectorCount > 1) + { + offsets[1] = dimensions[0]; + } + } + + virtual void calculate(const Record *&data[]) override + { + int k[]; + ArrayResize(k, size); + int n = ArraySize(data); + for(int i = 0; i < n; i++) + { + if(!filter(data[i])) continue; + + k[0] = i; + for(int j = 0; j < selectorCount; j++) + { + if(selectorCount > 1) k[1] = j; + update(mixIndex(k), data[i].get(selectors[j].getField())); + } + } + } + + virtual void update(const int index, const double value) override + { + totals[index] = value; + } + + virtual string getMetaCubeTitle() const override + { + return _typename + (field != FIELD_NONE ? " (field has no effect and ignored)" : ""); + } + + virtual string getDimensionTitle(const int d) const override + { + if(d < 0) + { + return Aggregator::getDimensionTitle(-d - 1); + } + else + if(d == 0) + { + if(selectorCount > 1) return "index"; + else return selectors[0].getTitle() + " by index"; + } + else + { + string titles = ""; + for(int i = 0; i < selectorCount; i++) + { + titles += (string)i + ":" + selectors[i].getTitle() + "; "; + } + return titles; + } + } + + virtual string getDimensionIndexLabel(const int d, const int index) const override + { + return "[" + (string)index + "]"; + } +}; + + +template +class SumAggregator: public Aggregator +{ + public: + SumAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void update(const int index, const double value) override + { + totals[index] += value; + } + +}; + + +template +class ProgressiveTotalAggregator: public Aggregator +{ + private: + double accumulators[]; + + public: + ProgressiveTotalAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void setSelectorBounds(const int length = 0) override + { + Aggregator::setSelectorBounds(); + int dim = 1; + for(int i = 1; i < selectorCount; i++) // except 1-st dimention, for which progressive total is calculated + { + dim *= dimensions[i]; + } + ArrayResize(accumulators, dim); + ArrayInitialize(accumulators, 0); + + Converter converter; + double nan = converter[0x7FF8000000000000]; // quiet NaN + ArrayInitialize(totals, nan); + } + + virtual void update(const int index, const double value) override + { + int subindex = 0; + if(selectorCount > 1) + { + int k[]; + decode(index, k); + // eliminate 1-st dimension, special case of index mix + for(int i = 1; i < selectorCount; i++) + { + subindex += k[i] * (offsets[i] / dimensions[0]); + } + } + + accumulators[subindex] += value; + totals[index] = accumulators[subindex]; + } +}; + +template +class AverageAggregator: public Aggregator +{ + protected: + int counters[]; + + public: + AverageAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void setSelectorBounds(const int length = 0) override + { + Aggregator::setSelectorBounds(); + ArrayResize(counters, ArraySize(totals)); + ArrayInitialize(counters, 0); + } + + virtual void update(const int index, const double value) override + { + totals[index] = (totals[index] * counters[index] + value) / (counters[index] + 1); + counters[index]++; + } +}; + +template +class ProfitFactorAggregator: public Aggregator +{ + protected: + double positives[]; + double negatives[]; + + public: + ProfitFactorAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void setSelectorBounds(const int length = 0) override + { + Aggregator::setSelectorBounds(); + ArrayResize(positives, ArraySize(totals)); + ArrayResize(negatives, ArraySize(totals)); + ArrayInitialize(positives, 0); + ArrayInitialize(negatives, 0); + } + + virtual void update(const int index, const double value) override + { + if(value >= 0) positives[index] += value; + else negatives[index] -= value; + + if(negatives[index] > 0) + { + totals[index] = positives[index] / negatives[index]; + } + else + { + Converter converter; + + totals[index] = converter[0x7FF0000000000000]; // infinity + } + } +}; + +template +class MaxAggregator: public Aggregator +{ + public: + MaxAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void update(const int index, const double value) override + { + totals[index] = MathMax(totals[index], value); + } +}; + +template +class MinAggregator: public Aggregator +{ + public: + MinAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void setSelectorBounds(const int length = 0) override + { + Aggregator::setSelectorBounds(); + ArrayInitialize(totals, DBL_MAX); + } + + virtual void update(const int index, const double value) override + { + totals[index] = MathMin(totals[index], value); + } +}; + +template +class CountAggregator: public Aggregator +{ + public: + CountAggregator(const E f, const Selector *&s[], const Filter *&t[]): Aggregator(f, s, t) + { + _typename = typename(this); + } + + virtual void update(const int index, const double value) override + { + totals[index]++; + } +}; + + +#define TRADE_RECORD_FIELDS_NUMBER_DEFAULT 19 +static TRADE_RECORD_FIELDS _f; +static int _dummy[]; +const static int TRADE_RECORD_FIELDS_NUMBER = EnumToArray(_f, _dummy, 0, 100); + +class TradeRecord: public Record +{ + protected: + static Vocabulary symbols; + static Vocabulary magics; + static int counter; + static IndexMap symbol2symbol; + static Vocabulary missing; + const static char datatypes[TRADE_RECORD_FIELDS_NUMBER_DEFAULT]; + + void fillByOrder(const double balance) + { + set(FIELD_NUMBER, counter++); + set(FIELD_TICKET, OrderTicket()); + set(FIELD_SYMBOL, symbols.add(OrderSymbol())); + set(FIELD_TYPE, OrderType()); + set(FIELD_DATETIME1, OrderOpenTime()); + set(FIELD_DATETIME2, OrderCloseTime()); + set(FIELD_DURATION, OrderCloseTime() - OrderOpenTime()); + set(FIELD_PRICE1, OrderOpenPrice()); + set(FIELD_PRICE2, OrderClosePrice()); + set(FIELD_MAGIC, OrderMagicNumber()); + magics.add(OrderMagicNumber()); + set(FIELD_LOT, OrderLots()); + set(FIELD_PROFIT_AMOUNT, OrderProfit()); + set(FIELD_PROFIT_PERCENT, (OrderProfit() / balance)); + set(FIELD_PROFIT_POINT, ((OrderType() == OP_BUY ? +1 : -1) * (OrderClosePrice() - OrderOpenPrice()) / SymbolInfoDouble(OrderSymbol(), SYMBOL_POINT))); + set(FIELD_COMMISSION, OrderCommission()); + set(FIELD_SWAP, OrderSwap()); + } + + private: + class _StaticCheck + { + private: + _StaticCheck() + { + // ASSERT that datatypes[] are provided for all TRADE_RECORD_FIELDS elements + if(TRADE_RECORD_FIELDS_NUMBER != TRADE_RECORD_FIELDS_NUMBER_DEFAULT) + { + Print("TRADE_RECORD_FIELDS cardinality mismatch: actual ", TRADE_RECORD_FIELDS_NUMBER, ", supposed ", TRADE_RECORD_FIELDS_NUMBER_DEFAULT); + Print("Execution stopped"); + ExpertRemove(); + } + } + + static void instantiate() + { + static _StaticCheck staticCheck; + } + }; + + public: + static string realsymbol(const string symbol) + { + string real; + double temp; + if(!SymbolInfoDouble(symbol, SYMBOL_BID, temp) && GetLastError() == ERR_MARKET_UNKNOWN_SYMBOL) + { + real = symbol2symbol.get(symbol); + if(real != NULL) return real; + + if(Suffix != "") + { + int pos = StringLen(symbol) - StringLen(Suffix); + if((pos > 0) && (StringFind(symbol, Suffix) == pos)) + { + real = StringSubstr(symbol, 0, pos); + if(SymbolInfoDouble(real, SYMBOL_BID, temp)) + { + symbol2symbol.setValue(symbol, real); + return real; + } + } + if(StringFind(symbol, Suffix) == -1) + { + real = symbol + Suffix; + if(SymbolInfoDouble(real, SYMBOL_BID, temp)) + { + symbol2symbol.setValue(symbol, real); + return real; + } + } + } + if(Prefix != "") + { + int diff = StringLen(symbol) - StringLen(Prefix); + if((diff > 0) && (StringFind(symbol, Prefix) == 0)) + { + real = StringSubstr(symbol, StringLen(Prefix)); + if(SymbolInfoDouble(real, SYMBOL_BID, temp)) + { + symbol2symbol.setValue(symbol, real); + return real; + } + } + if(StringFind(symbol, Prefix) == -1) + { + real = Prefix + symbol; + if(SymbolInfoDouble(real, SYMBOL_BID, temp)) + { + symbol2symbol.setValue(symbol, real); + return real; + } + } + } + int size = missing.size(); + if(missing.add(symbol) == size) + { + Print("Can't find correct symbol for ", symbol); + } + return NULL; + } + return symbol; + } + + public: + TradeRecord(): Record(TRADE_RECORD_FIELDS_NUMBER) + { + } + + TradeRecord(const double balance): Record(TRADE_RECORD_FIELDS_NUMBER) + { + fillByOrder(balance); + } + + static int getSymbolCount() + { + return symbols.size(); + } + + static string getSymbol(const int index) + { + if(index < 0 || index >= symbols.size()) return NULL; + return symbols[index]; + } + + static int getSymbolIndex(const string s) + { + return symbols.get(s); + } + + static int getMagicCount() + { + return magics.size(); + } + + static long getMagic(const int index) + { + return magics[index]; + } + + static int getMagicIndex(const long m) + { + return magics.get(m); + } + + static int getRecordCount() + { + return counter; + } + + static void reset() + { + symbols.clear(); + magics.clear(); + counter = 0; + } + + virtual string legend(const int index) const override + { + if(index >= 0 && index < TRADE_RECORD_FIELDS_NUMBER) + { + return EnumToString((TRADE_RECORD_FIELDS)index); + } + return "unknown"; + } + + virtual char datatype(const int index) const override + { + return datatypes[index]; + } +}; + +class CustomTradeRecord: public TradeRecord +{ + private: + // Start-up time announcement of custom fields specialization + class CustomFieldsDescription + { + private: + CustomFieldsDescription() + { + Print("Custom fields processing attached:"); + Print("FIELD_CUSTOM1 == Single Trade MFE (%)"); + Print("FIELD_CUSTOM2 == Single Trade MAE (%)"); + } + + public: + static CustomFieldsDescription *instantiate() + { + static CustomFieldsDescription instance; + return &instance; + } + }; + + public: + CustomTradeRecord(): TradeRecord() + { + } + CustomTradeRecord(const double balance): TradeRecord(balance) + { + } + + // calculate MFE/MAE in percents + virtual void fillCustomFields() override + { + static CustomFieldsDescription *legend = CustomFieldsDescription::instantiate(); + + double positiveExcursion = 0, negativeExcursion = 0; + string symbol = symbols[(int)get(FIELD_SYMBOL)]; + int t1 = iBarShift(symbol, _Period, (datetime)get(FIELD_DATETIME1), false); + int t2 = iBarShift(symbol, _Period, (datetime)get(FIELD_DATETIME2), false); + int type = (int)get(FIELD_TYPE); + double open = get(FIELD_PRICE1); + double close = get(FIELD_PRICE2); + + for(int t = t1; t >= t2; t--) + { + if(type == OP_BUY) + { + positiveExcursion = MathMax(positiveExcursion, MathMax((iHigh(symbol, _Period, t) - close), 0) / close); + negativeExcursion = MathMin(negativeExcursion, MathMin((iLow(symbol, _Period, t) - open), 0) / open); + } + else if(type == OP_SELL) + { + positiveExcursion = MathMax(positiveExcursion, MathMax((close - iLow(symbol, _Period, t)), 0) / close); + negativeExcursion = MathMin(negativeExcursion, MathMin((open - iHigh(symbol, _Period, t)), 0) / open); + } + } + set(FIELD_CUSTOM1, positiveExcursion * 100); + set(FIELD_CUSTOM2, negativeExcursion * 100); + } + + virtual string legend(const int index) const override + { + if(index == FIELD_CUSTOM1) return "MFE per trade(%)"; + else + if(index == FIELD_CUSTOM2) return "MAE per trade(%)"; + + return TradeRecord::legend(index); + } +}; + +static Vocabulary TradeRecord::symbols; +static Vocabulary TradeRecord::magics; +static int TradeRecord::counter = 0; +static IndexMap TradeRecord::symbol2symbol; +static Vocabulary TradeRecord::missing; + +const static char TradeRecord::datatypes[TRADE_RECORD_FIELDS_NUMBER_DEFAULT] = +{ + 0, // none + 'i', // serial number + 'i', // ticket + 's', // symbol + 'i', // type (OP_BUY/OP_SELL) + 't', // open datetime + 't', // close datetime + 'i', // duration (seconds) + 'd', // open price + 'd', // close price + 'i', // magic number + 'd', // lot + 'd', // profit amount + 'd', // profit percent + 'i', // profit points + 'd', // commission + 'd', // swap + 'd', // custom 1 + 'd' // custom 2 +}; + + +template +class HistoryTradeRecord: public T // CustomTradeRecord +{ + public: + HistoryTradeRecord() + { + fillCustomFields(); + } + HistoryTradeRecord(const double balance): T(balance) + { + fillCustomFields(); + } +}; + +template +class HistoryDataAdapter: public DataAdapter +{ + private: + int size; + int cursor; + double balance; + + protected: + void reset() + { + cursor = 0; + size = OrdersHistoryTotal(); + balance = 0; + } + + public: + HistoryDataAdapter() + { + reset(); + T::reset(); + } + + virtual int reservedSize() + { + return size; + } + + virtual Record *getNext() + { + if(cursor < size) + { + while(OrderSelect(cursor++, SELECT_BY_POS, MODE_HISTORY)) + { + if(OrderType() < 2 || OrderType() == OP_BALANCE) + { + if(SymbolInfoDouble(OrderSymbol(), SYMBOL_POINT) == 0) + { + Print("MarketInfo is missing:"); + OrderPrint(); + continue; + } + + balance += OrderProfit(); + if(OrderType() != OP_BALANCE) + { + return new HistoryTradeRecord(balance); + } + } + } + + return NULL; + } + return NULL; + } +}; + + +class Display +{ + public: + virtual void display(MetaCube *metaData, const SORT_BY sortby = SORT_BY_NONE, const bool identity = false) = 0; +}; + + +class LogDisplay: public Display +{ + private: + string format; + int digits; + + public: + LogDisplay(const int w, const int d) + { + digits = d; + format = StringFormat("%%%d.%df", w, d); + } + + + virtual void display(MetaCube *metaData, const SORT_BY sortby = SORT_BY_NONE, const bool identity = false) override + { + int n = metaData.getDimension(); + int indices[], cursors[]; + ArrayResize(indices, n); + ArrayResize(cursors, n); + ArrayInitialize(cursors, 0); + + Print(metaData.getMetaCubeTitle(), " [", metaData.getCubeSize(), "]"); + + bool sorting = n == 1 && sortby != SORT_BY_NONE; + + PairArray *flat = NULL; + + if(sorting) + { + flat = new PairArray(metaData.getCubeSize(), (SORT_ASCENDING(sortby) ? (Comparator *)(new Greater()) : (Comparator *)(new Lesser()))); + } + + for(int i = 0; i < n; i++) + { + indices[i] = metaData.getDimensionRange(i); + Print(CharToString((uchar)('X' + i)), ": ", metaData.getDimensionTitle(i), " [", indices[i], "]"); + } + + string labels[]; + ArrayResize(labels, n); + + bool looping = false; + int count = 0; + do + { + for(int j = 0; j < n; j++) + { + labels[j] = metaData.getDimensionIndexLabel(j, cursors[j]); + } + + if(sorting) + { + // sort single (first) dimension by sort_by + if(SORT_VALUE(sortby)) + { + flat.insert(count++, metaData.getValue(cursors), labels[0]); + } + else + { + flat.insert(count++, labels[0], metaData.getValue(cursors)); + } + } + else + { + arrayPrint(StringFormat(format, metaData.getValue(cursors)), labels); + } + + for(int i = 0; i < n; i++) + { + if(cursors[i] < indices[i] - 1) + { + looping = true; + cursors[i]++; + break; + } + else + { + cursors[i] = 0; + } + looping = false; + } + } + while(looping && !IsStopped()); + + if(sorting) + { + ArrayPrint(flat.array, digits); + delete flat; + } + } +}; + +template +bool Comparator::compare(const Pair &v1, const T v2) +{ + Greater *g = dynamic_cast(&this); + if(g != NULL) return g.compare(v1, v2); + Lesser *l = dynamic_cast(&this); + if(l != NULL) return l.compare(v1, v2); + return false; +} + + +template +void arrayPrint(const string title, const T &V[]) +{ + int n = ArraySize(V); + string s; + for(int i = 0; i < n; i++) + { + s = s + " " + (string)V[i]; + } + Print(title + ":" + s); +} + + +template +class Analyst +{ + private: + DataAdapter *adapter; + Record *data[]; + Aggregator *aggregator; + Display *output; + + public: + Analyst(DataAdapter &a, Aggregator &g, Display &d): adapter(&a), aggregator(&g), output(&d) + { + ArrayResize(data, adapter.reservedSize()); + } + + ~Analyst() + { + int n = ArraySize(data); + for(int i = 0; i < n; i++) + { + if(CheckPointer(data[i]) == POINTER_DYNAMIC) delete data[i]; + } + } + + void acquireData() + { + Record *record; + int i = 0; + while((record = adapter.getNext()) != NULL) + { + data[i++] = record; + } + ArrayResize(data, i); + + aggregator.prepareSelectors(data); + + aggregator.setSelectorBounds(i); + } + + void build() + { + aggregator.calculate(data); + } + + void display(const SORT_BY sortby = SORT_BY_NONE, const bool identity = false) + { + output.display(aggregator, sortby, identity); + } +}; diff --git a/test/OLAP/PairArray.mqh b/test/OLAP/PairArray.mqh new file mode 100644 index 0000000..3404907 --- /dev/null +++ b/test/OLAP/PairArray.mqh @@ -0,0 +1,148 @@ +//+------------------------------------------------------------------+ +//| PairArray.mqh | +//| Copyright © 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//+------------------------------------------------------------------+ + +class PairArray +{ + public: + // aux struct to populate temp array when sorting is enabled + struct Pair + { + double value; + string title; + Pair(): value(DBL_MAX), title(NULL) {} + Pair(const double v, const string s): value(v), title(s) {} + Pair(const string s, const double v): value(v), title(s) {} + bool operator>(const double v) const + { + return value > v; + } + bool operator>(const string s) const + { + return title > s; + } + }; + + // this is a common parent, so it can not be templatized + class Comparator + { + public: + // templatized method can not be virtual, + // so we do artificial dynamic dispatching manually + // (see below after declaration of descendant classes) + template + bool compare(const Pair &v1, const T v2); + }; + + class Greater: public Comparator + { + public: + template + bool compare(const Pair &v1, const T v2) + { + return v1 > v2; + } + }; + + class Lesser: public Comparator + { + public: + template + bool compare(const Pair &v1, const T v2) + { + return !(v1 > v2); + } + }; + + private: + Comparator *comparator; + + public: + // temp array for sorting (if enabled) + Pair array[]; + + PairArray(): comparator(NULL) + { + } + + PairArray(const int reserved, Comparator *c = NULL) + { + comparator = c; + ArrayResize(array, reserved); + } + + ~PairArray() + { + ArrayResize(array, 0); + if(CheckPointer(comparator) == POINTER_DYNAMIC) delete comparator; + } + + void allocate(const int reserved) + { + ArrayResize(array, reserved); + } + + void compareBy(Comparator *c) + { + if(CheckPointer(comparator) == POINTER_DYNAMIC) delete comparator; + comparator = c; + } + + void move(const int index, const int count) + { + for(int i = count - 1; i >= index; --i) + { + array[i + 1] = array[i]; + } + } + + template + void insert(const int count, const T1 v, const T2 s) + { + Pair p(v, s); + for(int i = 0; i < count; i++) + { + if(comparator != NULL && comparator.compare(array[i], v)) + { + move(i, count); + array[i] = p; + return; + } + } + array[count] = p; + } + + void convert(double &x[], string &s[]) const + { + int n = ArraySize(array); + ArrayResize(x, n); + ArrayResize(s, n); + for(int i = 0; i < n; i++) + { + x[i] = array[i].value; + s[i] = array[i].title; + } + } + + void convert(double &x[]) const + { + int n = ArraySize(array); + ArrayResize(x, n); + for(int i = 0; i < n; i++) + { + x[i] = array[i].value; + } + } + + void convert(string &s[]) const + { + int n = ArraySize(array); + ArrayResize(s, n); + for(int i = 0; i < n; i++) + { + s[i] = array[i].title; + } + } +}; diff --git a/test/PairPlot/Plot.mqh b/test/PairPlot/Plot.mqh new file mode 100644 index 0000000..e54d41a --- /dev/null +++ b/test/PairPlot/Plot.mqh @@ -0,0 +1,525 @@ +//+------------------------------------------------------------------+ +//| Plot.mqh | +//| Copyright (c) 2019, Marketeer | +//| https://www.mql5.com/en/users/marketeer | +//+------------------------------------------------------------------+ +#property copyright "Copyright (c) 2019, Marketeer" +#property link "https://www.mql5.com/en/users/marketeer" +#property version "1.0" + +#include +#include +#include + +class CurveSubtitles +{ + public: + CCurve *curve; + PairArray *data; + + void assign(const CCurve *c, const PairArray *d) + { + curve = (CCurve *)c; + data = (PairArray *)d; + } +}; + +class CGraphicInPlot: public CGraphic +{ + protected: + long m_chart_id; // chart ID + CurveSubtitles curvecache[]; + + void CGraphicInPlot::Customize(CCurve *c, const int points); + CCurve *CGraphicInPlot::CacheIt(const CCurve *c, const PairArray *data = NULL); + + public: + CGraphicInPlot(); + ~CGraphicInPlot(); + + virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2); + + CCurve *CurveAdd(const PairArray *data, ENUM_CURVE_TYPE type, const string name = NULL); // overload + CCurve *CurveAdd(const double &x[], const double &y[], ENUM_CURVE_TYPE type, const string name = NULL); // overload + + void CurvesRemoveAll(void); + + virtual bool Shift(const int dx, const int dy); + + virtual bool Show(void); + virtual bool Hide(void); + + void Destroy(void); + void ResetColors(void); + CCurve *CurveDetach(const int index); + bool CurveAttach(CCurve *curve); + + int getIndexInCache(CCurve *c); + void replaceInCache(const int index, CCurve *c); + + int cacheSize() const + { + return ArraySize(curvecache); + } + + const CurveSubtitles *cacheItem(const int index) const + { + return &curvecache[index]; + } + + void InitXAxis(const bool custom); + void InitYAxis(const bool custom); +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ + +string CustomDoubleToStringFunction(double value, void *ptr) +{ + CGraphicInPlot *self = dynamic_cast(ptr); + if(self != NULL) + { + if(self.cacheSize() > 0) + { + const int index = (int)value; + if(MathAbs(((double)index) - value) <= DBL_EPSILON) + { + const CurveSubtitles *s = self.cacheItem(0); + if(index < 0 || index >= ArraySize(s.data.array)) return NULL; // (string)(float)value; // debug + return s.data.array[index].title; + } + } + } + return NULL; +} + +CGraphicInPlot::CGraphicInPlot() +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CGraphicInPlot::~CGraphicInPlot() +{ + CurvesRemoveAll(); +} + +void CGraphicInPlot::Destroy(void) +{ + m_generator.Reset(); + m_canvas.Destroy(); +} + +void CGraphicInPlot::ResetColors(void) +{ + m_generator.Reset(); +} + +/* TODO: enable this to support Y marks customization +class AxisCustomizer +{ + public: + const bool Y; // true for Y, false for X (default) + const CGraphicInPlot *parent; + AxisCustomizer(const bool axisY, CGraphicInPlot *p): Y(axisY), parent(p) {} +}; +*/ + +void CGraphicInPlot::InitXAxis(const bool custom) +{ + if(custom) + { + m_x.Type(AXIS_TYPE_CUSTOM); + m_x.ValuesFunctionFormat(CustomDoubleToStringFunction); + m_x.ValuesFunctionFormatCBData(&this); + } + else + { + m_x.Type(AXIS_TYPE_DOUBLE); + } +} + +void CGraphicInPlot::InitYAxis(const bool custom) +{ + if(custom) + { + m_y.Type(AXIS_TYPE_CUSTOM); + m_y.ValuesFunctionFormat(CustomDoubleToStringFunction); + m_y.ValuesFunctionFormatCBData(&this); + } + else + { + m_y.Type(AXIS_TYPE_DOUBLE); + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGraphicInPlot::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) +{ + if(!CGraphic::Create(chart, name, subwin, x1, y1, x2, y2)) return false; + m_chart_id = chart; + return true; +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGraphicInPlot::Show(void) +{ + string obj_name = ChartObjectName(); + if(obj_name == NULL || ObjectFind(m_chart_id, obj_name) < 0) return false; + if(!ObjectSetInteger(m_chart_id, obj_name, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS)) return false; + Update(false); + return true; +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGraphicInPlot::Hide(void) +{ + string obj_name = ChartObjectName(); + if(obj_name == NULL || ObjectFind(m_chart_id, obj_name) < 0) return false; + return ObjectSetInteger(m_chart_id, obj_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CGraphicInPlot::Shift(const int dx, const int dy) +{ + string obj_name = ChartObjectName(); + if(obj_name == NULL || ObjectFind(m_chart_id, obj_name) < 0) return false; + + int x = (int)ObjectGetInteger(m_chart_id, obj_name, OBJPROP_XDISTANCE) + dx; + int y = (int)ObjectGetInteger(m_chart_id, obj_name, OBJPROP_YDISTANCE) + dy; + if(!ObjectSetInteger(m_chart_id, obj_name, OBJPROP_XDISTANCE, x)) return false; + if(!ObjectSetInteger(m_chart_id, obj_name, OBJPROP_YDISTANCE, y)) return false; + + return true; +} + +CCurve *CGraphicInPlot::CurveDetach(const int index) +{ + return m_arr_curves.Detach(index); +} + +bool CGraphicInPlot::CurveAttach(CCurve *curve) +{ + return m_arr_curves.Add(curve); +} + +void CGraphicInPlot::Customize(CCurve *c, const int points) +{ + int w = MathMax(Width() / points / 4, 1); + c.HistogramWidth(w); + c.LinesWidth(3); + c.PointsFill(true); +} + +CCurve *CGraphicInPlot::CacheIt(const CCurve *c, const PairArray *data = NULL) +{ + int n = ArraySize(curvecache); + ArrayResize(curvecache, n + 1); + curvecache[n].assign(c, data); + return (CCurve *)c; +} + +CCurve *CGraphicInPlot::CurveAdd(const PairArray *data, ENUM_CURVE_TYPE type, const string name = NULL) +{ + double y[]; + string s[]; + data.convert(y, s); + CCurve *c = CGraphic::CurveAdd(y, type, name); + Customize(c, ArraySize(y)); + + return CacheIt(c, data); +} + +CCurve *CGraphicInPlot::CurveAdd(const double &x[], const double &y[], ENUM_CURVE_TYPE type, const string name = NULL) +{ + CCurve *c = CGraphic::CurveAdd(x, y, type, name); + Customize(c, ArraySize(x)); + + return CacheIt(c); +} + +int CGraphicInPlot::getIndexInCache(CCurve *c) +{ + int n = ArraySize(curvecache); + for(int i = 0; i < n; i++) + { + if(curvecache[i].curve == c) return i; + } + return -1; +} + +void CGraphicInPlot::replaceInCache(const int index, CCurve *c) +{ + curvecache[index].curve = c; +} + +void CGraphicInPlot::CurvesRemoveAll(void) +{ + int n = m_arr_curves.Total(); + for(int i = n - 1; i >= 0; i--) + { + CurveRemoveByIndex(i); + } + n = ArraySize(curvecache); + for(int i = n - 1; i >= 0; i--) + { + if(CheckPointer(curvecache[i].data) == POINTER_DYNAMIC) delete curvecache[i].data; + } + ArrayResize(curvecache, 0); +} + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +class CPlot: public CWndClient +{ + private: + CGraphicInPlot *m_graphic; + ENUM_CURVE_TYPE type; + uint i_text_color; + CCurve *temp[]; + + public: + CPlot(); + ~CPlot(); + + bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2, const ENUM_CURVE_TYPE t = CURVE_HISTOGRAM); + virtual void Destroy(const int reason = 0) override; + bool Refresh(const bool enforce = false); + bool SetTextColor(color value); + + virtual bool Shift(const int dx, const int dy) override; + + virtual bool Show(void); + virtual bool Hide(void); + + bool Resize(const int x1, const int y1, const int x2, const int y2); + + CCurve *CurveAdd(const PairArray *data, const string name = NULL); + CCurve *CurveAdd(const double &x[], const double &y[], const string name = NULL); + + void CurvesRemoveAll(void); + + void SetDefaultCurveType(ENUM_CURVE_TYPE t) + { + type = t; + } + + void InitXAxis(const bool custom) + { + if(CheckPointer(m_graphic) != POINTER_INVALID) + { + m_graphic.InitXAxis(custom); + } + } +}; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CPlot::CPlot(): + type(CURVE_HISTOGRAM), i_text_color(ColorToARGB(clrBlack, 255)) +{ +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +CPlot::~CPlot() +{ + if(CheckPointer(m_graphic) != POINTER_INVALID) + { + m_graphic.Destroy(); + delete m_graphic; + } +} + +void CPlot::Destroy(const int reason = 0) +{ + if(CheckPointer(m_graphic) != POINTER_INVALID) + { + m_graphic.Destroy(); + delete m_graphic; + m_graphic = NULL; + } + CWndClient::Destroy(reason); +} + +CCurve *CPlot::CurveAdd(const PairArray *data, const string name = NULL) +{ + if(CheckPointer(m_graphic) == POINTER_INVALID) return NULL; + return m_graphic.CurveAdd(data, type, name); +} + +CCurve *CPlot::CurveAdd(const double &x[], const double &y[], const string name = NULL) +{ + if(CheckPointer(m_graphic) == POINTER_INVALID) return NULL; + return m_graphic.CurveAdd(x, y, type, name); +} + +void CPlot::CurvesRemoveAll(void) +{ + m_graphic.CurvesRemoveAll(); + m_graphic.ResetColors(); +} + +bool CPlot::Resize(const int x1, const int y1, const int x2, const int y2) +{ + if(CheckPointer(m_graphic) == POINTER_INVALID) return false; + + int width = Width(); + int height = Height(); + Size(x2 - x1, y2 - y1); + + string obj_name = m_name + "_0_0"; + int obj_x1 = m_rect.left; + int obj_x2 = obj_x1 + width; + int obj_y1 = m_rect.top; + int obj_y2 = obj_y1 + height; + + m_graphic.Destroy(); + if(!m_graphic.Create(m_chart_id, obj_name, m_subwin, obj_x1, obj_y1, obj_x2, obj_y2)) + { + Print(GetLastError()); + return false; + } + + for(int i = 0; i < ArraySize(temp); ++i) + { + if(CheckPointer(temp[i]) == POINTER_DYNAMIC) delete temp[i]; + } + + ArrayResize(temp, m_graphic.CurvesTotal()); + + // Graphic library does not provide a method to update curve without array copying + for(int i = m_graphic.CurvesTotal() - 1; i >= 0; --i) + { + temp[i] = m_graphic.CurveDetach(i); + } + + return true; +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CPlot::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2, const ENUM_CURVE_TYPE t = CURVE_HISTOGRAM) +{ + if(!CWndClient::Create(chart, name, subwin, x1, y1, x2, y2)) return false; + type = t; + + int width = Width(); + int height = Height(); + + string obj_name = m_name + "_0_0"; + int obj_x1 = m_rect.left; + int obj_x2 = obj_x1 + width; + int obj_y1 = m_rect.top; + int obj_y2 = obj_y1 + height; + + m_graphic = new CGraphicInPlot(); + if(CheckPointer(m_graphic) == POINTER_INVALID) return false; + if(!m_graphic.Create(m_chart_id, obj_name, m_subwin, obj_x1, obj_y1, obj_x2, obj_y2)) return false; + + return true; +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CPlot::Hide(void) +{ + if(CheckPointer(m_graphic) == POINTER_INVALID) return false; + if(!m_graphic.Hide()) return false; + + return CWndClient::Hide(); +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CPlot::Show(void) +{ + if(!CWndClient::Show()) return false; + + if(CheckPointer(m_graphic) == POINTER_INVALID) return false; + if(m_graphic.Show()) return false; + + return true; +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ + +bool CPlot::Shift(const int dx, const int dy) +{ + if(CheckPointer(m_graphic) == POINTER_INVALID) return false; + if(!m_graphic.Shift(dx, dy)) return false; + + return CWndClient::Shift(dx, dy); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CPlot::Refresh(const bool enforce = false) +{ + if(CheckPointer(m_graphic) == POINTER_INVALID) return false; + + if(ArraySize(temp) == 0 && enforce) + { + m_graphic.ResetColors(); + ArrayResize(temp, m_graphic.CurvesTotal()); + for(int i = m_graphic.CurvesTotal() - 1; i >= 0; --i) + { + temp[i] = m_graphic.CurveDetach(i); + } + } + + for(int i = 0; i < ArraySize(temp); ++i) + { + if(CheckPointer(temp[i]) == POINTER_DYNAMIC) + { + double x[], y[]; + temp[i].GetX(x); + temp[i].GetY(y); + string name = temp[i].Name(); + + int index = m_graphic.getIndexInCache(temp[i]); + + delete temp[i]; + CCurve *curve = NULL; + if(ArraySize(x) > 0) + { + if(ArraySize(y) > 0) + { + curve = m_graphic.CurveAdd(x, y, type, name); + } + else + { + curve = m_graphic.CurveAdd(x, type, name); + } + } + + m_graphic.replaceInCache(index, curve); + + // axis does not yet calculated, it's done only during CurvePlotAll + // so we can't automatically adjust histogram width + // double range = (m_graphic.XAxis().Max() - m_graphic.XAxis().Min()); + // double data = (x[ArrayMaximum(x)] - x[ArrayMinimum(x)]); + // int downsize = (int)(range / data); + curve.HistogramWidth(Width() / ArraySize(x) / 4); + curve.LinesWidth(3); + } + } + ArrayResize(temp, 0); + + if(!m_graphic.CurvePlotAll()) return false; + + m_graphic.Update(false); + + ChartRedraw(m_chart_id); + return true; +} diff --git a/triple_moving/MovingAverages.mqh b/triple_moving/MovingAverages.mqh new file mode 100644 index 0000000..34b89be --- /dev/null +++ b/triple_moving/MovingAverages.mqh @@ -0,0 +1,344 @@ +//+------------------------------------------------------------------+ +//| MovingAverages.mqh | +//| Copyright 2000-2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| Simple Moving Average | +//+------------------------------------------------------------------+ +double SimpleMA(const int position,const int period,const double &price[]) + { + double result=0.0; +//--- check period + if(period>0 && period<=(position+1)) + { + for(int i=0; i0) + { + if (!distance) + { + double pr=2.0/(period+1.0); + result=price[position]*pr+prev_value*(1-pr); + } + + if (distance) + { + if (buysell == 1) + { + + double pr=2.0/(period+1.0); + double _dis_point = distpip * Point(); + double val_dist = (price[position] / _dis_point) - 1.0; + double sum_val_dist = price[position] - _dis_point; //val_dist * _dis_point; + result = sum_val_dist*pr+prev_value*(1-pr); + } + else if (buysell == -1) + { + + double pr=2.0/(period+1.0); + double _dis_point = distpip * Point(); + double val_dist = (price[position] / _dis_point) - 1.0; + double sum_val_dist = price[position] + _dis_point; //val_dist * _dis_point; + result = sum_val_dist*pr+prev_value*(1-pr); + } + + } + } + + return(result); + } +//+------------------------------------------------------------------+ +//| Smoothed Moving Average | +//+------------------------------------------------------------------+ +double SmoothedMA(const int position,const int period,const double prev_value,const double &price[]) + { + double result=0.0; +//--- check period + if(period>0 && period<=(position+1)) + { + if(position==period-1) + { + for(int i=0; i0 && period<=(position+1)) + { + double sum =0.0; + int wsum=0; + + for(int i=period; i>0; i--) + { + wsum+=i; + sum +=price[position-i+1]*(period-i+1); + } + + result=sum/wsum; + } + + return(result); + } +//+------------------------------------------------------------------+ +//| Simple moving average on price array | +//+------------------------------------------------------------------+ +int SimpleMAOnBuffer(const int rates_total,const int prev_calculated,const int begin,const int period,const double& price[],double& buffer[]) + { +//--- check period + if(period<=1 || period>(rates_total-begin)) + return(0); +//--- save as_series flags + bool as_series_price=ArrayGetAsSeries(price); + bool as_series_buffer=ArrayGetAsSeries(buffer); + + ArraySetAsSeries(price,false); + ArraySetAsSeries(buffer,false); +//--- calculate start position + int start_position; + + if(prev_calculated==0) // first calculation or number of bars was changed + { + //--- set empty value for first bars + start_position=period+begin; + + for(int i=0; i(rates_total-begin)) + return(0); +//--- save and clear 'as_series' flags + bool as_series_price=ArrayGetAsSeries(price); + bool as_series_buffer=ArrayGetAsSeries(buffer); + + ArraySetAsSeries(price,false); + ArraySetAsSeries(buffer,false); +//--- calculate start position + int start_position; + double smooth_factor=2.0/(1.0+period); + + if(prev_calculated==0) // first calculation or number of bars was changed + { + //--- set empty value for first bars + for(int i=0; i(rates_total-begin)) + return(0); +//--- save as_series flags + bool as_series_price=ArrayGetAsSeries(price); + bool as_series_buffer=ArrayGetAsSeries(buffer); + + ArraySetAsSeries(price,false); + ArraySetAsSeries(buffer,false); +//--- calculate start position + int i,start_position; + + if(prev_calculated<=period+begin+2) // first calculation or number of bars was changed + { + //--- set empty value for first bars + start_position=period+begin; + + for(i=0; i(rates_total-begin)) + return(0); +//--- save as_series flags + bool as_series_price=ArrayGetAsSeries(price); + bool as_series_buffer=ArrayGetAsSeries(buffer); + + ArraySetAsSeries(price,false); + ArraySetAsSeries(buffer,false); +//--- calculate start position + int start_position; + + if(prev_calculated==0) // first calculation or number of bars was changed + { + //--- set empty value for first bars + start_position=period+begin; + + for(int i=0; i(rates_total-begin)) + return(0); +//--- save as_series flags + bool as_series_price=ArrayGetAsSeries(price); + bool as_series_buffer=ArrayGetAsSeries(buffer); + + ArraySetAsSeries(price,false); + ArraySetAsSeries(buffer,false); +//--- calculate start position + int start_position; + + if(prev_calculated==0) // first calculation or number of bars was changed + { + //--- set empty value for first bars + start_position=period+begin; + + for(int i=0; i