403 lines
32 KiB
MQL5
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
|
|
//+------------------------------------------------------------------+
|