Multi-TF AI SuperTrend with ADX - Strategy [PresentTrading]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © PresentTrading

//@version=5
strategy("Multi-TF AI-Enhanced SuperTrend with ADX Filter - Strategy [PresentTrading]", overlay=true, precision=3, default_qty_type=strategy.cash, 
 commission_value= 0.1, commission_type=strategy.commission.percent, slippage= 1, 
  currency=currency.USD, default_qty_type = strategy.percent_of_equity, default_qty_value = 10, initial_capital= 10000)

// ~~ ToolTips {
t1="Number of nearest neighbors in KNN algorithm (k): Increase to consider more neighbors, providing a more balanced view but possibly smoothing out local patterns. Decrease for fewer neighbors to make the algorithm more responsive to recent changes. \n\nNumber of data points to consider (n): Increase for more historical data, providing a broader context but possibly diluting recent trends. Decrease for less historical data to focus more on recent behavior."
t2="Length of weighted moving average for price (KNN_PriceLen): Higher values create a smoother price line, influencing the KNN algorithm to be more stable but less sensitive to short-term price movements. Lower values enhance responsiveness in KNN predictions to recent price changes but may lead to more noise. \n\nLength of weighted moving average for SuperTrend (KNN_STLen): Higher values lead to a smoother SuperTrend line, affecting the KNN algorithm to emphasize long-term trends. Lower values make KNN predictions more sensitive to recent SuperTrend changes but may result in more volatility."
t3="Length of the SuperTrend (len): Increase for a smoother trend line, ideal for identifying long-term trends but possibly ignoring short-term fluctuations. Decrease for more responsiveness to recent changes but risk of more false signals. \n\nMultiplier for ATR in SuperTrend calculation (factor): Increase for wider bands, capturing larger price movements but possibly missing subtle changes. Decrease for narrower bands, more sensitive to small shifts but risk of more noise."
t4="Type of moving average for SuperTrend calculation (maSrc): Choose based on desired characteristics. SMA is simple and clear, EMA emphasizes recent prices, WMA gives more weight to recent data, RMA is less sensitive to recent changes, and VWMA considers volume."
t5="Color for bullish trend (upCol): Select to visually identify upward trends. \n\nColor for bearish trend (dnCol): Select to visually identify downward trends.\n\nColor for neutral trend (neCol): Select to visually identify neutral trends."
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// Add Button for Trading Direction
tradeDirection = input.string("Both", "Select Trading Direction", options=["Long", "Short", "Both"])

// ~~ Input settings for K and N values
k  = input.int(3, title = "Neighbors", minval=1, maxval=100,inline="AI", group="AI Settings") 
n_ = input.int(24, title ="Data", minval=1, maxval=100,inline="AI", group="AI Settings", tooltip=t1) 
n  = math.max(k,n_)

// Second KNN Parameters
k2  = input.int(3, title = "2nd Neighbors", minval=1, maxval=100,inline="AI2", group="AI2 Settings") 
n2_ = input.int(24, title ="2nd Data", minval=1, maxval=100,inline="AI2", group="AI2 Settings") 
n2  = math.max(k2, n2_)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Input settings for prediction values
KNN_PriceLen = input.int(10, title="Price Trend", minval=2, maxval=500, step=10,inline="AITrend", group="AI Trend")
KNN_STLen    = input.int(80, title="Prediction Trend", minval=2, maxval=500, step=10, inline="AITrend", group="AI Trend", tooltip=t2)

KNN_PriceLen2 = input.int(10, title="2nd Price Trend", minval=2, maxval=500, step=10,inline="AI2Trend", group="AI Trend")
KNN_STLen2    = input.int(80, title="2nd Prediction Trend", minval=2, maxval=500, step=10, inline="AI2Trend", group="AI Trend")

aisignals    = input.bool(true,title="AI Trend Signals",inline="signal", group="AI Trend")
Bullish_col  = input.color(color.lime,"",inline="signal", group="AI Trend")
Bearish_col  = input.color(color.red,"",inline="signal", group="AI Trend")
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Define SuperTrend parameters
// Add Higher Timeframe Inputs
higherTf1 = input.timeframe(title='Higher Time Frame for 1st ST', defval='480',group="Super Trend Settings")
higherTf2 = input.timeframe(title='Higher Time Frame for 2nd ST', defval='D',group="Super Trend Settings")

maSrc  = input.string("WMA","Moving Average Source",["SMA","EMA","WMA","RMA","VWMA"],inline="", group="Super Trend Settings", tooltip=t4) 
len    = input.int(10, "Length", minval=1,inline="SuperTrend", group="Super Trend Settings") 
factor = input.float(3.0,step=.1,inline="SuperTrend", group="Super Trend Settings", tooltip=t3) 

