MQL5Book/Include/MqlTradeSync.mqh
super.admin 1c8e83ce31 convert
2025-05-30 16:09:41 +02:00

1142 lines
35 KiB
MQL5

//+------------------------------------------------------------------+
//| MqlTradeSync.mqh |
//| Copyright 2022, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#include "TradeRetcode.mqh"
#include "TradeUtils.mqh"
// until SHOW_WARNINGS not defined in your code,
// the following default definition of WARNING will disable warnings;
// you can define SHOW_WARNINGS and also redefine WARNING
// in your code with special logging
#ifndef SHOW_WARNINGS
#ifndef WARNING
#define WARNING(X) // this empty macro to disable warnings by default
#endif
#endif
#include "Warnings.mqh"
#ifndef MAX_REQUOTES
#define MAX_REQUOTES 10
#endif
// RETURN macro can be predefined in your source code as empty thing
// #define RETURN(X)
// to prevent early returns after failed checkups in MqlStructRequestSync methods,
// so incorrect requests will be sent and retcodes received from the server
#ifndef RETURN
#define RETURN(X) return(X)
#endif
//+------------------------------------------------------------------+
//| Data type for results of various trade requests, |
//| performed in a "synced" manner - with awaiting complete data. |
//+------------------------------------------------------------------+
struct MqlTradeResultSync: public MqlTradeResult
{
ulong position;
double partial;
MqlTradeResultSync()
{
ZeroMemory(this);
}
typedef bool (*condition)(MqlTradeResultSync &ref);
bool wait(condition p, const ulong msc = 1000)
{
const ulong start = GetTickCount64();
bool success;
while(!(success = p(this)) && GetTickCount64() - start < msc);
if(GetTickCount64() - start >= msc)
{
// on timeout update retcode if it was successful
if(retcode >= TRADE_RETCODE_PLACED && retcode <= TRADE_RETCODE_DONE_PARTIAL)
{
WARNING("wait internal timeout " + EnumToString((TRADE_RETCODE)retcode));
retcode = TRADE_RETCODE_TIMEOUT;
}
}
return success;
}
// NB: use static because MQL5 does not support member methods in typedefs so far
static bool orderExist(MqlTradeResultSync &ref)
{
return OrderSelect(ref.order) || HistoryOrderSelect(ref.order);
}
static bool positionExist(MqlTradeResultSync &ref)
{
ulong posid, ticket;
if(HistoryOrderGetInteger(ref.order, ORDER_POSITION_ID, posid))
{
// there is no a built-in way to get ticket from id
ticket = TU::PositionSelectById(posid);
if(HistorySelectByPosition(posid))
{
if(ticket != posid)
{
WARNING("Position ticket <> id: " + (string)ticket + ", " + (string)posid);
}
ref.position = ticket; // posid = ticket in many cases, but not always
for(int i = HistoryDealsTotal() - 1; i >= 0; i--)
{
const ulong d = HistoryDealGetTicket(i);
if(HistoryDealGetInteger(d, DEAL_ORDER) == ref.order)
{
ref.deal = d;
ref.price = HistoryDealGetDouble(d, DEAL_PRICE);
ref.volume = HistoryDealGetDouble(d, DEAL_VOLUME);
}
}
return true;
}
}
return false;
}
static bool checkSLTP(MqlTradeResultSync &ref)
{
if(PositionSelectByTicket(ref.position))
{
return TU::Equal(PositionGetDouble(POSITION_SL), ref.bid) // sl from request
&& TU::Equal(PositionGetDouble(POSITION_TP), ref.ask); // tp from request
}
else
{
WARNING("PositionSelectByTicket failed: P=" + (string)ref.position);
}
return false;
}
static bool positionRemoved(MqlTradeResultSync &ref)
{
if(ref.partial)
{
return PositionSelectByTicket(ref.position)
&& TU::Equal(PositionGetDouble(POSITION_VOLUME), ref.partial);
}
return !PositionSelectByTicket(ref.position);
}
static bool orderModified(MqlTradeResultSync &ref)
{
if(!(OrderSelect(ref.order) || HistoryOrderSelect(ref.order)))
{
WARNING("OrderSelect failed: #=" + (string)ref.order);
return false;
}
return TU::Equal(ref.bid, OrderGetDouble(ORDER_SL))
&& TU::Equal(ref.ask, OrderGetDouble(ORDER_TP))
&& TU::Equal(ref.price, OrderGetDouble(ORDER_PRICE_OPEN))
&& TU::Equal(ref.volume, OrderGetDouble(ORDER_PRICE_STOPLIMIT));
}
static bool orderRemoved(MqlTradeResultSync &ref)
{
return !OrderSelect(ref.order) && HistoryOrderSelect(ref.order);
}
bool placed(const ulong msc = 1000)
{
if(retcode != TRADE_RETCODE_DONE
&& retcode != TRADE_RETCODE_DONE_PARTIAL)
{
return false;
}
if(!wait(orderExist, msc))
{
WARNING("Waiting for order: #" + (string)order);
return false;
}
return true;
}
bool opened(const ulong msc = 1000)
{
if(retcode != TRADE_RETCODE_DONE
&& retcode != TRADE_RETCODE_DONE_PARTIAL)
{
return false;
}
if(!wait(orderExist, msc))
{
WARNING("Waiting for order: #" + (string)order);
}
if(deal != 0 && HistoryDealSelect(deal))
{
WARNING("Waiting for position for deal D=" + (string)deal);
ulong posid;
if(HistoryDealGetInteger(deal, DEAL_POSITION_ID, posid))
{
position = TU::PositionSelectById(posid);
return true;
}
}
if(!wait(positionExist, msc))
{
WARNING("Timeout");
return false;
}
return true;
}
bool adjusted(const ulong msc = 1000)
{
if(retcode != TRADE_RETCODE_DONE && retcode != TRADE_RETCODE_PLACED)
{
return false;
}
if(!wait(checkSLTP, msc))
{
WARNING("SL/TP modification timeout: P=" + (string)position);
return false;
}
return true;
}
bool closed(const ulong msc = 1000)
{
if(retcode != TRADE_RETCODE_DONE)
{
return false;
}
if(!wait(positionRemoved, msc))
{
WARNING("Position removal timeout: P=" + (string)position);
return false;
}
return true;
}
bool modified(const ulong msc = 1000)
{
if(retcode != TRADE_RETCODE_DONE && retcode != TRADE_RETCODE_PLACED)
{
return false;
}
if(!wait(orderModified, msc))
{
WARNING("Order not found in environment: #" + (string)order);
return false;
}
return true;
}
bool removed(const ulong msc = 1000)
{
if(retcode != TRADE_RETCODE_DONE)
{
return false;
}
if(!wait(orderRemoved, msc))
{
WARNING("Order removal timeout: #=" + (string)order);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| Data type for streamlined execution of various trade requests. |
//| Provides proper preparation of input data for the fields. |
//+------------------------------------------------------------------+
struct MqlTradeRequestSync: public MqlTradeRequest
{
MqlTradeResultSync result;
ulong timeout;
double partial; // remaining volume after partial close
static bool AsyncEnabled;
MqlTradeRequestSync(const string s = NULL, const ulong t = 1000)
{
ZeroMemory(this);
timeout = t;
symbol = s == NULL ? _Symbol : s;
}
// Common methods
bool completed()
{
if(action == TRADE_ACTION_DEAL)
{
if(position == 0)
{
const bool success = result.opened(timeout);
if(success) position = result.position;
return success;
}
else
{
result.position = position;
result.partial = partial;
return result.closed(timeout);
}
}
else if(action == TRADE_ACTION_SLTP)
{
// pass original data from request to compare with online position,
// this is required because result for TRADE_ACTION_SLTP contains retcode only
result.position = position;
result.bid = sl;
result.ask = tp;
return result.adjusted(timeout);
}
else if(action == TRADE_ACTION_CLOSE_BY)
{
return result.closed(timeout);
}
else if(action == TRADE_ACTION_PENDING)
{
return result.placed(timeout);
}
else if(action == TRADE_ACTION_MODIFY)
{
result.order = order;
result.bid = sl;
result.ask = tp;
result.price = price;
result.volume = stoplimit;
return result.modified(timeout);
}
else if(action == TRADE_ACTION_REMOVE)
{
result.order = order;
return result.removed(timeout);
}
return false;
}
static bool requote(const uint retcode)
{
switch(retcode)
{
case TRADE_RETCODE_REQUOTE:
case TRADE_RETCODE_PRICE_CHANGED:
case TRADE_RETCODE_PRICE_OFF:
return true;
}
return false;
}
// Market (immediate) trades
ulong buy(const double lot, const double p = 0,
const double stop = 0, const double take = 0)
{
return buy(symbol, lot, p, stop, take);
}
ulong sell(const double lot, const double p = 0,
const double stop = 0, const double take = 0)
{
return sell(symbol, lot, p, stop, take);
}
ulong buy(const string name, const double lot, const double p = 0,
const double stop = 0, const double take = 0)
{
type = ORDER_TYPE_BUY;
position = 0;
return _market(name, lot, p, stop, take);
}
ulong sell(const string name, const double lot, const double p = 0,
const double stop = 0, const double take = 0)
{
type = ORDER_TYPE_SELL;
position = 0;
return _market(name, lot, p, stop, take);
}
// Position modifications
bool adjust(const ulong pos, const double stop = 0, const double take = 0)
{
if(!PositionSelectByTicket(pos))
{
WARNING("No position: P=" + (string)pos);
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
return _adjust(pos, PositionGetString(POSITION_SYMBOL), stop, take);
}
bool adjust(const string name, const double stop = 0, const double take = 0)
{
if(!PositionSelect(name))
{
WARNING("No position: " + name);
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
return _adjust(PositionGetInteger(POSITION_TICKET), name, stop, take);
}
bool adjust(const double stop = 0, const double take = 0)
{
if(position != 0)
{
if(!PositionSelectByTicket(position))
{
WARNING("No position with ticket P=" + (string)position);
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
const string s = PositionGetString(POSITION_SYMBOL);
if(symbol != NULL && symbol != s)
{
WARNING("Position symbol is adjusted from " + symbol + " to " + s);
}
symbol = s;
}
else if(AccountInfoInteger(ACCOUNT_MARGIN_MODE) != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING
&& StringLen(symbol) > 0)
{
if(!PositionSelect(symbol))
{
WARNING("Can't select position for " + symbol);
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
position = PositionGetInteger(POSITION_TICKET);
}
else
{
WARNING("Neither position ticket nor symbol was provided");
result.retcode = TRADE_RETCODE_INVALID;
RETURN(false);
}
return _adjust(position, symbol, stop, take);
}
// Position closing
bool close(const string name, const double lot = 0)
{
if(!PositionSelect(name))
{
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
return close(PositionGetInteger(POSITION_TICKET), lot);
}
bool close(const ulong ticket, const double lot = 0)
{
if(!PositionSelectByTicket(ticket))
{
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
position = ticket;
magic = PositionGetInteger(POSITION_MAGIC);
symbol = PositionGetString(POSITION_SYMBOL);
type = (ENUM_ORDER_TYPE)(PositionGetInteger(POSITION_TYPE) ^ 1);
price = 0; // automatic setup for current price
const double total = lot == 0 ? PositionGetDouble(POSITION_VOLUME) : TU::NormalizeLot(lot, symbol);
double remaining = total;
partial = PositionGetDouble(POSITION_VOLUME) - total;
int k = 0;
bool sent = false;
do
{
if(total > SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX))
{
volume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
remaining = total - ++k * volume; // keep precision high
}
else
{
volume = remaining;
remaining = 0;
}
sent = _market(symbol, volume);
}
while(remaining > 0 && sent);
return sent;
}
bool closeby(const ulong ticket1, const ulong ticket2)
{
if(!PositionSelectByTicket(ticket1))
{
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
const double volume1 = PositionGetDouble(POSITION_VOLUME);
const string sym1 = PositionGetString(POSITION_SYMBOL);
if(!PositionSelectByTicket(ticket2))
{
result.retcode = TRADE_RETCODE_POSITION_CLOSED;
RETURN(false);
}
const double volume2 = PositionGetDouble(POSITION_VOLUME);
const string sym2 = PositionGetString(POSITION_SYMBOL);
if(sym1 != sym2)
{
WARNING(StringFormat("can't 'close by' positions of different symbols: %s and %s",
sym1, sym2));
result.retcode = TRADE_RETCODE_INVALID;
RETURN(false);
}
if((SymbolInfoInteger(sym1, SYMBOL_ORDER_MODE) & SYMBOL_ORDER_CLOSEBY) == 0)
{
WARNING("'close by' not allowed for " + sym1);
result.retcode = TRADE_RETCODE_INVALID_ORDER;
RETURN(false);
}
action = TRADE_ACTION_CLOSE_BY;
position = ticket1;
position_by = ticket2;
ZeroMemory(result);
if(volume1 != volume2)
{
// remember which position should vanish
if(volume1 < volume2)
result.position = ticket1;
else
result.position = ticket2;
}
return orderSend(this, result);
}
// Pending orders placing
ulong buyStop(const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
return buyStop(symbol, lot, p, stop, take, duration, until);
}
ulong sellStop(const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
return sellStop(symbol, lot, p, stop, take, duration, until);
}
ulong buyStop(const string name, const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_BUY_STOP;
return _pending(name, lot, p, stop, take, duration, until);
}
ulong sellStop(const string name, const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_SELL_STOP;
return _pending(name, lot, p, stop, take, duration, until);
}
ulong buyLimit(const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
return buyLimit(symbol, lot, p, stop, take, duration, until);
}
ulong sellLimit(const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
return sellLimit(symbol, lot, p, stop, take, duration, until);
}
ulong buyLimit(const string name, const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_BUY_LIMIT;
return _pending(name, lot, p, stop, take, duration, until);
}
ulong sellLimit(const string name, const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_SELL_LIMIT;
return _pending(name, lot, p, stop, take, duration, until);
}
ulong buyStopLimit(const double lot, const double p,
const double origin,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_BUY_STOP_LIMIT;
return _pending(symbol, lot, p, stop, take, duration, until, origin);
}
ulong sellStopLimit(const double lot, const double p,
const double origin,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_SELL_STOP_LIMIT;
return _pending(symbol, lot, p, stop, take, duration, until, origin);
}
ulong buyStopLimit(const string name, const double lot, const double p,
const double origin,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_BUY_STOP_LIMIT;
return _pending(name, lot, p, stop, take, duration, until, origin);
}
ulong sellStopLimit(const string name, const double lot, const double p,
const double origin,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0)
{
type = ORDER_TYPE_SELL_STOP_LIMIT;
return _pending(name, lot, p, stop, take, duration, until, origin);
}
// Pending order modification
bool modify(const ulong ticket,
const double p, const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0,
const double origin = 0)
{
if(!OrderSelect(ticket))
{
result.retcode = TRADE_RETCODE_INVALID;
RETURN(false);
}
action = TRADE_ACTION_MODIFY;
order = ticket;
// we need the following fields for check-ups inside subfunctions
type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
symbol = OrderGetString(ORDER_SYMBOL);
volume = OrderGetDouble(ORDER_VOLUME_CURRENT);
if(!setVolumePrices(volume, p, stop, take, origin)) return false;
if(!setExpiration(duration, until)) return false;
ZeroMemory(result);
return orderSend(this, result);
}
// Pending order removal
bool remove(const ulong ticket)
{
action = TRADE_ACTION_REMOVE;
order = ticket;
ZeroMemory(result);
if(!OrderSelect(ticket))
{
if(HistoryOrderSelect(ticket))
{
WARNING(StringFormat("Order %lld already removed", ticket));
return true;
}
result.retcode = TRADE_RETCODE_INVALID;
RETURN(false);
}
return orderSend(this, result);
}
// General purpose methods, name starts with underscore '_'
// (made public for advanced use only, use with caution)
// Prerequisite: field 'type' should be filled already with a pending order type
ulong _pending(const string name, const double lot, const double p,
const double stop = 0, const double take = 0,
ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0,
const double origin = 0)
{
if(!TU::IsPendingType(type))
{
result.retcode = TRADE_RETCODE_INVALID_ORDER;
RETURN(0);
}
action = TRADE_ACTION_PENDING;
if(!setSymbol(name)) return 0;
if(!setVolumePrices(lot, p, stop, take, origin)) return 0;
if(!setExpiration(duration, until)) return 0;
/*
Reference
ENUM_ORDER_TYPE (only pending are used here):
[ORDER_TYPE_BUY] 0 0
[ORDER_TYPE_SELL] 1 0
ORDER_TYPE_BUY_LIMIT 2 1
ORDER_TYPE_SELL_LIMIT 3 1
ORDER_TYPE_BUY_STOP 4 2
ORDER_TYPE_SELL_STOP 5 2
ORDER_TYPE_BUY_STOP_LIMIT 6 3
ORDER_TYPE_SELL_STOP_LIMIT 7 3
[ORDER_TYPE_CLOSE_BY] 8 4
SYMBOL_ORDER_MODE bits (only pending are used here):
[SYMBOL_ORDER_MARKET] 1
SYMBOL_ORDER_LIMIT 2
SYMBOL_ORDER_STOP 4
SYMBOL_ORDER_STOP_LIMIT 8
[SYMBOL_ORDER_SL] 16
[SYMBOL_ORDER_TP] 32
[SYMBOL_ORDER_CLOSEBY] 64
*/
if((SymbolInfoInteger(name, SYMBOL_ORDER_MODE) & (1 << (type / 2))) == 0)
{
WARNING(StringFormat("pending orders %s not allowed for %s",
EnumToString(type), name));
result.retcode = TRADE_RETCODE_INVALID_ORDER;
RETURN(0);
}
if(!checkMode())
{
WARNING(StringFormat("%s not allowed for %s trade mode", EnumToString(type), symbol));
RETURN(0);
}
ZeroMemory(result);
if(orderSend(this, result)) return result.order ? result.order :
(result.retcode == TRADE_RETCODE_PLACED ? result.request_id : 0);
return 0;
}
// Prerequisite: field 'type' should be filled already with a market order type
ulong _market(const string name, const double lot, const double p = 0,
const double stop = 0, const double take = 0)
{
if(!TU::IsMarketType(type))
{
result.retcode = TRADE_RETCODE_INVALID_ORDER;
RETURN(0);
}
action = TRADE_ACTION_DEAL;
if(!setSymbol(name)) return 0;
if(!setVolumePrices(lot, p, stop, take)) return 0;
if((SymbolInfoInteger(name, SYMBOL_ORDER_MODE) & SYMBOL_ORDER_MARKET) == 0)
{
WARNING("market orders not allowed for " + name);
result.retcode = TRADE_RETCODE_INVALID_ORDER;
RETURN(0);
}
if(!checkMode())
{
WARNING(StringFormat("%s not allowed for %s trade mode", EnumToString(type), symbol));
RETURN(0);
}
int count = 0;
do
{
ZeroMemory(result);
if(orderSend(this, result)) return result.order ? result.order :
(result.retcode == TRADE_RETCODE_PLACED ? result.request_id : 0);
// automatic price means automatic handling of requotes
if(requote(result.retcode))
{
WARNING("Requote N" + (string)++count);
if(p == 0)
{
price = TU::GetCurrentPrice(type, symbol);
}
}
}
while(p == 0 && requote(result.retcode) && count < MAX_REQUOTES);
return 0;
}
private:
// Prerequisite: Position 'pos' should be selected
bool _adjust(const ulong pos, const string name,
const double stop = 0, const double take = 0)
{
if(TU::Equal(stop, PositionGetDouble(POSITION_SL))
&& TU::Equal(take, PositionGetDouble(POSITION_TP)))
{
WARNING("SLTP already set as: " + TU::StringOf(stop) + ", " + TU::StringOf(take));
result.retcode = TRADE_RETCODE_NO_CHANGES;
RETURN(false);
}
action = TRADE_ACTION_SLTP;
position = pos;
type = (ENUM_ORDER_TYPE)PositionGetInteger(POSITION_TYPE); // only needed for our SLTP check
if(!setSymbol(name)) return false;
if(!setSLTP(stop, take)) return false;
ZeroMemory(result);
return orderSend(this, result);
}
void setFilling()
{
const int filling = (int)SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
const bool market = SymbolInfoInteger(symbol, SYMBOL_TRADE_EXEMODE) == SYMBOL_TRADE_EXECUTION_MARKET;
// type_filling may be already assigned,
// so matching bits mean that selected mode is allowed
if(((type_filling + 1) & filling) != 0
|| (type_filling == ORDER_FILLING_RETURN && !market)) return; // ok
if((filling & SYMBOL_FILLING_FOK) != 0)
{
type_filling = ORDER_FILLING_FOK;
}
else if((filling & SYMBOL_FILLING_IOC) != 0)
{
type_filling = ORDER_FILLING_IOC;
}
else
{
type_filling = ORDER_FILLING_RETURN;
}
}
bool setSymbol(const string s)
{
if(s == NULL)
{
if(symbol == NULL)
{
WARNING("symbol is NULL, defaults to " + _Symbol);
symbol = _Symbol;
setFilling();
}
else
{
WARNING("new symbol is NULL, current used " + symbol);
}
}
else
{
// check if provided symbol exists
if(SymbolInfoDouble(s, SYMBOL_POINT) == 0)
{
WARNING("incorrect symbol " + s);
result.retcode = TRADE_RETCODE_INVALID;
RETURN(false);
}
if(symbol != s)
{
symbol = s;
setFilling();
}
}
return true;
}
bool setVolumePrices(const double v, const double p,
const double stop, const double take,
const double origin = 0)
{
TU::SymbolMetrics sm(symbol);
volume = sm.volume(v); // NB! can become 0 if less then minimal
if(volume == 0)
{
WARNING("volume is 0 after normalization");
result.retcode = TRADE_RETCODE_INVALID_VOLUME;
RETURN(false);
}
const double current = TU::GetCurrentPrice(type, symbol);
if(p != 0)
{
price = sm.price(p);
if(TU::IsPendingType(type) && !TU::Equal(price, current))
{
if(price > current)
{
if(type == ORDER_TYPE_BUY_LIMIT
|| type == ORDER_TYPE_SELL_STOP
|| type == ORDER_TYPE_SELL_STOP_LIMIT)
{
WARNING(StringFormat("misplaced %s order: %s should be below current price %s",
EnumToString(type),
sm.StringOf(price), sm.StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_PRICE;
RETURN(false);
}
}
else
{
if(type == ORDER_TYPE_SELL_LIMIT
|| type == ORDER_TYPE_BUY_STOP
|| type == ORDER_TYPE_BUY_STOP_LIMIT)
{
WARNING(StringFormat("misplaced %s order: %s should be above current price %s",
EnumToString(type),
sm.StringOf(price), sm.StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_PRICE;
RETURN(false);
}
}
}
}
else
{
price = sm.price(current);
}
stoplimit = sm.price(origin); // filled for *_STOP_LIMIT orders only
return setSLTP(stop, take);
}
bool setSLTP(const double stop, const double take)
{
if((SymbolInfoInteger(symbol, SYMBOL_ORDER_MODE) & SYMBOL_ORDER_SL) == 0
&& stop != 0)
{
WARNING("Stop Loss not allowed for " + symbol);
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
if((SymbolInfoInteger(symbol, SYMBOL_ORDER_MODE) & SYMBOL_ORDER_TP) == 0
&& take != 0)
{
WARNING("Take Profit not allowed for " + symbol);
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
TU::SymbolMetrics sm(symbol);
TU::TradeDirection dir(type);
// for all orders except for *_STOP_LIMIT a nonzero specific price
// or current market price is used as reference point for sl/tp check-up;
// for *_STOP_LIMIT-orders sl/tp is marked against stoplimit, which is nonzero
// only for these 2 types of orders
const double current = stoplimit == 0 ?
(price == 0 ? TU::GetCurrentPrice((ENUM_ORDER_TYPE)(type ^ 1), symbol) : price) :
stoplimit;
const int level = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
const int freeze = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL);
if(stop != 0)
{
sl = sm.price(stop);
if(!dir.worse(sl, current))
{
WARNING(StringFormat("wrong SL (%s) against price (%s)",
TU::StringOf(sl), TU::StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
if(level > 0 && fabs(current - sl) < level * sm.point)
{
WARNING(StringFormat("too close SL (%s) to current price (%s)",
TU::StringOf(sl), TU::StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
if(action == TRADE_ACTION_SLTP
&& freeze > 0 && fabs(current - sl) < freeze * sm.point)
{
WARNING(StringFormat("frozen SL (%s) near current price (%s)",
TU::StringOf(sl), TU::StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
}
else
{
sl = 0; // no SL
}
if(take != 0)
{
tp = sm.price(take);
if(!dir.better(tp, current))
{
WARNING(StringFormat("wrong TP (%s) against price (%s)",
TU::StringOf(tp), TU::StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
if(level > 0 && fabs(current - tp) < level * sm.point)
{
WARNING(StringFormat("too close TP (%s) to current price (%s)",
TU::StringOf(tp), TU::StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
if(action == TRADE_ACTION_SLTP
&& freeze > 0 && fabs(current - tp) < freeze * sm.point)
{
WARNING(StringFormat("forzen TP (%s) near current price (%s)",
TU::StringOf(tp), TU::StringOf(current)));
result.retcode = TRADE_RETCODE_INVALID_STOPS;
RETURN(false);
}
}
else
{
tp = 0; // no TP
}
return true;
}
bool setExpiration(ENUM_ORDER_TYPE_TIME duration = -1, datetime until = 0) // ORDER_TIME_GTC
{
// if something already assigned to the struct fields,
// and parameters are default (unspecified), keep existing settings
if(duration == -1)
{
duration = type_time != ORDER_TIME_GTC ? type_time : ORDER_TIME_GTC;
}
if(expiration != 0 && until == 0)
{
until = expiration;
}
/*
Reference
Available modes (bits) in SYMBOL_EXPIRATION_MODE:
SYMBOL_EXPIRATION_GTC = 1
SYMBOL_EXPIRATION_DAY = 2
SYMBOL_EXPIRATION_SPECIFIED = 4
SYMBOL_EXPIRATION_SPECIFIED_DAY = 8
ENUM_ORDER_TYPE_TIME:
ORDER_TIME_GTC = 0
ORDER_TIME_DAY = 1
ORDER_TIME_SPECIFIED = 2
ORDER_TIME_SPECIFIED_DAY = 3
*/
const int modes = (int)SymbolInfoInteger(symbol, SYMBOL_EXPIRATION_MODE);
if(((1 << duration) & modes) != 0)
{
type_time = duration;
if((duration == ORDER_TIME_SPECIFIED || duration == ORDER_TIME_SPECIFIED_DAY)
&& until == 0)
{
WARNING(StringFormat("datetime is 0, but it's required for order expiration mode %s",
EnumToString(duration)));
result.retcode = TRADE_RETCODE_INVALID_EXPIRATION;
RETURN(false);
}
if(until > 0 && until <= TimeTradeServer())
{
WARNING(StringFormat("expiration datetime %s is in past, server time is %s",
TimeToString(until), TimeToString(TimeTradeServer())));
result.retcode = TRADE_RETCODE_INVALID_EXPIRATION;
RETURN(false);
}
expiration = until;
}
else
{
WARNING(StringFormat("order expiration mode %s is not allowed for %s",
EnumToString(duration), symbol));
result.retcode = TRADE_RETCODE_INVALID_EXPIRATION;
RETURN(false);
}
return true;
}
bool checkMode()
{
const ENUM_SYMBOL_TRADE_MODE mode =
(ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE);
if(mode == SYMBOL_TRADE_MODE_DISABLED)
{
result.retcode = TRADE_RETCODE_TRADE_DISABLED;
return false;
}
const bool closure = position != 0;
if(mode == SYMBOL_TRADE_MODE_CLOSEONLY && !closure)
{
result.retcode = TRADE_RETCODE_CLOSE_ONLY;
return false;
}
if(mode == SYMBOL_TRADE_MODE_LONGONLY && !(TU::IsBuyType(type) ^ closure))
{
result.retcode = TRADE_RETCODE_LONG_ONLY;
return false;
}
if(mode == SYMBOL_TRADE_MODE_SHORTONLY && !(TU::IsSellType(type) ^ closure))
{
result.retcode = TRADE_RETCODE_SHORT_ONLY;
return false;
}
return true;
}
bool orderSend(const MqlTradeRequest &req, MqlTradeResult &res)
{
return AsyncEnabled ? OrderSendAsync(req, res) : OrderSend(req, res);
}
};
static bool MqlTradeRequestSync::AsyncEnabled = false;
//+------------------------------------------------------------------+
//| Helper class for switching off async mode atomatically |
//+------------------------------------------------------------------+
class AsyncSwitcher
{
public:
AsyncSwitcher(const bool enabled = true)
{
MqlTradeRequestSync::AsyncEnabled = enabled;
}
~AsyncSwitcher()
{
MqlTradeRequestSync::AsyncEnabled = false;
}
};
//+------------------------------------------------------------------+