Super 8 - 30M BTC
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Good-Vibe

//@version=5

// ———————————————————— Strategy Settings
strategy('Super 8 - 30M BTC',
// The overlay parameter is set to true, which means that the strategy will be plotted on top of the price chart
 overlay = true,
// The default_qty_value parameter is set to 0, meaning that the position size will be calculated based on the available capital
 default_qty_value = 0,
// The initial_capital parameter is set to 10000, which is the starting capital for the strategy
 initial_capital = 10000,
// The pyramiding parameter is set to 0, because it will be controlled by the script
 pyramiding = 0,
// The commission_value parameter is set to 0.04, which represents a 0.04% commission for each trade
 commission_value = 0.05,
// The commission_type parameter is set to strategy.commission.percent, which means that the commission is a percentage of the trade value
 commission_type = strategy.commission.percent,
// The process_orders_on_close parameter is set to true, which means that orders will be executed at the close of the bar
 process_orders_on_close = true,
// The margin_long parameter is set to 100, which represents the margin requirement for long positions
 margin_long = 100,
// The margin_short parameter is set to 100, which represents the margin requirement for short positions
 margin_short = 100,
// The use_bar_magnifier parameter is set to false, which means that the chart will not use a bar magnifier
 use_bar_magnifier = false)

// ———————————————————— Inputs
// ————— Source input
// Define the position to take (long, short, or both)
Position = input.string('Both', 'Long / Short', options = ['Long', 'Short', 'Both'], tooltip = "Choose the position to take (long, short, or both)")

// ————— EMA inputs
// Define the slow and fast EMA lengths
sEma_Length = input.int(775, 'Slow EMA Length', minval = 0, step = 25, group = 'EXPONENTIAL MOVING AVERAGE', tooltip = "Set the length of the slow EMA")
fEma_Length = input.int(125, 'Fast EMA Length', minval = 0, step = 25, group = 'EXPONENTIAL MOVING AVERAGE', tooltip = "Set the length of the fast EMA")

// ————— ADX inputs
// Define the ADX parameters
ADX_len = input.int(28, 'ADX Length', minval = 1, group = 'AVERAGE DIRECTIONAL INDEX', tooltip = "Set the length of the ADX")
ADX_smo = input.int(10, 'ADX Smoothing', minval = 1, group = 'AVERAGE DIRECTIONAL INDEX', tooltip = "Set the smoothing of the ADX")
th = input.float(23, 'ADX Threshold', minval = 0, step = 0.5, group = 'AVERAGE DIRECTIONAL INDEX', tooltip = "Set the threshold for the ADX")

// ————— SAR inputs
// Define the SAR parameters
Sst = input.float(0.08, 'SAR star', minval = 0.01, step = 0.01, group = 'PARABOLIC SAR', tooltip = "Set the starting value for the SAR")
Sinc = input.float(0.04, 'SAR inc', minval = 0.01, step = 0.01, group = 'PARABOLIC SAR', tooltip = "Set the increment value for the SAR")
Smax = input.float(0.4, 'SAR max', minval = 0.01, step = 0.01, group = 'PARABOLIC SAR', tooltip = "Set the maximum value for the SAR")

// ————— MOVING AVERAGE CONVERGENCE DIVERGENCE
// Define the MACD and the MAC-Z parameters
MACD_options = input.string('MAC-Z', 'MACD OPTION', options = ['MACD', 'MAC-Z'], group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip="Select the type of MACD to use. The MAC-Z is designed to reduce false signals compared to the standard MACD")
fastLength = input.int(24, 'MACD Fast MA Length', minval = 1, group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip = "Number of periods for the fast moving average.")
slowLength = input.int(54, 'MACD Slow MA Length', minval = 1, group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip = "Number of periods for the slow moving average.")
signalLength = input.int(14, 'MACD Signal Length', minval = 1, group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip = "Number of periods for the signal line.")
lengthz = input.int(14, 'Z-VWAP Length', minval = 1, group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip = "Number of periods for the Z-VWAP used in the MAC-Z calculation.")
lengthStdev = input.int(11, 'StDev Length', minval = 1, group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip = "Number of periods for the standard deviation used in the MAC-Z calculation.")
A = input.float(0.1, "MAC-Z constant A", minval = -2.0, maxval = 2.0, step = 0.1, group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip = "Constant A used in the MAC-Z calculation.")
B = input.float(1.0, "MAC-Z constant B", minval = -2.0, maxval = 2.0, step = 0.1, group = 'MOVING AVERAGE CONVERGENCE DIVERGENCE', tooltip = "Constant B used in the MAC-Z calculation.")

