2026-07-27 10:58:29 -04:00
|
|
|
//+------------------------------------------------------------------+
|
2026-07-13 03:23:39 -04:00
|
|
|
//| Expert.mqh |
|
|
|
|
|
//| Copyright 2000-2023, MetaQuotes Ltd. |
|
|
|
|
|
//| https://www.mql5.com |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#include <Expert\Expert.mqh>
|
|
|
|
|
#include "ExpertSignalCustom.mqh"
|
2026-07-26 23:08:32 -04:00
|
|
|
#include "..\System\TradeChecks.mqh"
|
|
|
|
|
//--- Article 2555 #16: a soft budget for one OnTick() pass, in microseconds. The article's own
|
|
|
|
|
//--- yardstick is that the FIRST (heaviest) calculation over 10+ years of M1 data should stay
|
|
|
|
|
//--- under 100 ms; a steady-state tick that regularly costs more than that is starving every other
|
|
|
|
|
//--- program sharing this symbol's single terminal thread, so it gets a throttled journal warning
|
|
|
|
|
//--- pointing at the MetaEditor profiler. Diagnostic only - it never skips or truncates any work.
|
|
|
|
|
#define EXPERT_TICK_BUDGET_US 100000
|
|
|
|
|
//--- Article 2555 #16: soft ceiling, in MB, on MQL_MEMORY_USED before a throttled warning is logged.
|
|
|
|
|
//--- Sized well above what the largest shipped AI topology needs (3 models x live+shadow net under
|
|
|
|
|
//--- HYBRID) so it flags a genuine leak/runaway allocation, not ordinary training memory.
|
|
|
|
|
#define EXPERT_MEMORY_SOFT_LIMIT_MB 1024
|
2026-07-13 03:23:39 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Class CExpert. |
|
|
|
|
|
//| Purpose: Base class expert advisor. |
|
|
|
|
|
//| Derives from class CExpert. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CExpertCustom : public CExpert
|
|
|
|
|
{
|
|
|
|
|
protected:
|
|
|
|
|
CExpertSignalCustom* GetCustomSignal()
|
|
|
|
|
{
|
|
|
|
|
return dynamic_cast<CExpertSignalCustom*>(m_signal);
|
|
|
|
|
}
|
|
|
|
|
bool CloseAndDeleteAllForSymbol(void);
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- Article 2555 #7 - SYMBOL_TRADE_FREEZE_LEVEL. While the market sits within freeze-level
|
|
|
|
|
//--- points of a position's SL/TP (or of a pending order's activation price) the server refuses
|
|
|
|
|
//--- to modify, close or delete it, and answering that refusal with a request anyway is exactly
|
|
|
|
|
//--- the error the article says a Market product must never produce. These two report on the
|
|
|
|
|
//--- CURRENTLY SELECTED m_position / m_order, which is the state every caller below runs in.
|
|
|
|
|
bool SelectedPositionIsFrozen(void);
|
|
|
|
|
bool SelectedOrderIsFrozen(void);
|
|
|
|
|
//--- Mirrors CExpertTrade::Buy()/Sell()'s price-vs-stops-level routing, so the checks below know
|
|
|
|
|
//--- whether a given entry price is about to become a pending order or a market fill.
|
|
|
|
|
ENUM_ORDER_TYPE ResolveOrderType(bool isLong, double price);
|
2026-07-13 03:23:39 -04:00
|
|
|
public:
|
|
|
|
|
CExpertCustom(void);
|
|
|
|
|
~CExpertCustom(void);
|
|
|
|
|
//--- event handlers
|
|
|
|
|
virtual void OnTick(void) override;
|
|
|
|
|
virtual void OnTimer(void) override;
|
|
|
|
|
virtual void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override;
|
|
|
|
|
//--- initialization trading objects
|
|
|
|
|
virtual bool InitSignal(CExpertSignal *signal = NULL) override;
|
|
|
|
|
//--- methods of creating the indicator and timeseries
|
|
|
|
|
virtual bool SetPriceSeries(CiOpen *open, CiHigh *high, CiLow *low, CiClose *close) override;
|
|
|
|
|
virtual bool SetOtherSeries(CiSpread *spread, CiTime *time, CiTickVolume *tick_volume, CiRealVolume *real_volume) override;
|
|
|
|
|
//--- initialization trading objects
|
|
|
|
|
virtual bool InitTrade(ulong magic, CExpertTrade *trade = NULL) override;
|
|
|
|
|
protected:
|
|
|
|
|
//--- refreshing
|
|
|
|
|
virtual bool Refresh(void) override;
|
|
|
|
|
//--- processing (main method)
|
|
|
|
|
virtual bool Processing(void) override;
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- Article 2555 #2/#3/#4/#5/#6 - final gate immediately before the order is sent. Everything
|
|
|
|
|
//--- here was already validated when the setup was shaped (CExpertSignalCustom::OpenParams) and
|
|
|
|
|
//--- when the lot was sized (CExpertMoneyCustom::ValidateLotForTrade), but those ran earlier in
|
|
|
|
|
//--- the tick and the market has moved since: a price that was a legal pending distance then can
|
|
|
|
|
//--- be inside the stops level now. This is the last point at which a bad request can still be
|
|
|
|
|
//--- withheld rather than rejected by the server.
|
|
|
|
|
virtual bool OpenLong(double price, double sl, double tp) override;
|
|
|
|
|
virtual bool OpenShort(double price, double sl, double tp) override;
|
|
|
|
|
//--- Article 2555 #7 - never send a close/reverse request for a frozen position.
|
|
|
|
|
virtual bool CheckClose(void) override;
|
|
|
|
|
virtual bool CheckReverse(void) override;
|
|
|
|
|
//--- Article 2555 #7/#11 - never send a modification for a frozen position, and never send one
|
|
|
|
|
//--- that changes nothing (TRADE_RETCODE_NO_CHANGES = 10025).
|
|
|
|
|
virtual bool CheckTrailingStop(void) override;
|
|
|
|
|
virtual bool TrailingStopLong(double sl, double tp) override;
|
|
|
|
|
virtual bool TrailingStopShort(double sl, double tp) override;
|
|
|
|
|
//--- Article 2555 #7/#11 - the same two rules for pending orders.
|
|
|
|
|
virtual bool CheckDeleteOrderLong(void) override;
|
|
|
|
|
virtual bool CheckDeleteOrderShort(void) override;
|
|
|
|
|
virtual bool CheckTrailingOrderLong(void) override;
|
|
|
|
|
virtual bool CheckTrailingOrderShort(void) override;
|
|
|
|
|
virtual bool TrailingOrderLong(double delta) override;
|
|
|
|
|
virtual bool TrailingOrderShort(double delta) override;
|
2026-07-13 03:23:39 -04:00
|
|
|
};
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Constructor |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
CExpertCustom::CExpertCustom(void)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Destructor |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
CExpertCustom::~CExpertCustom(void)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Setting pointers of price timeseries. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::SetPriceSeries(CiOpen *open, CiHigh *high, CiLow *low, CiClose *close) override
|
|
|
|
|
{
|
|
|
|
|
//--- check the initialization phase
|
|
|
|
|
if(m_init_phase != INIT_PHASE_VALIDATION)
|
|
|
|
|
{
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
//--- check pointers
|
|
|
|
|
if((IS_OPEN_SERIES_USAGE && open == NULL) ||
|
|
|
|
|
(IS_HIGH_SERIES_USAGE && high == NULL) ||
|
|
|
|
|
(IS_LOW_SERIES_USAGE && low == NULL) ||
|
|
|
|
|
(IS_CLOSE_SERIES_USAGE && close == NULL))
|
|
|
|
|
{
|
|
|
|
|
Print(__FUNCTION__ + ": NULL pointer");
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
m_open = open;
|
|
|
|
|
m_high = high;
|
|
|
|
|
m_low = low;
|
|
|
|
|
m_close = close;
|
|
|
|
|
//--- ok
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Setting pointers of other timeseries. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::SetOtherSeries(CiSpread *spread, CiTime *time, CiTickVolume *tick_volume, CiRealVolume *real_volume) override
|
|
|
|
|
{
|
|
|
|
|
//--- check the initialization phase
|
|
|
|
|
if(m_init_phase != INIT_PHASE_VALIDATION)
|
|
|
|
|
{
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
//--- check pointers
|
|
|
|
|
if((IS_SPREAD_SERIES_USAGE && spread == NULL) ||
|
|
|
|
|
(IS_TIME_SERIES_USAGE && time == NULL) ||
|
|
|
|
|
(IS_TICK_VOLUME_SERIES_USAGE && tick_volume == NULL) ||
|
|
|
|
|
(IS_REAL_VOLUME_SERIES_USAGE && real_volume == NULL))
|
|
|
|
|
{
|
|
|
|
|
Print(__FUNCTION__ + ": NULL pointer");
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
m_spread = spread;
|
|
|
|
|
m_time = time;
|
|
|
|
|
m_tick_volume = tick_volume;
|
|
|
|
|
m_real_volume = real_volume;
|
|
|
|
|
//--- ok
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Initialization signal object |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::InitSignal(CExpertSignal *signal)
|
|
|
|
|
{
|
|
|
|
|
if(m_signal != NULL)
|
|
|
|
|
delete m_signal;
|
|
|
|
|
//---
|
|
|
|
|
if(signal == NULL)
|
|
|
|
|
{
|
|
|
|
|
if((m_signal = new CExpertSignalCustom) == NULL)
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
m_signal = signal;
|
|
|
|
|
//--- initializing signal object
|
|
|
|
|
if(!m_signal.Init(GetPointer(m_symbol), m_period, m_adjusted_point))
|
|
|
|
|
return(false);
|
|
|
|
|
m_signal.EveryTick(m_every_tick);
|
|
|
|
|
m_signal.Magic(m_magic);
|
|
|
|
|
//--- ok
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Refreshing data for processing |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::Refresh(void)
|
|
|
|
|
{
|
|
|
|
|
MqlDateTime time;
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- Article 2555 #8 - errors caused by insufficient quote history. Everything below this point
|
|
|
|
|
//--- (RefreshRates, then every indicator in m_indicators) reads a timeseries that the terminal may
|
|
|
|
|
//--- still be downloading: on a symbol/timeframe just switched to, on a thin instrument, or right
|
|
|
|
|
//--- after reconnecting, the series reports far fewer bars than the indicators' warm-up needs and
|
|
|
|
|
//--- the buffers come back zero-filled rather than empty. Skipping the tick until the series is
|
|
|
|
|
//--- synchronised is the article's "check for the required history and request the missing data"
|
|
|
|
|
//--- approach - touching the series here is itself what asks the terminal to build it.
|
|
|
|
|
string history_reason;
|
|
|
|
|
if(!TCHasEnoughHistory(m_symbol.Name(), m_period, 2, history_reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("refresh-history:" + m_symbol.Name(), __FUNCTION__ + ": skipping tick - " + history_reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
2026-07-13 03:23:39 -04:00
|
|
|
//--- refresh rates
|
|
|
|
|
if(!m_symbol.RefreshRates())
|
|
|
|
|
return(false);
|
|
|
|
|
//--- check need processing
|
|
|
|
|
TimeToStruct(m_symbol.Time(), time);
|
|
|
|
|
if(m_period_flags != WRONG_VALUE && m_period_flags != 0)
|
|
|
|
|
if((m_period_flags & TimeframesFlags(time)) == 0)
|
|
|
|
|
return(false);
|
|
|
|
|
m_last_tick_time = time;
|
|
|
|
|
//--- refresh indicators
|
|
|
|
|
m_indicators.Refresh();
|
|
|
|
|
//--- ok
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Main function |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::Processing(void)
|
|
|
|
|
{
|
|
|
|
|
//--- calculate signal direction once
|
|
|
|
|
m_signal.SetDirection();
|
|
|
|
|
//--- check if open positions
|
|
|
|
|
if(SelectPosition())
|
|
|
|
|
{
|
|
|
|
|
//--- open position is available
|
|
|
|
|
//--- check the possibility of reverse the position
|
|
|
|
|
if(CheckReverse())
|
|
|
|
|
return(true);
|
|
|
|
|
//--- check the possibility of closing the position/delete pending orders
|
|
|
|
|
if(!CheckClose())
|
|
|
|
|
{
|
|
|
|
|
//--- check the possibility of modifying the position
|
|
|
|
|
if(CheckTrailingStop())
|
|
|
|
|
return(true);
|
|
|
|
|
//--- return without operations
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//--- check if plased pending orders
|
|
|
|
|
int total = OrdersTotal();
|
|
|
|
|
if(total != 0)
|
|
|
|
|
{
|
|
|
|
|
for(int i = total - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
m_order.SelectByIndex(i);
|
|
|
|
|
if(m_order.Symbol() != m_symbol.Name())
|
|
|
|
|
continue;
|
|
|
|
|
if(m_order.OrderType() == ORDER_TYPE_BUY_LIMIT || m_order.OrderType() == ORDER_TYPE_BUY_STOP)
|
|
|
|
|
{
|
|
|
|
|
//--- check the ability to delete a pending order to buy
|
|
|
|
|
if(CheckDeleteOrderLong())
|
|
|
|
|
return(true);
|
|
|
|
|
//--- check the possibility of modifying a pending order to buy
|
|
|
|
|
if(CheckTrailingOrderLong())
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//--- check the ability to delete a pending order to sell
|
|
|
|
|
if(CheckDeleteOrderShort())
|
|
|
|
|
return(true);
|
|
|
|
|
//--- check the possibility of modifying a pending order to sell
|
|
|
|
|
if(CheckTrailingOrderShort())
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
//--- return without operations
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//--- check the possibility of opening a position/setting pending order
|
|
|
|
|
if(CheckOpen())
|
|
|
|
|
return(true);
|
|
|
|
|
//--- return without operations
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-26 23:08:32 -04:00
|
|
|
//| Article 2555 #7 - is the SELECTED position inside the broker's |
|
|
|
|
|
//| freeze zone? A frozen position cannot be modified or closed. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::SelectedPositionIsFrozen(void)
|
|
|
|
|
{
|
|
|
|
|
string reason;
|
|
|
|
|
if(TCFreezeOkForPosition(m_position.Symbol(), (ENUM_POSITION_TYPE)m_position.PositionType(),
|
|
|
|
|
m_position.StopLoss(), m_position.TakeProfit(), reason))
|
|
|
|
|
return false;
|
|
|
|
|
TCLog("frozen-position:" + m_position.Symbol(), __FUNCTION__ + ": " + reason + " - request withheld");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article 2555 #7 - is the SELECTED pending order inside the |
|
|
|
|
|
//| broker's freeze zone? A frozen order cannot be modified or deleted.|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::SelectedOrderIsFrozen(void)
|
|
|
|
|
{
|
|
|
|
|
string reason;
|
|
|
|
|
if(TCFreezeOkForOrder(m_order.Symbol(), (ENUM_ORDER_TYPE)m_order.OrderType(), m_order.PriceOpen(), reason))
|
|
|
|
|
return false;
|
|
|
|
|
TCLog("frozen-order:" + m_order.Symbol(), __FUNCTION__ + ": " + reason + " - request withheld");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Which order type an entry price will produce - see |
|
|
|
|
|
//| CExpertTrade::Buy()/Sell(), whose routing this reproduces. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
ENUM_ORDER_TYPE CExpertCustom::ResolveOrderType(bool isLong, double price)
|
|
|
|
|
{
|
|
|
|
|
if(price <= 0.0 || price == EMPTY_VALUE)
|
|
|
|
|
return(isLong ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
|
|
|
|
|
double stops = TCStopsLevel(m_symbol.Name());
|
|
|
|
|
if(isLong)
|
|
|
|
|
{
|
|
|
|
|
double ask = m_symbol.Ask();
|
|
|
|
|
if(price > ask + stops)
|
|
|
|
|
return(ORDER_TYPE_BUY_STOP);
|
|
|
|
|
if(price < ask - stops)
|
|
|
|
|
return(ORDER_TYPE_BUY_LIMIT);
|
|
|
|
|
return(ORDER_TYPE_BUY);
|
|
|
|
|
}
|
|
|
|
|
double bid = m_symbol.Bid();
|
|
|
|
|
if(price > bid + stops)
|
|
|
|
|
return(ORDER_TYPE_SELL_LIMIT);
|
|
|
|
|
if(price < bid - stops)
|
|
|
|
|
return(ORDER_TYPE_SELL_STOP);
|
|
|
|
|
return(ORDER_TYPE_SELL);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Final pre-send gate for a long entry (article #4/#6/#14). |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::OpenLong(double price, double sl, double tp)
|
|
|
|
|
{
|
|
|
|
|
if(price == EMPTY_VALUE)
|
|
|
|
|
return(false);
|
|
|
|
|
string reason;
|
|
|
|
|
//--- #14: symbol-property reads below are meaningless for an unquoted symbol
|
|
|
|
|
if(!TCSymbolIsTradeable(m_symbol.Name(), reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-symbol:" + m_symbol.Name(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
ENUM_ORDER_TYPE type = ResolveOrderType(true, price);
|
|
|
|
|
if(type != ORDER_TYPE_BUY)
|
|
|
|
|
{
|
|
|
|
|
//--- #4: the account's ACCOUNT_LIMIT_ORDERS cap applies to pending orders only
|
|
|
|
|
if(!TCIsNewOrderAllowed(reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-orderlimit", __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
//--- #6: the activation price must still clear the stops level after the price moved
|
|
|
|
|
if(!TCCheckPendingPrice(m_symbol.Name(), type, price, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-pendingprice:" + m_symbol.Name(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//--- #6: SL/TP against the stops level, measured from this order type's own reference price
|
|
|
|
|
if(!TCCheckStops(m_symbol.Name(), type, price, sl, tp, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-stops:" + m_symbol.Name(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
return(CExpert::OpenLong(price, sl, tp));
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Final pre-send gate for a short entry (article #4/#6/#14). |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::OpenShort(double price, double sl, double tp)
|
|
|
|
|
{
|
|
|
|
|
if(price == EMPTY_VALUE)
|
|
|
|
|
return(false);
|
|
|
|
|
string reason;
|
|
|
|
|
if(!TCSymbolIsTradeable(m_symbol.Name(), reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-symbol:" + m_symbol.Name(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
ENUM_ORDER_TYPE type = ResolveOrderType(false, price);
|
|
|
|
|
if(type != ORDER_TYPE_SELL)
|
|
|
|
|
{
|
|
|
|
|
if(!TCIsNewOrderAllowed(reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-orderlimit", __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
if(!TCCheckPendingPrice(m_symbol.Name(), type, price, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-pendingprice:" + m_symbol.Name(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(!TCCheckStops(m_symbol.Name(), type, price, sl, tp, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("open-stops:" + m_symbol.Name(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
return(CExpert::OpenShort(price, sl, tp));
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article #7 - do not attempt to close a frozen position. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::CheckClose(void)
|
|
|
|
|
{
|
|
|
|
|
if(SelectedPositionIsFrozen())
|
|
|
|
|
return(false);
|
|
|
|
|
return(CExpert::CheckClose());
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article #7 - a reverse closes the open position first, so the |
|
|
|
|
|
//| same freeze restriction applies to it. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::CheckReverse(void)
|
|
|
|
|
{
|
|
|
|
|
if(SelectedPositionIsFrozen())
|
|
|
|
|
return(false);
|
|
|
|
|
return(CExpert::CheckReverse());
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article #7 - do not attempt to modify a frozen position. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::CheckTrailingStop(void)
|
|
|
|
|
{
|
|
|
|
|
if(SelectedPositionIsFrozen())
|
|
|
|
|
return(false);
|
|
|
|
|
return(CExpert::CheckTrailingStop());
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article #11 - a PositionModify() that changes nothing is an error |
|
|
|
|
|
//| (TRADE_RETCODE_NO_CHANGES = 10025). CExpert's own caller compares |
|
|
|
|
|
//| the new levels for EXACT equality, which a recomputed double can |
|
|
|
|
|
//| miss by one ulp and still be "no change" to the server; compare |
|
|
|
|
|
//| with the article's one-point tolerance instead. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::TrailingStopLong(double sl, double tp)
|
|
|
|
|
{
|
|
|
|
|
if(!TCPositionModifyIsMeaningful(m_position.Symbol(), m_position.StopLoss(), sl, m_position.TakeProfit(), tp))
|
|
|
|
|
return(false);
|
|
|
|
|
//--- the new levels are themselves subject to the stops level (article #6)
|
|
|
|
|
string reason;
|
|
|
|
|
if(!TCCheckStops(m_position.Symbol(), ORDER_TYPE_BUY, 0.0, sl, tp, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("trail-stops-long:" + m_position.Symbol(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
return(CExpert::TrailingStopLong(sl, tp));
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| See TrailingStopLong() - same two rules for a short position. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::TrailingStopShort(double sl, double tp)
|
|
|
|
|
{
|
|
|
|
|
if(!TCPositionModifyIsMeaningful(m_position.Symbol(), m_position.StopLoss(), sl, m_position.TakeProfit(), tp))
|
|
|
|
|
return(false);
|
|
|
|
|
string reason;
|
|
|
|
|
if(!TCCheckStops(m_position.Symbol(), ORDER_TYPE_SELL, 0.0, sl, tp, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("trail-stops-short:" + m_position.Symbol(), __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
return(CExpert::TrailingStopShort(sl, tp));
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article #7 - a pending order inside the freeze zone cannot be |
|
|
|
|
|
//| deleted; withhold the request instead of having it rejected. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::CheckDeleteOrderLong(void)
|
|
|
|
|
{
|
|
|
|
|
if(SelectedOrderIsFrozen())
|
|
|
|
|
return(false);
|
|
|
|
|
return(CExpert::CheckDeleteOrderLong());
|
|
|
|
|
}
|
|
|
|
|
bool CExpertCustom::CheckDeleteOrderShort(void)
|
|
|
|
|
{
|
|
|
|
|
if(SelectedOrderIsFrozen())
|
|
|
|
|
return(false);
|
|
|
|
|
return(CExpert::CheckDeleteOrderShort());
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article #7 - nor can it be modified. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::CheckTrailingOrderLong(void)
|
|
|
|
|
{
|
|
|
|
|
if(SelectedOrderIsFrozen())
|
|
|
|
|
return(false);
|
|
|
|
|
return(CExpert::CheckTrailingOrderLong());
|
|
|
|
|
}
|
|
|
|
|
bool CExpertCustom::CheckTrailingOrderShort(void)
|
|
|
|
|
{
|
|
|
|
|
if(SelectedOrderIsFrozen())
|
|
|
|
|
return(false);
|
|
|
|
|
return(CExpert::CheckTrailingOrderShort());
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Article #6/#11 - the shifted order must still change something, |
|
|
|
|
|
//| and its new activation price plus SL/TP must clear the stops |
|
|
|
|
|
//| level. CExpert::TrailingOrder*() shifts price, sl and tp by the |
|
|
|
|
|
//| same delta and sends the modification unconditionally, so a |
|
|
|
|
|
//| delta of 0 (or one that walks the order inside the stops level) |
|
|
|
|
|
//| produced a guaranteed-rejected request. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::TrailingOrderLong(double delta)
|
|
|
|
|
{
|
|
|
|
|
string symbol = m_order.Symbol();
|
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)m_order.OrderType();
|
|
|
|
|
double price = m_symbol.NormalizePrice(m_order.PriceOpen() - delta);
|
|
|
|
|
double sl = m_symbol.NormalizePrice(m_order.StopLoss() - delta);
|
|
|
|
|
double tp = m_symbol.NormalizePrice(m_order.TakeProfit() - delta);
|
|
|
|
|
if(!TCOrderModifyIsMeaningful(symbol, m_order.PriceOpen(), price, m_order.StopLoss(), sl, m_order.TakeProfit(), tp))
|
|
|
|
|
return(false);
|
|
|
|
|
string reason;
|
|
|
|
|
if(!TCCheckPendingPrice(symbol, type, price, reason) || !TCCheckStops(symbol, type, price, sl, tp, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("trail-order-long:" + symbol, __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
return(CExpert::TrailingOrderLong(delta));
|
|
|
|
|
}
|
|
|
|
|
bool CExpertCustom::TrailingOrderShort(double delta)
|
|
|
|
|
{
|
|
|
|
|
string symbol = m_order.Symbol();
|
|
|
|
|
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)m_order.OrderType();
|
|
|
|
|
double price = m_symbol.NormalizePrice(m_order.PriceOpen() - delta);
|
|
|
|
|
double sl = m_symbol.NormalizePrice(m_order.StopLoss() - delta);
|
|
|
|
|
double tp = m_symbol.NormalizePrice(m_order.TakeProfit() - delta);
|
|
|
|
|
if(!TCOrderModifyIsMeaningful(symbol, m_order.PriceOpen(), price, m_order.StopLoss(), sl, m_order.TakeProfit(), tp))
|
|
|
|
|
return(false);
|
|
|
|
|
string reason;
|
|
|
|
|
if(!TCCheckPendingPrice(symbol, type, price, reason) || !TCCheckStops(symbol, type, price, sl, tp, reason))
|
|
|
|
|
{
|
|
|
|
|
TCLog("trail-order-short:" + symbol, __FUNCTION__ + ": withheld - " + reason);
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
return(CExpert::TrailingOrderShort(delta));
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-13 03:23:39 -04:00
|
|
|
//| OnTick handler |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CExpertCustom::OnTick(void)
|
|
|
|
|
{
|
|
|
|
|
//--- check process flag
|
|
|
|
|
if(!m_on_tick_process)
|
|
|
|
|
return;
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- Article 2555 #16 - measure the tick with GetMicrosecondCount() and report (throttled) when it
|
|
|
|
|
//--- overruns EXPERT_TICK_BUDGET_US. Purely diagnostic; see the macro's comment.
|
|
|
|
|
ulong tick_started_us = TCNow();
|
2026-07-13 03:23:39 -04:00
|
|
|
//--- close positions and orders at specified time
|
|
|
|
|
if(targetDayOfWeek != -1 && targetMinutes != -1 && targetHour != -1)
|
|
|
|
|
{
|
|
|
|
|
// Buffer in minutes for checking the condition
|
|
|
|
|
int bufferMinutes = 1; // ±1 minute buffer
|
|
|
|
|
// Get current server time
|
|
|
|
|
datetime currentTimeValue = TimeCurrent();
|
|
|
|
|
MqlDateTime currentTime;
|
|
|
|
|
TimeToStruct(currentTimeValue, currentTime); // Convert to MqlDateTime
|
|
|
|
|
// Extract the current hour and minute
|
|
|
|
|
int currentHour = currentTime.hour;
|
|
|
|
|
int currentMinute = currentTime.min;
|
|
|
|
|
// Check if the current day matches the target day
|
|
|
|
|
if(currentTime.day_of_week == targetDayOfWeek || targetDayOfWeek == CLOSE_EVERYDAY)
|
|
|
|
|
{
|
|
|
|
|
// Check if the current time is within the buffer range around the target time
|
|
|
|
|
if(currentHour == targetHour)
|
|
|
|
|
{
|
|
|
|
|
// Check if the current minute falls within the ±1 minute buffer of the target minute
|
|
|
|
|
if(currentMinute >= (targetMinutes - bufferMinutes) && currentMinute <= (targetMinutes + bufferMinutes))
|
|
|
|
|
{
|
|
|
|
|
// If it matches, call CloseAndDeleteAllForSymbol
|
|
|
|
|
if(CloseAndDeleteAllForSymbol())
|
|
|
|
|
{
|
|
|
|
|
// Log or handle the successful close
|
|
|
|
|
Print("Positions and orders closed.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CExpertSignalCustom* customSignal = GetCustomSignal();
|
|
|
|
|
if(customSignal != NULL)
|
|
|
|
|
customSignal.OnTickHandler();
|
2026-07-27 10:58:29 -04:00
|
|
|
//--- updated quotes and indicators
|
2026-07-13 03:23:39 -04:00
|
|
|
if(!Refresh())
|
2026-07-26 23:08:32 -04:00
|
|
|
{
|
2026-07-27 10:58:29 -04:00
|
|
|
if(VerboseMode)
|
|
|
|
|
TCWarnIfSlow("CExpertCustom::OnTick", tick_started_us, EXPERT_TICK_BUDGET_US);
|
2026-07-13 03:23:39 -04:00
|
|
|
return;
|
2026-07-26 23:08:32 -04:00
|
|
|
}
|
2026-07-13 03:23:39 -04:00
|
|
|
//--- expert processing
|
|
|
|
|
Processing();
|
2026-07-27 10:58:29 -04:00
|
|
|
if(VerboseMode)
|
|
|
|
|
TCWarnIfSlow("CExpertCustom::OnTick", tick_started_us, EXPERT_TICK_BUDGET_US);
|
2026-07-13 03:23:39 -04:00
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| OnChartEvent handler |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CExpertCustom::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
|
|
|
|
{
|
|
|
|
|
//--- check process flag
|
|
|
|
|
if(!m_on_chart_event_process)
|
|
|
|
|
return;
|
|
|
|
|
CExpertSignalCustom* customSignal = GetCustomSignal();
|
|
|
|
|
if(customSignal != NULL)
|
|
|
|
|
customSignal.OnChartEventHandler(id, lparam, dparam, sparam);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Close all positions and delete all pending orders for Symbol() |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::CloseAndDeleteAllForSymbol()
|
|
|
|
|
{
|
2026-07-26 23:08:32 -04:00
|
|
|
bool result = false; // Track if any action was successfully performed
|
|
|
|
|
string symbol = m_symbol.Name();
|
|
|
|
|
string reason;
|
|
|
|
|
// Iterate through all positions. The symbol/magic filter is read off the TICKET being closed:
|
|
|
|
|
// PositionSelect(_Symbol) selects "some position on the chart symbol" and says nothing about
|
|
|
|
|
// positionTicket, so the guard it used to stand in for let this close every position on the
|
|
|
|
|
// account - including other symbols' and other EAs' - the moment the chart symbol happened to
|
|
|
|
|
// hold one. Same for the order loop below, whose OrderSelect() succeeded for any ticket at all.
|
2026-07-13 03:23:39 -04:00
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
ulong positionTicket = PositionGetTicket(i);
|
2026-07-26 23:08:32 -04:00
|
|
|
if(positionTicket == 0 || !PositionSelectByTicket(positionTicket))
|
|
|
|
|
continue;
|
|
|
|
|
if(PositionGetString(POSITION_SYMBOL) != symbol)
|
|
|
|
|
continue;
|
|
|
|
|
if(PositionGetInteger(POSITION_MAGIC) != (long)m_magic)
|
|
|
|
|
continue;
|
|
|
|
|
//--- article 2555 #7: a frozen position cannot be closed; retry on the next timer/tick pass
|
|
|
|
|
if(!TCFreezeOkForPosition(symbol, (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE),
|
|
|
|
|
PositionGetDouble(POSITION_SL), PositionGetDouble(POSITION_TP), reason))
|
2026-07-13 03:23:39 -04:00
|
|
|
{
|
2026-07-26 23:08:32 -04:00
|
|
|
TCLog("closeall-frozen-position:" + symbol, __FUNCTION__ + ": " + reason + " - close deferred");
|
|
|
|
|
continue;
|
2026-07-13 03:23:39 -04:00
|
|
|
}
|
2026-07-26 23:08:32 -04:00
|
|
|
if(m_trade.PositionClose(positionTicket))
|
|
|
|
|
result = true;
|
2026-07-13 03:23:39 -04:00
|
|
|
}
|
|
|
|
|
// Iterate through all pending orders
|
|
|
|
|
for(int j = OrdersTotal() - 1; j >= 0; j--)
|
|
|
|
|
{
|
|
|
|
|
ulong orderTicket = OrderGetTicket(j);
|
2026-07-26 23:08:32 -04:00
|
|
|
if(orderTicket == 0)
|
|
|
|
|
continue;
|
|
|
|
|
if(OrderGetString(ORDER_SYMBOL) != symbol)
|
|
|
|
|
continue;
|
|
|
|
|
if(OrderGetInteger(ORDER_MAGIC) != (long)m_magic)
|
|
|
|
|
continue;
|
|
|
|
|
//--- article 2555 #7: nor can a frozen pending order be deleted
|
|
|
|
|
if(!TCFreezeOkForOrder(symbol, (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE),
|
|
|
|
|
OrderGetDouble(ORDER_PRICE_OPEN), reason))
|
2026-07-13 03:23:39 -04:00
|
|
|
{
|
2026-07-26 23:08:32 -04:00
|
|
|
TCLog("closeall-frozen-order:" + symbol, __FUNCTION__ + ": " + reason + " - delete deferred");
|
|
|
|
|
continue;
|
2026-07-13 03:23:39 -04:00
|
|
|
}
|
2026-07-26 23:08:32 -04:00
|
|
|
if(m_trade.OrderDelete(orderTicket))
|
|
|
|
|
result = true;
|
2026-07-13 03:23:39 -04:00
|
|
|
}
|
|
|
|
|
return result; // Return true if any action was performed, false otherwise
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| OnTimer handler |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CExpertCustom::OnTimer(void)
|
|
|
|
|
{
|
|
|
|
|
//--- check process flag
|
|
|
|
|
if(!m_on_timer_process)
|
|
|
|
|
return;
|
2026-07-26 23:08:32 -04:00
|
|
|
//--- Article 2555 #16 - keep an eye on the memory the program holds. The AI signals allocate the
|
|
|
|
|
//--- bulk of it (network weights, replay buffers), so the timer - not the tick - is the right place
|
|
|
|
|
//--- to sample it. Throttled and diagnostic only; nothing is freed or truncated on the back of it.
|
2026-07-27 10:58:29 -04:00
|
|
|
if(VerboseMode)
|
|
|
|
|
TCWarnIfMemoryAbove(EXPERT_MEMORY_SOFT_LIMIT_MB);
|
2026-07-13 03:23:39 -04:00
|
|
|
CExpertSignalCustom* customSignal = GetCustomSignal();
|
2026-07-26 12:12:14 -04:00
|
|
|
if(customSignal == NULL)
|
|
|
|
|
return;
|
|
|
|
|
if(!dbm.OpenDatabase())
|
2026-07-13 03:23:39 -04:00
|
|
|
{
|
2026-07-26 12:12:14 -04:00
|
|
|
Print(__FUNCTION__ + ": Failed to open database, skipping this timer cycle's signal processing/weight update.");
|
|
|
|
|
return;
|
2026-07-13 03:23:39 -04:00
|
|
|
}
|
2026-07-26 12:12:14 -04:00
|
|
|
customSignal.ProcessBufferedSignals();
|
|
|
|
|
if(!customSignal.UpdateSignalsWeights())
|
|
|
|
|
Print(__FUNCTION__ + ": UpdateSignalsWeights failed, DB-derived filter weights not refreshed this cycle.");
|
|
|
|
|
// This function owns the connection it opened above - ProcessBufferedSignals() deliberately
|
|
|
|
|
// leaves it open so UpdateSignalsWeights() can reuse the same handle.
|
|
|
|
|
if(!IsBacktesting)
|
|
|
|
|
dbm.CloseDatabase();
|
2026-07-13 03:23:39 -04:00
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Initialization trade object |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool CExpertCustom::InitTrade(ulong magic, CExpertTrade *trade = NULL)
|
|
|
|
|
{
|
|
|
|
|
if(m_trade != NULL)
|
|
|
|
|
delete m_trade;
|
|
|
|
|
//---
|
|
|
|
|
if(trade == NULL)
|
|
|
|
|
{
|
|
|
|
|
if((m_trade = new CExpertTrade) == NULL)
|
|
|
|
|
return(false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
m_trade = trade;
|
|
|
|
|
//--- tune trade object
|
|
|
|
|
m_trade.SetSymbol(GetPointer(m_symbol));
|
|
|
|
|
m_trade.SetExpertMagicNumber(magic);
|
|
|
|
|
m_trade.SetMarginMode();
|
|
|
|
|
m_trade.SetAsyncMode(true);
|
|
|
|
|
//--- set default deviation for trading in adjusted points
|
|
|
|
|
m_trade.SetDeviationInPoints((ulong)(3 * m_adjusted_point / m_symbol.Point()));
|
|
|
|
|
//--- ok
|
|
|
|
|
return(true);
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|