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

349 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;
}
};
//+------------------------------------------------------------------+