// ————— Volume inputs for entries condition and for calculate quantities later
// Define volume factor and simple moving average length for volume condition
volume_f = input.float(1.1, 'Volume Factor', minval = 0, step = 0.1, group = 'VOLUME CONDITION', tooltip = "Factor used to determine the minimum required volume for an entry.")
sma_Length = input.int(89, 'SMA Volume Length', minval = 1, group = 'VOLUME CONDITION', tooltip = "Number of periods for the simple moving average used in the volume condition.")

// ————— Bollinger Bands inputs
// Define inputs for Bollinger Bands
BB_Length = input.int(40, 'BB Length', minval = 1, group = 'BOLLINGER BANDS', tooltip = "Length of the Bollinger Bands period")
BB_mult = input.float(2.0, 'BB Multiplier', minval = 0.1, step = 0.1, group = 'BOLLINGER BANDS', tooltip = "Multiplier for the Bollinger Bands width")
bbMinWidth01 = input.float(5.0, 'Min. BB Width % (New Position)', minval = 0, step = 0.5, group = 'BOLLINGER BANDS', tooltip = "Minimum width of the Bollinger Bands to enter a new position")
bbMinWidth02 = input.float(2.0, 'Min. BB Width % (Pyramiding)', minval = 0, step = 0.5, group = 'BOLLINGER BANDS', tooltip = "Minimum width of the Bollinger Bands to add to an existing position")

// ————— Take Profit / Trailing Stop input
// Define take profit/trailing stop options and parameters
TP_options = input.string('Both', 'Take Profit Option', options = ['Normal', 'Donchian', 'Both'], group = 'TAKE PROFIT / TRAILING STOP', tooltip = "Option for the take profit / trailing stop")
tp = input.float(1.8, 'Take Profit %', minval = 0, step = 0.1, group = 'TAKE PROFIT / TRAILING STOP', tooltip = "Percentage profit target")
trailOffset = input.float(0.3, 'Trail offset %', minval = 0, step = 0.1, group = 'TAKE PROFIT / TRAILING STOP', tooltip = "The offset percentage used for the trailing stop. If set to 0, only the Take Profit option will be used. If set to a value greater than 0, the Trailing Stop option will be used and the use_bar_magnifier parameter must also be set to true for more realistic results.")

// ————— TP Donchian Channel Input
// Define Donchian Channel period for take profit option
DClength = input.int(52, title='Donchian Channel Period', minval = 1, group='TAKE PROFIT / TRAILING STOP', tooltip = 'Defines the period of the Donchian Channel used for the take profit option.')

// ————— Stop Loss input
// Define stop loss options and parameters
SL_options = input.string('Both', 'Stop Loss Option', options = ['Normal', 'ATR', 'Both'], group = 'STOP LOSS', tooltip = 'Select the type of stop loss option you want to use.')
sl = input.float(9.0, 'Stop Loss %', minval = 0, step = 0.5, group = 'STOP LOSS', tooltip = 'Defines the percentage value of the stop loss.')

// ————— SL ATR Inputs
// Define ATR period and multiplier for ATR stop loss option
atrPeriodSl = input.int(14, title='ATR Period', minval = 0, group='STOP LOSS', tooltip = 'Defines the period of the Average True Range used in the ATR stop loss option.')
multiplierPeriodSl = input.float(13.5, 'ATR Multiplier', minval = 0, step = 0.5, group = 'STOP LOSS', tooltip = 'Defines the multiplier used in the ATR stop loss option.')

// ————— Risk input
// Define maximum risk percentage
Risk = input.float(5, 'Max. Risk %', tooltip = "Set the maximum percentage of account balance you're willing to risk per position", minval = 0, maxval = 100, group = 'RISK')

// ————— Pyramiding
// Define pyramiding options and parameters
Pyr = input.int(3, 'Max. Pyramiding', tooltip = "Set the maximum number of trades you are willing to open within a position", minval = 1, maxval = 10, group = 'PYRAMIDING')
StepEntry = input.string('Incremental', 'Step Entry Mode', tooltip = "Select the way you want to add new trades", options = ['Normal', 'Incremental'], group = 'PYRAMIDING')
bbBetterPrice = input.float(1.1, 'Min. Better Price %', tooltip = "Set the minimum better price percentage you want to get when adding new trades", minval = 0.1, step = 0.1, group = 'PYRAMIDING')

