Adwizard/Virtual/VirtualSymbolReceiver.mqh

551 lines
21 KiB
MQL5
Raw Permalink Normal View History

2025-09-19 13:47:10 +03:00
//+------------------------------------------------------------------+
//| 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"
2026-05-14 00:00:27 +03:00
#property version "1.01"
2025-09-19 13:47:10 +03:00
#include <Trade\PositionInfo.mqh>
#include <Trade\Trade.mqh>
#include "../Base/Receiver.mqh"
#include "../Utils/Macros.mqh"
2026-05-14 00:00:27 +03:00
#include "../Utils/SymbolsMonitor.mqh"
2025-09-19 13:47:10 +03:00
#include "Money.mqh"
#include "VirtualOrder.mqh"
//+------------------------------------------------------------------+
//| Класс символьного получателя |
//+------------------------------------------------------------------+
class CVirtualSymbolReceiver : public CReceiver {
string m_symbol; // Символ
2026-05-14 00:00:27 +03:00
CVirtualOrder* m_orders[]; // Массив открытых виртуальных позиций
2025-09-19 13:47:10 +03:00
bool m_isNetting; // Это неттинг-счёт?
double m_minMargin; // Минимальная маржа для открытия
CPositionInfo m_position; // Объект для получения свойств рыночных позиций
2026-05-14 00:00:27 +03:00
CSymbolInfo* m_symbolInfo; // Объект для получения свойств символа
2025-09-19 13:47:10 +03:00
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);
2026-05-14 00:00:27 +03:00
double StopLoss(ENUM_POSITION_TYPE type, double volume, double price, double availLoss);
2025-09-19 13:47:10 +03:00
public:
2026-05-14 00:00:27 +03:00
CVirtualSymbolReceiver(string p_symbol); // Конструктор
2025-09-19 13:47:10 +03:00
bool operator==(const string symbol) {// Оператор сравнения по имени символа
return m_symbol == symbol;
}
void Open(CVirtualOrder *p_order); // Регистрация открытия виртуальной позиции
void Close(CVirtualOrder *p_order); // Регистрация закрытия виртуальной позиции
virtual bool Correct() override; // Корректировка открытых объёмов
2026-05-14 00:00:27 +03:00
virtual bool CorrectSL(ulong ticket, double availLoss);
static double s_slPercent;
static double s_tpPercent;
double m_marketVolume;
2025-09-19 13:47:10 +03:00
};
2026-05-14 00:00:27 +03:00
double CVirtualSymbolReceiver::s_slPercent = 0;
double CVirtualSymbolReceiver::s_tpPercent = 0;
2025-09-19 13:47:10 +03:00
//+------------------------------------------------------------------+
//| Конструктор |
//+------------------------------------------------------------------+
CVirtualSymbolReceiver::CVirtualSymbolReceiver(string p_symbol) :
m_symbol(p_symbol),
m_minMargin(100) {
2026-05-14 00:00:27 +03:00
m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol];
2025-09-19 13:47:10 +03:00
ArrayResize(m_orders, 0, 128);
m_trade.SetExpertMagicNumber(s_magic);
2026-05-14 00:00:27 +03:00
s_slPercent = CVirtualReceiver::s_slPercent;
s_tpPercent = CVirtualReceiver::s_tpPercent;
2025-09-19 13:47:10 +03:00
}
//+------------------------------------------------------------------+
//| Регистрация открытия виртуальной позиции |
//+------------------------------------------------------------------+
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);
}
}
2026-05-14 00:00:27 +03:00
//res &= CorrectSL();
2025-09-19 13:47:10 +03:00
m_isChanged = !res;
}
2026-05-14 00:00:27 +03:00
return res;
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double CVirtualSymbolReceiver::StopLoss(ENUM_POSITION_TYPE type, double volume, double price, double availLoss) {
if(price == 0.0) {
price = (type == POSITION_TYPE_BUY ?
SymbolInfoDouble(m_symbol, SYMBOL_BID)
: SymbolInfoDouble(m_symbol, SYMBOL_ASK));
}
double tickValue = m_symbolInfo.TickValueLoss();
double point = m_symbolInfo.Point();
double sl = availLoss / (tickValue * volume) * point;
if(sl > point) {
if(type == POSITION_TYPE_BUY) {
sl = price - sl;
} else if(type == POSITION_TYPE_SELL) {
sl = price + sl;
}
}
return NormalizeDouble(sl, m_symbolInfo.Digits());
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CVirtualSymbolReceiver::CorrectSL(ulong ticket, double availLoss) {
bool res = true;
CPositionInfo p;
if(p.SelectByTicket(ticket)) {
string symbol = p.Symbol();
ulong magic = p.Magic();
if(magic == s_magic && symbol == m_symbol) {
ENUM_POSITION_TYPE type = p.PositionType();
double bid = m_symbolInfo.Bid();
double ask = m_symbolInfo.Ask();
double sl = StopLoss(type, p.Volume(), p.PriceOpen(), availLoss);
double stopLoss = p.StopLoss();
double point = m_symbolInfo.Point();
if(MathAbs(sl - stopLoss) > point * 0.5) {
if((type == POSITION_TYPE_BUY && sl > bid) || (type == POSITION_TYPE_SELL && sl < ask)) {
res = m_trade.PositionClose(p.Ticket());
if(res) {
PrintFormat(__FUNCTION__" | CLOSE for SET SL: %s [#%I64u] %.5f -> %.5f", m_symbol, p.Ticket(), stopLoss, sl);
} else {
PrintFormat(__FUNCTION__" | ERROR CLOSE for SET SL: %.5f -> %.5f, code: %d", stopLoss, sl, GetLastError());
}
} else {
res = m_trade.PositionModify(p.Ticket(), sl, p.TakeProfit());
if(res) {
PrintFormat(__FUNCTION__" | SET SL: %s [#%I64u] %.5f -> %.5f", m_symbol, p.Ticket(), stopLoss, sl);
} else {
PrintFormat(__FUNCTION__" | ERROR SET SL: %.5f -> %.5f, code: %d", stopLoss, sl, GetLastError());
}
}
}
}
}
2025-09-19 13:47:10 +03:00
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)) {
2026-05-14 00:00:27 +03:00
double sl = 0;
PrintFormat(__FUNCTION__"#%s | OPEN %s %.2f, sl=%.5f", m_symbol, (diffVolume > 0 ? "BUY" : "SELL"), volume, sl);
2025-09-19 13:47:10 +03:00
if(diffVolume > 0) {
2026-05-14 00:00:27 +03:00
res = m_trade.Buy(volume, m_symbol, 0, sl);
2025-09-19 13:47:10 +03:00
} else {
2026-05-14 00:00:27 +03:00
res = m_trade.Sell(volume, m_symbol, 0, sl);
2025-09-19 13:47:10 +03:00
}
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);
}
2026-05-14 00:00:27 +03:00
//res &= CorrectSL();
2025-09-19 13:47:10 +03:00
}
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
}
//+------------------------------------------------------------------+