// Calculate the Second SuperTrend
len2 = input.int(5, "2nd Length", minval=1,inline="SuperTrend", group="Super Trend Settings")
factor2 = input.float(3.0,step=.1,inline="SuperTrend", group="Super Trend Settings")
upCol  = input.color(color.lime,"Bullish Color",inline="col", group="Super Trend Coloring") 
dnCol  = input.color(color.red,"Bearish Color",inline="col", group="Super Trend Coloring") 
neCol  = input.color(color.blue,"Neutral Color",inline="col", group="Super Trend Coloring", tooltip=t5) 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// Add an input to select the higher timeframe for the ADX and DMI filter:
adxLength = input.int(14, title="ADX Length", minval=1, group="ADX and DMI Settings")
adxTf = input.timeframe(title='Time Frame for ADX and DMI Filter 1', defval='D', group="ADX and DMI Settings")
useAdxFilter = input.bool(true, title="Use ADX and DMI Filter?", group="ADX and DMI Settings")

HTF_Close = request.security(syminfo.tickerid, adxTf, close)
HTF_High = request.security(syminfo.tickerid, adxTf, high)
HTF_Low = request.security(syminfo.tickerid, adxTf, low)

deltaHigh = HTF_High - HTF_High[1]
deltaLow = HTF_Low[1] - HTF_Low

plusDM = deltaHigh > deltaLow and deltaHigh > 0? deltaHigh: 0
minusDM = deltaLow > deltaHigh and deltaLow > 0? deltaLow: 0

ATR = ta.wma(math.abs(HTF_High - HTF_Low) + math.abs(HTF_High - HTF_Close[1]) + math.abs(HTF_Low - HTF_Close[1]), adxLength)

plusDI = 100 * ta.wma(plusDM, adxLength) / ATR
minusDI = 100 * ta.wma(minusDM, adxLength) / ATR

DX = 100 * math.abs(plusDI - minusDI) / (plusDI + minusDI)
ADX = ta.wma(DX, adxLength)

// ~~ Calculate the SuperTrend based on the user's choice
// ~~ First Supertrend
TfClose1 = request.security(syminfo.tickerid, higherTf1, close)
vwma1 = switch maSrc
    "SMA"  => ta.sma(TfClose1*volume, len) / ta.sma(volume, len)
    "EMA"  => ta.ema(TfClose1*volume, len) / ta.ema(volume, len)
    "WMA"  => ta.wma(TfClose1*volume, len) / ta.wma(volume, len)
    "RMA"  => ta.rma(TfClose1*volume, len) / ta.rma(volume, len)
    "VWMA" => ta.vwma(TfClose1*volume, len) / ta.vwma(volume, len)

atr = request.security(syminfo.tickerid, higherTf1, ta.atr(len)) 
upperBand = vwma1 + factor * atr
lowerBand = vwma1 - factor * atr
prevLowerBand = nz(lowerBand[1])
prevUpperBand = nz(upperBand[1])

// ~~ Second Supertrend
TfClose2 = request.security(syminfo.tickerid, higherTf2, close)
vwma2 = switch maSrc
    "SMA"  => ta.sma(TfClose2*volume, len2) / ta.sma(volume, len2)
    "EMA"  => ta.ema(TfClose2*volume, len2) / ta.ema(volume, len2)
    "WMA"  => ta.wma(TfClose2*volume, len2) / ta.wma(volume, len2)
    "RMA"  => ta.rma(TfClose2*volume, len2) / ta.rma(volume, len2)
    "VWMA" => ta.vwma(TfClose2*volume, len2) / ta.vwma(volume, len2)

atr2 = request.security(syminfo.tickerid, higherTf2, ta.atr(len2))
upperBand2 = vwma2 + factor2 * atr2
lowerBand2 = vwma2 - factor2 * atr2
prevLowerBand2 = nz(lowerBand2[1])
prevUpperBand2 = nz(upperBand2[1])

lowerBand := lowerBand > prevLowerBand or close[1] < prevLowerBand ? lowerBand : prevLowerBand
upperBand := upperBand < prevUpperBand or close[1] > prevUpperBand ? upperBand : prevUpperBand
int direction = na
float superTrend = na

lowerBand2 := lowerBand2 > prevLowerBand2 or close[1] < prevLowerBand2 ? lowerBand2 : prevLowerBand2
upperBand2 := upperBand2 < prevUpperBand2 or close[1] > prevUpperBand2 ? upperBand2 : prevUpperBand2
int direction2 = na
float superTrend2 = na

prevSuperTrend = superTrend[1]
if na(atr[1])
    direction := 1
