ForexCalculators/Functions.mqh
2025-09-19 20:19:17 +00:00

515 lines
42 KiB
MQL5

//+------------------------------------------------------------------+
//| Functions.mqh |
//| Copyright © 2018, Amr Ali |
//| https://www.mql5.com/en/users/amrali |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Amr Ali"
#property link "https://www.mql5.com/en/users/amrali"
#property version "2.85"
#property description "Functions to perform several Forex calculations."
//+------------------------------------------------------------------+
//| Calculate point value for a profitable or losing position. |
//| (profit/loss per lot per point in account currency). |
//+------------------------------------------------------------------+
double mPointValue(string pSymbol = NULL, bool isProfit = true)
{
if(pSymbol == NULL) pSymbol = _Symbol;
double PointSize = SymbolInfoDouble(pSymbol, SYMBOL_POINT);
double TickSize = SymbolInfoDouble(pSymbol, SYMBOL_TRADE_TICK_SIZE);
// double TickValue = SymbolInfoDouble(pSymbol, SYMBOL_TRADE_TICK_VALUE);
double PointValue= safeDiv(PointSize, TickSize) * mTickValue(pSymbol, isProfit); // fix for non-forex symbols
return PointValue;
}
//+------------------------------------------------------------------+
//| Calculate tick value for a profitable or losing position. |
//| (profit/loss per lot per tick in account currency). |
//+------------------------------------------------------------------+
double mTickValue(string pSymbol = NULL, bool isProfit = true)
{
if(pSymbol == NULL) pSymbol = _Symbol;
//--- fix for non-forex symbols (metals) on some brokers.
long CalcMode = SymbolInfoInteger(pSymbol, SYMBOL_TRADE_CALC_MODE);
if((CalcMode == SYMBOL_CALC_MODE_CFD) || (CalcMode == SYMBOL_CALC_MODE_CFDINDEX) || (CalcMode == SYMBOL_CALC_MODE_CFDLEVERAGE))
{
return CalculateTickValue(pSymbol, isProfit);
}
return SymbolInfoDouble(pSymbol, isProfit
? SYMBOL_TRADE_TICK_VALUE_PROFIT
: SYMBOL_TRADE_TICK_VALUE_LOSS);
}
//+------------------------------------------------------------------+
//| TV = ContractSize * TickSize * (Profit/Account) Exchange Rate. |
//+------------------------------------------------------------------+
double CalculateTickValue(const string pSymbol, bool isProfit = true)
{
//--- calculate profit/loss per lot per tick, in symbol's profit currency.
double ContractSize = SymbolInfoDouble(pSymbol, SYMBOL_TRADE_CONTRACT_SIZE);
double TickSize = SymbolInfoDouble(pSymbol, SYMBOL_TRADE_TICK_SIZE);
double TickValue = ContractSize * TickSize;
//--- converting into account currency.
const string AccountCurrency = AccountInfoString(ACCOUNT_CURRENCY);
const string ProfitCurrency = SymbolInfoString(pSymbol, SYMBOL_CURRENCY_PROFIT);
if(AccountCurrency != ProfitCurrency)
{
//--- multiply by the profit/account exchange rate.
TickValue *= GetExchangeRate(ProfitCurrency, AccountCurrency, (ENUM_ORDER_TYPE)isProfit);
}
//---
return TickValue;
}
//+------------------------------------------------------------------+
//| Returns the exchange rate for the "CCY1/CCY2" currency pair. |
//+------------------------------------------------------------------+
double GetExchangeRate(string CCY1, string CCY2, ENUM_ORDER_TYPE OrderType)
{
string symbol = NULL;
//---
if(CCY1 != CCY2)
{
//--- Try direct quote
symbol = GetSymbolByCurrencies(CCY1, CCY2);
if(symbol != NULL)
{
if(OrderType == ORDER_TYPE_SELL)
//--- Using Sell price for direct quote.
return SymbolInfoDouble(symbol, SYMBOL_BID);
else
//--- Using Buy price for direct quote.
return SymbolInfoDouble(symbol, SYMBOL_ASK);
}
//--- Try reverse quote
symbol = GetSymbolByCurrencies(CCY2, CCY1);
if(symbol != NULL)
{
if(OrderType == ORDER_TYPE_SELL)
//--- Using Buy price for reverse quote.
return safeDiv(1, SymbolInfoDouble(symbol, SYMBOL_ASK));
else
//--- Using Sell price for reverse quote.
return safeDiv(1, SymbolInfoDouble(symbol, SYMBOL_BID));
}
//--- Get the cross rate through US dollar, but only if it's not USD it is trying to find the rate for.
if((CCY1 != "USD") && (CCY2 != "USD"))
{
return GetExchangeRate(CCY1, "USD", OrderType) * GetExchangeRate("USD", CCY2, OrderType);
}
}
//---
return (1.0);
}
//+------------------------------------------------------------------+
//| Returns a currency pair with the specified base/profit currency. |
//+------------------------------------------------------------------+
/**
* Returns the fully qualified name of a currency pair, and adds it to
* the Market Watch window if successful. If there is no applicable
* symbol name, it returns NULL. This is necessary while working with
* symbols other than the current chart symbol, as some brokers add
* suffixes at the end of symbol names.
*/
// https://github.com/EarnForex/PositionSizeCalculator
string GetSymbolByCurrencies(string base_currency, string profit_currency)
{
string symbol = NULL;
static string CurrencyPairsList[];
//---
bool is_custom = false;
if(SymbolExist(base_currency + profit_currency, is_custom))
{
symbol = base_currency + profit_currency;
}
//--- the symbol name is inverted or it may have a prefix/suffix.
else
{
//--- cache all currency pairs to speed up further search.
int CacheSize = ArraySize(CurrencyPairsList);
if(!CacheSize)
{
int n = SymbolsTotal(false);
for(int i = 0; i < n; i++)
{
string symbolname = SymbolName(i, false);
long CalcMode = SymbolInfoInteger(symbolname, SYMBOL_TRADE_CALC_MODE);
if((CalcMode == SYMBOL_CALC_MODE_FOREX) || (CalcMode == SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE))
{
//--- add symbol to the cache.
ArrayResize(CurrencyPairsList, CacheSize + 1, 128);
CurrencyPairsList[CacheSize++] = symbolname;
}
}
}
//--- cycle through all currency pairs.
for(int i = 0; i < CacheSize; i++)
{
string symbolname = CurrencyPairsList[i];
string b_cur = SymbolInfoString(symbolname, SYMBOL_CURRENCY_BASE);
string p_cur = SymbolInfoString(symbolname, SYMBOL_CURRENCY_PROFIT);
if((b_cur == base_currency) && (p_cur == profit_currency))
{
symbol = symbolname;
break;
}
}
}
//--- if the fully qualified name is found.
if(symbol != NULL)
{
//--- add symbol to the MarketWatch, if necessary.
if(!CheckMarketWatch(symbol))
{
//--- if symbol cannot be added in the market watch
return(NULL);
}
}
//---
return (symbol);
}
//+------------------------------------------------------------------+
//| Checks if symbol is selected in the MarketWatch |
//| and adds symbol to the MarketWatch, if necessary |
//+------------------------------------------------------------------+
bool CheckMarketWatch(string symbol)
{
ResetLastError();
//--- check if symbol is selected in the MarketWatch
if(!SymbolInfoInteger(symbol,SYMBOL_SELECT))
{
if(GetLastError()==ERR_MARKET_UNKNOWN_SYMBOL)
{
printf(__FUNCTION__+": Unknown symbol '%s'",symbol);
return(false);
}
if(!SymbolSelect(symbol,true))
{
printf(__FUNCTION__+": Error adding symbol %d",GetLastError());
return(false);
}
printf(__FUNCTION__+": Symbol '%s' is added in the MarketWatch.",symbol);
}
//--- succeed
return(true);
}
//+------------------------------------------------------------------+
//| Calculate the appropriate volume for the trade operation planned.|
//| |
//| ordertype : ORDER_TYPE_BUY or ORDER_TYPE_SELL only. |
//| symbol : Symbol name |
//| risk_money : Loss money when SL is hit, in account currency. |
//| price_open : Open price |
//| price_sl : Close price |
//| commission_lot : Comm. per lot per side, in account currency. |
//+------------------------------------------------------------------+
double OrderCalcVolume(ENUM_ORDER_TYPE ordertype, string symbol, double risk_money, double price_open, double price_sl, double commission_lot = 0.0)
{
/**
* Calculation using TICK_VALUE is less accurate:
* double TickSize = SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE);
* //double TickValue = SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_VALUE_LOSS);
* double TickValue = mTickValue(symbol,(bool)ordertype);
* double sl_ticks = MathRound((ordertype == ORDER_TYPE_BUY ? price_open - price_sl : price_sl - price_open) / TickSize);
* double volume = risk_money/(sl_ticks*TickValue+2*commission_lot);
*
* Instead, OrderCalcProfit() function is used here to:
* (1) apply the correct profit calculation method depending on SYMBOL_TRADE_CALC_MODE,
* (2) adjust the tick_value to future rate (SL) if the base currency == account currency.
*/
double volume=0;
double profit=0;
double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
//---
if(OrderCalcProfit(ordertype,symbol,maxvol,price_open,price_sl,profit) && profit < 0)
{
volume=risk_money/(MathAbs(profit/maxvol)+2*commission_lot);
volume=CorrectVolume(symbol,volume,price_open,ordertype);
}
//---
return volume;
}
//+------------------------------------------------------------------+
//| Correct the volume of market order |
//+------------------------------------------------------------------+
double CorrectVolume(string symbol,double volume,double price,ENUM_ORDER_TYPE ordertype)
{
//--- Adjust volume to broker limits.
double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
if(volume<minvol)
{
volume=0.0; // not taking this trade
}
double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
if(volume>maxvol)
{
volume=maxvol;
}
double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
volume=MathRound(volume/stepvol)*stepvol;
//--- Adjust volume to account free margin (considering opposite open positions on the symbol).
while(AccountFreeMarginCheck(ordertype,symbol,volume,price) < 0)
{
//--- normalize and check limits
volume=MathFloor(volume/stepvol-1)*stepvol;
//---
if(volume<minvol)
volume=0.0;
}
//---
return volume;
}
//+------------------------------------------------------------------+
//| Returns free margin that remains after the specified order has |
//| been opened at the current price on the current account. |
//| If the free margin is insufficient, the function returns -1. |
//| If parameters are filled out incorrectly, it returns 0. |
//+------------------------------------------------------------------+
double AccountFreeMarginCheck(ENUM_ORDER_TYPE ordertype, string symbol, double volume, double price)
{
//--- Display an alert if Algo Trading is disabled.
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && !MQLInfoInteger(MQL_TESTER))
{
Alert("Algo Trading must be enabled to check the free margin.");
}
MqlTradeRequest request; // request structure
MqlTradeCheckResult result; // result structure
//--- clean
ZeroMemory(request);
ZeroMemory(result);
//--- setting request
request.action=TRADE_ACTION_DEAL;
request.symbol=symbol;
request.volume=volume;
request.type =ordertype;
request.price =price;
request.type_filling=(bool)(SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE) & SYMBOL_FILLING_FOK) ? ORDER_FILLING_FOK : ORDER_FILLING_IOC;
//--- action and return the result
if(OrderCheck(request, result))
return(result.margin_free);
else
if(result.retcode==TRADE_RETCODE_NO_MONEY)
return(-1);
//--- wrong parameters
else
return(0);
}
//+------------------------------------------------------------------+
//| Calculate the swap value per one holding day. |
//+------------------------------------------------------------------+
double OrderCalcSwap(ENUM_ORDER_TYPE ordertype, string symbol, double volume)
{
double swap = 0.0;
//---
ENUM_SYMBOL_SWAP_MODE swap_mode = (ENUM_SYMBOL_SWAP_MODE)SymbolInfoInteger(symbol, SYMBOL_SWAP_MODE);
switch(swap_mode)
{
// no swaps
case SYMBOL_SWAP_MODE_DISABLED:
swap = 0;
break;
// in points
case SYMBOL_SWAP_MODE_POINTS:
{
double PointValue_long = mPointValue(symbol, (SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG) > 0));
double PointValue_short = mPointValue(symbol, (SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT) > 0));
if(ordertype == ORDER_TYPE_BUY)
swap = volume * SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG) * PointValue_long;
if(ordertype == ORDER_TYPE_SELL)
swap = volume * SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT) * PointValue_short;
}
break;
// in money, in client deposit currency
case SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT:
{
if(ordertype == ORDER_TYPE_BUY)
swap = volume * SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG);
if(ordertype == ORDER_TYPE_SELL)
swap = volume * SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT);
}
break;
// in money, in base or margin currency of the symbol
case SYMBOL_SWAP_MODE_CURRENCY_SYMBOL:
case SYMBOL_SWAP_MODE_CURRENCY_MARGIN:
{
if(ordertype == ORDER_TYPE_BUY)
swap = volume * SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG);
if(ordertype == ORDER_TYPE_SELL)
swap = volume * SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT);
string base_or_margin_currency;
if(swap_mode == SYMBOL_SWAP_MODE_CURRENCY_SYMBOL)
{
base_or_margin_currency = SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE);
}
else
{
base_or_margin_currency = SymbolInfoString(symbol, SYMBOL_CURRENCY_MARGIN);
}
string account_currency = AccountInfoString(ACCOUNT_CURRENCY);
//--- converting into deposit currency.
if(base_or_margin_currency != account_currency)
{
swap *= GetExchangeRate(base_or_margin_currency, account_currency, ordertype);
}
}
break;
// as annual interest, using the current or open price
case SYMBOL_SWAP_MODE_INTEREST_CURRENT:
case SYMBOL_SWAP_MODE_INTEREST_OPEN:
{
double tradeValue=TradeNotionalValue(symbol,volume);
if(ordertype == ORDER_TYPE_BUY)
swap = tradeValue * SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG) / 100 / 360;
if(ordertype == ORDER_TYPE_SELL)
swap = tradeValue * SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT) / 100 / 360;
}
break;
// not implemented.
case SYMBOL_SWAP_MODE_REOPEN_CURRENT:
case SYMBOL_SWAP_MODE_REOPEN_BID:
swap = 0;
break;
default:
break;
}
//---
return (swap);
}
//+------------------------------------------------------------------+
//| Calculates the symbol's dynamic leverage. |
//+------------------------------------------------------------------+
/**
* Leverage = notional value / required margin
*/
int SymbolLeverage(const string symbol)
{
int leverage=-1;
double margin=0.0;
double volume=SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double tradeValue=TradeNotionalValue(symbol,volume);
if(OrderCalcMargin(ORDER_TYPE_BUY,symbol,volume,SymbolInfoDouble(symbol,SYMBOL_ASK),margin) && margin > 0.1)
{
leverage=(int)MathRound(tradeValue/margin);
}
return leverage;
}
//+------------------------------------------------------------------+
//| Calculates the symbol's dynamic leverage (another way). |
//+------------------------------------------------------------------+
/**
* Leverage = % change in account / % change in price
*/
int Leverage(const string symbol)
{
int leverage=-1;
double margin=0.0;
double profit=0;
double volume=SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double ask=SymbolInfoDouble(symbol, SYMBOL_ASK);
if(OrderCalcMargin(ORDER_TYPE_BUY,symbol,volume,ask,margin) && margin > 0.01)
if(OrderCalcProfit(ORDER_TYPE_BUY,symbol,volume,ask,ask*1.001,profit))
leverage=(int)MathRound(profit / 0.001 / margin);
return leverage;
}
//+------------------------------------------------------------------+
//| Calculates the trade's notional value, in account currency. |
//+------------------------------------------------------------------+
/**
* Notional value = unit price * no. of units * profit/account exchange rate.
*/
double TradeNotionalValue(const string symbol, const double volume)
{
const double ContractSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE);
const string BaseCurrency = SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE);
const string ProfitCurrency = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
const string AccountCurrency = AccountInfoString(ACCOUNT_CURRENCY);
//--- Some brokers have wrong configuration of the base currency.
// const double CrossRate = GetExchangeRate(BaseCurrency, AccountCurrency, ORDER_TYPE_BUY);
const double CrossRate = SymbolInfoDouble(symbol, SYMBOL_ASK) * GetExchangeRate(ProfitCurrency, AccountCurrency, ORDER_TYPE_BUY); // base/prof * prof/acc = base/acc
const double TradeValue = volume * ContractSize * CrossRate;
return TradeValue;
}
//+------------------------------------------------------------------+
//| Calculates the trade's notional value, in account currency. |
//+------------------------------------------------------------------+
/**
* Notional value = unit price * no. of units * profit/account exchange rate.
* = unit price * lots * ContractSize * profit/account exchange rate * (TickSize / TickSize)
* Notional value = unit price * lots * TickValue / TickSize
*/
double TradeValue(const string symbol, const double volume)
{
const double TickSize=SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
const double TickValue=mTickValue(symbol,false); // fix for non-forex symbols
const double Price=SymbolInfoDouble(symbol, SYMBOL_ASK);
const double TradeValue = volume * Price * TickValue / TickSize;
return TradeValue;
}
//+------------------------------------------------------------------+
//| Formats double with thousands separator and specified decimals. |
//+------------------------------------------------------------------+
string FormatDouble(const double value, const int digits, const string separator=",")
{
//--- Convert double to string
string str_value = DoubleToString(NormalizeDouble(value, digits), digits); // FormatDouble(1.005, 2) => "1.01"
//--- Find "." position.
int pos = StringFind(str_value, ".");
string integer = str_value;
string decimal = "";
if(pos > -1)
{
integer = StringSubstr(str_value, 0, pos);
decimal = StringSubstr(str_value, pos);
}
string formatted = "";
string comma = "";
while(StringLen(integer) > 3)
{
int length = StringLen(integer);
string group = StringSubstr(integer, length - 3);
formatted = group + comma + formatted;
comma = separator;
integer = StringSubstr(integer, 0, length - 3);
}
if(integer == "-")
comma = "";
if(integer != "")
formatted = integer + comma + formatted;
return (formatted + decimal);
}
//+------------------------------------------------------------------+
//| Avoids zero divide error that forces the mql program to stop. |
//+------------------------------------------------------------------+
double safeDiv(double a, double b)
{
//--- force double division.
return (b != 0) ? a / b : 0;
}
//+------------------------------------------------------------------+
//| Get number of decimal digits after the decimal point. |
//+------------------------------------------------------------------+
int GetDigits(const double value)
{
int d = 0;
for(double p = 1; value != MathRound(value * p) / p; p *= 10)
d++;
return d;
}
//+------------------------------------------------------------------+