Adwizard/Strategies/SimpleVolumesStrategy.mqh

386 lines
32 KiB
MQL5
Raw Permalink Normal View History

2025-04-11 13:28:40 +03:00
<EFBFBD><EFBFBD>//+------------------------------------------------------------------+
//| SimpleVolumesStrategy.mqh |
//| Copyright 2024, Yuriy Bykov |
//| https://www.mql5.com/ru/users/antekov |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Yuriy Bykov"
#property link "https://www.mql5.com/ru/articles/16452"
#property version "1.11"
#include "../Utils/NewBarEvent.mqh"
#include "../Virtual/VirtualStrategy.mqh"
/** ?8A0=85 AB@0B5388
E>4=K5 ?0@0<5B@K AB@0B5388:
- !8<2>;
- 5@8>4
- >;8G5AB2> A25G59 4;O CA@54=5=8O >1J5<>2 (K)
- B=>A8B5;L=>5 >B:;>=5=85 >B A@54=53> 4;O >B:@KB8O ?5@2>3> >@45@0 (D)
- B=>A8B5;L=>5 >B:;>=5=85 >B A@54=53> 4;O >B:@KB8O 2B>@>3> 8 ?>A;54CNI8E >@45@>2 (D_add)
- 0AAB>O=85 >B F5=K 4> >B;>65==>3> >@45@0
- Stop Loss (2 ?C=:B0E)
- Take Profit (2 ?C=:B0E)
- @5<O 8AB5G5=8O >B;>65==KE >@45@>2 (2 <8=CB0E)
- 0:A8<0;L=>5 :>;8G5AB2> >4=>2@5<5==> >B@KBKE >@45@>2 (N_max)
- 1J5< >4=>3> >@45@0
0E>48< :>;8G5AB2> >B:@KBKE >@45@>2 8 ?>78F89 (N).
A;8 >=> <5=LH5 N_max, B>:
- 2KG8A;O5< A@54=89 B8:>2K9 >1JQ< 70 ?>A;54=85 K 70:@KBKE A25G59, ?>;CG05< 25;8G8=C V_avr.
- A;8 2K?>;=5=> CA;>285 V > V_avr * (1 + D + N * D_add), B>:
- >?@545;O5< =0?@02;5=85 87<5=5=8O F5=K =0 B5:CI59 A25G5: 5A;8 F5=0 C25;8G8;0AL,
B> 1C45< 2KAB02;OBL BUY, BUY_STOP 8;8 BUY_LIMIT, 0 8=0G5 - SELL, SELL_STOP 8;8 SELL_LIMIT
- 2KAB02;O5< @K=>G=K9 8;8 >B;>65==K9 >@45@ 8AE>4O 87 7040==>3> 2 ?0@0<5B@0E @0AAB>O=8O
(0 - @K=>G=K9, >0 - >B;>65==K9 AB>?>2K9, <0 - >B;>65==K9 ;8<8B=K9),
2@5<5=8 8AB5G5=8O 8 C@>2=O<8 StopLoss 8 TakeProfit.
*/
//+------------------------------------------------------------------+
//| ">@3>20O AB@0B538O A 8A?>;L7>20=85< B8:>2KE >1J5<>2 |
//+------------------------------------------------------------------+
class CSimpleVolumesStrategy : public CVirtualStrategy {
protected:
string m_symbol; // !8<2>; (B>@3>2K9 8=AB@C<5=B)
ENUM_TIMEFRAMES m_timeframe; // 5@8>4 3@0D8:0 (B09<D@59<)
//--- 0@0<5B@K A83=0;0 : >B:@KB8N
int m_signalPeriod; // >;8G5AB2> A25G59 4;O CA@54=5=8O >1J5<>2
double m_signalDeviation; // B=>A. >B:;. >B A@54=53> 4;O >B:@KB8O ?5@2>3> >@45@0
double m_signaAddlDeviation; // B=>A. >B:;. >B A@54=53> 4;O >B:@KB8O 2B>@>3> 8 ?>A;54CNI8E >@45@>2
//--- 0@0<5B@K >B;>65==KE >@45@>2
int m_openDistance; // 0AAB>O=85 >B F5=K 4> >B;>6. >@45@0
double m_stopLevel; // Stop Loss (2 ?C=:B0E)
double m_takeLevel; // Take Profit (2 ?C=:B0E)
int m_ordersExpiration; // @5<O 8AB5G5=8O >B;>65==KE >@45@>2 (2 <8=CB0E)
//--- 0@0<5B@K C?@02;5=85 :0?8B0;><
int m_maxCountOfOrders; // 0:A. :>;8G5AB2> >4=>2@5<5==> >B@KBKE >@45@>2
CSymbolInfo *m_symbolInfo; // 1J5:B 4;O ?>;CG5=8O 8=D>@<0F88 > A2>9AB20E A8<2>;0
int m_iVolumesHandle; // %M=4; 8=48:0B>@0 B8:>2KE >1J5<>2
double m_volumes[]; // 0AA82-?@85<=8: 7=0G5=89 8=48:0B>@0 (A0<8E >1J5<>2)
//--- 5B>4K
int SignalForOpen(); // !83=0; 4;O >B:@KB8O >B;>65==KE >@45@>2
void OpenBuyOrder(); // B:@KB85 >@45@0 BUY_STOP
void OpenSellOrder(); // B:@KB85 >@45@0 SELL_STOP
double ArrayAverage(
const double &array[]); // !@54=55 7=0G5=85 <0AA820 G8A5;
public:
//--- C1;8G=K5 <5B>4K
CSimpleVolumesStrategy(string p_params); // >=AB@C:B>@
virtual string operator~() override; // @5>1@07>20=85 >1J5:B0 2 AB@>:C
virtual void Tick() override; // 1@01>BG8: A>1KB8O OnTick
virtual void Save() override; // !>E@0=5=85 A>AB>O=8O
virtual bool Load() override; // 03@C7:0 A>AB>O=8O
// 0<5=0 =0720=89 A8<2>;>2
virtual bool SymbolsReplace(CHashMap<string, string> &p_symbolsMap);
};
//+------------------------------------------------------------------+
//| >=AB@C:B>@ |
//+------------------------------------------------------------------+
CSimpleVolumesStrategy::CSimpleVolumesStrategy(string p_params) {
m_params = p_params;
m_symbol = ReadString(p_params);
m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params);
m_signalPeriod = (int) ReadLong(p_params);
m_signalDeviation = ReadDouble(p_params);
m_signaAddlDeviation = ReadDouble(p_params);
m_openDistance = (int) ReadLong(p_params);
m_stopLevel = ReadDouble(p_params);
m_takeLevel = ReadDouble(p_params);
m_ordersExpiration = (int) ReadLong(p_params);
m_maxCountOfOrders = (int) ReadLong(p_params);
if(IsValid()) {
CVirtualReceiver::Get(GetPointer(this), m_orders, m_maxCountOfOrders);
// 03@C605< 8=48:0B>@ 4;O ?>;CG5=8O B8:>2KE >1J5<>2
m_iVolumesHandle = iVolumes(m_symbol, m_timeframe, VOLUME_TICK);
// A;8 8=48:0B>@ 703@C65= CA?5H=>
if(m_iVolumesHandle != INVALID_HANDLE) {
// #AB0=02;8205< @07<5@ <0AA820-?@85<=8:0 B8:>2KE >1J5<>2 8 =C6=CN 04@5A0F8N
ArrayResize(m_volumes, m_signalPeriod);
ArraySetAsSeries(m_volumes, true);
// 538AB@8@C5< >1@01>BG8: A>1KB8O =>2>3> 10@0 =0 <8=8<0;L=>< B09<D@59<5
//IsNewBar(m_symbol, PERIOD_M1);
m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol];
} else {
// =0G5 CAB0=02;8205< >H81>G=>5 A>AB>O=85 >1J5:B0
SetInvalid(__FUNCTION__, "Can't load iVolumes()");
}
}
}
//+------------------------------------------------------------------+
//| @5>1@07>20=85 >1J5:B0 2 AB@>:C |
//+------------------------------------------------------------------+
string CSimpleVolumesStrategy::operator~() {
return StringFormat("%s(%s)", typename(this), m_params);
}
//+------------------------------------------------------------------+
//| "Tick" event handler function |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::Tick() override {
if(IsNewBar(m_symbol, PERIOD_M1)) {
// A;8 8E :>;8G5AB2> <5=LH5 4>?CAB8<>3>
if(m_ordersTotal < m_maxCountOfOrders) {
// >;CG05< A83=0; =0 >B:@KB85
int signal = SignalForOpen();
if(signal == 1 /* || m_ordersTotal < 1 */) { // A;8 A83=0; =0 ?>:C?:C, B>
OpenBuyOrder(); // >B:@K205< >@45@ BUY_STOP
} else if(signal == -1) { // A;8 A83=0; =0 ?@>406C, B>
OpenSellOrder(); // >B:@K205< >@45@ SELL_STOP
}
}
}
}
//+------------------------------------------------------------------+
//| 0<5=0 =0720=89 A8<2>;>2 |
//+------------------------------------------------------------------+
bool CSimpleVolumesStrategy::SymbolsReplace(CHashMap<string, string> &p_symbolsMap) {
// A;8 2 A;>20@5 5ABL :;NG, A>2?040NI89 A B5:CI8< A8<2>;><
if(p_symbolsMap.ContainsKey(m_symbol)) {
string targetSymbol; // &5;52>9 A8<2>;
// A;8 F5;52>9 A8<2>; 4;O B5:CI53> CA?5H=> ?>;CG5= 87 A;>20@O
if(p_symbolsMap.TryGetValue(m_symbol, targetSymbol)) {
// 1=>2;O5< B5:CI89 A8<2>;
m_symbol = targetSymbol;
}
}
return true;
}
//+------------------------------------------------------------------+
//| !83=0; 4;O >B:@KB8O >B;>65==KE >@45@>2 |
//+------------------------------------------------------------------+
int CSimpleVolumesStrategy::SignalForOpen() {
// >-C<>;G0=8N A83=0;0 =0 >B:@KB85 =5B
int signal = 0;
// >?8@C5< 7=0G5=8O >1J5<>2 87 8=48:0B>@=>3> 1CD5@0 2 <0AA82-?@8Q<=8:
int res = CopyBuffer(m_iVolumesHandle, 0, 0, m_signalPeriod, m_volumes);
// A;8 A:>?8@>20;>AL =C6=>5 :>;8G5AB2> G8A5;
if(res == m_signalPeriod) {
// KG8A;O5< 8E A@54=55 7=0G5=85
double avrVolume = ArrayAverage(m_volumes);
// A;8 B5:CI89 >1J5< ?@52KA8; 7040==K9 C@>25=L, B>
if(m_volumes[0] > avrVolume * (1 + m_signalDeviation + m_ordersTotal * m_signaAddlDeviation)) {
// 5A;8 F5=0 >B:@KB8O A25G8 <5=LH5 B5:CI59 F5=K (70:@KB8O), B>
if(iOpen(m_symbol, m_timeframe, 0) < iClose(m_symbol, m_timeframe, 0)) {
signal = 1; // A83=0; =0 ?>:C?:C
} else {
signal = -1; // 8=0G5 - A83=0; =0 ?@>406C
}
}
}
return signal;
}
//+------------------------------------------------------------------+
//| B:@KB85 >@45@0 BUY |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::OpenBuyOrder() {
// 1=>2;O5< 8=D>@<0F8N > B5:CI8E F5=0E 4;O A8<2>;0
//m_symbolInfo.Name(m_symbol);
//m_symbolInfo.RefreshRates();
// 5@5< =5>1E>48<CN =0< 8=D>@<0F8N > A8<2>;5 8 F5=0E
double point = m_symbolInfo.Point();
int digits = m_symbolInfo.Digits();
double bid = m_symbolInfo.Bid();
double ask = m_symbolInfo.Ask();
int spread = (int) ((ask - bid) / point); //m_symbolInfo.Spread();
// !45;05<, GB>1K @0AAB>O=85 >B:@KB8O 1K;> =5 <5=LH5 A?@540
int distance = MathMax(MathAbs(m_openDistance), spread) * (m_openDistance < 0 ? -1 : 1);
// &5=0 >B:@KB8O
double price = ask + distance * point;
// #@>2=8 StopLoss 8 TakeProfit
double sl = NormalizeDouble(price - m_stopLevel * point, digits);
double tp = NormalizeDouble(price + (m_takeLevel + spread) * point, digits);
// @5<O 8AB5G5=8O
datetime expiration = TimeCurrent() + m_ordersExpiration * 60;
bool res = false;
for(int i = 0; i < m_maxCountOfOrders; i++) { // 5@518@05< 2A5 28@BC0;L=K5 ?>78F88
if(!m_orders[i].IsOpen()) { // A;8 =0H;8 =5 >B:@KBCN, B> >B:@K205<
if(m_openDistance > 0) {
// #AB0=02;8205< >B;>65==K9 >@45@ SELL STOP
res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_STOP, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else if(m_openDistance < 0) {
// #AB0=02;8205< >B;>65==K9 >@45@ SELL LIMIT
res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_LIMIT, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else {
// B:@KB85 28@BC0;L=>9 ?>78F88 SELL
res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot,
0,
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits));
}
break; // 8 2KE>48<
}
}
if(!res) {
PrintFormat(__FUNCTION__" | ERROR opening BUY virtual order", 0);
}
}
//+------------------------------------------------------------------+
//| B:@KB85 >@45@0 SELL |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::OpenSellOrder() {
// 1=>2;O5< 8=D>@<0F8N > B5:CI8E F5=0E 4;O A8<2>;0
//m_symbolInfo.Name(m_symbol);
//m_symbolInfo.RefreshRates();
// 5@5< =5>1E>48<CN =0< 8=D>@<0F8N > A8<2>;5 8 F5=0E
double point = m_symbolInfo.Point();
int digits = m_symbolInfo.Digits();
double bid = m_symbolInfo.Bid();
double ask = m_symbolInfo.Ask();
int spread = (int) ((ask - bid) / point); //m_symbolInfo.Spread();
// !45;05<, GB>1K @0AAB>O=85 >B:@KB8O 1K;> =5 <5=LH5 A?@540
int distance = MathMax(MathAbs(m_openDistance), spread) * (m_openDistance < 0 ? -1 : 1);
// &5=0 >B:@KB8O
double price = bid - distance * point;
// #@>2=8 StopLoss 8 TakeProfit
double sl = NormalizeDouble(price + m_stopLevel * point, digits);
double tp = NormalizeDouble(price - (m_takeLevel + spread) * point, digits);
// @5<O 8AB5G5=8O
datetime expiration = TimeCurrent() + m_ordersExpiration * 60;
bool res = false;
for(int i = 0; i < m_maxCountOfOrders; i++) { // 5@518@05< 2A5 28@BC0;L=K5 ?>78F88
if(!m_orders[i].IsOpen()) { // A;8 =0H;8 =5 >B:@KBCN, B> >B:@K205<
if(m_openDistance > 0) {
// #AB0=02;8205< >B;>65==K9 >@45@ SELL STOP
res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL_STOP, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else if(m_openDistance < 0) {
// #AB0=02;8205< >B;>65==K9 >@45@ SELL LIMIT
res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL_LIMIT, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else {
// B:@KB85 28@BC0;L=>9 ?>78F88 SELL
res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL, m_fixedLot,
0,
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits));
}
break; // 8 2KE>48<
}
}
if(!res) {
PrintFormat(__FUNCTION__" | ERROR opening SELL virtual order", 0);
}
}
//+------------------------------------------------------------------+
//| !@54=55 7=0G5=85 <0AA820 G8A5; A> 2B>@>3> M;5<5=B0 |
//+------------------------------------------------------------------+
double CSimpleVolumesStrategy::ArrayAverage(const double &array[]) {
double s = 0;
int total = ArraySize(array) - 1;
for(int i = 1; i <= total; i++) {
s += array[i];
}
return s / MathMax(1, total);
}
//+------------------------------------------------------------------+
//| !>E@0=5=85 A>AB>O=8O |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::Save() {
double avrVolume = ArrayAverage(m_volumes);
// !D>@<8@C5< >1ICN G0ABL :;NG0 A B8?>< 8 E5H5< AB@0B5388
string key = "CSimpleVolumesStrategy[" + this.Hash() + "]";
// !>E@0=O5< A@54=89 B8:>2K9 >1JQ<
CStorage::Set(key + ".avrVolume", avrVolume);
// !>E@0=O5< <0AA82 B8:>2KE >1JQ<>2
CStorage::Set(key + ".m_volumes", m_volumes);
// K7K205< <5B>4 107>2>3> :;0AA0 (4;O A>E@0=5=8O 28@BC0;L=KE ?>78F89)
CVirtualStrategy::Save();
}
//+------------------------------------------------------------------+
//| 03@C7:0 A>AB>O=8O |
//+------------------------------------------------------------------+
bool CSimpleVolumesStrategy::Load() {
bool res = true;
double avrVolume = 0;
// !D>@<8@C5< >1ICN G0ABL :;NG0 A B8?>< 8 E5H5< AB@0B5388
string key = "CSimpleVolumesStrategy[" + this.Hash() + "]";
// 03@C605< <0AA82 B8:>2KE >1JQ<>2
res &= CStorage::Get(key + ".avrVolume", avrVolume);
// 03@C605< <0AA82 B8:>2KE >1JQ<>2
res &= CStorage::Get(key + ".m_volumes", m_volumes);
// K7K205< <5B>4 107>2>3> :;0AA0 (4;O 703@C7:8 28@BC0;L=KE ?>78F89)
res &= CVirtualStrategy::Load();
return res;
}
//+------------------------------------------------------------------+