350 lines
12 KiB
MQL5
350 lines
12 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| TradeUtils.mqh |
|
||
|
//| Copyright 2022, MetaQuotes Ltd. |
|
||
|
//| https://www.mql5.com |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#include "TradeRetcode.mqh"
|
||
|
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Trade Utilities |
|
||
|
//+------------------------------------------------------------------+
|
||
|
namespace TU
|
||
|
{
|
||
|
//+---------------------------------------------------------------+
|
||
|
//| Helper class to normalize prices/volumes of specific symbol |
|
||
|
//+---------------------------------------------------------------+
|
||
|
class SymbolMetrics
|
||
|
{
|
||
|
public:
|
||
|
const string symbol;
|
||
|
const double point;
|
||
|
const int digits;
|
||
|
const int lotDigits;
|
||
|
|
||
|
SymbolMetrics(const string s): symbol(s),
|
||
|
point(SymbolInfoDouble(s, SYMBOL_POINT)),
|
||
|
digits((int)SymbolInfoInteger(s, SYMBOL_DIGITS)),
|
||
|
lotDigits((int)MathLog10(1.0 / SymbolInfoDouble(s, SYMBOL_VOLUME_STEP))) { }
|
||
|
|
||
|
SymbolMetrics(const int d, const int v): symbol(NULL), digits(d), lotDigits(v) { }
|
||
|
|
||
|
double price(const double p)
|
||
|
{
|
||
|
return symbol != NULL ? TU::NormalizePrice(p, symbol) : NormalizeDouble(p, digits);
|
||
|
}
|
||
|
|
||
|
double volume(const double v)
|
||
|
{
|
||
|
return symbol != NULL ? TU::NormalizeLot(v, symbol) : NormalizeDouble(v, lotDigits);
|
||
|
}
|
||
|
|
||
|
string StringOf(const double v, const int d = INT_MAX)
|
||
|
{
|
||
|
return DoubleToString(v, d == INT_MAX ? digits : d);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//+---------------------------------------------------------------+
|
||
|
//| Unified estimation of 2 prices as profit or loss depending |
|
||
|
//| from trading direction buy or sell |
|
||
|
//+---------------------------------------------------------------+
|
||
|
struct TradeDirection
|
||
|
{
|
||
|
const bool direction; // 0/false - buy, 1/true - sell
|
||
|
TradeDirection(const bool sell = false): direction(sell) { }
|
||
|
TradeDirection(const ENUM_ORDER_TYPE type): direction(IsSellType(type)) { }
|
||
|
|
||
|
// p1 is better than p2
|
||
|
bool better(const double p1, const double p2) const
|
||
|
{
|
||
|
if(direction) return p1 < p2;
|
||
|
return p1 > p2;
|
||
|
}
|
||
|
|
||
|
// p1 is worse than p2
|
||
|
bool worse(const double p1, const double p2) const
|
||
|
{
|
||
|
if(direction) return p1 > p2;
|
||
|
return p1 < p2;
|
||
|
}
|
||
|
|
||
|
int positive() const
|
||
|
{
|
||
|
return direction ? -1 : +1;
|
||
|
}
|
||
|
|
||
|
int negative() const
|
||
|
{
|
||
|
return direction ? +1 : -1;
|
||
|
}
|
||
|
|
||
|
double positive(const double p0, const double move) const
|
||
|
{
|
||
|
if(direction) return p0 - move;
|
||
|
return p0 + move;
|
||
|
}
|
||
|
|
||
|
double negative(const double p0, const double move) const
|
||
|
{
|
||
|
if(direction) return p0 + move;
|
||
|
return p0 - move;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//+---------------------------------------------------------------+
|
||
|
//| Quick and dirty beautifier of floating numbers, which produce |
|
||
|
//| strings with endless fractional part with '0's or '9's |
|
||
|
//+---------------------------------------------------------------+
|
||
|
string StringOf(const double number)
|
||
|
{
|
||
|
string str = (string)number;
|
||
|
const int n = StringLen(str);
|
||
|
if(n >= 16)
|
||
|
{
|
||
|
const int e = StringFind(str, "e");
|
||
|
if(e > 0)
|
||
|
{
|
||
|
string sign = "";
|
||
|
string num = StringSubstr(str, 0, e);
|
||
|
if(num[0] == '-')
|
||
|
{
|
||
|
num = StringSubstr(num, 1);
|
||
|
sign = "-";
|
||
|
}
|
||
|
StringReplace(num, ".", "");
|
||
|
const int nexp = (int)StringToInteger(StringSubstr(str, e + 1));
|
||
|
string zeros;
|
||
|
StringInit(zeros, fabs(nexp) - 1, '0');
|
||
|
if(nexp > 0)
|
||
|
{
|
||
|
str = sign + num + zeros+ ".0";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
str = sign + "0." + zeros + num;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int j = n - 2;
|
||
|
string s2 = StringSubstr(str, 0, n - 1);
|
||
|
const uchar c = (uchar)s2[j];
|
||
|
if(c == '0' || c == '9')
|
||
|
{
|
||
|
while(--j >= 0 && s2[j] == c);
|
||
|
|
||
|
if(j + 1 < n - 2)
|
||
|
{
|
||
|
StringSetLength(s2, j + 1);
|
||
|
if(c == '9')
|
||
|
{
|
||
|
const ushort dig = (ushort)s2[j];
|
||
|
if(dig == '.')
|
||
|
{
|
||
|
StringSetCharacter(s2, j - 1, (ushort)(s2[j - 1] + 1));
|
||
|
s2 += "0";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StringSetCharacter(s2, j, (ushort)(dig + 1));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return s2;
|
||
|
}
|
||
|
}
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
string StringOf(const MqlTradeRequest &r)
|
||
|
{
|
||
|
SymbolMetrics p(r.symbol);
|
||
|
|
||
|
// main block: action, symbol, type
|
||
|
string text = EnumToString(r.action);
|
||
|
if(StringLen(r.symbol) != 0) text += ", " + r.symbol;
|
||
|
text += ", " + EnumToString(r.type);
|
||
|
// volume block
|
||
|
if(r.volume != 0) text += ", V=" + p.StringOf(r.volume, p.lotDigits);
|
||
|
text += ", " + EnumToString(r.type_filling);
|
||
|
// all prices block
|
||
|
if(r.price != 0) text += ", @ " + p.StringOf(r.price);
|
||
|
if(r.stoplimit != 0) text += ", X=" + p.StringOf(r.stoplimit);
|
||
|
if(r.sl != 0) text += ", SL=" + p.StringOf(r.sl);
|
||
|
if(r.tp != 0) text += ", TP=" + p.StringOf(r.tp);
|
||
|
if(r.deviation != 0) text += ", D=" + (string)r.deviation;
|
||
|
// pending block
|
||
|
if(IsPendingType(r.type)) text += ", " + EnumToString(r.type_time);
|
||
|
if(r.expiration != 0) text += ", " + TimeToString(r.expiration);
|
||
|
// modification block
|
||
|
if(r.order != 0) text += ", #=" + (string)r.order;
|
||
|
if(r.position != 0) text += ", P=" + (string)r.position;
|
||
|
if(r.position_by != 0) text += ", b=" + (string)r.position_by;
|
||
|
// auxiliary block
|
||
|
if(r.magic != 0) text += ", M=" + (string)r.magic;
|
||
|
if(StringLen(r.comment)) text += ", " + r.comment;
|
||
|
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
string StringOf(const MqlTradeResult &r)
|
||
|
{
|
||
|
// NB: we don't have a reference to symbol here,
|
||
|
// so all numbers are formatted by unified StringOf
|
||
|
|
||
|
string text = TRCSTR(r.retcode);
|
||
|
if(r.deal != 0) text += ", D=" + (string)r.deal;
|
||
|
if(r.order != 0) text += ", #=" + (string)r.order;
|
||
|
if(r.volume != 0) text += ", V=" + StringOf(r.volume);
|
||
|
if(r.price != 0) text += ", @ " + StringOf(r.price);
|
||
|
if(r.bid != 0) text += ", Bid=" + StringOf(r.bid);
|
||
|
if(r.ask != 0) text += ", Ask=" + StringOf(r.ask);
|
||
|
if(StringLen(r.comment)) text += ", " + r.comment;
|
||
|
if(r.request_id != 0) text += ", Req=" + (string)r.request_id;
|
||
|
if(r.retcode_external != 0) text += ", Ext=" + (string)r.retcode_external;
|
||
|
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
string StringOf(const MqlTradeTransaction &t)
|
||
|
{
|
||
|
SymbolMetrics p(t.symbol);
|
||
|
|
||
|
string text = EnumToString(t.type);
|
||
|
if(t.deal != 0)
|
||
|
{
|
||
|
text += ", D=" + (string)t.deal
|
||
|
+ "(" + EnumToString(t.deal_type) + ")";
|
||
|
}
|
||
|
|
||
|
if(t.order)
|
||
|
{
|
||
|
text += ", #=" + (string)t.order
|
||
|
+ "(" + EnumToString(t.order_type)
|
||
|
+ "/" + EnumToString(t.order_state) + ")";
|
||
|
}
|
||
|
|
||
|
if(IsPendingType(t.order_type)) text += ", " + EnumToString(t.time_type);
|
||
|
if(t.time_expiration != 0) text += ", " + TimeToString(t.time_expiration);
|
||
|
|
||
|
if(StringLen(t.symbol) != 0) text += ", " + t.symbol;
|
||
|
|
||
|
if(t.price != 0) text += ", @ " + p.StringOf(t.price);
|
||
|
if(t.price_sl != 0) text += ", SL=" + p.StringOf(t.price_sl);
|
||
|
if(t.price_tp != 0) text += ", TP=" + p.StringOf(t.price_tp);
|
||
|
if(t.price_trigger != 0) text += ", X=" + p.StringOf(t.price_trigger);
|
||
|
|
||
|
if(t.volume != 0) text += ", V=" + p.StringOf(t.volume, p.lotDigits);
|
||
|
|
||
|
if(t.position != 0) text += ", P=" + (string)t.position;
|
||
|
if(t.position_by != 0) text += ", b=" + (string)t.position_by;
|
||
|
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
bool Equal(const double v1, const double v2)
|
||
|
{
|
||
|
return v1 == v2 || fabs(v1 - v2) < DBL_EPSILON * fmax(fabs(v1), fabs(v2));
|
||
|
}
|
||
|
|
||
|
double NormalizeLot(const double lot, const string symbol = NULL)
|
||
|
{
|
||
|
const string s = symbol == NULL ? _Symbol : symbol;
|
||
|
const double stepLot = SymbolInfoDouble(s, SYMBOL_VOLUME_STEP);
|
||
|
if(stepLot <= 0)
|
||
|
{
|
||
|
Print("SYMBOL_VOLUME_STEP=0 for ", s);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const double nlot = NormalizeDouble(lot, -(int)MathLog10(stepLot));
|
||
|
const double newLotsRounded = MathFloor(nlot / stepLot) * stepLot;
|
||
|
|
||
|
const double minLot = SymbolInfoDouble(s, SYMBOL_VOLUME_MIN);
|
||
|
if(minLot <= 0)
|
||
|
{
|
||
|
Print("SYMBOL_VOLUME_MIN=0 for ", s);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if(newLotsRounded < minLot)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const double maxLot = SymbolInfoDouble(s, SYMBOL_VOLUME_MAX);
|
||
|
if(newLotsRounded > maxLot) return maxLot;
|
||
|
|
||
|
return newLotsRounded;
|
||
|
}
|
||
|
|
||
|
bool IsNormalizedLot(const double lot, const string symbol = NULL)
|
||
|
{
|
||
|
const double m = MathMin(SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN),
|
||
|
SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP));
|
||
|
if(m == 0) return false;
|
||
|
const int d = (int)MathLog10(1.0 / m);
|
||
|
return Equal(NormalizeDouble(lot, d), lot);
|
||
|
}
|
||
|
|
||
|
double NormalizePrice(const double price, const string symbol = NULL)
|
||
|
{
|
||
|
const double tick = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
if(tick == 0)
|
||
|
{
|
||
|
Print("SYMBOL_TRADE_TICK_SIZE=0 for ", symbol == NULL ? _Symbol : symbol);
|
||
|
return price;
|
||
|
}
|
||
|
return MathRound(price / tick) * tick;
|
||
|
}
|
||
|
|
||
|
bool IsNormalizedPrice(const double price, const string symbol = NULL)
|
||
|
{
|
||
|
return Equal(NormalizePrice(price, symbol), price);
|
||
|
}
|
||
|
|
||
|
double GetCurrentPrice(const ENUM_ORDER_TYPE type, const string symbol = NULL)
|
||
|
{
|
||
|
if(type == ORDER_TYPE_CLOSE_BY) return 0;
|
||
|
return SymbolInfoDouble(symbol, IsBuyType(type) ? SYMBOL_ASK : SYMBOL_BID);
|
||
|
}
|
||
|
|
||
|
bool IsBuyType(const ENUM_ORDER_TYPE type)
|
||
|
{
|
||
|
return (type & 1) == 0 && type < ORDER_TYPE_CLOSE_BY;
|
||
|
}
|
||
|
|
||
|
bool IsSellType(const ENUM_ORDER_TYPE type)
|
||
|
{
|
||
|
return (type & 1) == 1;
|
||
|
}
|
||
|
|
||
|
bool IsMarketType(const ENUM_ORDER_TYPE type)
|
||
|
{
|
||
|
return type == ORDER_TYPE_BUY || type == ORDER_TYPE_SELL;
|
||
|
}
|
||
|
|
||
|
bool IsPendingType(const ENUM_ORDER_TYPE type)
|
||
|
{
|
||
|
return type > ORDER_TYPE_SELL && type < ORDER_TYPE_CLOSE_BY;
|
||
|
}
|
||
|
|
||
|
bool IsSameType(const ENUM_ORDER_TYPE type1, const ENUM_ORDER_TYPE type2)
|
||
|
{
|
||
|
return (type1 & 1) == (type2 & 1);
|
||
|
}
|
||
|
|
||
|
ulong PositionSelectById(const ulong id)
|
||
|
{
|
||
|
for(int i = 0; i < PositionsTotal(); ++i)
|
||
|
{
|
||
|
const ulong t = PositionGetTicket(i); // selected
|
||
|
if(PositionGetInteger(POSITION_IDENTIFIER) == id)
|
||
|
{
|
||
|
return t;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|