// This source code is subject to the terms of the Mozilla Public License 2.
0 at
https://mozilla.org/MPL/2.0/
// © tradeforopp
//@version=5
indicator("Seasonality [TFO]", "Seasonality v1.0 [TFO]", false, max_lines_count = 500,
max_boxes_count = 500, max_labels_count = 500)
// -------------------- Inputs --------------------
var g_ANALYSIS = "Analysis"
show_table       = input.bool(true, "Statistics Table", tooltip = "Summarized table describing
seasonal data for the selected timeframe", group = g_ANALYSIS)
show_analysis   = input.bool(true, "Performance Analysis", inline = "ANALYSIS", tooltip =
"Histogram showing the average percentage performance for the selected timeframe", group =
g_ANALYSIS)
analysis_period        = input.string('Daily', "", ['Daily', 'Monthly', 'Quarterly'], inline = "ANALYSIS", group
= g_ANALYSIS)
szn_pivots     = input.bool(false, "Seasonal Pivots", tooltip = "Shows when the current asset tends
to make highs and lows throughout the year, based on an aggregation of daily performance", group
= g_ANALYSIS)
pivot_strength = input.int(10, "Pivot Strength", tooltip = "Using a value of 10 for example, a high
must be greater than the 10 bars to the left and 10 bars to the right of it in order to be a valid pivot,
and vice versa for lows", group = g_ANALYSIS)
var g_EXTRAS = "Extras"
highlight_today = input.bool(true, "Highlight Current Trading Day", tooltip = "Easily find where the
current trading day meets up with seasonal averages", group = g_EXTRAS)
show_labels    = input.bool(true, "Performance Labels", tooltip = "Labels will be shown in the
Performance Analysis to describe the average percent move over the specified period of time",
group = g_EXTRAS)
track_change    = input.bool(false, "Track Changes", tooltip = "Follows the changes in average
performance throughout the year", group = g_EXTRAS)
var g_INDEX = "Indexing"
anchor_boy        = input.bool(true, "Anchor to Beginning of Year", tooltip = "All drawings will start
anchored to the first trading day of the current year, to easily overlay past performance on the
current Daily chart", group = g_INDEX)
use_manual_offset = input.bool(false, "Manual Offset", inline = "OFFSET", tooltip = "Offers a
secondary offset so users can overlay these drawings on previous years (with an offset of ~252 *
number of years, for example)", group = g_INDEX)
manual_offset       = input.int(252, "", inline = "OFFSET", group = g_INDEX)
var g_DIV = "Dividers"
month_div         = input.bool(false, "Month", inline = "MDIV", group = g_DIV)
mdiv_style        = input.string('Dotted', "", ['Dotted', 'Dashed', 'Solid'], inline = "MDIV", group =
g_DIV)
mdiv_color        = input.color(color.white, "", inline = "MDIV", group = g_DIV)
qr_div          = input.bool(false, "Quarter", inline = "QDIV", group = g_DIV)
qdiv_style       = input.string('Dotted', "", ['Dotted', 'Dashed', 'Solid'], inline = "QDIV", group = g_DIV)
qdiv_color        = input.color(color.white, "", inline = "QDIV", group = g_DIV)
var g_STYLE = "Style"
main_color        = input.color(color.white, "Main Color", group = g_STYLE)
track_color       = input.color(color.yellow, "Track Changes", group = g_STYLE)
upper_color        = input.color(#08998180, "Performance", inline = "PERFORMANCE", group =
g_STYLE)
lower_color       = input.color(#f2364580, "", inline = "PERFORMANCE", group = g_STYLE)
high_color        = input.color(#08998180, "Seasonal Pivots", inline = "PIVOTS", group = g_STYLE)
low_color        = input.color(#f2364580, "", inline = "PIVOTS", group = g_STYLE)
pivot_width       = input.int(2, "Seasonal Pivot Width", group = g_STYLE)
divider_width      = input.int(1, "Divider Width", group = g_STYLE)
text_size      = input.string('Normal', "Text Size", options = ['Auto', 'Tiny', 'Small', 'Normal', 'Large',
'Huge'], group = g_STYLE)
var g_TABLE = "Table"
table_pos        = input.string('Top Right', "Position", options = ['Bottom Center', 'Bottom Left',
'Bottom Right', 'Middle Center', 'Middle Left', 'Middle Right', 'Top Center', 'Top Left', 'Top Right'],
group = g_TABLE)
table_text          = input.color(color.black, "Text Color", group = g_TABLE)
table_frame           = input.color(color.black, "Frame Color", group = g_TABLE)
table_border          = input.color(color.black, "Border Color", group = g_TABLE)
table_bg           = input.color(color.white, "Background Color", group = g_TABLE)
// -------------------- Inputs --------------------
// -------------------- Basic Functions --------------------
get_table_pos(i) =>
  result = switch i
     "Bottom Center" => position.bottom_center
     "Bottom Left" => position.bottom_left
     "Bottom Right" => position.bottom_right
     "Middle Center" => position.middle_center
     "Middle Left" => position.middle_left
     "Middle Right" => position.middle_right
     "Top Center" => position.top_center
     "Top Left" => position.top_left
     "Top Right" => position.top_right
  result
get_text_size(i) =>
  result = switch i
     'Tiny' => size.tiny
     'Small' => size.small
     'Normal' => size.normal
     'Large' => size.large
     'Huge' => size.huge
     'Auto' => size.auto
  result
get_line_style(i) =>
  result = switch i
    'Dotted' => line.style_dotted
    'Dashed' => line.style_dashed
    'Solid' => line.style_solid
  result
get_month(i) =>
  result = switch i
    1 => 'JAN'
    2 => 'FEB'
    3 => 'MAR'
    4 => 'APR'
    5 => 'MAY'
    6 => 'JUN'
    7 => 'JUL'
    8 => 'AUG'
    9 => 'SEP'
    10 => 'OCT'
    11 => 'NOV'
    12 => 'DEC'
get_qr(i) =>
  result = switch i
    1 => 'Q1'
    2 => 'Q2'
    3 => 'Q3'
    4 => 'Q4'
get_period() =>
  result = switch analysis_period
     'Daily' => 'D'
     'Monthly' => 'M'
     'Quarterly' => 'Q'
// -------------------- Basic Functions --------------------
// -------------------- Variables & Constants --------------------
[d_open, d_close] = request.security(syminfo.tickerid, "D", [open, close], barmerge.gaps_off,
barmerge.lookahead_on)
[m_open, m_close] = request.security(syminfo.tickerid, "M", [open, close], barmerge.gaps_off,
barmerge.lookahead_on)
[q_open, q_close] = request.security(syminfo.tickerid, "3M", [open, close], barmerge.gaps_off,
barmerge.lookahead_on)
//
var arr_days = array.new_int()
var d_pct = array.new_float()
var d_points = array.new_float()
var d_count = array.new_int()
var d_day = array.new_string()
var d_sorted_pct = array.new_float()
var d_sorted_points = array.new_float()
var d_sorted_count = array.new_int()
var d_sorted_day = array.new_string()
var m_pct_avg = array.new_float()
var q_pct_avg = array.new_float()
//
var int doy = na
var int first_year = na
var int first_xoy = na
var float first_yoy = na
//
if month == 1 and month[1] != 1
     first_xoy := bar_index
     first_yoy := close[1]
     doy := 0
     if na(first_year)
       first_year := year
if not na(doy)
     doy += 1
//
offset = use_manual_offset ? manual_offset : 0
start_idx = anchor_boy ? first_xoy - offset : bar_index
highlight_offset = 20
days_in_year = 252
days_in_month = 21
days_in_qr = 63
period = get_period()
text_size := get_text_size(text_size)
mdiv_style := get_line_style(mdiv_style)
qdiv_style := get_line_style(qdiv_style)
table_pos := get_table_pos(table_pos)
//
szn_idx = switch period
     'D' => doy - 1
     'M' => month - 1
     'Q' => month % 3 - 1
szn_arr = switch period
     'D' => d_sorted_pct
     'M' => m_pct_avg
     'Q' => q_pct_avg
current_pct = switch period
     'D' => (d_close - d_open) / d_open
     'M' => (m_close - m_open) / m_open
     'Q' => (q_close - q_open) / q_open
current_pts = switch period
     'D' => (d_close - d_open)
     'M' => (m_close - m_open)
     'Q' => (q_close - q_open)
num_days = switch period
     'D' => days_in_year
     'M' => days_in_month
     'Q' => days_in_qr
// -------------------- Variables & Constants --------------------
// -------------------- Saving Performance Data --------------------
if not na(doy) and doy <= days_in_year
  idx = arr_days.indexof(doy)
  pct = (d_close - d_open) / d_open
  points = (d_close - d_open)
  if idx == -1
     arr_days.push(doy)
     d_pct.push(pct)
     d_points.push(points)
     d_count.push(1)
     d_day.push(str.tostring(month) + "/" + str.tostring(dayofmonth))
  else
     d_pct.set(idx, d_pct.get(idx) + pct)
     d_points.set(idx, d_points.get(idx) + points)
     d_count.set(idx, d_count.get(idx) + 1)
// -------------------- Saving Performance Data --------------------
// -------------------- Plotting Functions --------------------
dividers(base) =>
  if month_div
     for i = 0 to m_pct_avg.size()
         left = start_idx + i * days_in_month
      line.new(left, base, left, base, color = mdiv_color, style = mdiv_style, extend = extend.both,
width = divider_width)
  if qr_div
     for i = 0 to q_pct_avg.size()
         left = start_idx + i * days_in_qr
      line.new(left, base, left, base, color = qdiv_color, style = qdiv_style, extend = extend.both,
width = divider_width)
plot_analysis(period, szn_arr, szn_idx, num_days, base) =>
  for i = 0 to szn_arr.size() - 1
    not_daily = period != 'D'
    left = start_idx + i * (not_daily ? num_days : 1)
    right = left + (not_daily ? num_days - 1 : 1)
    label_x = math.floor(math.avg(left, right))
    box_text = switch period
       'D' => na
       'M' => get_month(i + 1)
       'Q' => get_qr(i + 1)
    perf = szn_arr.get(i)
    col = perf > 0 ? upper_color : lower_color
    if not_daily
      box.new(left, base + perf, right, base, bgcolor = col, border_color = col, text = box_text,
text_color = col)
    else
       line.new(left, base + perf, left, base, color = col, width = 5)
    if show_labels
        label.new(label_x, base + perf, str.tostring(perf * 100, format.percent), style = perf > 0 ?
label.style_label_down : label.style_label_up, textcolor = main_color, size = text_size, color =
#ffffff00)
    if track_change and i != szn_arr.size() - 1
       next_perf = szn_arr.get(i + 1)
      line.new(label_x, base + perf, label_x + (not_daily ? num_days : 1), base + next_perf, color =
track_color, width = 2)
    if highlight_today and i == szn_idx
       line.new(left, base + perf, start_idx + days_in_year - 1, base + perf, style = line.style_dotted,
color = main_color, width = 2)
       label.new(start_idx + days_in_year - 1 + highlight_offset, base + perf, "Current Avg: " +
str.tostring(perf * 100, format.percent), style = label.style_label_center, textcolor = main_color, size
= text_size, color = #ffffff00)
     if i == szn_arr.size() - 1
         line.new(start_idx, base, start_idx + days_in_year - 1, base, color = main_color, width = 2)
// -------------------- Plotting Functions --------------------
// -------------------- Statistics Table --------------------
var table stats = table.new(table_pos, 50, 50, bgcolor = table_bg, frame_color = table_frame,
border_color = table_border, frame_width = 1, border_width = 1)
if barstate.islast
  if timeframe.in_seconds() != timeframe.in_seconds("D")
     table.cell(stats, 0, 0, "Please Use the 'D' Timeframe")
  else
     // Sort daily arrays by day --------------------------------------------------
     temp_arr_days = arr_days.copy()
     for i = temp_arr_days.size() - 1 to 0
         min = arr_days.indexof(temp_arr_days.min())
         d_sorted_pct.push(d_pct.get(min))
         d_sorted_points.push(d_points.get(min))
         d_sorted_count.push(d_count.get(min))
         d_sorted_day.push(d_day.get(min))
         temp_arr_days.set(min, na)
     for i = 0 to d_sorted_pct.size() - 1
  d_sorted_pct.set(i, d_sorted_pct.get(i) / d_sorted_count.get(i))
  d_sorted_points.set(i, d_sorted_points.get(i) / d_sorted_count.get(i))
// Sort daily arrays by day --------------------------------------------------
// Get month performance --------------------------------------------------
m_pct_avg.clear()
for i = 0 to 11
  sum = 0.0
  for j = 0 to days_in_month - 1
     sum += d_sorted_pct.get(i * days_in_month + j)
  m_pct_avg.push(sum)
q_pct_avg.clear()
for i = 0 to 3
  sum = 0.0
  for j = 0 to days_in_qr - 1
     sum += d_sorted_pct.get(i * days_in_qr + j)
  q_pct_avg.push(sum)
// Get month performance --------------------------------------------------
// Seasonal highs and lows --------------------------------------------------
szn_pct = szn_arr.get(szn_idx)
base = 1.0
var price_projection = array.new_float()
var szn_lows = array.new_string()
var szn_highs = array.new_string()
if szn_pivots
x1 = start_idx
y1 = base
x2 = x1 + 1
for i = 0 to d_pct.size() - 1
  price_projection.push(y1)
  y2 = y1 * (1 + d_sorted_pct.get(i))
  y1 := y2
  x1 += 1
  x2 += 1
left_bound = pivot_strength
right_bound = price_projection.size() - pivot_strength - 1
for i = left_bound to right_bound
  new_low = true
  new_high = true
  for j = -pivot_strength to pivot_strength
     if i != j
        if price_projection.get(i) > price_projection.get(i + j)
           new_low := false
           break
  for j = -pivot_strength to pivot_strength
     if i != j
        if price_projection.get(i) < price_projection.get(i + j)
           new_high := false
           break
  if new_low
     new_low_month = get_month(math.ceil(i / days_in_month))
     if szn_lows.indexof(new_low_month) == -1
        szn_lows.push(new_low_month)
           line.new(start_idx + i, base, start_idx + i, base, extend = extend.both, color = low_color,
width = pivot_width)
           label.new(start_idx + i, math.min(base, base + szn_arr.min()), "SEASONAL\nLOW", color =
color.new(low_color, 0), textcolor = main_color, size = text_size, style = label.style_label_up)
         if new_high
            new_high_month = get_month(math.ceil(i / days_in_month))
            if szn_highs.indexof(new_high_month) == -1
               szn_highs.push(new_high_month)
           line.new(start_idx + i, base, start_idx + i, base, extend = extend.both, color = high_color,
width = pivot_width)
           label.new(start_idx + i, math.max(base, base + szn_arr.max()), "SEASONAL\nHIGH", color
= color.new(high_color, 0), textcolor = main_color, size = text_size, style = label.style_label_down)
    // Seasonal highs and lows --------------------------------------------------
    // Plotting analysis --------------------------------------------------
    dividers(base)
    if show_analysis
       plot_analysis(period, szn_arr, szn_idx, num_days, base)
    // Plotting analysis --------------------------------------------------
    // Table --------------------------------------------------
    if show_table
       curr_month_pct = (m_close - m_open) / m_open
       curr_month_points = (m_close - m_open)
       table.cell(stats, 0, 0, "Timeframe: ", text_size = text_size, text_color = table_text)
       table.cell(stats, 1, 0, str.tostring(period), text_size = text_size, text_color = table_text)
       table.cell(stats, 0, 1, "Collected Over: ", text_size = text_size, text_color = table_text)
      table.cell(stats, 1, 1, str.tostring(year - first_year) + " Years", text_size = text_size, text_color =
table_text)
        table.cell(stats, 0, 2, "Seasonality: ", text_size = text_size, text_color = table_text)
      table.cell(stats, 1, 2, str.tostring(szn_pct > 0 ? 'BULLISH' : 'BEARISH'), bgcolor = szn_pct > 0 ?
upper_color : lower_color, text_size = text_size, text_color = table_text)
        table.cell(stats, 0, 3, "Average % Change: ", text_size = text_size, text_color = table_text)
      table.cell(stats, 1, 3, str.tostring(math.round(szn_pct * 10000) / 100, format.percent), bgcolor
= szn_pct > 0 ? upper_color : lower_color, text_size = text_size, text_color = table_text)
        table.cell(stats, 0, 4, "Current % Change: ", text_size = text_size, text_color = table_text)
      table.cell(stats, 1, 4, str.tostring(math.round(current_pct * 10000) / 100, format.percent),
bgcolor = current_pct > 0 ? upper_color : lower_color, text_size = text_size, text_color = table_text)
        table.cell(stats, 0, 5, "Current Point Change: ", text_size = text_size, text_color = table_text)
      table.cell(stats, 1, 5, str.tostring(current_pts), bgcolor = current_pts > 0 ? upper_color :
lower_color, text_size = text_size, text_color = table_text)
        if szn_pivots
           table.cell(stats, 0, 6, "Major Lows Made in: ", text_size = text_size, text_color = table_text)
           table.cell(stats, 1, 6, str.tostring(szn_lows), text_size = text_size, text_color = table_text)
           table.cell(stats, 0, 7, "Major Highs Made in: ", text_size = text_size, text_color = table_text)
           table.cell(stats, 1, 7, str.tostring(szn_highs), text_size = text_size, text_color = table_text)
     // Table --------------------------------------------------
// -------------------- Statistics Table --------------------