//+------------------------------------------------------------------+ //| 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(volumemaxvol) { 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 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; } //+------------------------------------------------------------------+