1142 行
35 KiB
MQL5
1142 行
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;
| |||
}
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
|