// ————— Backtest input
// Setting the start date for the backtesting period as January 1, 2000 at 1:00 UTC
StartDate = timestamp('01 Jan 2000 01:00 +000')
// Setting the start of the trading period for the backtest, using the start date above as the default value
testPeriodStart = input.time(StartDate, 'Start of Trading', tooltip = "Set the start date and time of the backtesting period", group = 'BACKTEST')

// ———————————————————— Average Price Variable
// Setting the average price variable to the close price of each candle, and using the strategy's average price if available
float AveragePrice = close
AveragePrice := nz(strategy.position_avg_price, close)

// ———————————————————— Exponential Moving Average
// Calculating the slow and fast Exponential Moving Averages (EMAs) using the close price
sEMA = ta.ema(close, sEma_Length)
fEMA = ta.ema(close, fEma_Length)

// Setting the conditions for a long or short signal based on the relationship between the fast and slow EMAs
bool EMA_longCond = na
bool EMA_shortCond = na
EMA_longCond := fEMA > sEMA and sEMA > sEMA[1]
EMA_shortCond := fEMA < sEMA and sEMA < sEMA[1]

// Plotting the EMAs on the chart and coloring them based on the long and short conditions
EMA_color = EMA_longCond ? color.new(color.green, 50) : EMA_shortCond ? color.new(color.red, 50) : color.new(color.orange, 50)
plot(sEMA, 'Slow EMA', EMA_color, 3)
plot(fEMA, 'Fast EMA', EMA_color, 2)

// ———————————————————— ADX
// Calculating the Directional Indicator Plus (+DI), Directional Indicator Minus (-DI), and Average Directional Movement Index (ADX)
[DIPlus, DIMinus, ADX] = ta.dmi(ADX_len, ADX_smo)

// Setting the conditions for a long or short signal based on the relationship between +DI and -DI and the ADX value
bool ADX_longCond = na
bool ADX_shortCond = na
ADX_longCond := DIPlus > DIMinus and ADX > th
ADX_shortCond := DIPlus < DIMinus and ADX > th

// Coloring the bars on the chart based on the long and short conditions
ADX_color = ADX_longCond ? color.new(color.green, 25) : ADX_shortCond ? color.new(color.red, 25) : color.new(color.orange, 25)
barcolor(color = ADX_color, title = 'ADX')

// ———————————————————— SAR
// Calculating the Parabolic SAR (SAR) based on the parameters set for step, max, and acceleration factor
SAR = ta.sar(Sst, Sinc, Smax)

// Setting the conditions for a long or short signal based on the relationship between the SAR value and the close price
bool SAR_longCond = na
bool SAR_shortCond = na
SAR_longCond := SAR < close
SAR_shortCond := SAR > close

// Plotting the SAR on the chart and coloring it based on the long and short conditions
plot(SAR, 'SAR', SAR_longCond ? color.new(color.green, 50) : color.new(color.red, 50), style = plot.style_circles)

// ———————————————————— MACD
// Calculating the Moving Average Convergence Divergence (MACD) and its signal line, as well as the MACD-Z value
// Define three variables lMACD, sMACD, and hist by calling the ta.macd() function using the 'close', 'fastLength', 'slowLength', and 'signalLength' as parameters
[lMACD, sMACD, hist] = ta.macd(close, fastLength, slowLength, signalLength)

// ————— MAC-Z calculation
// Define a function calc_zvwap(pds) that calculates the z-score of the volume-weighted average price (VWAP) of 'close' for a given period 'pds'
calc_zvwap(pds) =>
    mean = math.sum(volume * close, pds) / math.sum(volume, pds) 
    vwapsd = math.sqrt(ta.sma(math.pow(close - mean, 2), pds))
    (close - mean) / vwapsd

// Define float variables
float zscore = na
float fastMA = na
float slowMA = na
float macd = na
float macz = na
float signal = na
float histmacz = na

// Call the function calc_zvwap(lengthz) to calculate the z-score of the VWAP for a given period 'lengthz'
zscore := calc_zvwap(lengthz)
// Calculate the simple moving averages of the 'close' prices using 'fastLength' and 'slowLength' periods, and assign them to 'fastMA' and 'slowMA' respectively
fastMA := ta.sma(close, fastLength)
slowMA := ta.sma(close, slowLength)
// Assign the 'lMACD' variable to 'macd'
macd := lMACD
// Calculate 'macz' by multiplying the z-score by a constant 'A', 
// adding the 'macd' value, and dividing by the product of the standard deviation of the 'close' prices over a period 'lengthStdev' and a constant 'B'
macz := (zscore * A) + (macd / (ta.stdev(close, lengthStdev) * B))
// Calculate the simple moving average of the 'macz' values over a period 'signalLength' and assign it to 'signal'
signal := ta.sma(macz, signalLength)
// Calculate the difference between 'macz' and 'signal' and assign it to 'histmacz'
histmacz := macz - signal

