Earnings Levels
/// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Vollchaot and Tim West 

//@version=5
//This script essentially handles 4 plots, that paint the KELs: 
//There is a counter that counts from 1 to 4 (for the 1st, 2nd, 3rd, 4th earnings report of the year) -> Each one has its own plot.
//That particular plot gets invisible at least shortly before it's next value/earnings report.
//In this way the script doesn't need to draw any "line.new"-items, which can only be drawn up to a limited number on the chart...
//In the daily it draws the normal KELs (and marks the high and the low prices used by green points).
//If weekly timeframe, it uses the midpoint of the weekly bar as a level -> that seems to be a not too bad approximation for the levels
//In all other timeframes it makes the plots of the KELs invisible to not annoy on the chart...


indicator('Earnings', '', overlay=true)
ELines = input(title='Earnings Lines', defval=true)
extend = input(title='Extend last 4 Lines', defval=true)
numbers = input(title='Revenue and EPS YoY change on chart', defval=true)



//earnings = security("ESD:" + syminfo.prefix + "_" + syminfo.ticker + "_EARNINGS", "1D", close[1], lookahead = barmerge.lookahead_on)
earnings = syminfo.type == 'stock' or syminfo.type == 'dr' ? request.security('ESD:' + syminfo.prefix + ';' + syminfo.ticker + ';EARNINGS', 'D', close[1], gaps=barmerge.gaps_on, lookahead=barmerge.lookahead_on) : na


//0, 1 und 2 Tage nach Earnings Date: // when Elines == false -> sind immer false (um Rechenpower zu sparen)
earningsDate = not ELines ? false : ta.barssince(not na(earnings)) == 0  //earnings[2] != earnings[3] //earnings date war vorgestern
preCalculations = not ELines ? false : ta.barssince(not na(earnings)) == 1  //earnings[1] != earnings[2] //earnings date war gestern -> linie auf na setzen
calculationDate = not ELines ? false : ta.barssince(not na(earnings)) == 2  //earnings[2] != earnings[3] //earnings date war vorgestern
Day3 = not ELines ? false : ta.barssince(not na(earnings)) == 3  //earnings date war vor 3 T
Day4 = not ELines ? false : ta.barssince(not na(earnings)) == 4  //earnings date war vor 4 T
Day5 = not ELines ? false : ta.barssince(not na(earnings)) == 5  //earnings date war vor 5 T
Day6 = not ELines ? false : ta.barssince(not na(earnings)) == 6  //earnings date war vor 6 T


//Inputs
months = input.int(title='Earnings Line Months', defval=9, minval=0, maxval=12)

//Variable Definitions
linecol = color.new(color.green, 30)
mid1 = 0.0  //Midprice Line 1
mid2 = 0.0  //Midprice Line 2
mid3 = 0.0  //Midprice Line 3
mid4 = 0.0  //Midprice Line 4
Nr = 1  //Nr of the current earnings bar (with 4 per year -> Nr from 1 to 4)
ind1 = bar_index  //for the length of the line
ind2 = bar_index  //for the length of the line
ind3 = bar_index  //for the length of the line
ind4 = bar_index  //for the length of the line

//an earningsDate: Increase Nr (or break to 1 if >4). Else take yesterdays Nr.
Nr := earningsDate ? Nr[1] + 1 > 4 ? 1 : Nr[1] + 1 : nz(Nr[1], 1)

//Low and High for earnings date // only calculate them at calculations Date (to make script faster)
L = not calculationDate ? 0 : math.min(low[1], math.min(low[2], math.min(low[3], close[4])))
H = not calculationDate ? 0 : math.max(high[1], math.max(high[2], math.max(high[3], close[4])))
Hbar = not calculationDate ? 0 : H == high[3] ? bar_index[3] : H == high[2] ? bar_index[2] : bar_index[1]
Lbar = not calculationDate ? 0 : L == low[3] ? bar_index[3] : L == low[2] ? bar_index[2] : bar_index[1]

//Define Midprice: If CalculationsDate of that line nr -> new Midprice-Line. Else: take yesterdays midprice
Mid = (H + L) / 2
mid1 := calculationDate and Nr == 1 ? Mid : nz(mid1[1], 0)
mid2 := calculationDate and Nr == 2 ? Mid : nz(mid2[1], 0)
mid3 := calculationDate and Nr == 3 ? Mid : nz(mid3[1], 0)
mid4 := calculationDate and Nr == 4 ? Mid : nz(mid4[1], 0)
ind1 := calculationDate and Nr == 1 ? bar_index : ind1[1]  //If CalculationsDate of Line Nr 1 -> set ind1 to current bar index
ind2 := calculationDate and Nr == 2 ? bar_index : ind2[1]  //If CalculationsDate of Line Nr 2 -> set ind2 to current bar index
ind3 := calculationDate and Nr == 3 ? bar_index : ind3[1]  //If CalculationsDate of Line Nr 3 -> set ind3 to current bar index
ind4 := calculationDate and Nr == 4 ? bar_index : ind4[1]  //If CalculationsDate of Line Nr 4 -> set ind4 to current bar index


//Don't plot that line when not daily timeframe or preCalcDate or line already longer than months (with 22 days per trading Month)
StopPlot1d = timeframe.period != 'D' or preCalculations and Nr == 1 or bar_index - ind1 > 22 * months
StopPlot2d = timeframe.period != 'D' or preCalculations and Nr == 2 or bar_index - ind2 > 22 * months
StopPlot3d = timeframe.period != 'D' or preCalculations and Nr == 3 or bar_index - ind3 > 22 * months
StopPlot4d = timeframe.period != 'D' or preCalculations and Nr == 4 or bar_index - ind4 > 22 * months

