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