// ————— MACD conditions
// Define two boolean variables 'MACD_longCond' and 'MACD_shortCond'
bool MACD_longCond = na
bool MACD_shortCond = na
// If 'MACD_options' is equal to 'MACD', check if the 'hist' value is greater than 0 and assign the result to 'MACD_longCond'; 
// otherwise, check if 'histmacz' is greater than 0 and assign the result to 'MACD_longCond'
MACD_longCond := MACD_options == 'MACD' ? hist > 0 : histmacz > 0
// If 'MACD_options' is equal to 'MACD', check if the
MACD_shortCond :=  MACD_options == 'MACD' ? hist < 0 : histmacz < 0

// ———————————————————— Bollinger Bands
// ————— BB calculation
// Calculates the middle, upper and lower bands using the Bollinger Bands technical analysis indicator
[BB_middle, BB_upper, BB_lower] = ta.bb(close, BB_Length, BB_mult)

// ————— Bollinger Bands width
// Calculates the width of the Bollinger Bands
float BB_width = na
BB_width := (BB_upper - BB_lower) / BB_middle

// ————— Long Bollinger Bands conditions
// Defines the conditions for entering a long position using Bollinger Bands
// New Longs
bool BB_long01 = na
BB_long01 := Position != 'Short' and not ADX_shortCond and ta.crossunder(low, BB_lower) and EMA_longCond and BB_width > (bbMinWidth01 / 100)

// Pyramiding Longs
bool BB_long02 = na
BB_long02 := Position != 'Short' and not ADX_shortCond and ta.crossunder(low, BB_lower) and EMA_longCond and BB_width > (bbMinWidth02 / 100)

// ————— Short Bollinger Bands conditions
// Defines the conditions for entering a short position using Bollinger Bands
// New Shorts
bool BB_short01 = na
BB_short01 := Position != 'Long' and not ADX_longCond and ta.crossover(high, BB_upper) and EMA_shortCond and BB_width > (bbMinWidth01 / 100)

// Pyramiding Shorts
bool BB_short02 = na
BB_short02 := Position != 'Long' and not ADX_longCond and ta.crossover(high, BB_upper) and EMA_shortCond and BB_width > (bbMinWidth02 / 100)

// ————— Bollinger Bands plotting
// Plots the Bollinger Bands on the chart.
u_BB = plot(BB_upper, 'Upper Bollinger Band', EMA_color, 2)
l_BB = plot(BB_lower, 'Lower Bollinger Band', EMA_color, 2)
fill(u_BB, l_BB, title = 'Bollinger Band Background', color = fEMA > sEMA ? color.new(color.green, 95) : color.new(color.red, 95))

// ———————————————————— Volume
// Defines conditions for long and short positions based on volume
bool VOL_longCond = na
bool VOL_shortCond = na
VOL_longCond := volume > ta.sma(volume, sma_Length) * volume_f
VOL_shortCond := VOL_longCond

// ———————————————————— Strategy
// Defines the long and short conditions for entering a trade based on multiple indicators and volume
bool longCond = na
longCond := Position != 'Short' and EMA_longCond and ADX_longCond and SAR_longCond and MACD_longCond and VOL_longCond

bool shortCond = na
shortCond := Position != 'Long' and EMA_shortCond and ADX_shortCond and SAR_shortCond and MACD_shortCond and VOL_shortCond

// ———————————————————— Take Profit
// ————— Donchian Channels Calculation
// Calculates the Donchian Channels for determining take profit levels
float DClower = na
float DCupper = na
float DCbasis = na
DClower := ta.lowest(DClength)
DCupper := ta.highest(DClength)
DCbasis := math.avg(DCupper, DClower)

// ————— Take Profit Conditions
// Determines the take profit levels based on the selected options
float longPriceProfit = na
float shortPriceProfit = na

if TP_options == 'Both'
// Calculate the long and short take profit levels if both options are selected
    longPriceProfit := math.max(DCupper, (1 + (tp / 100)) * AveragePrice)
    shortPriceProfit := math.min(DClower, (1 - (tp / 100)) * AveragePrice)

else if TP_options == 'Normal'
// Calculate the normal take profit levels if that option is selected
    longPriceProfit := (1 + (tp / 100)) * AveragePrice
    shortPriceProfit := (1 - (tp / 100)) * AveragePrice