else if prevSuperTrend == prevUpperBand
    direction := close > upperBand ? -1 : 1
else
    direction := close < lowerBand ? 1 : -1
superTrend := direction == -1 ? lowerBand : upperBand

prevSuperTrend2 = superTrend2[1]
if na(atr2[1])
    direction2 := 1
else if prevSuperTrend2 == prevUpperBand2
    direction2 := close > upperBand2 ? -1 : 1
else
    direction2 := close < lowerBand2 ? 1 : -1
superTrend2 := direction2 == -1 ? lowerBand2 : upperBand2

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Collect data points and their corresponding labels
price  = ta.wma(close,KNN_PriceLen)
sT     = ta.wma(superTrend,KNN_STLen)
data   = array.new_float(n) 
labels = array.new_int(n) 
for i = 0 to n - 1
    data.set(i, superTrend[i]) 
    label_i = price[i] > sT[i] ? 1 : 0 
    labels.set(i, label_i) 

// Collect data points for second SuperTrend and their corresponding labels
price2  = ta.wma(close,KNN_PriceLen2)
sT2     = ta.wma(superTrend2,KNN_STLen2)
data2   = array.new_float(n) 
labels2 = array.new_int(n) 
for i = 0 to n - 1
    data2.set(i, superTrend2[i]) 
    label_i2 = price2[i] > sT2[i] ? 1 : 0  // Assuming same price for second SuperTrend
    labels2.set(i, label_i2) 

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Define a function to compute distance between two data points
distance(x1, x2) => 
    math.abs(x1 - x2)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Define the weighted k-nearest neighbors (KNN) function
knn_weighted(data, labels, k, x) =>
    n1 = data.size() 
    distances = array.new_float(n1) 
    indices   = array.new_int(n1) 
    // Compute distances from the current point to all other points
    for i = 0 to n1 - 1 
        x_i = data.get(i)
        dist = distance(x, x_i) 
        distances.set(i, dist)
        indices.set(i, i)
    // Sort distances and corresponding indices in ascending order
    // Bubble sort method
    for i = 0 to n1 - 2
        for j = 0 to n1 - i - 2
            if distances.get(j) > distances.get(j + 1)
                tempDist = distances.get(j) 
                distances.set(j, distances.get(j + 1))
                distances.set(j + 1, tempDist)
                tempIndex = indices.get(j)
                indices.set(j, indices.get(j + 1))
                indices.set(j + 1, tempIndex)
    // Compute weighted sum of labels of the k nearest neighbors
    weighted_sum = 0.
    total_weight = 0.
    for i = 0 to k - 1
        index = indices.get(i)
        label_i = labels.get(index)
        weight_i = 1 / (distances.get(i) + 1e-6) 
        weighted_sum += weight_i * label_i
        total_weight += weight_i
    weighted_sum / total_weight 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Classify the current data point
current_superTrend = superTrend
label_             = knn_weighted(data, labels, k, current_superTrend)
label_upTrend = label_ == 1 ? 1:0 
label_downTrend = label_ == 0 ? -1:0 

// Classify the current data point for second SuperTrend
current_superTrend2 = superTrend2
label2_ = knn_weighted(data2, labels2, k, current_superTrend2)
label2_upTrend = label2_ == 1 ? 1:0 
label2_downTrend = label2_ == 0 ? -1:0 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~} 

// ~~ Plot
col = label_ == 1?upCol:label_ == 0?dnCol:neCol
plot(current_superTrend, color=col, title="Volume Super Trend AI")

upTrend   = plot(superTrend==lowerBand?current_superTrend:na, title="Up Volume Super Trend AI", color=col, style=plot.style_linebr)
Middle    = plot((open + close) / 2, display=display.none, editable=false)
downTrend = plot(superTrend==upperBand?current_superTrend:na, title="Down Volume Super Trend AI", color=col, style=plot.style_linebr)
fill_col  = color.new(col,95)
fill(Middle, upTrend, fill_col, fillgaps=false,title="Up Volume Super Trend AI")
fill(Middle, downTrend, fill_col, fillgaps=false, title="Down Volume Super Trend AI")

// Plot Second SuperTrend
col2 = label2_ == 1 ? upCol : label2_ == 0 ? dnCol : neCol
plot(current_superTrend2, color=col2, title="Volume Super Trend AI 2")

upTrend2   = plot(superTrend2==lowerBand2 ? current_superTrend2 : na, title="Up Volume Super Trend AI 2", color=col2, style=plot.style_linebr)
downTrend2 = plot(superTrend2==upperBand2 ? current_superTrend2 : na, title="Down Volume Super Trend AI 2", color=col2, style=plot.style_linebr)
fill_col2  = color.new(col2, 95)
fill(Middle, upTrend2, fill_col2, fillgaps=false, title="Up Volume Super Trend AI 2")
fill(Middle, downTrend2, fill_col2, fillgaps=false, title="Down Volume Super Trend AI 2")


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

