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

//@version=5
indicator("Minervini Qualifier", "MQ")

// From Mark Minervini's book "Trade Like a Stock Market Wizard"
// Trend Template
// 1. The current stock price is above both the 150-day (30-week) and the 200-day 
// (40-week) moving average price lines.
// 2. The 150-day moving average is above the 200-day moving average.
// 3. The 200-day moving average line is trending up for at least 1 month (preferably 
// 4–5 months minimum in most cases).
// 4. The 50-day (10-week) moving average is above both the 150-day and 200-day 
// moving averages.
// 5. The current stock price is trading above the 50-day moving average.
// 6. The current stock price is at least 30 percent above its 52-week low. (Many of 
// the best selections will be 100 percent, 300 percent, or greater above their 
// 52-week low before they emerge from a solid consolidation period and mount 
// a large scale advance.)
// 7. The current stock price is within at least 25 percent of its 52-week high (the 
// closer to a new high the better).
// 8. The relative strength ranking (as reported in Investor’s Business Daily) is no 
// less than 70, and preferably in the 80s or 90s, which will generally be the case 
// with the better selections 

RefTickerId = input.symbol("SP:SPX+NASDAQ_DLY:NDX+DJ:DWCPF", title="Reference index", tooltip = "e.g. SPX/NDX/RUT/DWCPF or combinations of it")
bCriteria = input.bool(true,"Show qualifiying criterias seperately")
bStrength = input.bool(true,"Calculate relative strength")
bSMA      = input.bool(true,"Show SMA lines")
bBuysell  = input.bool(true,"Draw verical buy signal lines")

var table infoTable = table.new(position.top_right, 1, 2, border_width = 0)
f_fillCell(_table, _column, _row, _value) =>
	_cellText = str.tostring(_value, "#.##")
	table.cell(_table, _column, _row, _cellText, text_color = color.yellow)
f_fillCell2(_table, _column, _row, _value) =>
	_cellText = _value
	table.cell(_table, _column, _row, _cellText, text_color = color.yellow)

bShowText = input(false, "ShowLabel")
_label(bool showlabel, float yPosition, string Name) =>
    var TEXTCOLOR = color.new(color.white,50)
    if barstate.islast and showlabel
        offset = str.length(Name)/2 + 1
        var _label = label.new(bar_index+offset, y=yPosition-2, text=Name, color=color.white, textcolor=TEXTCOLOR, style=label.style_none, size=size.tiny, textalign=text.align_left, text_font_family=font.family_monospace) 
        label.set_x(_label, bar_index+offset)


// SMA/EMA Calculation
s_close = request.security(syminfo.tickerid, 'D', close)

low_of_52week   = ta.lowest(s_close,260)
high_of_52week  = ta.highest(s_close,260)

//sma5            = ta.sma(s_close,5)
sma20           = ta.sma(s_close, 20)
sma50           = ta.sma(s_close, 50)
sma150          = ta.sma(s_close, 150)
sma200          = ta.sma(s_close, 200)

//Condition 1: Current Price > 150 SMA and > 200 SMA
condition_1 = s_close > sma150 and s_close > sma200

// # Condition 2: 150 SMA > 200 SMA
condition_2 = sma150 > sma200

// # Condition 3: 200 SMA trending up for at least 1 month, enhanced for 5 months
condition_3             = ta.rising(sma200,30) //sma200 > sma200[30] //sma200 > sma200[10] and sma200[10] > sma200[20] and sma200[20] > sma200[30]
condition_3_enhanced    = ta.rising(sma200,5*30) //condition_3 and sma200[30] > sma200[60] and sma200[60] > sma200[90] and sma200[90] > sma200[120] and sma200[120] > sma200[150]

// # Condition 4: 50 SMA> 150 SMA and 50 SMA> 200 SMA
condition_4 = sma50 > sma150 and sma50 > sma200 

// # Condition 5: Current Price > 50 SMA
condition_5 = s_close > sma50