else if TP_options == 'Donchian'
// Calculate the Donchian take profit levels if that option is selected
    longPriceProfit := math.max(DCupper, AveragePrice)
    shortPriceProfit := math.min(DClower, AveragePrice)

// ————— Take Profit Plotting
// Plot the take profit levels on the chart
plot(strategy.position_size > 0 ? longPriceProfit : strategy.position_size < 0 ? shortPriceProfit : na,
 'Take Profit', strategy.position_size > 0 ? color.new(color.blue, 30) : color.new(color.red, 30), 2, plot.style_circles)

// ———————————————————— ATR Stop Loss
// ————— ATR Calculation
// Calculate the ATR stop loss levels
float ATR_SL_Long = low - ta.atr(atrPeriodSl) * multiplierPeriodSl
float ATR_SL_Short = high + ta.atr(atrPeriodSl) * multiplierPeriodSl
float longStopPrev = nz(ATR_SL_Long[1], ATR_SL_Long)
float shortStopPrev = nz(ATR_SL_Short[1], ATR_SL_Short)
ATR_SL_Long := open > longStopPrev ? math.max(ATR_SL_Long, longStopPrev) : ATR_SL_Long
ATR_SL_Short := open < shortStopPrev ? math.min(ATR_SL_Short, shortStopPrev) : ATR_SL_Short

// ————— Price Stop
// Calculate the price stop levels
float longPriceStop = na
float shortPriceStop = na

if SL_options == 'Both'
// Calculate the long and short price stop levels if both options are selected
    longPriceStop := math.max(ATR_SL_Long, (1 - (sl / 100)) * AveragePrice)
    shortPriceStop := math.min(ATR_SL_Short, (1 + (sl / 100)) * AveragePrice)

else if SL_options == 'Normal'
// Calculate the normal price stop levels if that option is selected
    longPriceStop := (1 - (sl / 100)) * AveragePrice
    shortPriceStop := (1 + (sl / 100)) * AveragePrice

else if SL_options == 'ATR'
// Calculate the ATR price stop levels if that option is selected
    longPriceStop := ATR_SL_Long
    shortPriceStop := ATR_SL_Short

// ————— Variable Stop Loss %
// Calculate the variable stop loss percentage levels
float slPercentLong = na
slPercentLong := (math.max(0, (AveragePrice - longPriceStop)) / AveragePrice) * 100
float slPercentShort = na
slPercentShort := (math.max(0, (shortPriceStop - AveragePrice)) / AveragePrice) * 100

// ————— Stop Loss plotting
// Plots the stop loss level based on the current position size and whether it's a long or short position
// Uses circles to visualize the stop loss level
plot(strategy.position_size > 0 ? longPriceStop : strategy.position_size < 0 ? shortPriceStop : na,
 'Stop Loss', strategy.position_size > 0 ? color.new(color.aqua, 80) : color.new(color.purple, 70), 2, plot.style_circles)

// ————— Average price plotting
// Plots the average price of the current position, using a cross to visualize the price level
// Uses different colors depending on whether it's a long or short position
plot(ta.change(strategy.position_avg_price) ? strategy.position_avg_price : na,
 'Average price', strategy.position_size > 0 ? color.new(color.aqua, 20) : color.new(color.yellow, 20), 2, plot.style_cross)

// ———————————————————— Backtest
// Define the variable "Equity" and assign it the value of "strategy.equity"
float Equity = strategy.equity
// Define the variable "Balance" and assign it the sum of "strategy.initial_capital" and "strategy.netprofit"
float Balance = strategy.initial_capital + strategy.netprofit
// Define the variable "RealizedPnL" and assign it the value of "strategy.netprofit"
float RealizedPnL = strategy.netprofit
// Define the variable "Floating" and assign it the value of "strategy.openprofit"
float Floating = strategy.openprofit
// Calculate the percentage of "Floating" relative to "Balance" and assign it to "PFloating"
float PFloating = (Floating / Balance) * 100
// Calculate the percentage of "RealizedPnL" relative to "strategy.initial_capital" and assign it to "PRealizedPnL"
float PRealizedPnL = (RealizedPnL / strategy.initial_capital) * 100
// Calculate the sum of "Floating" and "RealizedPnL" and assign it to "URealizedPnL"
float URealizedPnL = Floating + RealizedPnL
// Calculate the percentage of "URealizedPnL" relative to "strategy.initial_capital" and assign it to "PURealizedPnL"
float PURealizedPnL = ((URealizedPnL) / strategy.initial_capital) * 100
// Calculate the profit factor by dividing "strategy.grossprofit" by "strategy.grossloss" and assign it to "ProfitFactor"
float ProfitFactor = strategy.grossprofit / strategy.grossloss
// Calculate the position size by multiplying "strategy.position_size" and "strategy.position_avg_price" and assign it to "PositionSize"
float PositionSize = strategy.position_size * strategy.position_avg_price
// Calculate the cash by subtracting the product of "nz(PositionSize)" and "marginRate" from "Balance" and assign it to "Cash"
float Cash = Balance
// Calculate the profitability by dividing "strategy.wintrades" by the sum of "strategy.wintrades" and "strategy.losstrades", multiplying by 100, and assign it to "Profitability"
float Profitability = (strategy.wintrades / (strategy.wintrades + strategy.losstrades)) * 100
// Calculate the leverage parameter by dividing the absolute value of "PositionSize" by "Balance" and assign it to "LeverageParameter"
float LeverageParameter = math.abs(nz(PositionSize / Balance))
// Calculate the trading time by subtracting "strategy.closedtrades.entry_time(0)" from "time_close" and assign it to "TradingTime"
int TradingTime = time_close - strategy.closedtrades.entry_time(0)

