TechnicalRating
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingView

//@version=5
library("TechnicalRating")

// TechnicalRating Library
// v1, 2022.12.30

// This code was written using the recommendations from the Pine Script™ User Manual's Style Guide:
//   https://www.tradingview.com/pine-script-docs/en/v5/writing/Style_guide.html



import TradingView/ta/5



//#region ———————————————————— Library functions


// @function        General MA rating (+1/0/-1) calculated using position of `src` price with regards to `ma`.       
// @param ma        (series int/float) The moving average to check against the `src`. 
// @param src       (series int/float) The `src` to check against the moving average. 
// @returns         (int) A rating integer (+1/0/-1).
calcRatingMA(series float ma, series float src) => 
    float result = math.sign(src - ma)
    

// @function        Converts bull/bear conditions into a +1/0/-1 value corresponding to a bull/neutral/bear bias.    
// @param src       (<type>) The source to monitor for an `na` condition.   
// @param bullCond  (series bool) The bullish condition to convert to a rating.     
// @param bearCond  (series bool) The bearish condition to convert to a rating.      
// @returns         (int) A rating integer (+1/0/-1), or `na` if `src` to be evaluated is `na`.
calcRating(src, series bool bullCond, series bool bearCond) => 
    int result = na(src) ? na : bullCond ? 1 : bearCond ? -1 : 0


// @function        Calculates 3 ratings (ratings total, MA ratings, indicator ratings) using the aggregate biases of 26 different technical indicators. 
// @returns         A 3-element tuple: ([(float) ratingTotal, (float) ratingOther, (float) ratingMA].
export calcRatingAll() =>
    [con, base, lead1, lead2, _] = ta.ichimoku()
    [kStoch, dStoch]             = ta.stochFull(14, 3, 3)
    [kStochRsi, dStochRsi]       = ta.stochRsi(14, 14, 3, 3)
    [diP, diN, adx]              = ta.dmi(14, 14)
    [macd, signal, _]            = ta.macd(close, 12, 26, 9)
    float bullPower              = high - ta.ema(close, 13)
    float bearPower              = low  - ta.ema(close, 13)
    float rsi                    = ta.rsi(close, 14)
    float cci                    = ta.cci(close, 20)
    float mom                    = ta.mom(close, 10)
    float priceAvg               = ta.ema(close, 50)
    bool  downTrend              = close < priceAvg
    bool  upTrend                = close > priceAvg
    float uo                     = ta.uo(7, 14, 28)
    float wr                     = ta.wpr(14)
    float ao                     = ta.ao()

    var array<float> indRatings = array.new<float>()
    array.clear(indRatings)  
    array.push(indRatings, calcRatingMA(ta.sma(close ,  10), close))
    array.push(indRatings, calcRatingMA(ta.sma(close ,  20), close))
    array.push(indRatings, calcRatingMA(ta.sma(close ,  30), close))  
    array.push(indRatings, calcRatingMA(ta.sma(close ,  50), close))
    array.push(indRatings, calcRatingMA(ta.sma(close , 100), close))
    array.push(indRatings, calcRatingMA(ta.sma(close , 200), close)) 
    array.push(indRatings, calcRatingMA(ta.ema(close ,  10), close))
    array.push(indRatings, calcRatingMA(ta.ema(close ,  20), close)) 
    array.push(indRatings, calcRatingMA(ta.ema(close ,  30), close))
    array.push(indRatings, calcRatingMA(ta.ema(close ,  50), close))    
    array.push(indRatings, calcRatingMA(ta.ema(close , 100), close))
    array.push(indRatings, calcRatingMA(ta.ema(close , 200), close))
    array.push(indRatings, calcRatingMA(ta.hma(close ,   9), close))
    array.push(indRatings, calcRatingMA(ta.vwma(close,  20), close))
    array.push(indRatings, calcRating(lead2, 
         lead1[26] > lead2[26] and close > lead1[26] and close < base and close[1] < con and close > con,
         lead2[26] > lead1[26] and close < lead2[26] and close > base and close[1] > con and close < con))
    float ratingMA = array.avg(indRatings)

    array.clear(indRatings)
    array.push(indRatings, calcRating(rsi[1], 
         rsi < 30 and rsi[1] < rsi, 
         rsi > 70 and rsi[1] > rsi))
    array.push(indRatings, calcRating(dStoch[1], 
         kStoch < 20 and dStoch < 20 and kStoch > dStoch and kStoch[1] < dStoch[1], 
         kStoch > 80 and dStoch > 80 and kStoch < dStoch and kStoch[1] > dStoch[1]))
    array.push(indRatings, calcRating(cci[1], 
         cci < -100 and cci > cci[1],
         cci >  100 and cci < cci[1]))
    array.push(indRatings, calcRating(adx, 
         adx > 20 and diP[1] < diN[1] and diP > diN, 
         adx > 20 and diP[1] > diN[1] and diP < diN))
    array.push(indRatings, calcRating(ao[1], 
         ta.crossover(ao, 0) or (ao > 0 and ao[1] > 0 and ao > ao[1] and ao[2] > ao[1]), 
         ta.crossunder(ao,0) or (ao < 0 and ao[1] < 0 and ao < ao[1] and ao[2] < ao[1])))
    array.push(indRatings, calcRating(mom[1], mom > mom[1], mom < mom[1]))
    array.push(indRatings, calcRating(signal, 
         macd > signal, 
         macd < signal))
    array.push(indRatings, calcRating(upTrend, 
         downTrend and kStochRsi < 20 and dStochRsi < 20 and kStochRsi > dStochRsi and kStochRsi[1] < dStochRsi[1],
         upTrend   and kStochRsi > 80 and dStochRsi > 80 and kStochRsi < dStochRsi and kStochRsi[1] > dStochRsi[1]))
    array.push(indRatings, calcRating(wr[1],
         wr < -80 and wr > wr[1], 
         wr > -20 and wr < wr[1]))
    array.push(indRatings, calcRating(upTrend,
         upTrend   and bearPower < 0 and bearPower > bearPower[1],
         downTrend and bullPower > 0 and bullPower < bullPower[1]))
    array.push(indRatings, calcRating(uo, uo > 70, uo < 30))
    float ratingOther = array.avg(indRatings)

    float total = nz(ratingMA, 0) + nz(ratingOther, 0)
    float ratingTotal = switch 
        not na(ratingMA) and not na(ratingOther) => total / 2
        not na(ratingMA) or  not na(ratingOther) => total
        => na

    [ratingTotal, ratingOther, ratingMA]