StopPlot1w = timeframe.period != 'W' or earningsDate and Nr == 1  // or ((bar_index - ind1) > 4*months)  
StopPlot2w = timeframe.period != 'W' or earningsDate and Nr == 2  // or ((bar_index - ind2) > 4*(months))
StopPlot3w = timeframe.period != 'W' or earningsDate and Nr == 3  // or ((bar_index - ind3) > 4*(months))
StopPlot4w = timeframe.period != 'W' or earningsDate and Nr == 4  // or ((bar_index - ind4) > 4*(months)) 

//Including triangles:
//To draw a triangle at the earnings, one just needs to check, if it's daily timeframe and then:
//Compare if low[1] == L or low[2] == L or low[3] == L -> then get the bar_index of that older bar. -> price and bar for highest triangle point
//Same for high[1] == H or...and so on -> price and bar for lowest triangle point
//current bar and Mid is bar and price for 3rd triangle point. 


if timeframe.isweekly  //Overwriting position for weekly -> just using 50% of the earnings weeks true range
    MidW = (math.max(close[3], high[2]) + math.min(close[3], low[2])) / 2  //for weekly TF
    mid1 := preCalculations and Nr == 1 ? MidW : nz(mid1[1], 0)
    mid2 := preCalculations and Nr == 2 ? MidW : nz(mid2[1], 0)
    mid3 := preCalculations and Nr == 3 ? MidW : nz(mid3[1], 0)
    mid4 := preCalculations and Nr == 4 ? MidW : nz(mid4[1], 0)
    mid4


//plot them
plot(StopPlot1d and StopPlot1w or not ELines or mid1 == 0 ? na : mid1, color=linecol, style=plot.style_linebr, linewidth=2)
plot(StopPlot2d and StopPlot2w or not ELines or mid2 == 0 ? na : mid2, color=linecol, style=plot.style_linebr, linewidth=2)
plot(StopPlot3d and StopPlot3w or not ELines or mid3 == 0 ? na : mid3, color=linecol, style=plot.style_linebr, linewidth=2)
plot(StopPlot4d and StopPlot4w or not ELines or mid4 == 0 ? na : mid4, color=linecol, style=plot.style_linebr, linewidth=2)


// check it we are within the last year
drawlines = extend ? time > timenow - 1000 * 60 * 60 * 24 * 30 * 12 : false
if drawlines and mid1 != mid1[1]
    line.new(x1=time, y1=mid1, x2=time + int(1000 * 60 * 60 * 24 * 30 * months), y2=mid1, color=linecol, xloc=xloc.bar_time, width=2)
if drawlines and mid2 != mid2[1]
    line.new(x1=time, y1=mid2, x2=time + int(1000 * 60 * 60 * 24 * 30 * months), y2=mid2, color=linecol, xloc=xloc.bar_time, width=2)
if drawlines and mid3 != mid3[1]
    line.new(x1=time, y1=mid3, x2=time + int(1000 * 60 * 60 * 24 * 30 * months), y2=mid3, color=linecol, xloc=xloc.bar_time, width=2)
if drawlines and mid4 != mid4[1]
    line.new(x1=time, y1=mid4, x2=time + int(1000 * 60 * 60 * 24 * 30 * months), y2=mid4, color=linecol, xloc=xloc.bar_time, width=2)


TrU = not ELines or timeframe.period != 'D' ? na : calculationDate ? L : Day3 ? (5 * L[1] + H[1]) / 6 : Day4 ? (4 * L[2] + 2 * H[2]) / 6 : Day5 ? (3 * L[3] + 3 * H[3]) / 6 : na
TrL = not ELines or timeframe.period != 'D' ? na : calculationDate ? H : Day3 ? (5 * H[1] + L[1]) / 6 : Day4 ? (4 * H[2] + 2 * L[2]) / 6 : Day5 ? (3 * L[3] + 3 * H[3]) / 6 : na
p1 = plot(TrU, style=plot.style_linebr, offset=-1, color=linecol, linewidth=3)
p2 = plot(TrL, style=plot.style_linebr, offset=-1, color=linecol, linewidth=3)
fill(p1, p2, color=color.new(linecol, 40))



// --------------- EPS number -----------------
// - Ideas - : Option to choose between basic EPS("EARNINGS_PER_SHARE_BASIC","EARNINGS_PER_SHARE_BASIC_ONE_YEAR_GROWTH")
//                                      diluted EPS ("EARNINGS_PER_SHARE", "EARNINGS_PER_SHARE_DILUTED_ONE_YEAR_GROWTH")
//                                      Difference to estimates ("EARNINGS_ESTIMATE")
//eps_style = input(title="EPS number in", defval="YoY percent", options=["absolute","YoY percent"])
eps = request.financial(syminfo.tickerid, 'EARNINGS_PER_SHARE', 'TTM', ignore_invalid_symbol=true)
epsg = request.financial(syminfo.tickerid, 'EARNINGS_PER_SHARE_DILUTED_ONE_YEAR_GROWTH', 'FQ', barmerge.gaps_off, ignore_invalid_symbol=true)
revg = request.financial(syminfo.tickerid, 'REVENUE_ONE_YEAR_GROWTH', 'FQ', barmerge.gaps_off, ignore_invalid_symbol=true)
shares = request.financial(syminfo.tickerid, 'TOTAL_SHARES_OUTSTANDING', 'FQ',  barmerge.gaps_off, ignore_invalid_symbol=true)