// ————— Creating the Table
// Create a table with a specific format and assign it to a variable
var InfoPanel = table.new(position.bottom_left, 2, 10, border_width = 1)

// Define a function for the table formatting
ftable(_table_id, _column, _row, _text, _bgcolor) =>
    table.cell(_table_id, _column, _row, _text, 0, 0, color.black, text.align_left, text.align_center, size.tiny, _bgcolor)

// Function to convert time in milliseconds to string format
tfString(int timeInMs) =>

    // convert to seconds
    float s = timeInMs / 1000
    // convert to minutes     
    float m = s / 60
    // convert to hours            
    float h = m / 60
    // convert to days          
    float d = h / 24
    // convert to months      
    float mo = d / 30.416
    // get minutes     
    int tm = math.floor(m % 60)
    // get hours 
    int tr = math.floor(h % 24)
    // get days
    int td = math.floor(d % 30.416)
    // get months
    int tmo = math.floor(mo % 12)
    // get years
    int ys = math.floor(d / 365)    

    // Format the time in the appropriate string format
    string result = switch
        // if time interval is 1 month, return "1M"
        d == 30 and tr == 10 and tm == 30 => "1M" 
        // if time interval is 1 week, return "1W"
        d == 7  and tr == 0  and tm == 0  => "1W" 
        =>
            // get the years string
            string  yStr  = ys  ? str.tostring(ys)  + "Y "  : ""
            // get the months string
            string  moStr = tmo ? str.tostring(tmo) + "M "  : ""
            // get the days string
            string  dStr  = td  ? str.tostring(td)  + "D "  : ""
            // get the hours string
            string  hStr  = tr  ? str.tostring(tr)  + "H "  : ""
            // get the minutes string
            string  mStr  = tm  ? str.tostring(tm)  + "min" : ""
            // concatenate all the strings to get the final result string
            yStr + moStr + dStr + hStr + mStr

