PHP Code:
//@version=6
indicator("combine", shorttitle=".", overlay=true,
max_lines_count=400, max_boxes_count=50, max_labels_count=5)
/////////////////////
//@version=6
// General Settings
i_trade_duration = input.int(defval=20, title="Max Trade Duration (bars)", minval=1, group="General Settings", tooltip="The maximum number of bars to hold a trade. On a Daily chart, 252 is ~1 year for stocks.")
i_position_size = input.float(defval=100.0, title="Position Size ($)", minval=1.0, group="General Settings")
// Calculation Settings
i_momentum_lookback = input.int(defval=252, title="Projection Lookback (bars)", minval=10, group="Calculation Settings", tooltip="The lookback period for calculating the trend and volatility that the Monte Carlo projection is based on. A shorter period makes the projection more sensitive to recent price action, which can reveal recency bias.")
i_lookback_bars = input.int(defval=250, title="Historical Lookback (bars)", minval=1, maxval=5000, group="Calculation Settings", tooltip="Defines the total historical data for the backtest. A longer period provides more statistically significant results.")
i_monte_carlo_sims = input.int(defval=100, title="Monte Carlo Simulations", minval=100, maxval=5000, group="Calculation Settings")
i_risk_free_rate = input.float(defval=5.0, title="Annual Risk-Free Rate (%)", group="Calculation Settings", tooltip="Used for Sharpe Ratio calculation.")
// Historical TP/SL Settings
i_use_tp_sl = input.bool(defval=false, title="Use TP/SL for Historical EV", group="Historical Strategy Settings")
i_tp_pct = input.float(defval=12.0, title="Take Profit (%)", minval=0.1, group="Historical Strategy Settings")
i_sl_pct = input.float(defval=2.0, title="Stop Loss (%)", minval=0.1, maxval=99.9, group="Historical Strategy Settings", tooltip="Must be between 0.1 and 99.9.")
// Display Settings
i_font_size_option = input.string(defval="Normal", title="Table Font Size", options=["Small", "Normal", "Large"], group="Display Settings")
// --- SETUP ---
entry_price = close
table_font_size = switch i_font_size_option
"Small" => size.small
"Normal" => size.normal
"Large" => size.large
=> size.normal
// --- DYNAMIC ANNUALIZATION FACTOR ---
var trading_days_in_year = 252.0 // Default for stocks, forex, etc.
switch syminfo.type
"crypto" => trading_days_in_year := 365.0
ms_in_year = 1000.0 * 60 * 60 * 24 * trading_days_in_year
ms_in_bar = timeframe.in_seconds() * 1000
annualization_factor = ms_in_bar > 0 ? ms_in_year / ms_in_bar : trading_days_in_year
// --- STATISTICAL PROPERTY CALCULATIONS ---
returns = math.log(close / nz(close[1], close))
long_vol = ta.stdev(returns, i_momentum_lookback) * math.sqrt(annualization_factor) * 100
long_trend = ta.sma(returns, i_momentum_lookback) * annualization_factor * 100
// --- FUNCTION TO CALCULATE SHARPE RATIO ---
calculate_sharpe(avg_annual_return, ann_volatility) =>
excess_return = avg_annual_return - i_risk_free_rate
ann_volatility > 0 ? excess_return / ann_volatility : na
// --- HISTORICAL BACKTESTING ---
calculate_historical_stats() =>
var float hist_ev = na, var float hist_ev_pct = na, var float hist_sigma = na, var float hist_win_rate = na, var float hist_sharpe = na, var int total_trades = 0
if barstate.islast
hist_ev := na, hist_ev_pct := na, hist_sigma := na, hist_win_rate := na, hist_sharpe := na
min_required_lookback = i_trade_duration + 50
if i_lookback_bars < min_required_lookback
total_trades := -1
else
hist_returns = array.new<float>()
step_size = math.max(3, int(i_trade_duration / 10))
for entry_ago = i_trade_duration + step_size to i_lookback_bars by step_size
if bar_index > entry_ago
entry_price_hist = close[entry_ago]
trade_return = 0.0
if not i_use_tp_sl
exit_price_hist = close[entry_ago - i_trade_duration]
trade_return := (exit_price_hist - entry_price_hist) / entry_price_hist
else
tp_level = entry_price_hist * (1 + i_tp_pct / 100)
sl_level = entry_price_hist * (1 - i_sl_pct / 100)
exit_found = false
exit_price_hist = 0.0
for i = 1 to i_trade_duration
bar_offset = entry_ago - i
if low[bar_offset] <= sl_level
exit_price_hist := sl_level, exit_found := true, break
if high[bar_offset] >= tp_level
exit_price_hist := tp_level, exit_found := true, break
if not exit_found
exit_price_hist := close[entry_ago - i_trade_duration]
trade_return := (exit_price_hist - entry_price_hist) / entry_price_hist
array.push(hist_returns, trade_return)
trade_count = array.size(hist_returns)
if trade_count > 0
avg_period_return = array.sum(hist_returns) / trade_count
annualized_return = math.pow(1 + avg_period_return, annualization_factor / i_trade_duration) - 1
variance = 0.0
for ret in hist_returns
variance += math.pow(ret - avg_period_return, 2)
period_vol = math.sqrt(variance / trade_count)
annualized_vol = period_vol * math.sqrt(annualization_factor / i_trade_duration)
win_count = 0
for ret in hist_returns
if ret > 0
win_count += 1
hist_ev := avg_period_return * i_position_size, hist_ev_pct := avg_period_return * 100, hist_sigma := period_vol * i_position_size
hist_win_rate := win_count / trade_count * 100, hist_sharpe := calculate_sharpe(annualized_return * 100, annualized_vol * 100)
total_trades := trade_count
else
total_trades := 0
[hist_ev, hist_ev_pct, hist_sigma, hist_win_rate, hist_sharpe, total_trades]
// --- MONTE CARLO SIMULATION (Buy and Hold) ---
calculate_monte_carlo_stats() =>
var float mc_ev = na, var float mc_ev_pct = na, var float mc_sigma = na, var float mc_win_rate = na, var float mc_sharpe = na
if barstate.islast
mc_ev := na, mc_ev_pct := na, mc_sigma := na, mc_win_rate := na, mc_sharpe := na
mc_returns = array.new<float>()
annual_return_mc = long_trend, annual_vol_mc = long_vol
per_bar_drift = annual_return_mc / annualization_factor / 100
per_bar_vol = annual_vol_mc / 100 / math.sqrt(annualization_factor)
total_drift = per_bar_drift * i_trade_duration
total_vol = per_bar_vol * math.sqrt(i_trade_duration)
for sim = 1 to i_monte_carlo_sims
// FINAL POLISH: Non-repainting random number seeds for stable projections.
seed1 = (sim * 31415 + bar_index) % 2147483647
seed2 = (sim * 14142 + time_tradingday) % 2147483647 // time_tradingday provides a stable, non-repainting seed.
random1 = (seed1 % 100000) / 100000.0
random2 = (seed2 % 100000) / 100000.0
if random1 > 0.0001 and random2 > 0.0001
normal_random = math.sqrt(-2 * math.log(random1)) * math.cos(2 * math.pi * random2)
log_return = total_drift + normal_random * total_vol
array.push(mc_returns, math.exp(log_return) - 1)
sim_count = array.size(mc_returns)
if sim_count > 0
avg_period_return = array.sum(mc_returns) / sim_count
variance = 0.0
for ret in mc_returns
variance += math.pow(ret - avg_period_return, 2)
period_vol = math.sqrt(variance / sim_count)
win_count = 0
for ret in mc_returns
if ret > 0
win_count += 1
mc_ev := avg_period_return * i_position_size, mc_ev_pct := avg_period_return * 100, mc_sigma := period_vol * i_position_size
mc_win_rate := win_count / sim_count * 100, mc_sharpe := calculate_sharpe(annual_return_mc, annual_vol_mc)
[mc_ev, mc_ev_pct, mc_sigma, mc_win_rate, mc_sharpe]
// --- GLOBAL DECLARATIONS ---
var table info_table = na
var line ev_line = na, var line upper_line = na, var line lower_line = na
var line hist_ev_line = na, var linefill projection_fill = na
var label hist_label = na, var label upper_sigma_label = na, var label lower_sigma_label = na
// --- CALCULATIONS & DISPLAY ---
[hist_ev, hist_ev_pct, hist_sigma, hist_win_rate, hist_sharpe, total_trades] = calculate_historical_stats()
[mc_ev, mc_ev_pct, mc_sigma, mc_win_rate, mc_sharpe] = calculate_monte_carlo_stats()
if barstate.islast
// --- VISUALIZATION ---
line.delete(ev_line[1]), line.delete(upper_line[1]), line.delete(lower_line[1]), line.delete(hist_ev_line[1]), linefill.delete(projection_fill[1]), label.delete(hist_label[1]), label.delete(upper_sigma_label[1]), label.delete(lower_sigma_label[1])
if not na(mc_ev_pct)
future_ev_price = entry_price * (1 + mc_ev_pct / 100)
future_upper_price = entry_price * (1 + (mc_ev_pct + mc_sigma / i_position_size * 100) / 100)
future_lower_price = entry_price * (1 + (mc_ev_pct - mc_sigma / i_position_size * 100) / 100)
upper_sigma_label := label.new(bar_index + i_trade_duration, future_upper_price, text=str.tostring(future_upper_price, "#.##") + " (+)", color=color.new(color.white, 100), textcolor=color.white, style=label.style_label_left, textalign=text.align_right)
lower_sigma_label := label.new(bar_index + i_trade_duration, future_lower_price, text=str.tostring(future_lower_price, "#.##") + " (-)", color=color.new(color.white, 100), textcolor=color.white, style=label.style_label_left, textalign=text.align_right)
if not na(hist_ev)
hist_ev_price = entry_price * (1 + hist_ev_pct/100)
hist_label := label.new(bar_index, hist_ev_price, text="." + str.tostring(hist_ev_price, "#.##")+ " (Medyan)", color=color.new(color.green, 100), textcolor=color.yellow, style=label.style_label_left, textalign=text.align_right)
//////////////////////////////////////////////////////////////////////
numprct = input.int(defval = 3, title = 'Number of Bars to Predict', minval = 1, maxval = 100)
numsim = input.int(defval = 1, title = 'Number of Simulations', minval = 1)
datalen = input.int(defval = 10, title = 'Number of Bars to use as Data Source', minval = 1)
keepmm = input(defval = false, title = 'Keep Past Min-Max Levels')
minmaxupcol = input(defval = color.fuchsia, title = 'Min-Max Levels Up Color')
minmaxdncol = input(defval = color.fuchsia, title = 'Min-Max Levels Down Color')
rwupcol = input(defval = color.fuchsia, title = 'Random Walk Up Color')
rwdncol = input(defval = color.fuchsia, title = 'Random Walk Down Color')
//==================================================================================================
// Thanks to Ricardo Santos for letting me use his Random Number Generator Function (@RicardoSantos)
//==================================================================================================
var array<float> seed = array.new_float(size = 1, initial_value = 42.0)
f_prng(_range) =>
//| pseudo random function:
var float _return = 1.0
float _seed = array.get(id = seed, index = 0)
float _reseed = 271.828
_return := 3.1416 * _return % _seed * _reseed % _range
array.set(id = seed, index = 0, value = 1 + _seed * 1.618 % _range + _return)
_return
//==================================================================================================
timediff = time - time[1]
// total walks except min/max levels
var totalwalk = math.floor(10 / numprct) - 2
var prdc_lines = array.new_line(numprct * (totalwalk + 2))
if barstate.isfirst
for x = 0 to array.size(prdc_lines) - 1 by 1
array.set(prdc_lines, x, line.new(x1 = time, y1 = close, x2 = time, y2 = close, xloc = xloc.bar_time, width = x < totalwalk * numprct ? 2 : 3))
// keep periodic daily returns using the natural logarithm
var pc = array.new_float(0)
array.unshift(pc, math.log(close / close[1]))
if array.size(pc) > datalen
array.pop(pc)
float minlevel = na
float maxlevel = na
if barstate.islast
cls = array.new_float(numprct, na)
maxcls = array.new_float(numprct, -1e15)
mincls = array.new_float(numprct, 1e15)
r = 0
drift = array.avg(pc) - array.variance(pc) / 2
//start simulations and get random 5 random and max-min paths
for x = 0 to numsim - 1 by 1
lastclose = close
for y = 0 to numprct - 1 by 1
rchg = math.round(f_prng(math.min(array.size(pc) - 1, bar_index - 1)))
nextclose = math.max(0, lastclose * math.exp(array.get(pc, rchg) + drift))
array.set(cls, y, nextclose)
lastclose := nextclose
array.set(maxcls, y, math.max(array.get(maxcls, y), nextclose))
array.set(mincls, y, math.min(array.get(mincls, y), nextclose))
// draw random paths
if r < totalwalk
line.set_xy1(array.get(prdc_lines, r * numprct), time, close)
line.set_xy2(array.get(prdc_lines, r * numprct), time + timediff, array.get(cls, 0))
line.set_color(array.get(prdc_lines, r * numprct), array.get(cls, 0) >= close ? rwupcol : rwdncol)
for y = 1 to numprct - 1 by 1
line.set_xy1(array.get(prdc_lines, r * numprct + y), time + timediff * y, array.get(cls, y - 1))
line.set_xy2(array.get(prdc_lines, r * numprct + y), time + timediff * (y + 1), array.get(cls, y))
line.set_color(array.get(prdc_lines, r * numprct + y), array.get(cls, y) >= array.get(cls, y - 1) ? rwupcol : rwdncol)
r := r + 1
r
// draw estimaded max-min closing prices
line.set_xy1(array.get(prdc_lines, totalwalk * numprct), time, close)
line.set_xy2(array.get(prdc_lines, totalwalk * numprct), time + timediff, array.get(maxcls, 0))
line.set_color(array.get(prdc_lines, totalwalk * numprct), array.get(maxcls, 0) >= close ? minmaxupcol : minmaxdncol)
line.set_xy1(array.get(prdc_lines, (totalwalk + 1) * numprct), time, close)
line.set_xy2(array.get(prdc_lines, (totalwalk + 1) * numprct), time + timediff, array.get(mincls, 0))
line.set_color(array.get(prdc_lines, (totalwalk + 1) * numprct), array.get(mincls, 0) >= close ? minmaxupcol : minmaxdncol)
for y = 1 to numprct - 1 by 1
line.set_xy1(array.get(prdc_lines, totalwalk * numprct + y), time + timediff * y, array.get(maxcls, y - 1))
line.set_xy2(array.get(prdc_lines, totalwalk * numprct + y), time + timediff * (y + 1), array.get(maxcls, y))
line.set_color(array.get(prdc_lines, totalwalk * numprct + y), array.get(maxcls, y) >= array.get(maxcls, y - 1) ? minmaxupcol : minmaxdncol)
line.set_xy1(array.get(prdc_lines, (totalwalk + 1) * numprct + y), time + timediff * y, array.get(mincls, y - 1))
line.set_xy2(array.get(prdc_lines, (totalwalk + 1) * numprct + y), time + timediff * (y + 1), array.get(mincls, y))
line.set_color(array.get(prdc_lines, (totalwalk + 1) * numprct + y), array.get(mincls, y) >= array.get(mincls, y - 1) ? minmaxupcol : minmaxdncol)
maxlevel := array.get(maxcls, 0)
minlevel := array.get(mincls, 0)
minlevel
//plot(maxlevel, color = keepmm ? maxlevel >= maxlevel[1] ? minmaxupcol : minmaxdncol : na)
//plot(minlevel, color = keepmm ? minlevel >= minlevel[1] ? minmaxupcol : minmaxdncol : na)
//////////////////////
// ====== INPUTS: CONFIGURATION ======
group_model = "Forecast Model"
historical_period_hours = input.float(2*24.0, "Historical Lookback (Hours)", tooltip="Number of past hours to analyze for volatility and drift.", group=group_model, step=1.0)
forecast_period_hours = input.float(24.0, "Forecast Period (Hours)", tooltip="Number of hours into the future to project.", group=group_model, step=1.0)
num_simulations = input.int(10, "Number of Simulations", tooltip="Higher numbers increase accuracy but may slow down the script.", group=group_model)
group_quartiles = "Forecast Quartiles"
show_quartiles = input.bool(true, "Show Quartile Forecast", group=group_quartiles)
q1_percentile = input.int(25, "Quartile 1 (%)", inline="q1", group=group_quartiles)
q2_percentile = input.int(50, "Quartile 2 (Median, %)", inline="q1", group=group_quartiles)
q3_percentile = input.int(75, "Quartile 3 (%)", inline="q2", group=group_quartiles)
q4_percentile = input.int(95, "Quartile 4 (Outer Bound, %)", inline="q2", group=group_quartiles)
// ====== PROBABILITY SIGNAL INPUTS (UP and DOWN are now separate) ======
group_signal_up = "Bullish Signal"
show_signal_up = input.bool(true, "Show UP Signal", group=group_signal_up)
signal_confidence_up = input.int(75, "UP Signal Confidence (%)", tooltip="The required probability for the UP signal to appear.", group=group_signal_up)
signal_target_percent_up = input.float(2.0, "UP Target Price Change (%)", step=0.5, tooltip="The target price increase to calculate the probability for.", group=group_signal_up) / 100
group_signal_down = "Bearish Signal"
show_signal_down = input.bool(true, "Show DOWN Signal", group=group_signal_down)
signal_confidence_down = input.int(75, "DOWN Signal Confidence (%)", tooltip="The required probability for the DOWN signal to appear.", group=group_signal_down)
signal_target_percent_down = input.float(1.0, "DOWN Target Price Change (%)", step=0.5, tooltip="The target price decrease to calculate the probability for.", group=group_signal_down) / 100
// ====== VISUALS ======
group_visuals = "Visuals"
c_quartile_fill = input.color(color.new(color.blue, 75), "Quartile Fill", group=group_visuals)
c_quartile_line = input.color(color.new(#2195f3, 50), "Quartile Lines", group=group_visuals)
// ====== Timeframe-Adaptive Period Calculation ======
seconds_per_bar = timeframe.in_seconds(timeframe.period)
historical_period_bars = int(math.round(math.max(1, historical_period_hours * 3600 / seconds_per_bar)))
forecast_period_bars = int(math.round(math.max(1, forecast_period_hours * 3600 / seconds_per_bar)))
// ====== CORE LOGIC ======
box_muller(u1, u2) =>
math.sqrt(-2.0 * math.log(u1)) * math.cos(2.0 * math.pi * u2)
if barstate.islastconfirmedhistory
log_returns = array.new_float()
for i = 0 to historical_period_bars - 1
array.push(log_returns, math.log(close[i] / close[i+1]))
drift = array.avg(log_returns)
volatility = array.stdev(log_returns)
start_price = close
final_prices = array.new_float(num_simulations)
for sim = 0 to num_simulations - 1
current_price = start_price
for day = 1 to forecast_period_bars
random_shock = box_muller(math.random(), math.random())
current_price := current_price * math.exp(drift - 0.5 * volatility * volatility + volatility * random_shock)
array.set(final_prices, sim, current_price)
if show_quartiles
array.sort(final_prices)
price_q1 = array.percentile_linear_interpolation(final_prices, q1_percentile)
price_q2 = array.percentile_linear_interpolation(final_prices, q2_percentile)
price_q3 = array.percentile_linear_interpolation(final_prices, q3_percentile)
price_q4 = array.percentile_linear_interpolation(final_prices, q4_percentile)
forecast_end_time = time + int(forecast_period_hours * 3600 * 1000)
//line_q1 = line.new(time, start_price, forecast_end_time, price_q1, color=c_quartile_line, width=1, xloc=xloc.bar_time)
//line_q4 = line.new(time, start_price, forecast_end_time, price_q4, color=c_quartile_line, width=1, xloc=xloc.bar_time)
//linefill.new(line_q1, line_q4, color=c_quartile_fill)
//label.new(forecast_end_time, price_q2, text=str.format("{0,number,#.##}", price_q2), xloc=xloc.bar_time, color=color.new(color.white, 60), textcolor=color.black, style=label.style_label_left)
//label.new(forecast_end_time, price_q4, text=str.format("{0,number,#.##}", price_q4), xloc=xloc.bar_time, color=color.new(color.white, 60), textcolor=color.black, style=label.style_label_left)
//label.new(forecast_end_time, price_q1, text=str.format("{0,number,#.##}", price_q1), xloc=xloc.bar_time, color=color.new(color.white, 60), textcolor=color.black, style=label.style_label_left)
// --- Signal Generation (Using separate UP and DOWN inputs) ---
up_count = 0
down_count = 0
price_target_up = start_price * (1 + signal_target_percent_up)
price_target_down = start_price * (1 - signal_target_percent_down)
for price_val in final_prices
if price_val >= price_target_up
up_count += 1
if price_val <= price_target_down
down_count +=1
prob_up = up_count / num_simulations * 100
prob_down = down_count / num_simulations * 100
if show_signal_up and prob_up >= signal_confidence_up
label.new(bar_index, high, str.format("A", prob_up, signal_target_percent_up),
style=label.style_label_down, color=color.new(color.green, 50), textcolor=color.white, size=size.normal)
if show_signal_down and prob_down >= signal_confidence_down
label.new(bar_index, low, str.format("S", prob_down, signal_target_percent_down),
style=label.style_label_up, color=color.new(color.red, 50), textcolor=color.white, size=size.normal)
////////////////////////////
// Define Groups
const string simulation = "Monte Carlo Simulation"
const string historical = "Historical Analysis"
const string plots = "Plotting"
const string ui = "UI Settings"
// Define tooltips
const string source_tt = "Select the price series to run the Monte Carlo simulation on."
const string enable_forecasts_tt = "Toggle to enable or disable Monte Carlo probability forecast generation."
const string simulation_runs_tt = "Number of Monte Carlo simulation iterations to perform."
const string forecast_horizon_tt = "Number of future days to project in the simulation."
const string confidence_levels_tt = "Choose which probability bands to display on the chart."
const string custom_confidence_tt = "Custom confidence level percentage for the probability bands."
const string seasonal_mode_tt = "Select the seasonal pattern resolution: daily or weekly."
const string volatility_adjustment_tt = "Scale simulation results according to recent volatility."
const string show_probability_cone_tt = "Display the probability cone showing forecast uncertainty."
const string show_mean_projection_tt = "Display the mean expected price path from simulations."
const string show_historical_overlay_tt = "Overlay historical seasonal patterns on the chart."
const string forecast_transparency_tt = "Transparency level for probability bands."
const string primary_forecast_color_tt = "Color used for the outer (e.g., 95%) forecast bands."
const string secondary_forecast_color_tt = "Color used for the inner (e.g., 68%) forecast bands."
const string mean_path_color_tt = "Color used for the expected mean price path."
// User Inputs
float source = input.source(close, "Price Series for Calculation", group = simulation, tooltip = source_tt)
bool enable_forecasts = input.bool(true, "Enable Probability Forecasts", group = simulation, tooltip = enable_forecasts_tt)
int simulation_runs = input.int(100, "Simulation Iterations", minval = 1, maxval = 1000, group = simulation, tooltip = simulation_runs_tt)
int forecast_horizon = input.int(5, "Forecast Days Ahead", minval = 1, group = simulation, tooltip = forecast_horizon_tt)
string confidence_levels = input.string('All Bands', "Probability Bands", ['All Bands', '95% Only', '68% Only', 'Custom'], group = simulation, tooltip = confidence_levels_tt)
float custom_confidence = input.float(80.0, "Custom Confidence %", minval = 50.0, maxval = 99.0, group = simulation, tooltip = custom_confidence_tt)
string seasonal_mode = input.string('Daily', "Pattern Resolution", ['Daily', 'Weekly'], group = historical, tooltip = seasonal_mode_tt)
bool volatility_adjustment = input.bool(true, "Volatility Scaling", group = historical, tooltip = volatility_adjustment_tt)
bool show_probability_cone = input.bool(true, "Probability Cone", group = plots, tooltip = show_probability_cone_tt)
bool show_mean_projection = input.bool(true, "Expected Path", group = plots, tooltip = show_mean_projection_tt)
bool show_historical_overlay = input.bool(true, "Historical Overlay", group = plots, tooltip = show_historical_overlay_tt)
int forecast_transparency = input.int(50, "Band Transparency", group = ui, tooltip = forecast_transparency_tt)
color mean_path_color = input.color(color.rgb(190, 7, 246), "Expected Path", group = ui, tooltip = mean_path_color_tt)
color primary_forecast_color = input.color(#00ff0000, "Primary Forecast", group = ui, tooltip = primary_forecast_color_tt, inline = "1")
color secondary_forecast_color = input.color(#ff000000, "Secondary Forecast", group = ui, tooltip = secondary_forecast_color_tt, inline = "1")
// Declare Data Structures
var historical_returns = array.new_float()
var daily_patterns = array.new_float()
var volatility_patterns = array.new_float()
var simulation_results = array.new_float()
var series int yearly_start_index = na
var series int current_day_of_year = na
var series int base_year = na
current_volatility = ta.atr(20) / source
// Initialize year tracking and calculate day of year manually
if na(yearly_start_index)
yearly_start_index := bar_index
base_year := year
// Calculate day of year manually using month and day
days_in_months = array.from(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
current_day_of_year := 0
if month > 1
for i = 0 to month - 2
current_day_of_year += days_in_months.get(i)
current_day_of_year += dayofmonth
// Add leap year adjustment
is_leap_year = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
if is_leap_year and month > 2
current_day_of_year += 1
// Pattern Recognition Engine
pattern_discovery() =>
if not na(source) and not na(source[1]) and barstate.isconfirmed
daily_return = math.log((source / source[1]))
daily_volatility = math.abs(daily_return)
// Store patterns, use modulo to cycle through year
pattern_index = current_day_of_year % 365
if historical_returns.size() <= pattern_index
while historical_returns.size() <= pattern_index
historical_returns.push(0.0)
volatility_patterns.push(0.01)
// Update running average for this day of year
existing_return = historical_returns.get(pattern_index)
updated_return = existing_return * 0.9 + daily_return * 0.1
historical_returns.set(pattern_index, updated_return)
existing_vol = volatility_patterns.get(pattern_index)
updated_vol = existing_vol * 0.9 + daily_volatility * 0.1
volatility_patterns.set(pattern_index, updated_vol)
// Monte Carlo Simulation
// Helper Functions
// Gaussian random number generator (Box-Muller transform)
gauss_random(mean_val, std_val, seed_val) =>
r1 = math.random(0.01, 0.99, seed_val)
r2 = math.random(0.01, 0.99, seed_val + 100)
z = math.sqrt(-2.0 * math.log(r1)) * math.cos(2.0 * math.pi * r2)
mean_val + std_val * z
// Bootstrap sampling from historical patterns
bootstrap_sample(returns_array, seed_val) =>
if returns_array.size() > 0
random_index = math.round(math.random(0, returns_array.size() - 1, seed_val))
returns_array.get(random_index)
else
0.0
monte_carlo_forecast(start_price, days_ahead) =>
var simulation_matrix = matrix.new<float>(100, days_ahead + 1, 0.0)
// Only run if we have enough historical data
if historical_returns.size() < 30
simulation_matrix
else
// Calculate statistics from historical patterns
mean_return = historical_returns.sum() / historical_returns.size()
std_return = historical_returns.stdev()
// Limit simulations for performance
actual_sims = math.min(simulation_runs, 100)
// Run Monte Carlo simulations
for sim = 0 to actual_sims - 1
current_price = start_price
simulation_matrix.set(sim, 0, current_price)
// Generate path for each day ahead
for day = 0 to days_ahead
// Get seasonal bias for this day
pattern_index = (current_day_of_year + day - 1) % historical_returns.size()
seasonal_bias = historical_returns.get(pattern_index)
// Generate random return using seasonal bias
sim_ret = 0.0
seed_val = sim * 10000 + day * 137
if seasonal_mode == 'Daily'
// Use Gaussian distribution with seasonal bias
sim_ret := gauss_random(seasonal_bias, std_return * 0.5, seed_val)
else
// Bootstrap from historical data
sim_ret := bootstrap_sample(historical_returns, seed_val) + seasonal_bias * 0.3
// Apply volatility scaling
vol_scaling = volatility_adjustment ? math.max(0.5, math.min(2.0, current_volatility / 0.02)) : 1.0
sim_ret *= vol_scaling
// Update price
current_price := current_price * math.exp(sim_ret)
matrix.set(simulation_matrix, sim, day, current_price)
simulation_matrix
// Calculate Probability Bands
calculate_percentiles(simulation_matrix, day_index) =>
if simulation_matrix.rows() > 0 and day_index < simulation_matrix.columns()
var day_values = array.new_float()
day_values.clear()
// Extract all simulation values for this day
for sim = 0 to simulation_matrix.rows() - 1
price_value = simulation_matrix.get(sim, day_index)
day_values.push(price_value)
// Sort for percentile calculation
day_values.sort()
total_values = day_values.size()
if total_values > 0
p5 = day_values.get(math.max(0, math.round(total_values * 0.05) - 1))
p16 = day_values.get(math.max(0, math.round(total_values * 0.16) - 1))
p50 = day_values.get(math.max(0, math.round(total_values * 0.50) - 1))
p84 = day_values.get(math.min(total_values - 1, math.round(total_values * 0.84)))
p95 = day_values.get(math.min(total_values - 1, math.round(total_values * 0.95)))
[p5, p16, p50, p84, p95]
else
[na, na, na, na, na]
else
[na, na, na, na, na]
// Probability Bands
draw_probability_cone(simulation_matrix, start_bar, start_price) =>
if show_probability_cone and simulation_matrix.rows() > 0
// Create arrays for confidence band points
var p5_points = array.new<chart.point>()
var p16_points = array.new<chart.point>()
var p50_points = array.new<chart.point>()
var p84_points = array.new<chart.point>()
var p95_points = array.new<chart.point>()
p5_points.clear()
p16_points.clear()
p50_points.clear()
p84_points.clear()
p95_points.clear()
// Calculate percentiles for each day
for day = 0 to math.min(forecast_horizon, simulation_matrix.columns() - 1)
[p5, p16, p50, p84, p95] = calculate_percentiles(simulation_matrix, day)
if not na(p50)
bar_x = start_bar + day
p5_points.push(chart.point.from_index(bar_x, p5))
p16_points.push(chart.point.from_index(bar_x, p16))
p50_points.push(chart.point.from_index(bar_x, p50))
p84_points.push(chart.point.from_index(bar_x, p84))
p95_points.push(chart.point.from_index(bar_x, p95))
// Draw confidence bands
//if confidence_levels == 'All Bands' or confidence_levels == '95% Only'
// polyline.new(p5_points, line_color=color.new(primary_forecast_color, forecast_transparency), line_width=1)
//polyline.new(p95_points, line_color=color.new(primary_forecast_color, forecast_transparency), line_width=1)
//if confidence_levels == 'All Bands' or confidence_levels == '68% Only'
// polyline.new(p16_points, line_color=color.new(secondary_forecast_color, forecast_transparency), line_width=1)
//polyline.new(p84_points, line_color=color.new(secondary_forecast_color, forecast_transparency), line_width=1)
if show_mean_projection
polyline.new(p50_points, line_color=mean_path_color, line_width=2)
// Call Function
pattern_discovery()
if barstate.islastconfirmedhistory and enable_forecasts
if historical_returns.size() > 30
// Run Monte Carlo simulation
forecast_matrix = monte_carlo_forecast(source, forecast_horizon)
// Visualize results
if forecast_matrix.rows() > 0
draw_probability_cone(forecast_matrix, bar_index + 1, source)
[p5_final, p16_final, median_final, p84_final, p95_final] = calculate_percentiles(forecast_matrix, forecast_horizon - 1)
if not na(median_final)
expected_return = (median_final - source) / source * 100
confidence_range = (p95_final - p5_final) / source * 100
//
if show_historical_overlay and historical_returns.size() > 30
// Plot historical seasonal pattern as reference
historical_projection = source
for i = 1 to math.min(30, 365 - current_day_of_year)
pattern_idx = (current_day_of_year + i) % historical_returns.size()
if pattern_idx < historical_returns.size()
expected_return = historical_returns.get(pattern_idx)
new_projection = historical_projection * (1 + expected_return)
line.new(bar_index + i - 1, historical_projection,
bar_index + i, new_projection,
color = color.new(#e8f408, 50), width = 1, style = line.style_dotted)
historical_projection := new_projection
///////////////////////
//anchor_time = input.time(timestamp('04 Mar 2024 00:00'), title = 'Anchor Point', group = 'Anchor', confirm = true)
lb = input.int(20, title = 'Lookback', maxval = 499, tooltip = 'Number of bars to use for calculations.', group = 'Simulations')
sim_count = input.int(10, title = 'Simulation Count', maxval = 1000, tooltip = 'Number of simulations to run.', group = 'Simulations')
randomize = input.bool(false, title = 'Randomize Direction', tooltip = 'Changes the directions half of the randomized bars to produce a more evenly distributed range.', group = 'Simulations')
display_sims = input.bool(false, title = 'Visualize Simulations', tooltip = 'Display Max = 100 Polylines per Script', group = 'Simulations')
sim_transp = input.int(70, minval = 0, maxval = 100, title = 'Simulation Trasparency', group = 'Simulations')
fill_color = input.color(color.new(color.fuchsia, 70), title = 'Shadow Color', group = 'Simulation Range', tooltip = 'Fills in-between the Highest and Lowest simulations.')
max_min_tog = input.bool(true, title = 'Max/Min Value Lines', group = 'Max/Min/Avg Simulation Value Lines')
max_color = input.color(#f23645, title = 'Max | Min Colors', inline = 'maxmin', group = 'Max/Min/Avg Simulation Value Lines')
min_color = input.color(#089981, title = '', inline = 'maxmin', group = 'Max/Min/Avg Simulation Value Lines')
avg_tog = input.bool(false, title = 'Average Line (Slope)', group = 'Max/Min/Avg Simulation Value Lines')
avg_color = input.color(color.rgb(25, 98, 255), title = 'Average Color', group = 'Max/Min/Avg Simulation Value Lines')
stdev_tog = input.bool(false, title = 'Standard Deviations', group = 'Standard Deviations')
multi = input.float(2, title = 'Multiplier', step = 0.5, minval = 0, group = 'Standard Deviations')
dev_color = input.color(color.rgb(25, 98, 255), title = 'Deviation Color', group = 'Standard Deviations')
x_tog = input.bool(true, title = 'Extend Lines', group = 'Style')
//---------------------------------------------------------------------------------------------------------------------}
//UDTs
//---------------------------------------------------------------------------------------------------------------------{
type fa
array<float> ary
//---------------------------------------------------------------------------------------------------------------------}
//Functions
//---------------------------------------------------------------------------------------------------------------------{
is_odd(_num) =>
_num / 2 - math.floor(_num / 2) > 0
d_places() =>
decimals = str.contains(str.tostring(syminfo.mintick), '.') ? str.length(array.get(str.split(str.tostring(syminfo.mintick), '.'), 1)) : 0
Zs = array.new_string(na)
for i = 1 to decimals by 1
Zs.push('0')
out = str.replace_all(str.replace_all(str.replace_all(str.tostring(Zs), ',', ''), ']', ''), '[', '')
'0.' + out
random_in_range(seed, min, max) =>
min + (1103515245 * seed + 12345) % 2147483647 % (max + 1 - min)
get_col(_num) =>
color col = na
for i = 0 to 100 by 1
r = random_in_range(_num * i, 0, 255)
g = random_in_range(math.pow(_num * i, 2), 0, 255)
b = random_in_range(math.pow(_num * i, 3), 0, 255)
if r + g + b > 255
col := color.rgb(r, g, b, sim_transp)
break
col
fy_shuffle(_ary, _seed) =>
final = array.copy(_ary)
working = array.copy(_ary)
for [index, value] in final
r_index = random_in_range(_seed, 0, working.size() - 1)
final.set(index, working.get(r_index))
working.remove(r_index)
final
//---------------------------------------------------------------------------------------------------------------------}
//Global Variables
//---------------------------------------------------------------------------------------------------------------------{
var p_lin_ary = array.new<polyline>(na) // Polyline Array, Its the array for Polylines!
var can_ary = array.new_float(na) // Candle Movement Array
//---------------------------------------------------------------------------------------------------------------------}
//General Calcs
//---------------------------------------------------------------------------------------------------------------------{
movement = close - open
can_ary.push(movement)
if can_ary.size() > lb
can_ary.shift()
//---------------------------------------------------------------------------------------------------------------------}
//Generating Simulations, Compiling Data, & Displaying Visualizations
//---------------------------------------------------------------------------------------------------------------------{
//if time == anchor_time
//Local Variables
full_ary = array.new<fa>(na)
all_data = array.new<fa>(na)
outer_points = array.new<chart.point>(1, chart.point.now(close))
stdevs = array.new_float(na)
float highest_sim = na
float lowest_sim = na
//Clearing polyline array
for lin in p_lin_ary
polyline.delete(lin)
array.clear(p_lin_ary)
//Shuffling bar data and sending the array if points to the full array
//Full array stores each simulation line's "Full" set of values (Left to Right)
for i = 1 to sim_count by 1
data = fy_shuffle(can_ary, bar_index * i * sim_count)
p_ary = array.new<chart.point>(1, chart.point.now(close))
temp_point_ary = array.new_float(na)
for [index, value] in data
add_value = is_odd(index) and randomize ? -value : value
point = p_ary.last().price + add_value
p_ary.push(chart.point.from_index(bar_index + index + 1, point))
temp_point_ary.push(point)
full_ary.push(fa.new(temp_point_ary))
if display_sims
p_lin_ary.push(polyline.new(p_ary, line_color = get_col(i)))
//Re-arranging the data to be stored in arrays of vertical data
//"All Data" is all data on that bar of simulations
if full_ary.size() > 0
for i = 0 to full_ary.get(0).ary.size() - 1 by 1
vert_ary = array.new_float(na)
for e = 0 to sim_count - 1 by 1
vert_ary.push(full_ary.get(e).ary.get(i))
all_data.push(fa.new(vert_ary))
//Now that the simulation data is vertically organized we can find averages and stdevs for each point along the way.
for [index, data] in all_data
ary = data.ary
avg = ary.avg()
data_max = ary.max()
data_min = ary.min()
stdevs.push(ary.stdev())
if data_max > highest_sim or na(highest_sim)
highest_sim := data_max
highest_sim
if data_min < lowest_sim or na(lowest_sim)
lowest_sim := data_min
lowest_sim
outer_points.push(chart.point.from_index(bar_index + index + 1, nz(data_max, avg)))
outer_points.unshift(chart.point.from_index(bar_index + index + 1, nz(data_min, avg)))
avg_dev = stdevs.avg() * multi
end_point = math.avg(outer_points.first().price, outer_points.last().price)
//Visualizations
p_lin_ary.push(polyline.new(outer_points, line_color = fill_color, fill_color = fill_color))
if max_min_tog
line.delete(line.new(bar_index - lb, highest_sim, bar_index + lb, highest_sim, color = max_color, width = 2)[1])
line.delete(line.new(bar_index - lb, lowest_sim, bar_index + lb, lowest_sim, color = min_color, width = 2)[1])
if x_tog
line.delete(line.new(bar_index + lb, highest_sim, bar_index + lb + 1, highest_sim, color = max_color, width = 2, extend = extend.right, style = line.style_dotted)[1])
line.delete(line.new(bar_index + lb, lowest_sim, bar_index + lb + 1, lowest_sim, color = min_color, width = 2, extend = extend.right, style = line.style_dotted)[1])
label.delete(label.new(bar_index - lb, highest_sim, str.tostring(highest_sim, d_places()), style = label.style_label_right, color = color.rgb(0, 0, 0, 100), textcolor = max_color, tooltip = 'Highest Simulated Value')[1])
label.delete(label.new(bar_index - lb, lowest_sim, str.tostring(lowest_sim, d_places()), style = label.style_label_right, color = color.rgb(0, 0, 0, 100), textcolor = min_color, tooltip = 'Lowest Simulated Value')[1])
if avg_tog
line.delete(line.new(bar_index, close, bar_index + lb, end_point, color = avg_color, width = 2)[1])
if x_tog
line.delete(line.new(bar_index + lb, end_point, bar_index + lb + 1, end_point + (end_point - close) / lb, color = avg_color, width = 2, extend = extend.right, style = line.style_dotted)[1])
if stdev_tog
line.delete(line.new(bar_index, close + avg_dev, bar_index + lb, end_point + avg_dev, color = dev_color)[1])
line.delete(line.new(bar_index, close - avg_dev, bar_index + lb, end_point - avg_dev, color = dev_color)[1])
if x_tog
line.delete(line.new(bar_index + lb, end_point + avg_dev, bar_index + lb + 1, end_point + (end_point - close) / lb + avg_dev, color = dev_color, extend = extend.right, style = line.style_dashed)[1])
line.delete(line.new(bar_index + lb, end_point - avg_dev, bar_index + lb + 1, end_point + (end_point - close) / lb - avg_dev, color = dev_color, extend = extend.right, style = line.style_dashed)[1])
//---------------------------------------------------------------------------------------------------------------------}
önemli not....
Yer İmleri