//+------------------------------------------------------------------+ //| 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 #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 //+------------------------------------------------------------------+