// This source code is subject to the terms of the Mozilla Public License 2.0
// © Yaseen Khalil
// @version=5
//Define as indicator, overlay on chart, assign maximum line count to 500
indicator(“Multinomial Retrogression Projection (MRP)”, overlay = true, max_lines_count = 500)
//------------------------------------ SETTINGS ------------------------------------
//User inputs to define the length or period, the projection, and the degree of the multinomial retrogression
length = input.int(100, minval = 0)
extrapolate = input.int(10, minval = 0)
degree = input.int(3, ‘Polynomial Degree’, minval = 0, maxval = 8)
src = input(close)
lock = input(false, ‘Lock Forecast’)
//Style to define the color and width of the MRP line
up_css = input.color(#0cb51a, ‘Upward Color’, group = ‘Style’)
dn_css = input.color(#ff1100, ‘Downward Color’, group = ‘Style’)
ex_css = input.color(#ff5d00, ‘Extrapolation Color’, group = ‘Style’)
width = input(1, ‘Width’, group = ‘Style’)
//------------------------------------ Fill lines array ------------------------------------
//Create a new array, type line
var lines = array.new_line(0)
//Check if current bar is first bar in the barset. Repeat as new bar is set.
if barstate.isfirst
for i = -extrapolate to length-1
array.push(lines, line.new(na, na, na, na))
//------------------------------------ Design matrix & partially solve system ----------------------------
//Set n to the current bar index
n = bar_index
//Assign variables to new matrix object, type float
var design = matrix.new<float>(0, 0)
var response = matrix.new<float>(0, 0)
//Check if current bar is first bar in barset. Develop loop that iterates through each degree, starting //from 0 up to the specified degree. //Initialize a new array named 'column' to store floating-point values, will be initially empty. //Create loop through each element in the range from 0 to the length of the array minus 1.
if barstate.isfirst
for i = 0 to degree
column = array.new_float(0)
//Push the result of raising j to the power of i to the 'column' array.
for j = 0 to length-1
array.push(column, math.pow(j,i))
//Add the 'column' array as a new column to the 'design' matrix.
matrix.add_col(design, i, column)
//calculate the inverse of the product of the changed position of the 'design' matrix and the 'design' matrix itself and assigns it to variable 'a'. //Then calculate the product of 'a' and the changed position of the 'design' matrix and assigns it to variable 'b
var a = matrix.inv(matrix.mult(matrix.transpose(design), design))
var b = matrix.mult(a, matrix.transpose(design))
//-------------Get response matrix and compute rolling polynomial retrogression ----------------
//Now we initialize the variables 'pass', 'coefficients', 'x', and 'forecast' with default values
var pass = 1
var matrix<float> coefficients = na
var x = -extrapolate
var float forecast = na
//Check if the current bar state is the last one. Check if 'pass' is true. If yes, initialize an array 'price' to store float values.
if barstate.islast
if pass
prices = array.new_float(0)
//Loop through each element in the 'src' array and add each element of 'src' array to the 'prices' array.
for i = 0 to length-1
array.push(prices, src[i])
//Add the 'prices' array as a new column to the 'response' matrix at index 0 and calculate coefficients by multiplying matrix 'b' with matrix 'response'
matrix.add_col(response, 0, prices)
coefficients := matrix.mult(b, response)
//Initialize 'y1' variable as 'na' and 'idx' as 0
float y1 = na
idx = 0
//Loop through -extrapolate to length-1.
for i = -extrapolate to length-1
y2 = 0.
//Calculate 'y2' using multinomial retrogression based on 'i' and 'coefficients'
for j = 0 to degree
y2 += math.pow(i, j)*matrix.get(coefficients, j, 0)
if idx == 0
forecast := y2
//Here we set the line attributes
//Set 'css' variable based on the comparison of 'y2' and 'y1'
//Then Update line positions, colors, and width
css = y2 < y1 ? up_css : dn_css
get_line = array.get(lines, idx)
line.set_xy1(get_line, n – i + 1, y1)
line.set_xy2(get_line, n – i, y2)
line.set_color(get_line, i <= 0 ? ex_css : css)
line.set_width(get_line, width)
//Update 'y1' with 'y2' value and increment 'idx' by 1
y1 := y2
idx += 1
//Set 'pass' to 0 if 'lock' is true
if lock
pass := 0
//Reset 'y2' and reduce 'x' if 'pass' is false
else
y2 = 0.
x -= 1
//Now, we calculate the forecast for extrapolation or projection - 'y2' for extrapolation based on 'x' and 'coefficients'
for j = 0 to degree
y2 += math.pow(x, j)*matrix.get(coefficients, j, 0)
forecast := y2
//Finally, we plot the forecasted values if 'pass' is 0, otherwise plot 'na'
plot(pass == 0 ? forecast : na, ‘Extrapolation’
, color = ex_css
, offset = extrapolate
, linewidth = width)
Download and see more projects at my GitHub page.
Now, let’s check out how well it back tests!
Powerful Closures: Leaving a Lasting Impression
Concluding your blog post isn’t just about wrapping things up – it’s your final opportunity to leave a strong impact. Summarize the key takeaways from your post, reinforcing your main points. If relevant, provide actionable solutions or thought-provoking questions to keep readers thinking beyond the post. Encourage engagement by inviting comments, questions, or sharing. A well-crafted conclusion should linger in your readers’ minds, inspiring them to explore further or apply what they’ve learned.