forked from rosh/MQL5Book
1142 lines
35 KiB
MQL5
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;
| |
}
| |
};
| |
| |
//+------------------------------------------------------------------+
|