forked from nique_372/MQLArticles
424 lines
16 KiB
MQL5
424 lines
16 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 MQLARTICLES_RM_LOTSIZECALC_MQH
| |
#define MQLARTICLES_RM_LOTSIZECALC_MQH
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
#include <Trade/Trade.mqh>
| |
#include "RM_Defines.mqh"
| |
#include "..\\Utils\\FA\\Events.mqh"
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
namespace TSN
| |
{
| |
//+------------------------------------------------------------------+
| |
//| Defines |
| |
//+------------------------------------------------------------------+
| |
/*
| |
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
| |
*/
| |
#define MqlArticlesOrderTToOrder(v) ENUM_ORDER_TYPE(v == ORDER_TYPE_CLOSE_BY ? WRONG_VALUE : (v&1))
| |
/*
| |
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
| |
};*/
| |
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
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);
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
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
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
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(double risk_per_operation, double entry_price, double lot, ulong deviation, ulong stop_limit);
| |
| |
// Transforma dinero a puntos..
| |
// Transform money to points
| |
long MoneyToPoints(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(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::MoneyToPoints(const ENUM_ORDER_TYPE type, double money, double entry_price, double &chosen_lot, ulong deviation, ulong stop_limit)
| |
{
| |
chosen_lot = GetLoteByRiskPerOperation(type, money, entry_price, deviation, stop_limit);
| |
| |
if(chosen_lot <= 0)
| |
{
| |
LogError(StringFormat("No se pudo calcular lote valido para el dinero especificado = %.2f", money), FUNCION_ACTUAL);
| |
return 0;
| |
}
| |
| |
long spread = (long)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
| |
double result = (money / (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 money = %.2f", ideal_sl, money), 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(MqlArticlesOrderTToOrder(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)
| |
{
| |
LogError(StringFormat("The lot %.2f is invalid, the risk %.2f increases, margin = %.2f, vol step = %.2f",
| |
result, risk_per_operation, margin, volumeStep), 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;
| |
ResetLastError();
| |
if(!OrderCalcMargin(MqlArticlesOrderTToOrder(type), symbol, 1.0, 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);
| |
| |
//---
| |
const 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();
| |
}
| |
}
| |
//+------------------------------------------------------------------+
| |
#endif // MQLARTICLES_RM_LOTSIZECALC_MQH
| |
//+------------------------------------------------------------------+
|