MQLArticles/RM/LoteSizeCalc.mqh
Nique_372 6bd05e2ef0
2025-09-22 12:18:57 -05:00

403 lines
32 KiB
MQL5

//+------------------------------------------------------------------+
//| LoteSizeCalc.mqh |
//| Copyright 2025, Niquel Mendoza. |
//| https://www.mql5.com/es/users/nique_372/news |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Niquel Mendoza."
#property link "https://www.mql5.com/es/users/nique_372/news"
#property strict
#ifndef RISK_LOT_SIZE_CALC
#define RISK_LOT_SIZE_CALC
#include <Trade/Trade.mqh>
#include "RM_Defines.mqh"
#include "..\\Utils\\Funciones Array.mqh"
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
typedef double (*PriceCalculatorFunc)(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits);
namespace GetLoteFunctions
{
//+------------------------------------------------------------------+
inline double PriceCal_CalcMarketPrice(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits)
{
return price;
}
//+------------------------------------------------------------------+
inline double PriceCal_CalcBuyLimitPrice(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits)
{
return NormalizeDouble(price - deviation * point_value, digits);
}
//+------------------------------------------------------------------+
inline double PriceCal_CalcSellLimitPrice(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits)
{
return NormalizeDouble(price + deviation * point_value, digits);
}
//+------------------------------------------------------------------+
inline double PriceCal_CalcBuyStopPrice(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits)
{
return NormalizeDouble(price + deviation * point_value, digits);
}
//+------------------------------------------------------------------+
inline double PriceCal_CalcSellStopPrice(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits)
{
return NormalizeDouble(price - deviation * point_value, digits);
}
//+------------------------------------------------------------------+
inline double PriceCal_CalcBuyStopLimitPrice(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits)
{
return NormalizeDouble(price + deviation * point_value - stop_limit * point_value, digits);
}
//+------------------------------------------------------------------+
inline double PriceCal_CalcSellStopLimitPrice(double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits)
{
return NormalizeDouble(price - deviation * point_value + stop_limit * point_value, digits);
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CGetLote : public CLoggerBase
{
private:
string symbol;
double volumeStep;
double minLote;
double maxLote;
double volume_limit;
double point_value;
int8_t digits;
double ganancia_por_tick_real;
public:
CGetLote(string _symbol);
//--- Update
void Update();
//--- Symbol
void SetSymbol(string new_value);
inline string GetSymbol() const { return symbol; }
//---
inline double MinVolume() const { return minLote; }
inline double MaxVolume() const { return maxLote; }
//--- General functions
// Calcula el stop loss ideal en puntos, basandose en el riesgo de operacion y en un lote especificado
// Calculates the ideal stop loss in points, based on the transaction risk and a specified lot
long CalculateSLWithLot(const ENUM_ORDER_TYPE type, double risk_per_operation, double entry_price, double lot, ulong deviation, ulong stop_limit);
// Calcula el stop loss ideal en puntos, basandose en el riesgo de operacion, ademas devuelve el lote ideal
// Calculates the ideal stop loss in points, based on the operation risk, and also returns the ideal lot
long CalculateSL(const ENUM_ORDER_TYPE type, double risk_per_operation, double entry_price, double &chosen_lot, ulong deviation, ulong stop_limit);
// Calcula el lote en base al riesgo por operacion y el stop loss en puntos
// Calculate the lot based on the risk per operation and the stop loss in points
double GetLoteByRiskPerOperationAndSL(double glot, double max_risk_per_operation, double &new_risk_per_operation, long StopLoss);
// Obtiene el lote maximo que se puede ejecutar
// Get the maximum batch that can be executed
double GetMaxLote(ENUM_ORDER_TYPE type, double entry_price, ulong deviation, ulong stop_limit);
// Obtiene el lote maximo que se puede comprar por el riesgo por operacion en USD (no %, osea un riesgo ya calculado)
// Gets the maximum lot that can be purchased for the risk per operation in USD (not %, i.e. a risk already calculated)
double GetLoteByRiskPerOperation(ENUM_ORDER_TYPE type, double risk_per_operation, double entry_price, ulong deviation, ulong stop_limit);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGetLote::CGetLote(string _symbol)
{
this.symbol = _symbol;
Update();
}
//+---------------------------------------------------------------------+
//| Determine the optimal lot size based on risk and current conditions |
//+---------------------------------------------------------------------+
double CGetLote::GetLoteByRiskPerOperationAndSL(double glot, double max_risk_per_operation, double &new_risk_per_operation, long StopLoss)
{
new_risk_per_operation = 0.0;
double nlot = 0.00;
//---
if(glot <= 0)
{
LogError(StringFormat("Lote maximo invalido: %.2f", glot), FUNCION_ACTUAL);
return 0.00;
}
if(StopLoss <= 0)
{
LogError(StringFormat("Stop Loss distance is less than or equal to zero, now correct the stoploss distance: %d", StopLoss), FUNCION_ACTUAL);
return nlot;
}
if(max_risk_per_operation <= 0)
{
LogError(StringFormat("El riesgo maximo por operacion %.2f es invalido", max_risk_per_operation), FUNCION_ACTUAL);
return nlot;
}
//---
long spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
double rpo = glot * (ganancia_por_tick_real * (spread + 1 + StopLoss));
if(rpo > max_risk_per_operation)
{
double new_lot = (max_risk_per_operation / rpo) * glot;
new_lot = RoundToStep(new_lot, volumeStep);
new_risk_per_operation = new_lot * (ganancia_por_tick_real * (spread + 1 + StopLoss));
nlot = new_lot;
}
else
{
new_risk_per_operation = rpo;
nlot = glot;
}
//---
if(nlot > maxLote)
nlot = maxLote;
LogInfo(StringFormat("Risk per operation(%.2f) = %.2f * ( %f * ( %i + 1 + %i)))", new_risk_per_operation, nlot, ganancia_por_tick_real, spread, StopLoss), FUNCION_ACTUAL);
//---
if(nlot <= 0.00)
LogWarning(StringFormat("The lot %.2f is invalid, the risk %.2f increases or the sl %d decreases", nlot, max_risk_per_operation, StopLoss), FUNCION_ACTUAL);
else
LogInfo(StringFormat("For the stop loss %i in points and risk per operation: %.2f, the ideal lot is: %.2f", StopLoss, max_risk_per_operation, nlot), FUNCION_ACTUAL);
return nlot;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
long CGetLote::CalculateSLWithLot(const ENUM_ORDER_TYPE type, double risk_per_operation, double entry_price, double lot, ulong deviation, ulong stop_limit)
{
//--- Check lot
if(lot <= 0)
{
LogError(StringFormat("No se pudo calcular lote valido para el riesgo especificado = %.2f", risk_per_operation), FUNCION_ACTUAL);
return 0;
}
//--- Calculamos el stop loss en base al riesgo por operacion y al lote
const long spread = (long)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
const double result = (risk_per_operation / (lot * ganancia_por_tick_real)) - spread - 1;
const long ideal_sl = long(MathRound(result));
//--- Check SL
if(ideal_sl <= 0)
{
LogError(StringFormat("Stop loss %i invalid, please increase the risk per trade %.2f", ideal_sl, risk_per_operation), FUNCION_ACTUAL);
return 0;
}
return ideal_sl;
}
//+-----------------------------------------------------------------------+
//| Calculate the stop loss distance in points based on risk and lot size |
//+-----------------------------------------------------------------------+
long CGetLote::CalculateSL(const ENUM_ORDER_TYPE type, double risk_per_operation, double entry_price, double &chosen_lot, ulong deviation, ulong stop_limit)
{
chosen_lot = GetLoteByRiskPerOperation(type, risk_per_operation, entry_price, deviation, stop_limit);
if(chosen_lot <= 0)
{
LogError(StringFormat("No se pudo calcular lote valido para el riesgo especificado = %.2f", risk_per_operation), FUNCION_ACTUAL);
return 0;
}
long spread = (long)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
double result = (risk_per_operation / (chosen_lot * ganancia_por_tick_real)) - spread - 1;
long ideal_sl = long(MathRound(result));
if(ideal_sl <= 0)
{
LogError(StringFormat("Stop loss %i invalid, please increase the risk per trade %.2f", ideal_sl, risk_per_operation), FUNCION_ACTUAL);
return 0;
}
return ideal_sl;
}
//+--------------------------------------------------------------------+
//| Function to obtain the ideal lot based on your risk per operation |
//+--------------------------------------------------------------------+
// risk_per_operation in USD, not %
double CGetLote::GetLoteByRiskPerOperation(ENUM_ORDER_TYPE type, double risk_per_operation, double entry_price, ulong deviation, ulong stop_limit)
{
double price = OrderTypeToPriceCalculator[type](entry_price, deviation, stop_limit, point_value, digits);
double margin_free = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
//---
if(margin_free <= 0.00)
{
LogError(StringFormat("Free margin of %+.2f is invalid, you cannot open trades right now", margin_free), FUNCION_ACTUAL);
return 0.0;
}
//---
double margin = EMPTY_VALUE;
const static double VOLUME = 1.0;
ResetLastError();
if(!OrderCalcMargin(MarketOrderToOrderType[type], symbol, VOLUME, price, margin))
{
LogError(StringFormat("OrderCalcMargin() failed. Error = %d", GetLastError()), FUNCION_ACTUAL);
return 0.0;
}
//---
if(risk_per_operation >= margin_free)
risk_per_operation = margin_free;
//---
double result = MathFloor((risk_per_operation / margin) / volumeStep) * volumeStep;
if(result < minLote)
{
LogWarning(StringFormat("The lot %.2f is invalid, the risk %.2f increases", result, risk_per_operation), FUNCION_ACTUAL);
return result;
}
if(result >= maxLote)
return maxLote;
if(result >= volume_limit && volume_limit > 0.00)
return volume_limit;
LogInfo(StringFormat("The ideal lot for %.2f risk per trade is %.2f lots, margin for 1 lote: %.2f", risk_per_operation, result, margin), FUNCION_ACTUAL);
return result;
}
//+------------------------------------------------------------------+
double CGetLote::GetMaxLote(ENUM_ORDER_TYPE type, double entry_price, ulong deviation, ulong stop_limit)
{
//double price, ulong deviation, ulong stop_limit, double point_value, int8_t digits
double price = OrderTypeToPriceCalculator[type](entry_price, deviation, stop_limit, point_value, digits);
double margin_free = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
//---
if(margin_free <= 0.00)
{
LogError(StringFormat("Free margin of %+.2f is invalid, you cannot open trades right now", margin_free), FUNCION_ACTUAL);
return 0.0;
}
//---
double margin = EMPTY_VALUE;
const static double VOLUME = 1.0;
ResetLastError();
if(!OrderCalcMargin(MarketOrderToOrderType[type], symbol, VOLUME, price, margin))
{
LogError(StringFormat("OrderCalcMargin() failed. Error = %d", GetLastError()), FUNCION_ACTUAL);
return 0.0;
}
//---
double result = MathFloor((margin_free / margin) / volumeStep) * volumeStep;
if(result < minLote)
return 0.0;
if(result >= maxLote)
return maxLote;
if(result >= volume_limit && volume_limit > 0.00)
return volume_limit;
LogInfo(StringFormat("Lote maximo = %.2f >> precio = %.*f >> margen(1) = %.2f", result, digits, entry_price, margin), FUNCION_ACTUAL);
return result;
}
//+------------------------------------------------------------------+
void CGetLote::Update(void)
{
this.volumeStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
this.minLote = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
this.maxLote = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
this.volume_limit = SymbolInfoDouble(symbol, SYMBOL_VOLUME_LIMIT);
this.digits = (int8_t)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
this.point_value = SymbolInfoDouble(symbol, SYMBOL_POINT);
//---
ENUM_SYMBOL_CALC_MODE calc_mode = (ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE);;
switch(calc_mode)
{
case SYMBOL_CALC_MODE_FOREX:
case SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE:
this.ganancia_por_tick_real = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
break;
case SYMBOL_CALC_MODE_CFD:
case SYMBOL_CALC_MODE_CFDINDEX:
case SYMBOL_CALC_MODE_CFDLEVERAGE:
this.ganancia_por_tick_real = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE) * SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
break;
default:
this.ganancia_por_tick_real = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE) * SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
break;
}
}
//+------------------------------------------------------------------+
void CGetLote::SetSymbol(string new_value)
{
this.symbol = new_value;
Update();
}
/*
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_BUY = 0
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_SELL = 1
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_BUY_LIMIT = 2
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_SELL_LIMIT = 3
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_BUY_STOP = 4
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_SELL_STOP = 5
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_BUY_STOP_LIMIT = 6
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_SELL_STOP_LIMIT = 7
2025.08.06 16:50:41.989 GetID (XAUUSD,M1) ORDER_TYPE_CLOSE_BY = 8
*/
const ENUM_ORDER_TYPE MarketOrderToOrderType[9] =
{
ORDER_TYPE_BUY,
ORDER_TYPE_SELL,
ORDER_TYPE_BUY,
ORDER_TYPE_SELL,
ORDER_TYPE_BUY,
ORDER_TYPE_SELL,
ORDER_TYPE_BUY,
ORDER_TYPE_SELL,
WRONG_VALUE
};
const PriceCalculatorFunc OrderTypeToPriceCalculator[9] =
{
GetLoteFunctions::PriceCal_CalcMarketPrice,
GetLoteFunctions::PriceCal_CalcMarketPrice,
GetLoteFunctions::PriceCal_CalcBuyLimitPrice,
GetLoteFunctions::PriceCal_CalcSellLimitPrice,
GetLoteFunctions::PriceCal_CalcBuyStopPrice,
GetLoteFunctions::PriceCal_CalcSellStopPrice,
GetLoteFunctions::PriceCal_CalcBuyStopLimitPrice,
GetLoteFunctions::PriceCal_CalcSellStopLimitPrice,
GetLoteFunctions::PriceCal_CalcMarketPrice
};
//+------------------------------------------------------------------+
#endif
//+------------------------------------------------------------------+