// # Condition 6: Current Price is at least 30% above 52 week low
condition_6         = s_close >= (1.3 * low_of_52week) // 30% above 52 week low
condition_6_plus    = s_close >= (1.7 * low_of_52week) // 70% above 52 week low

// # Condition 7: Current Price is within 25% of 52 week high
condition_7 = s_close >= (0.75 * high_of_52week)

condition_minervini          = condition_1 and condition_2 and condition_3          and condition_4 and condition_5 and condition_6 and condition_7
condition_minervini_light    = condition_1 and condition_2 and condition_3          and condition_4 and                 condition_6 and condition_7
condition_minervini_enhanced = condition_1 and condition_2 and condition_3_enhanced and condition_4 and condition_5 and condition_6 and condition_7

condCol = condition_minervini_light ? condition_minervini ? condition_minervini_enhanced ? color.new(color.blue,0) : color.new(color.green, 0) : color.new(color.yellow, 0) : na

// draw vertical line when SMA20 crossover and the conditions are fullfilled
buy = ta.crossover(s_close,sma20) and condition_minervini_light
plot(bBuysell and buy ?  28 : na,color=color.new(color.green,50), linewidth=1,style = plot.style_histogram)
plot(bBuysell and buy ? -45 : na,color=color.new(color.green,50), linewidth=1,style = plot.style_histogram)

// calculate and draw envelope curve with condition matching fillcolor
lb = last_bar_index < 200 ? last_bar_index : 200
range_m = sma20-s_close
factor = 20 / (ta.highest(range_m,lb)-ta.lowest(range_m,lb))
range_m := range_m * factor
condCol2 = range_m < 0 ? color.new(color.blue,0) : color.new(color.red,50)
mp1 = plot(range_m ,        title = "SMA20 + conditions match", color=condCol2, linewidth=2)
mp2 = plot(range_m * -1 ,   title = "SMA20 + conditions match", color=condCol2, linewidth=2)
fill(mp1, mp2, color=condCol)

// Plot flatline SMAs
FirstlineSMA = input.int(20,"First line SMA")
sma20a = ta.sma(s_close,FirstlineSMA)
//plot(bSMA ? 32 : na, color = (sma5 - sma20) > 0? color.blue : color.red, linewidth = 3, title = "SMA5/SMA20 crossover")
plot(bSMA ? 28 : na, color = sma20a < s_close ? color.green : color.red, linewidth = 3, title = "SMA20 or so")
plot(bSMA ? 24 : na, color = sma50  < s_close ? color.green : color.red, linewidth = 3, title = "SMA50")
plot(bSMA ? 20 : na, color = sma150 < s_close ? color.green : color.red, linewidth = 3, title = "SMA150")
plot(bSMA ? 16 : na, color = sma200 < s_close ? color.green : color.red, linewidth = 3, title = "SMA200")