// ~~ Ai Super Trend Signals
Start_TrendUp = col==upCol and (col[1]!=upCol or col[1]==neCol) and aisignals
Start_TrendDn = col==dnCol and (col[1]!=dnCol or col[1]==neCol) and aisignals
// Second AI Super Trend Signals
Start_TrendUp2 = col2==upCol and (col2[1]!=upCol or col2[1]==neCol) and aisignals
Start_TrendDn2 = col2==dnCol and (col2[1]!=dnCol or col2[1]==neCol) and aisignals

TrendUp       = direction == -1 and direction[1] == 1  and label_ == 1 and aisignals
TrendDn       = direction == 1  and direction[1] ==-1  and label_ == 0 and aisignals
TrendUp2       = direction2 == -1 and direction2[1] == 1  and label2_ == 1 and aisignals
TrendDn2       = direction2 == 1  and direction2[1] ==-1  and label2_ == 0 and aisignals

plotshape(Start_TrendUp?superTrend:na, location=location.absolute, style= shape.circle, size=size.tiny, color=Bullish_col, title="AI Bullish Trend Start")
plotshape(Start_TrendDn?superTrend:na, location=location.absolute, style= shape.circle,size=size.tiny, color=Bearish_col, title="AI Bearish Trend Start")
plotshape(TrendUp?superTrend:na, location=location.absolute, style= shape.triangleup, size=size.small, color=Bullish_col, title="AI Bullish Trend Signal")
plotshape(TrendDn?superTrend:na, location=location.absolute, style= shape.triangledown,size=size.small, color=Bearish_col, title="AI Bearish Trend Signal")
plotshape(Start_TrendUp2 ? superTrend2 : na, location=location.absolute, style= shape.circle, size=size.tiny, color=Bullish_col, title="AI Bullish Trend Start 2")
plotshape(Start_TrendDn2 ? superTrend2 : na, location=location.absolute, style= shape.circle, size=size.tiny, color=Bearish_col, title="AI Bearish Trend Start 2")
plotshape(TrendUp2?superTrend2:na, location=location.absolute, style= shape.triangleup, size=size.small, color=Bullish_col, title="AI Bullish Trend Signal")
plotshape(TrendDn2?superTrend2:na, location=location.absolute, style= shape.triangledown,size=size.small, color=Bearish_col, title="AI Bearish Trend Signal")
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}

//longCondition = (Start_TrendUp or Start_TrendUp2) or (TrendUp or TrendUp2)
//shortCondition = (Start_TrendDn or Start_TrendDn2) or (TrendDn  or TrendDn2)

longAdxCondition = useAdxFilter ? (ADX > 20 and plusDI > minusDI) : true
shortAdxCondition = useAdxFilter ? (ADX > 20 and minusDI > plusDI) : true

longCondition = direction == -1 and label_ == 1 and direction2 == -1 and label2_ == 1 and longAdxCondition
shortCondition = direction == 1 and label_ == 0 and direction2 == 1 and label2_ == 0 and shortAdxCondition

// Double AI Trend Continuation Signals
longExitCondition1 = direction == -1 and label_ == 1
longExitCondition2 = direction2 == -1 and label2_ == 1
shortExitCondition1 = direction == 1 and label_ == 0
shortExitCondition2 = direction2 == 1 and label2_ == 0

longExitCondition = not (TrendUp or TrendUp2)
shortExitCondition = not (TrendDn or TrendDn2)

// Dynamic Trailing Stop Loss
longTrailingStop = superTrend - atr * factor
shortTrailingStop = superTrend + atr * factor

// Adjust Enter and Exit Conditions based on Trading Direction
if (tradeDirection == "Long" or tradeDirection == "Both")
    strategy.entry("Long", strategy.long, when=longCondition)
    strategy.exit("Exit Long", "Long", stop=longTrailingStop, when=longExitCondition)

if (tradeDirection == "Short" or tradeDirection == "Both")
    strategy.entry("Short", strategy.short, when=shortCondition)
    strategy.exit("Exit Short", "Short", stop=shortTrailingStop, when=shortExitCondition)


// Create Alert Conditions
alertcondition(longCondition, title="Long Entry Alert", message="Long Entry Signal Generated")
alertcondition(shortCondition, title="Short Entry Alert", message="Short Entry Signal Generated")
alertcondition(longExitCondition, title="Long Exit Alert", message="Long Exit Signal Generated")
alertcondition(shortExitCondition, title="Short Exit Alert", message="Short Exit Signal Generated")