// This code is using the ftable function to create a table of information in a panel.
// Display equity information in the info panel using the ftable function
ftable(InfoPanel, 0, 0, 'Equity: ', #9598a1)
// Display current position information in the info panel using the ftable function
ftable(InfoPanel, 0, 1, 'Position: ', #9598a1)
// Display available cash information in the info panel using the ftable function
ftable(InfoPanel, 0, 2, 'Cash: ', #9598a1)
// Display floating profit/loss information in the info panel using the ftable function
ftable(InfoPanel, 0, 3, 'Floating: ', #9598a1)
// Display realized profit/loss information in the info panel using the ftable function
ftable(InfoPanel, 0, 4, 'Realized PnL: ' , #9598a1)
// Display unrealized profit/loss information in the info panel using the ftable function
ftable(InfoPanel, 0, 5, 'Unrealized PnL: ' , #9598a1)
// Display leverage information in the info panel using the ftable function
ftable(InfoPanel, 0, 6, 'Leverage: ' , #9598a1)
// Display profitability information in the info panel using the ftable function
ftable(InfoPanel, 0, 7, 'Profitability: ' , #9598a1)
// Display profit factor information in the info panel using the ftable function
ftable(InfoPanel, 0, 8, 'Profit Factor: ' , #9598a1)
// Display trading time information in the info panel using the ftable function
ftable(InfoPanel, 0, 9, 'Time of Trading: ', #9598a1)
// Display equity value with currency information in the info panel using the ftable function
ftable(InfoPanel, 1, 0, str.tostring(Equity, '#.##') + ' ' + syminfo.currency, Equity >= 0 ? color.green : color.red)
// Display position size with base currency information in the info panel using the ftable function
ftable(InfoPanel, 1, 1, str.tostring(strategy.position_size, '#.#####') + ' ' + syminfo.basecurrency, strategy.position_size >= 0 ? color.green : color.red)
// Display available cash with currency information in the info panel using the ftable function
ftable(InfoPanel, 1, 2, str.tostring(Cash, '#.##') + ' ' + syminfo.currency, Cash >= 0 ? color.green : color.red)
// Display percentage of floating profit/loss information in the info panel using the ftable function
ftable(InfoPanel, 1, 3, str.tostring(PFloating, '#.##') + ' %', PFloating >= 0 ? color.green : color.red)
// Display percentage of realized profit/loss information in the info panel using the ftable function
ftable(InfoPanel, 1, 4, str.tostring(PRealizedPnL, '#.##') + ' %' , PRealizedPnL >= 0 ? color.green : color.red)
// Display percentage of unrealized profit/loss information in the info panel using the ftable function
ftable(InfoPanel, 1, 5, str.tostring(PURealizedPnL, '#.##') + ' %', PURealizedPnL >= 0 ? color.green : color.red)
// Show leverage parameter in the info panel
ftable(InfoPanel, 1, 6, str.tostring(LeverageParameter, '#.##') + ' x', math.round(LeverageParameter, 1) <= 1 ? color.green : color.red)
// Show profitability percentage in the info panel
ftable(InfoPanel, 1, 7, str.tostring(Profitability, '#.##') + ' %', Profitability >= 1 ? color.green : color.red)
// Show the profit factor in the info panel
ftable(InfoPanel, 1, 8, str.tostring(ProfitFactor, '#.###'), ProfitFactor >= 1 ? color.green : color.red)
// Show the time of trading in the info panel in human-readable format
ftable(InfoPanel, 1, 9, tfString(TradingTime), #9598a1)

// ————— Long / Short quantities according to risk
// Define a function that calculates the factor sum of a number
fSum(_Num)=>
    (math.pow(_Num, 2) + _Num) / 2

// Calculate long and short quantities based on different scenarios
float QuantityLong = na
float QuantityShort = na

// The QuantityLong and QuantityShort variables represent the calculated position sizes based on different risk scenarios
if StepEntry == 'Normal'
    QuantityLong := (1 / Pyr) * (Balance / close) * ((Risk / 100) / (slPercentLong / 100))
    QuantityShort := (1 / Pyr) * (Balance / close) * ((Risk / 100) / (slPercentShort / 100))
else
    QuantityLong := ((strategy.opentrades + 1) / (fSum(Pyr))) * (Balance / close) * ((Risk / 100) / (slPercentLong / 100))
    QuantityShort := ((strategy.opentrades + 1) / (fSum(Pyr))) * (Balance / close) * ((Risk / 100) / (slPercentShort / 100))

// ————— Long alert message
// Define long alert messages with placeholders
string LongMessage = na
LongMessage := '{"code": "ENTER-LONG_OKEX-SWAP__Super-8_30M_1234567890", "qty":"' + str.tostring(QuantityLong) + '"}'

string XLongMessage = na
XLongMessage := '{"code": "EXIT-LONG_OKEX-SWAP__Super-8_30M_1234567890", "qty":"' + str.tostring(strategy.position_size) + '"}'

// ————— Short alert message
// Define short alert messages with placeholders
string ShortMessage = na
ShortMessage := '{"code": "ENTER-SHORT_OKEX-SWAP__Super-8_30M_1234567890", "qty":"' + str.tostring(QuantityShort) + '"}'

string XShortMessage = na
XShortMessage := '{"code": "EXIT-SHORT_OKEX-SWAP__Super-8_30M_1234567890", "qty":"' + str.tostring(math.abs(strategy.position_size)) + '"}'

// The LongMessage and ShortMessage variables are strings that define the alert messages for the long and short positions respectively,
// including placeholders for dynamic values like the current close price and the calculated position size
// The XLongMessage and XShortMessage variables represent messages to close out long and short positions respectively

// Copy and paste this message on the alert box:
// {{strategy.order.alert_message}}

// Copy and paste this url on the WebHook box:
// https://wundertrading.com/bot/exchange 

// ————— Entering long positions
// Check if it's time to start the test period and if there are less open trades than the maximum allowed
if time >= testPeriodStart and strategy.opentrades < Pyr
    // Main long entries
    // Check if the long condition is met or if the upper Bollinger Band is crossed and if there are no open trades or a short position is already open or if the price is lower than the previous entry price minus the percentage allowed
    // If the conditions are met, execute the long entry with the desired quantity and alert message
    if  (longCond or BB_long01) and strategy.opentrades == 0
     or (longCond or BB_long01) and strategy.position_size < 0
     or (longCond or BB_long01) and close < strategy.opentrades.entry_price(strategy.opentrades - 1) * (1 - (bbBetterPrice / 100))
        strategy.order('Long', strategy.long, qty = QuantityLong, alert_message = LongMessage)
    // BB long entries
    // Check if the upper Bollinger Band is crossed and a long position is already open and if the price is lower than the previous entry price minus the percentage allowed
    // If the conditions are met, execute the long entry with the desired quantity and alert message
    if BB_long02 and strategy.position_size > 0
     and close < strategy.opentrades.entry_price(strategy.opentrades - 1) * (1 - (bbBetterPrice / 100))
        strategy.order('Long', strategy.long, qty = QuantityLong, alert_message = LongMessage)

// ————— Entering short positions
// Check if it's time to start the test period and if there are less open trades than the maximum allowed
if time >= testPeriodStart and strategy.opentrades < Pyr
    // Main short entries
    // Check if the short condition is met or if the lower Bollinger Band is crossed and if there are no open trades or a long position is already open or if the price is higher than the previous entry price plus the percentage allowed
    // If the conditions are met, execute the short entry with the desired quantity and alert message
    if  (shortCond or BB_short01) and strategy.opentrades == 0
     or (shortCond or BB_short01) and strategy.position_size > 0
     or (shortCond or BB_short01) and close > strategy.opentrades.entry_price(strategy.opentrades - 1) * (1 + (bbBetterPrice / 100))
        strategy.order('Short', strategy.short, qty = QuantityShort, alert_message = ShortMessage)
    // BB short entries
    // Check if the lower Bollinger Band is crossed and a short position is already open and if the price is higher than the previous entry price plus the percentage allowed
    // If the conditions are met, execute the short entry with the desired quantity and alert message
    if BB_short02 and strategy.position_size < 0
     and close > strategy.opentrades.entry_price(strategy.opentrades - 1) * (1 + (bbBetterPrice / 100))
        strategy.order('Short', strategy.short, qty = QuantityShort, alert_message = ShortMessage)

// ————— Closing positions with first long TP
// Check if there is a long position open
if strategy.position_size > 0
    // If there is, check if a trailing offset is set
    if trailOffset > 0
    // If there is a trailing offset, execute the exit with a trailing stop
        strategy.exit('TPl', 'Long', trail_price = longPriceProfit, 
                                     trail_offset = ((trailOffset / 100) * high) / syminfo.mintick, 
                                     stop = longPriceStop, 
                                     comment_profit = 'TPl', 
                                     comment_loss = 'SLl', alert_message = XLongMessage)
    else
    // If there is no trailing offset, execute the exit with a limit and stop loss
        strategy.exit('TPl', 'Long', limit = longPriceProfit, 
                                     stop = longPriceStop, 
                                     comment_profit = 'TPl', 
                                     comment_loss = 'SLl', 
                                     alert_message = XLongMessage)

// ————— Canceling long exit orders to avoid simultaneity with re-entry
// This code block cancels any previously set long exit orders if the Bollinger Bands indicate a potential re-entry into the market
if BB_long02 and strategy.position_size > 0
    strategy.cancel('TPl')

// ————— Closing positions with first short TP
// This code block checks if the strategy has a short position and if so, it closes it with a trailing stop loss or a take profit order
if strategy.position_size < 0
    if trailOffset > 0
        strategy.exit('TPs', 'Short', trail_price = shortPriceProfit, 
                                      trail_offset = ((trailOffset / 100) * low) / syminfo.mintick, 
                                      stop = shortPriceStop, 
                                      comment_profit = 'TPs', 
                                      comment_loss = 'SLs', 
                                      alert_message = XShortMessage)
    else
        strategy.exit('TPs', 'Short', limit = shortPriceProfit, 
                                      stop = shortPriceStop, 
                                      comment_profit = 'TPs', 
                                      comment_loss = 'SLs', 
                                      alert_message = XShortMessage)

// ————— Canceling short exit orders to avoid simultaneity with re-entry
// This code block cancels any previously set short exit orders if the Bollinger Bands indicate a potential re-entry into the market
if BB_short02 and strategy.position_size < 0
    strategy.cancel('TPs')