You are on page 1of 14

// 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 --------------------

You might also like