Adwizard/Virtual/VirtualSymbolReceiver.mqh
2025-04-11 13:28:40 +03:00

471 lines
34 KiB
MQL5

//+------------------------------------------------------------------+
//| VirtualSymbolReceiver.mqh |
//| Copyright 2022-2025, Yuriy Bykov |
//| https://www.mql5.com/ru/users/antekov |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022-2025, Yuriy Bykov"
#property link "https://www.mql5.com/ru/users/antekov"
#property version "1.00"
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\Trade.mqh>
#include "../Base/Receiver.mqh"
#include "../Utils/Macros.mqh"
#include "Money.mqh"
#include "VirtualOrder.mqh"
//+------------------------------------------------------------------+
//| Класс символьного получателя |
//+------------------------------------------------------------------+
class CVirtualSymbolReceiver : public CReceiver {
string m_symbol; // Символ
CVirtualOrder *m_orders[]; // Массив открытых виртуальных позиций
bool m_isNetting; // Это неттинг-счёт?
double m_minMargin; // Минимальная маржа для открытия
CPositionInfo m_position; // Объект для получения свойств рыночных позиций
CSymbolInfo m_symbolInfo; // Объект для получения свойств символа
CTrade m_trade; // Объект для совершения торговых операций
double MarketVolume(); // Объём открытых рыночных позиций
double VirtualVolume(); // Объём открытых виртуальных позиций
bool IsTradeAllowed(); // Торговля по символу доступна?
// Необходимая разница объёмов
double DiffVolume(double marketVolume, double virtualVolume);
// Коррекция объема на необходимую разницу
bool Correct(double oldVolume, double diffVolume);
// Вспомогательные методы открытия
bool ClearOpen(double diffVolume);
bool AddBuy(double volume);
bool AddSell(double volume);
// Вспомогательные методы закрытия
bool CloseBuyPartial(double volume);
bool CloseSellPartial(double volume);
bool CloseHedgingPartial(double volume, ENUM_POSITION_TYPE type);
bool CloseFull();
// Проверка маржинальных требований
bool FreeMarginCheck(double volume, ENUM_ORDER_TYPE type);
public:
CVirtualSymbolReceiver(string p_symbol); // Конструктор
bool operator==(const string symbol) {// Оператор сравнения по имени символа
return m_symbol == symbol;
}
void Open(CVirtualOrder *p_order); // Регистрация открытия виртуальной позиции
void Close(CVirtualOrder *p_order); // Регистрация закрытия виртуальной позиции
virtual bool Correct() override; // Корректировка открытых объёмов
};
//+------------------------------------------------------------------+
//| Конструктор |
//+------------------------------------------------------------------+
CVirtualSymbolReceiver::CVirtualSymbolReceiver(string p_symbol) :
m_symbol(p_symbol),
m_minMargin(100) {
if(!m_symbolInfo.Name(m_symbol)) {
PrintFormat(__FUNCTION__"#%s | ERROR: This symbol not found. Trade operations are disabled.", m_symbol);
m_minMargin = -1;
}
ArrayResize(m_orders, 0, 128);
m_trade.SetExpertMagicNumber(s_magic);
}
//+------------------------------------------------------------------+
//| Регистрация открытия виртуальной позиции |
//+------------------------------------------------------------------+
void CVirtualSymbolReceiver::Open(CVirtualOrder *p_order) {
APPEND(m_orders, p_order); // Добавляем позицию в массив
m_isChanged = true; // Устанавливаем флаг изменений
}
//+------------------------------------------------------------------+
//| Регистрация закрытия виртуальной позиции |
//+------------------------------------------------------------------+
void CVirtualSymbolReceiver::Close(CVirtualOrder *p_order) {
REMOVE(m_orders, p_order); // Удаляем позицию из массива
m_isChanged = true; // Устанавливаем флаг изменений
}
//+------------------------------------------------------------------+
//| Корректировка открытых объёмов |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::Correct() {
bool res = true;
if(m_isChanged && IsTradeAllowed()) {
double marketVolume = MarketVolume();
double virtualVolume = VirtualVolume();
double diffVolume = DiffVolume(marketVolume, virtualVolume);
// Если есть необходимость коррекции объема, то выполняем её
if(MathAbs(diffVolume) > 0.001) {
res = Correct(marketVolume, diffVolume);
if(res) {
PrintFormat(__FUNCTION__"#%s | CORRECTED %.2f -> %.2f", m_symbol, marketVolume, virtualVolume);
}
}
m_isChanged = !res;
}
return res;
}
//+------------------------------------------------------------------+
//| Объём открытых рыночных позиций |
//+------------------------------------------------------------------+
double CVirtualSymbolReceiver::MarketVolume() {
double volume = 0;
string symbol;
ulong magic;
int type;
CPositionInfo p;
for(int i = 0; i < PositionsTotal(); i++) {
if(p.SelectByIndex(i)) {
symbol = p.Symbol();
magic = p.Magic();
type = (int) p.PositionType();
if(magic == s_magic && symbol == m_symbol) {
volume += p.Volume() * (-(type) * 2 + 1);
}
}
}
return volume;
}
//+------------------------------------------------------------------+
//| Объём открытых витруальных позиций |
//+------------------------------------------------------------------+
double CVirtualSymbolReceiver::VirtualVolume() {
double volume = 0;
FOREACH(m_orders) volume += CMoney::Volume(m_orders[i]);
return volume;
}
//+------------------------------------------------------------------+
//| Торговля по символу доступна? |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::IsTradeAllowed() {
return (true
&& m_minMargin > 0
&& m_symbolInfo.TradeMode() == SYMBOL_TRADE_MODE_FULL
);
}
//+------------------------------------------------------------------+
//| Необходимая разница объёмов |
//+------------------------------------------------------------------+
double CVirtualSymbolReceiver::DiffVolume(double marketVolume, double virtualVolume) {
// Получаем граничные значения допустимых объмов
double minLot = MathMax(0.01, m_symbolInfo.LotsMin());
double maxLot = m_symbolInfo.LotsMax();
double lotStep = MathMax(0.01, m_symbolInfo.LotsStep());
// Находим, на сколько надо изменить объём открытых позиций по символу
double oldVolume = marketVolume;
double newVolume = virtualVolume;
int ratio = 0;
// Проверяем, что новый объем укладывается в допустимые рамки
if(MathAbs(newVolume) > maxLot) {
newVolume = maxLot * MathAbs(newVolume) / newVolume;
}
if(MathAbs(newVolume) < minLot && MathAbs(newVolume) > 0) {
if(MathAbs(newVolume) < 0.5 * minLot) {
newVolume = 0;
} else {
newVolume = minLot * MathAbs(newVolume) / newVolume;
}
}
// На сколько надо изменить открытый объем
double diffVolume = newVolume - oldVolume;
int digits = 2;
if (lotStep >= 0.1 && lotStep < 1.0) {
digits = 1;
} else if (lotStep >= 1.0) {
digits = 0;
}
if(oldVolume == 0) {
if (minLot >= 0.1 && lotStep < 1.0) {
digits = 1;
} else if (minLot >= 1.0) {
digits = 0;
}
}
diffVolume = NormalizeDouble(diffVolume, digits);
ratio = (int) MathRound(MathAbs(diffVolume) / lotStep);
if(MathAbs(ratio * lotStep - MathAbs(diffVolume)) > 0.0000001) {
diffVolume = ratio * lotStep * MathAbs(diffVolume) / diffVolume;
}
return diffVolume;
}
//+------------------------------------------------------------------+
//| Коррекция объема на необходимую разницу |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::Correct(double oldVolume, double diffVolume) {
bool res = false;
double volume = MathAbs(diffVolume);
if(oldVolume > 0) { // Have BUY position
if(diffVolume > 0) { // New BUY position
res = AddBuy(volume);
} else if(diffVolume < 0) { // New SELL position
if(volume < oldVolume) {
res = CloseBuyPartial(volume);
} else {
res = CloseFull();
if(res && volume > oldVolume) {
res = AddSell(volume - oldVolume);
}
}
}
} else if(oldVolume < 0) { // Have SELL position
if(diffVolume < 0) { // New SELL position
res = AddSell(volume);
} else if(diffVolume > 0) { // New BUY position
if(volume < -oldVolume) {
res = CloseSellPartial(volume);
} else {
res = CloseFull();
if(res && volume > -oldVolume) {
res = AddBuy(volume + oldVolume);
}
}
}
} else { // No old position
res = ClearOpen(diffVolume);
}
return res;
}
//+------------------------------------------------------------------+
//| Открытие рыночной позиции BUY или SELL |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::ClearOpen(double diffVolume) {
double volume = MathAbs(diffVolume);
double minLot = MathAbs(m_symbolInfo.LotsMin());
if(minLot < 1e-12 || volume < minLot) {
return true;
}
bool res = true;
ENUM_ORDER_TYPE type = (diffVolume > 0 ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
if(FreeMarginCheck(volume, type)) {
PrintFormat(__FUNCTION__"#%s | OPEN %s %.2f", m_symbol, (diffVolume > 0 ? "BUY" : "SELL"), volume);
if(diffVolume > 0) {
res = m_trade.Buy(volume, m_symbol);
} else {
res = m_trade.Sell(volume, m_symbol);
}
if(!res) {
PrintFormat(__FUNCTION__"#%s | ERROR: %d, Result Code: %d", m_symbol, _LastError, m_trade.ResultRetcode());
}
}
return res;
}
//+------------------------------------------------------------------+
//| Открытие дополнительного объёма BUY |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::AddBuy(double volume) {
return ClearOpen(volume);
}
//+------------------------------------------------------------------+
//| Открытие дополнительного объёма SELL |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::AddSell(double volume) {
return ClearOpen(-volume);
}
//+------------------------------------------------------------------+
//| Частичное закрытие объёма BUY по символу |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::CloseBuyPartial(double volume) {
bool res = true;
PrintFormat(__FUNCTION__"#%s | CLOSE BUY partial | volume = %.2f", m_symbol, volume);
if(volume > 0) {
if(m_isNetting) {
res = m_trade.Sell(volume, m_symbol, 0, 0, 0);
} else {
res = CloseHedgingPartial(volume, POSITION_TYPE_BUY);
}
}
if(!res) {
PrintFormat(__FUNCTION__"#%s | ERROR: %d, Result Code: %d", m_symbol, _LastError, m_trade.ResultRetcode());
ResetLastError();
}
return res;
}
//+------------------------------------------------------------------+
//| Частичное закрытие объёма SELL по символу |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::CloseSellPartial(double volume) {
bool res = true;
PrintFormat(__FUNCTION__"#%s | CLOSE SELL partial | volume = %.2f", m_symbol, volume);
if(volume > 0) {
if(m_isNetting) {
res &= m_trade.Buy(volume, m_symbol, 0, 0, 0);
} else {
res &= CloseHedgingPartial(volume, POSITION_TYPE_SELL);
}
}
if(!res) {
PrintFormat(__FUNCTION__"#%s | ERROR: %d, Result Code: %d", m_symbol, _LastError, m_trade.ResultRetcode());
ResetLastError();
}
return res;
}
//+------------------------------------------------------------------+
//| Частичное закрытие BUY или SELL по символу на счете Hedge |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::CloseHedgingPartial(double volume, ENUM_POSITION_TYPE type) {
bool res = true;
ulong ticket;
double positionVolume;
for(int i = 0; i < PositionsTotal(); i++) {
if (m_position.SelectByIndex(i)) {
ticket = m_position.Ticket();
if(m_position.Magic() == s_magic && m_position.Symbol() == m_symbol && m_position.PositionType() == type) {
positionVolume = m_position.Volume();
if(volume > 0) {
if(positionVolume <= volume) {
res &= m_trade.PositionClose(ticket);
volume -= positionVolume;
} else {
res &= m_trade.PositionClosePartial(ticket, volume);
volume = 0;
break;
}
} else {
break;
}
}
}
}
if(volume > 0) {
res = false;
}
return res;
}
//+------------------------------------------------------------------+
//| Полное закрытие объёма по символу |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::CloseFull() {
bool res = true;
ulong ticket;
bool found = true;
while(found && !IsStopped()) {
found = false;
for(int i = 0; i < PositionsTotal(); i++) {
if (m_position.SelectByIndex(i)) {
if(m_position.Magic() == s_magic && (m_position.Symbol() == m_symbol)) {
found = true;
ticket = m_position.Ticket();
res &= m_trade.PositionClose(ticket);
break;
}
}
}
if(!res) {
found = false;
}
}
return res;
}
//+------------------------------------------------------------------+
//| Проверка маржинальных требований |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::FreeMarginCheck(double volume, ENUM_ORDER_TYPE type) {
double freeMarginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
if (freeMarginLevel != 0 && freeMarginLevel < m_minMargin) {
PrintFormat(__FUNCTION__" | Margin level (%.2f) is less than minimum required (%.2f)",
freeMarginLevel, m_minMargin);
return false;
}
#ifdef __MQL4__
double free_margin = AccountFreeMarginCheck(m_symbol, type, volume);
//-- если денег не хватает
if(free_margin < 0) {
PrintFormat(__FUNCTION__" | ERROR: Not enough money for %s %.2f, %s",
(type == OP_BUY) ? "BUY" : "SELL", volume, m_symbol);
return(false);
}
//-- проверка прошла успешно
return(true);
#endif
#ifdef __MQL5__
//--- получим цену открытия
MqlTick mqltick;
SymbolInfoTick(m_symbol, mqltick);
double price = mqltick.ask;
if(type == ORDER_TYPE_SELL) {
price = mqltick.bid;
}
//--- значения необходимой и свободной маржи
double margin, free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
//--- вызовем функцию проверки
if(!OrderCalcMargin(type, m_symbol, volume, price, margin)) {
//--- что-то пошло не так, сообщим и вернем false
PrintFormat(__FUNCTION__" | ERROR: Can't calc margin via OrderCalcMargin(), code=%d", GetLastError());
return(false);
}
//--- если не хватает средств на проведение операции
if(margin > free_margin) {
//--- сообщим об ошибке и вернем false
PrintFormat(__FUNCTION__" | ERROR: Not enough money for %s %.2f, %s",
(type == ORDER_TYPE_BUY) ? "BUY" : "SELL", volume, m_symbol);
return(false);
}
//--- проверка прошла успешно
return(true);
#endif
}
//+------------------------------------------------------------------+