// @function        Calculates the number of times the values in the given series increase in value up to a maximum count of 5.
// @param plot      (series float) The series of values to check for rising values.
// @returns         (int) The number of times the values in the series increased in value.
export countRising(series float plot) =>
    float vPlot = math.abs(plot)
    var int result = 0
    result := switch 
        vPlot == 0        => 0
        vPlot >= vPlot[1] => math.min(5, result + 1)
        vPlot <  vPlot[1] => math.max(1, result - 1)
    result


// @function             Determines the rating status of a given series based on its values and defined bounds.
// @param ratingValue    (series float) The series of values to determine the rating status for.
// @param strongBound    (series float) The upper bound for a "strong" rating.
// @param weakBound      (series float) The upper bound for a "weak" rating.
// @returns              (string) The rating status of the given series ("Strong Buy", "Buy", "Neutral", "Sell", or "Strong Sell").
export ratingStatus(series float ratingValue, series float strongBound = 0.5, series float weakBound = 0.1) =>
    string result = switch
        na(ratingValue)            => "-"
        ratingValue < -strongBound => "Strong\nSell"
        ratingValue < -weakBound   => "Sell"
        ratingValue >  strongBound => "Strong\nBuy "
        ratingValue >  weakBound   => "Buy"
        =>                            "Neutral"
//#endregion



//#region ———————————————————— Example Code


// Find indicator ratings. 
[ratingTotal, ratingOther, ratingMa]  = calcRatingAll()

// Colors. 
color SOFT    = #5b9cf6
color ROYAL   = #2962ff
color MISCHKA = #a8adbc
color PINK    = #ef9a9a
color RED     = #f44336

// Constants.
float STRONG_BOUND = 0.5
float WEAK_BOUND   = 0.1
float BAND         = 0.2
float ZERO         = 0.0

// Calculate color gradient.
color buyColor  = color.from_gradient(ratingTotal,  ZERO, BAND, MISCHKA,   ROYAL)
color sellColor = color.from_gradient(ratingTotal, -BAND, ZERO, RED,       MISCHKA)
color gradColor = color.from_gradient(ratingTotal, -BAND, BAND, sellColor, buyColor)

// Determine values and conditions for the positive and negative ratings series. 
bool  posCond   = ratingTotal >  WEAK_BOUND
bool  negCond   = ratingTotal < -WEAK_BOUND
float posSeries = posCond ? ratingTotal : ZERO
float negSeries = negCond ? ratingTotal : ZERO

// Count the number of bars with increasing positive or negative ratings to determine the transparency of the color.
int   posCount  = countRising(posSeries)
int   negCount  = countRising(negSeries)
int   perc      = posCond ? posCount : negCond ? negCount : 0
color curColor  = color.new(gradColor, 50 - perc * 10)

// Plot the rating as columns. 
plot(ratingTotal, "Rating", curColor, 1, plot.style_columns)

// Draw a label on the last bar displaying the ratings status. 
if barstate.islast
	var label signal = label.new(bar_index, 0, "", textcolor = chart.fg_color, style = label.style_label_left)
	label.set_xy(signal, bar_index + 1, ratingTotal)
	label.set_text(signal, ratingStatus(ratingTotal, STRONG_BOUND, WEAK_BOUND))
	label.set_color(signal, curColor)
//#endregion