// Plot Minervini condition lines
plot (bCriteria ? -15 : na, linewidth = 3, title = "close>150MA and >200MA"      , color = condition_1 ? color.new(color.blue,0) : color.new(color.red,0))
plot (bCriteria ? -20 : na, linewidth = 3, title = "150MA>200MA"                 , color = condition_2 ? color.new(color.blue,0) : color.new(color.red,0))
plot (bCriteria ? -25 : na, linewidth = 3, title = "200MA uptrending 1+month"    , color = condition_3 ? color.new(color.blue,0) : color.new(color.red,0))
plot (bCriteria and condition_3_enhanced ? -25 : na, linewidth = 6, title = "200MA uptrending 5+month", color = condition_3_enhanced ? color.new(#2521f3, 0) : color.new(color.red,0))
plot (bCriteria ? -30 : na, linewidth = 3, title = "50MA>150MA and 50MA>200MA"   , color = condition_4 ? color.new(color.blue,0) : color.new(color.red,0))
plot (bCriteria ? -35 : na, linewidth = 3, title = "close>50MA"                  , color = condition_5 ? color.new(color.green,0) : color.new(color.red,0))
plot (bCriteria ? -40 : na, linewidth = 3, title = "close 30%+ above 52-week low", color = condition_6 ? color.new(color.blue,0) : color.new(color.red,0))
plot (bCriteria and condition_6_plus ? -40 : na, linewidth = 6, title = "close 70%+ above 52-week low", color = condition_6_plus ? color.new(color.green, 0) : color.new(color.red,0))
plot (bCriteria ? -45 : na, linewidth = 3, title = "close within 25% 52-week high", color = condition_7 ? color.new(color.blue,0) : color.new(color.red,0))

// Create label next to the lines to ease identification
//_label(bShowText, 32, "SMA5/SMA20 crossover")
_label(bShowText, 28, "SMA20 ")
_label(bShowText, 24 ,"SMA50 ")
_label(bShowText, 20 ,"SMA150")
_label(bShowText, 16 ,"SMA200")
_label(bShowText, -15, "close>150MA and >200MA" )
_label(bShowText, -20, "150MA>200MA " )
_label(bShowText, -25, "200MA uptrending 1+month" )
_label(bShowText, -30, "50MA>150MA and 50MA>200MA " )
_label(bShowText, -35, "close>50MA " )
_label(bShowText, -40, "close 30%+ above 52-week low" )
_label(bShowText, -45, "close within 25% 52-week high " )

if not timeframe.isdaily
    f_fillCell2(infoTable, 0, 1, "only valid on daily timeframes")

// Calculate relative strength, just one way to calculate it
// value > 100, better performance compared to reference index
// value < 100, worse performance compared to reference index 
// ...over the period of one year, last quarter double counted

quarter_performance(closes, n) =>
    length  = math.min(last_bar_index, (252/4))
    start   = (n-1) * length
    stop    = n * length
    pct_cum = 0.0
    for i=start to stop -1
        pct_cum := pct_cum + closes[i]
    pct_cum

strength(closes) =>
    quarters1 = quarter_performance(closes ,1)
    quarters2 = quarter_performance(closes ,2)
    quarters3 = quarter_performance(closes ,3)
    quarters4 = quarter_performance(closes ,4)
    ret = 0.4*quarters1 + 0.2*quarters2 + 0.2*quarters3 + 0.2*quarters4
    ret

relative_strength(closes, ref_close) =>
    rs_stock = strength(closes)
    rs_ref   = strength(ref_close)
    rs  = (1 + rs_stock) / (1 + rs_ref) * 100
    rs := math.round(rs,2)
    rs


s_ref_close = request.security(RefTickerId, 'D', close)
relStrength = 0.0
if bStrength
    s_close_pct = (s_close - s_close[1]) / s_close[1]
    s_ref_close_pct = (s_ref_close - s_ref_close[1]) / s_ref_close[1]
    relStrength := relative_strength(s_close_pct,s_ref_close_pct)
    f_fillCell(infoTable, 0, 0, relStrength)

// alerts ----------------------------------------------

alertcondition(condition_minervini and not condition_minervini[1],"MQ - All Miverini conditions match", "All Minervini conditions match")
alertcondition(condition_minervini_light and not condition_minervini_light[1],"MQ - Miverini (light) conditions match", "All Minervini light conditions matched")
alertcondition(not condition_minervini_light and condition_minervini_light[1],"MQ - Miverini conditions unmatch", "At least one Minervini condition doesn't match")

alertcondition(buy,"MQ - buy signal", "SMA20 crossover and Minervini conditions match")

UptrendAlert = input.int(70, "Alertcondition when close xx% above 52week low", minval = 30)
Percent_above_52weeklow = ((s_close * 100) / low_of_52week) - 100 // Strong uptrend signal when close is 70%+ above 52week low 
alertcondition(Percent_above_52weeklow >= UptrendAlert, "MQ - in strong uptrend mode", "close is xx% (70% default value) above 52week low")

