443 lines
36 KiB
MQL5
443 lines
36 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| ProjectName |
|
|
//| Copyright 2020, CompanyName |
|
|
//| http://www.companyname.net |
|
|
//+------------------------------------------------------------------+
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\SymbolInfo.mqh>
|
|
#include <Trade\AccountInfo.mqh>
|
|
#include <Trade\DealInfo.mqh>
|
|
#include <Trade\OrderInfo.mqh>
|
|
#include <Expert\Money\MoneyFixedMargin.mqh>
|
|
|
|
|
|
CTrade a_trade;
|
|
CPositionInfo a_position;
|
|
CSymbolInfo a_symbol;
|
|
CAccountInfo a_accauntInfo;
|
|
CDealInfo a_deal;
|
|
COrderInfo a_order;
|
|
|
|
//--- Объявляем структуры, которые будут использоваться для торговли
|
|
MqlTick latest_price; // Будет использоваться для текущих котировок
|
|
MqlTradeRequest mrequest; // Будет использоваться для отсылки торговых запросов
|
|
MqlTradeResult mresult; // Будет использоваться для получения результатов выполнения торговых запросов
|
|
MqlRates mrate[]; // Будет содержать цены, объемы и спред для каждого бара
|
|
|
|
sinput int Magic = 0;
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CGeneral_class
|
|
{
|
|
private:
|
|
|
|
public:
|
|
CGeneral_class();
|
|
~CGeneral_class();
|
|
|
|
double LotSize(int _SL, double MR);
|
|
bool OpenBuyPosition(double volume,double price, double StopLoss_, double TP_);
|
|
bool OpenSellPosition(double volume,double price, double StopLoss_, double TP_);
|
|
int CheckOpen(int &ob, int &os, datetime & t_ob, datetime & t_os);
|
|
void CheckVolume(double &Volume_current_sell,double &Volume_current_buy);
|
|
void CloseBuy();
|
|
void CloseSell();
|
|
void CloseBuyPartial(string Symbol_, int x);
|
|
void CloseSellPartial(string Symbol_, int x);
|
|
|
|
bool isNewBar(ENUM_TIMEFRAMES per);
|
|
string st5(double x);
|
|
bool CheckTimePeriod(string Start_Time, string End_Time);
|
|
bool SpreadFilter(int bars, int pauseTime, int x);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CGeneral_class::CGeneral_class()
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
CGeneral_class::~CGeneral_class()
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
//+------------------------------------------------------------------------+
|
|
//+- Расчет лота в зависимости от расстояния до StopLoss +//
|
|
//+------------------------------------------------------------------------+
|
|
double CGeneral_class::LotSize(int _SL, double MR)// SL-StopLoss MR-MaxRisk в % от баланса
|
|
{
|
|
if(_SL == 0)
|
|
return(0); // исключение деления на ноль
|
|
double Free = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double LotVal = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); //стоимость 1 пункта 1 лота
|
|
double Min_Lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
double Max_Lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
double Step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
double Lot = MathFloor((Free * MR / 100) / (_SL * LotVal) / Step) * Step;
|
|
if(Lot < Min_Lot)
|
|
Lot = Min_Lot;
|
|
if(Lot > Max_Lot)
|
|
Lot = Max_Lot;
|
|
return(Lot);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CGeneral_class :: OpenSellPosition(double volume,double price, double StopLoss_, double TP_)
|
|
{
|
|
ZeroMemory(mrequest);
|
|
ZeroMemory(mresult);
|
|
mrequest.action = TRADE_ACTION_DEAL; // немедленное исполнение
|
|
mrequest.price = NormalizeDouble(price, _Digits); // последняя цена Bid
|
|
mrequest.sl = StopLoss_; // Stop Loss
|
|
mrequest.tp = TP_ ;// NormalizeDouble(latest_price.bid - TP * _Point, _Digits); // Take Profit
|
|
mrequest.symbol = _Symbol; // символ
|
|
mrequest.volume = volume; // количество лотов для торговли
|
|
mrequest.magic = Magic; // Magic Number
|
|
mrequest.type = ORDER_TYPE_SELL; // ордер на продажу
|
|
mrequest.type_filling = ORDER_FILLING_FOK; // тип исполнения ордера - все или ничего
|
|
mrequest.deviation = 100; // проскальзывание от текущей цены
|
|
//--- отсылаем ордер
|
|
OrderSend(mrequest, mresult);
|
|
// анализируем код возврата торгового сервера
|
|
if(mresult.retcode == 10009 || mresult.retcode == 10008) //Request is completed or order placed
|
|
{
|
|
Print("Ордер Sell успешно помещен, тикет ордера #:", mresult.order, "!!");
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
SendMail(_Symbol + " Запрос на установку ордера Buy не выполнен. код ошибки: " + GetLastError(),
|
|
_Symbol + " result retcode: " + mresult.retcode + "result.deal: " + mresult.deal + "result.order " + mresult.order + "Период: " + GetNameTF());
|
|
|
|
Print("Sell -> false. Result Retcode: ",+ mresult.retcode + " result.deal: " + mresult.deal + "result.order " + mresult.order + "Период: " + GetNameTF());
|
|
|
|
return false;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CGeneral_class :: OpenBuyPosition(double volume, double price, double StopLoss_, double TP_)
|
|
{
|
|
ZeroMemory(mrequest);
|
|
ZeroMemory(mresult);
|
|
mrequest.action = TRADE_ACTION_DEAL; // немедленное исполнение
|
|
mrequest.price = NormalizeDouble(price, _Digits); // последняя цена Bid
|
|
mrequest.sl = StopLoss_; // Stop Loss
|
|
mrequest.tp = TP_;//NormalizeDouble(latest_price.ask + TP * _Point, _Digits); // Take Profit
|
|
mrequest.symbol = _Symbol; // символ
|
|
mrequest.volume = volume; // количество лотов для торговли
|
|
mrequest.magic = Magic; // Magic Number
|
|
mrequest.type = ORDER_TYPE_BUY; // ордер на продажу
|
|
mrequest.type_filling = ORDER_FILLING_FOK; // тип исполнения ордера - все или ничего
|
|
mrequest.deviation = 100; // проскальзывание от текущей цены
|
|
//--- отсылаем ордер
|
|
OrderSend(mrequest, mresult);
|
|
// анализируем код возврата торгового сервера
|
|
if(mresult.retcode == 10009 || mresult.retcode == 10008) //Request is completed or order placed
|
|
{
|
|
return true;
|
|
Print("Ордер Sell успешно помещен, тикет ордера #:", mresult.order, "!!");
|
|
}
|
|
else
|
|
{
|
|
SendMail(_Symbol + " Запрос на установку ордера Buy не выполнен. код ошибки: " + GetLastError(),
|
|
_Symbol + " result retcode: " + mresult.retcode + "result.deal: " + mresult.deal + "result.order " + mresult.order + "Период: " + GetNameTF());
|
|
|
|
Print("Buy -> false. Result Retcode: ",+ mresult.retcode + " result.deal: " + mresult.deal + "result.order " + mresult.order + "Период: " + GetNameTF());
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
int CGeneral_class :: CheckOpen(int &ob, int &os, datetime & t_ob, datetime & t_os)
|
|
{
|
|
ob = 0;
|
|
os = 0;
|
|
int res = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
a_position.SelectByIndex(i);
|
|
if(a_position.Magic() == Magic && a_position.Symbol() == _Symbol)
|
|
{
|
|
if(a_position.PositionType() == POSITION_TYPE_BUY)
|
|
{
|
|
t_ob = a_position.Time();
|
|
ob++;
|
|
}
|
|
if(a_position.PositionType() == POSITION_TYPE_SELL)
|
|
{
|
|
t_os = a_position.Time();
|
|
os++;
|
|
}
|
|
}
|
|
}
|
|
res = ob + os;
|
|
return res;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
//double Volume_current_sell;
|
|
//double Volume_current_buy;
|
|
void CGeneral_class :: CheckVolume(double &Volume_current_sell,double &Volume_current_buy)
|
|
{
|
|
Volume_current_sell = 0;
|
|
Volume_current_buy = 0;
|
|
for(int i = PositionsTotal()-1; i >= 0; i--)
|
|
{
|
|
a_position.SelectByIndex(i);
|
|
if(a_position.Magic() == Magic && a_position.Symbol() == _Symbol)
|
|
{
|
|
if(a_position.PositionType() == POSITION_TYPE_BUY)
|
|
{
|
|
Volume_current_buy += PositionGetDouble(POSITION_VOLUME);
|
|
}
|
|
if(a_position.PositionType() == POSITION_TYPE_SELL)
|
|
{
|
|
Volume_current_sell += PositionGetDouble(POSITION_VOLUME);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CGeneral_class :: CloseSell()
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
a_position.SelectByIndex(i);
|
|
if(a_position.Magic() == Magic && a_position.Symbol() == _Symbol)
|
|
{
|
|
if(a_position.PositionType() == POSITION_TYPE_SELL)
|
|
a_trade.PositionClose(a_position.Ticket());
|
|
}
|
|
}
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CGeneral_class :: CloseBuy()
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
a_position.SelectByIndex(i);
|
|
|
|
|
|
if(a_position.Magic() == Magic && a_position.Symbol() == _Symbol)
|
|
{
|
|
if(a_position.PositionType() == POSITION_TYPE_BUY)
|
|
{
|
|
int o = a_trade.PositionClose(a_position.Ticket());
|
|
if(o <= 0)
|
|
Print("<--- CloseBuy ", _LastError, " ", __LINE__);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CGeneral_class :: CloseBuyPartial(string Symbol_, int x)
|
|
{
|
|
double Volume_current_buy = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
a_position.SelectByIndex(i);
|
|
if(a_position.Magic() == Magic && a_position.Symbol() == Symbol_)
|
|
{
|
|
if(a_position.PositionType() == POSITION_TYPE_BUY)
|
|
{
|
|
Volume_current_buy = PositionGetDouble(POSITION_VOLUME);
|
|
double Lot = NormalizeDouble(Volume_current_buy/x, 2);
|
|
bool o = a_trade.PositionClosePartial(a_position.Ticket(),Lot,-1);
|
|
if(o == false)
|
|
Print("<--- CloseBuy ", _LastError, " ", __LINE__);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CGeneral_class ::CloseSellPartial(string Symbol_, int x)
|
|
{
|
|
double Volume_current_sell = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
a_position.SelectByIndex(i);
|
|
if(a_position.Magic() == Magic && a_position.Symbol() == Symbol_)
|
|
{
|
|
if(a_position.PositionType() == POSITION_TYPE_SELL)
|
|
{
|
|
Volume_current_sell = PositionGetDouble(POSITION_VOLUME);
|
|
double Lot = NormalizeDouble(Volume_current_sell/x, 2);
|
|
bool o = a_trade.PositionClosePartial(a_position.Ticket(),Lot,-1);
|
|
if(o == false)
|
|
Print("<--- CloseBuy ", _LastError, " ", __LINE__);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CGeneral_class :: isNewBar(ENUM_TIMEFRAMES per)
|
|
{
|
|
//--- в статической переменной будем помнить время открытия последнего бара
|
|
static datetime last_time = 0;
|
|
//--- текущее время
|
|
datetime lastbar_time = SeriesInfoInteger(Symbol(), per, SERIES_LASTBAR_DATE);
|
|
|
|
//--- если это первый вызов функции
|
|
if(last_time == 0)
|
|
{
|
|
//--- установим время и выйдем
|
|
last_time = lastbar_time;
|
|
return(false);
|
|
}
|
|
|
|
//--- если время отличается
|
|
if(last_time != lastbar_time)
|
|
{
|
|
//--- запомним время и вернем true
|
|
last_time = lastbar_time;
|
|
return(true);
|
|
}
|
|
//--- дошли до этого места - значит бар не новый, вернем false
|
|
return(false);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
string CGeneral_class :: st5(double x)
|
|
{
|
|
return DoubleToString(x, _Digits);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CGeneral_class :: CheckTimePeriod(string Start_Time, string End_Time)
|
|
{
|
|
datetime currentTime = TimeCurrent();
|
|
datetime start_time = StringToTime(Start_Time);
|
|
datetime end_time = End_Time > Start_Time ? StringToTime(End_Time) : StringToTime(StringToTime(End_Time) + 3600 * 24);
|
|
//+------------------------------------------------------------------+
|
|
// Такой вид конструкции называется тернарной операцией. Это более красивая форма записи кода ниже:
|
|
//
|
|
//if (End_Time > Start_Time)
|
|
//{
|
|
// end_time = StringToTime(End_Time)
|
|
//}
|
|
//else
|
|
//{
|
|
// end_time = StringToTime(StringToTime(End_Time) + 3600 * 24)
|
|
//}
|
|
// Структура тернарной операции такая: if() ? valueIfTrue : valueIfFalse;
|
|
//
|
|
// Если требуется выполнить несколько операций в результате исхода, то используется if.
|
|
// Т.е так не получится
|
|
// end_time = End_Time > Start_Time ? StringToTime(End_Time) *тут еще какая то операция* : StringToTime(StringToTime(End_Time) + 3600 * 24) *или тут*;
|
|
//+------------------------------------------------------------------+
|
|
//Print("Current time: ", currentTime);
|
|
//Print("Start time: ", start_time);
|
|
//Print("End time: ", end_time);
|
|
return currentTime > start_time && currentTime < end_time;
|
|
// Аналогично этому:
|
|
// if(currentTime > start_time && currentTime < end_time)
|
|
// {
|
|
// return true;
|
|
// }
|
|
// return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//|Проверяем размах спреда |
|
|
//+------------------------------------------------------------------+
|
|
bool CGeneral_class :: SpreadFilter(int bars, int pauseTime, int x)
|
|
{
|
|
double spread = 0;
|
|
int values[];
|
|
int count = CopySpread(_Symbol, PERIOD_H1, 0, bars, values);
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
spread += values[i];
|
|
}
|
|
double middleSpread = spread / bars * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
double currentSpread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
|
datetime lastAllowedSpreadTime = 0;
|
|
// Если спред большой, то запоминаем текущее время и выжидаем некоторую паузу, после которой он может уменьшится.
|
|
if(currentSpread > (middleSpread+x))// x - это погрешность, которую мы учитываем
|
|
{
|
|
lastAllowedSpreadTime = TimeCurrent();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(TimeCurrent() < lastAllowedSpreadTime + pauseTime)
|
|
return false;
|
|
lastAllowedSpreadTime = 0;
|
|
return true;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
string GetNameTF(int TimeFrame = 0)
|
|
{
|
|
if(TimeFrame == 0)
|
|
TimeFrame = Period();
|
|
switch(TimeFrame)
|
|
{
|
|
case PERIOD_M1:
|
|
return("M1");
|
|
case PERIOD_M5:
|
|
return("M5");
|
|
case PERIOD_M10:
|
|
return("M10");
|
|
case PERIOD_M15:
|
|
return("M15");
|
|
case PERIOD_M20:
|
|
return("M20");
|
|
case PERIOD_M30:
|
|
return("M30");
|
|
case PERIOD_H1:
|
|
return("H1");
|
|
case PERIOD_H2:
|
|
return("H2");
|
|
case PERIOD_H4:
|
|
return("H4");
|
|
case PERIOD_D1:
|
|
return("Daily");
|
|
case PERIOD_W1:
|
|
return("Weekly");
|
|
case PERIOD_MN1:
|
|
return("Monthly");
|
|
default:
|
|
return("Unknown Period");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|