forked from rosh/MQL5Book
150 lines
5.6 KiB
MQL5
150 lines
5.6 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| LotMarginExposure.mqh |
| |
//| Copyright (c) 2021-2022, Marketeer |
| |
//| https://www.mql5.com/en/users/marketeer |
| |
//+------------------------------------------------------------------+
| |
| |
//+------------------------------------------------------------------+
| |
//| Lot Exposure Margin Level Risk |
| |
//+------------------------------------------------------------------+
| |
namespace LEMLR
| |
{
| |
// Container for all calculated margin characteristics per symbol
| |
struct SymbolLotExposureRisk
| |
{
| |
double lot; // requested lot
| |
int atrPointsNormalized; // normalized by tick size
| |
double atrValue; // money value for 1 lot
| |
double lotFromExposureRaw; // non-normalized (can be less than mininal lot)
| |
double lotFromExposure; // normalized
| |
double lotFromRiskOfStopLossRaw; // non-normalized (can be less than mininal lot)
| |
double lotFromRiskOfStopLoss; // normalized
| |
double exposureFromLot; // per 'lot'
| |
double marginLevelFromLot; // per 'lot'
| |
int lotDigits; // digits in normalized lots
| |
};
| |
| |
bool IsOfKindBuy(const ENUM_ORDER_TYPE type)
| |
{
| |
return (type & 1) == 0;
| |
}
| |
| |
bool IsOfKindSell(const ENUM_ORDER_TYPE type)
| |
{
| |
return (type & 1) == 1;
| |
}
| |
| |
double GetCurrentPrice(const string symbol, const ENUM_ORDER_TYPE type)
| |
{
| |
return SymbolInfoDouble(symbol, IsOfKindBuy(type) ? SYMBOL_ASK : SYMBOL_BID);
| |
}
| |
| |
// Main function to calculate all marginal characteristics for given symbol and money
| |
bool Estimate(const ENUM_ORDER_TYPE type, const string symbol, const double lot,
| |
const double price, const double exposure,
| |
const double riskLevel, const int riskPoints, const ENUM_TIMEFRAMES riskPeriod,
| |
double money, SymbolLotExposureRisk &r)
| |
{
| |
double lot1margin;
| |
if(!OrderCalcMargin(type, symbol, 1.0,
| |
price == 0 ? GetCurrentPrice(symbol, type) : price,
| |
lot1margin))
| |
{
| |
Print("OrderCalcMargin ", symbol, " failed: ", _LastError);
| |
return false;
| |
}
| |
if(lot1margin == 0)
| |
{
| |
Print("Margin ", symbol, " is zero, ", _LastError);
| |
return false;
| |
}
| |
| |
const double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
| |
const int pointsInTick = (int)(SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE) / SymbolInfoDouble(symbol, SYMBOL_POINT));
| |
const double pointValue = tickValue / pointsInTick;
| |
if(pointValue == 0)
| |
{
| |
Print(symbol, " is not available (probably yet), ", _LastError);
| |
return false;
| |
}
| |
const int atrPoints = (riskPoints > 0) ? riskPoints :
| |
(int)(((MathMax(iHigh(symbol, riskPeriod, 1), iHigh(symbol, riskPeriod, 0))
| |
- MathMin(iLow(symbol, riskPeriod, 1), iLow(symbol, riskPeriod, 0)))
| |
/ SymbolInfoDouble(symbol, SYMBOL_POINT)));
| |
r.atrPointsNormalized = atrPoints / pointsInTick * pointsInTick; // rounding
| |
| |
if(r.atrPointsNormalized == 0)
| |
{
| |
Print(symbol, " ATR on " , EnumToString(riskPeriod), " is not available (probably yet), ", _LastError);
| |
return false;
| |
}
| |
| |
r.atrValue = r.atrPointsNormalized * pointValue;
| |
| |
double usedMargin = 0;
| |
if(money == 0)
| |
{
| |
money = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
| |
usedMargin = AccountInfoDouble(ACCOUNT_MARGIN);
| |
}
| |
| |
r.lotFromExposureRaw = money * exposure / 100.0 / lot1margin;
| |
r.lotFromExposure = NormalizeLot(symbol, r.lotFromExposureRaw);
| |
| |
r.lotFromRiskOfStopLossRaw = money * riskLevel / 100.0 / (pointValue * r.atrPointsNormalized);
| |
r.lotFromRiskOfStopLoss = NormalizeLot(symbol, r.lotFromRiskOfStopLossRaw);
| |
| |
r.lot = lot <= 0 ? SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN) : lot;
| |
double margin = r.lot * lot1margin;
| |
| |
#ifdef DebugLogging
| |
{
| |
Print((float)r.lot, " ", symbol, "=", margin, " ", lot1margin, " ",
| |
EnumToString((ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE)),
| |
" ", (float)pointValue);
| |
}
| |
#endif
| |
| |
if(lot < 0)
| |
{
| |
margin = r.lotFromRiskOfStopLossRaw * lot1margin;
| |
}
| |
| |
r.exposureFromLot = (margin + usedMargin) / money * 100.0;
| |
r.marginLevelFromLot = margin > 0 ? money / (margin + usedMargin) * 100.0 : 0;
| |
r.lotDigits = (int)MathLog10(1.0 / SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));
| |
| |
return true;
| |
}
| |
| |
// Normalize lot in double according to symbol specs
| |
double NormalizeLot(const string symbol, const double lot)
| |
{
| |
const double stepLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
| |
if(stepLot == 0)
| |
{
| |
Print("LOTSTEP IS ZERO");
| |
return 0;
| |
}
| |
| |
const double newLotsRounded = MathFloor(lot / stepLot) * stepLot;
| |
| |
const double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
| |
if(minLot == 0)
| |
{
| |
Print("MINLOT IS ZERO ", symbol);
| |
return 0;
| |
}
| |
| |
if(newLotsRounded < minLot)
| |
{
| |
return 0;
| |
}
| |
| |
const double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
| |
if(newLotsRounded > maxLot) return maxLot;
| |
| |
return newLotsRounded;
| |
}
| |
};
| |
//+------------------------------------------------------------------+
|