Fortunato Lead-Lag Multi-Asset (POC) v5_fix2

indicator("Fortunato Lead-Lag Multi-Asset (POC) v5_fix2", shorttitle="FLL Multi POC v5_fix2", overlay=false, max_lines_count=200, max_labels_count=200)
// ========== USER CONFIG ==========
res = input.timeframe("1", "Resolution for analysis (ex: 1, 5, 3)")
corr_length = input.int(60, "Rolling window (bars) for correlation", minval=10, maxval=500)
max_lag = input.int(5, "Max lag to test (bars)", minval=1, maxval=20)
corr_threshold = input.float(0.60, "Correlation threshold (abs)", step=0.01)
min_lag_for_signal = input.int(1, "Min lag to consider (bars)", minval=0)
plot_lag_as_columns = input.bool(true, "Plot lag as columns")
// --- symbols (change to the exact tickers your feed uses) ---
sym_ndx = input.symbol("NASDAQ:NDX", "NDX (leader candidate) - change if needed")
sym_spx = input.symbol("SPX:SPX", "SPX (follower candidate) - change if needed")
// Optional add-ons
sym_vix = input.symbol("CBOE:VIX", "VIX (volatility index) - optional")
sym_dxy = input.symbol("ICEUS:DXY", "DXY (Dollar Index) - optional")
sym_xau = input.symbol("OANDA:XAUUSD","Gold (XAU/USD) - optional")
sym_oil = input.symbol("NYMEX:CL1!", "Crude Oil (continuous) - optional")
sym_btc = input.symbol("BINANCE:BTCUSDT","Bitcoin (BTC) - optional")
// ========== DATA FETCH (selected resolution) ==========
ndx = request.security(sym_ndx, res, close)
spx = request.security(sym_spx, res, close)
vix = request.security(sym_vix, res, close)
dxy = request.security(sym_dxy, res, close)
xau = request.security(sym_xau, res, close)
oil = request.security(sym_oil, res, close)
btc = request.security(sym_btc, res, close)
// ========== HELPERS ==========
has_history(len) => bar_index >= len
// rolling Pearson correlation implemented with ta.cum differences (replaces ta.sum)
rolling_corr(a, b, n) =>
if not has_history(n)
na
else
// compute rolling sums via cumulative sums
// sum_ab = sum_{k=0..n-1} a[k]*b[k]
float cum_ab = ta.cum(a * b)
float cum_ab_lag = cum_ab[n]
float sum_ab = cum_ab - cum_ab_lag
float cum_a = ta.cum(a)
float cum_a_lag = cum_a[n]
float sum_a = cum_a - cum_a_lag
float cum_b = ta.cum(b)
float cum_b_lag = cum_b[n]
float sum_b = cum_b - cum_b_lag
float cum_a2 = ta.cum(a * a)
float cum_a2_lag = cum_a2[n]
float sum_a2 = cum_a2 - cum_a2_lag
float cum_b2 = ta.cum(b * b)
float cum_b2_lag = cum_b2[n]
float sum_b2 = cum_b2 - cum_b2_lag
float nn = n * 1.0
float num = sum_ab - (sum_a * sum_b) / nn
float den_part_a = sum_a2 - (sum_a * sum_a) / nn
float den_part_b = sum_b2 - (sum_b * sum_b) / nn
float den = den_part_a * den_part_b
if den <= 0.0
na
else
num / math.sqrt(den)
// ========== COMPUTE CORRELATIONS FOR ALL LAGS (USING rolling_corr) ==========
var float[] corr_dir1 = array.new_float()
var float[] corr_dir2 = array.new_float()
// ensure arrays sized correctly each bar
if array.size(corr_dir1) != (max_lag + 1)
array.clear(corr_dir1)
for i = 0 to max_lag
array.push(corr_dir1, na)
if array.size(corr_dir2) != (max_lag + 1)
array.clear(corr_dir2)
for i = 0 to max_lag
array.push(corr_dir2, na)
// fill arrays with correlation values (call rolling_corr every bar for consistency)
for i = 0 to max_lag
float val1 = na
if has_history(corr_length + i) and not na(ndx) and not na(spx)
// ndx aligned with spx shifted by +i (ndx leads spx by i)
val1 := rolling_corr(ndx, spx, corr_length)
array.set(corr_dir1, i, val1)
float val2 = na
if i > 0 and has_history(corr_length + i) and not na(ndx) and not na(spx)
// spx leads ndx by i
val2 := rolling_corr(ndx, spx, corr_length)
array.set(corr_dir2, i, val2)
// ========== FIND BEST ABSOLUTE CORRELATION AND DIRECTION ==========
float best_corr = na
int best_lag = 0
int best_dir = 0 // 1 = ndx -> spx, -1 = spx -> ndx
// scan dir1 (i = 0..max_lag)
for i = 0 to max_lag
float c = array.get(corr_dir1, i)
if not na(c)
if na(best_corr) or math.abs(c) > math.abs(best_corr)
best_corr := c
best_lag := i
best_dir := 1
// scan dir2 (i = 1..max_lag)
for i = 1 to max_lag
float c = array.get(corr_dir2, i)
if not na(c)
if na(best_corr) or math.abs(c) > math.abs(best_corr)
best_corr := c
best_lag := i
best_dir := -1
// ========== MULTI-ASSET LIGHT CONFIRMATION (explicit calls with rolling_corr) ==========
float sum_corr = 0.0
int count_corr = 0
// VIX
float local_best_vix = na
if not na(vix)
for j = 0 to max_lag
if has_history(corr_length + j)
float cc = rolling_corr(ndx, vix[j], corr_length)
if not na(cc)
if na(local_best_vix) or math.abs(cc) > math.abs(local_best_vix)
local_best_vix := cc
if not na(local_best_vix)
sum_corr := sum_corr + local_best_vix
count_corr := count_corr + 1
// DXY
float local_best_dxy = na
if not na(dxy)
for j = 0 to max_lag
if has_history(corr_length + j)
float cc = rolling_corr(ndx, dxy[j], corr_length)
if not na(cc)
if na(local_best_dxy) or math.abs(cc) > math.abs(local_best_dxy)
local_best_dxy := cc
if not na(local_best_dxy)
sum_corr := sum_corr + local_best_dxy
count_corr := count_corr + 1
// XAU
float local_best_xau = na
if not na(xau)
for j = 0 to max_lag
if has_history(corr_length + j)
float cc = rolling_corr(ndx, xau[j], corr_length)
if not na(cc)
if na(local_best_xau) or math.abs(cc) > math.abs(local_best_xau)
local_best_xau := cc
if not na(local_best_xau)
sum_corr := sum_corr + local_best_xau
count_corr := count_corr + 1
// OIL
float local_best_oil = na
if not na(oil)
for j = 0 to max_lag
if has_history(corr_length + j)
float cc = rolling_corr(ndx, oil[j], corr_length)
if not na(cc)
if na(local_best_oil) or math.abs(cc) > math.abs(local_best_oil)
local_best_oil := cc
if not na(local_best_oil)
sum_corr := sum_corr + local_best_oil
count_corr := count_corr + 1
// BTC
float local_best_btc = na
if not na(btc)
for j = 0 to max_lag
if has_history(corr_length + j)
float cc = rolling_corr(ndx, btc[j], corr_length)
if not na(cc)
if na(local_best_btc) or math.abs(cc) > math.abs(local_best_btc)
local_best_btc := cc
if not na(local_best_btc)
sum_corr := sum_corr + local_best_btc
count_corr := count_corr + 1
float confirm_avg = na
if count_corr > 0
confirm_avg := sum_corr / count_corr
// ========== SIGNAL LOGIC ==========
bool lead_detected = false
string lead_direction_text = "NoLeader"
if not na(best_corr) and math.abs(best_corr) >= corr_threshold and best_lag >= min_lag_for_signal
lead_detected := true
lead_direction_text := best_dir == 1 ? "NDX -> SPX" : (best_dir == -1 ? "SPX -> NDX" : "NoLeader")
// ========== PLOTS (GLOBAL) ==========
plot_best_corr = best_corr
plot_best_lag = (lead_detected ? best_lag : na)
plot_confirm_avg = confirm_avg
plot(plot_best_corr, title="Best Corr (signed)", linewidth=2)
hline(0, "zero", linestyle=hline.style_dashed)
hline(corr_threshold, "threshold +", linestyle=hline.style_solid)
hline(-corr_threshold, "threshold -", linestyle=hline.style_solid)
plot(plot_lag_as_columns ? plot_best_lag : na, title="Best Lag (bars)", style=plot.style_columns, linewidth=2)
plot(not na(plot_confirm_avg) ? plot_confirm_avg : na, title="Multi-asset confirm (avg)", linewidth=1, style=plot.style_line)
// ========== LABEL MANAGEMENT ==========
var label lbl = na
if lead_detected and barstate.isconfirmed
if not na(lbl)
label.delete(lbl)
lbl := label.new(bar_index, plot_best_corr, text="Lead: " + lead_direction_text + " lag:" + str.tostring(best_lag) + " corr:" + str.tostring(best_corr, "#.##"),
style=label.style_label_left, color=color.new(color.green, 75), textcolor=color.white, size=size.small)
// ========== ALERTS ==========
alertcondition(lead_detected and best_dir == 1, title="NDX leads SPX detected", message="NDX leads SPX — lag: {{plot_1}} corr: {{plot_0}}")
alertcondition(lead_detected and best_dir == -1, title="SPX leads NDX detected", message="SPX leads NDX — lag: {{plot_1}} corr: {{plot_0}}")
// ========== INFORMATION TABLE ==========
var table t = table.new(position.top_right, 1, 5, border_width=1)
if barstate.islast
table.cell(t, 0, 0, "Resolution: " + res)
table.cell(t, 0, 1, "Best corr: " + (na(best_corr) ? "na" : str.tostring(best_corr, "#.##")))
table.cell(t, 0, 2, "Best lag: " + (na(best_lag) ? "na" : str.tostring(best_lag)))
table.cell(t, 0, 3, "Direction: " + lead_direction_text)
table.cell(t, 0, 4, "Confirm avg: " + (na(confirm_avg) ? "na" : str.tostring(confirm_avg, "#.##")))
Skript nur auf Einladung
Ausschließlich Nutzer mit einer Erlaubnis des Autors können Zugriff auf dieses Script erhalten. Sie müssen diese Genehmigung bei dem Autor beantragen. Dies umfasst üblicherweise auch eine Zahlung. Wenn Sie mehr erfahren möchten, dann sehen Sie sich unten die Anweisungen des Autors an oder kontaktieren Sie TiagoFort direkt.
TradingView empfiehlt NICHT, für die Nutzung eines Scripts zu bezahlen, wenn Sie den Autor nicht als vertrauenswürdig halten und verstehen, wie das Script funktioniert. Sie können außerdem auch kostenlose Open-Source-Alternativen in unseren Community-Scripts finden.
Hinweise des Autors
Haftungsausschluss
Skript nur auf Einladung
Ausschließlich Nutzer mit einer Erlaubnis des Autors können Zugriff auf dieses Script erhalten. Sie müssen diese Genehmigung bei dem Autor beantragen. Dies umfasst üblicherweise auch eine Zahlung. Wenn Sie mehr erfahren möchten, dann sehen Sie sich unten die Anweisungen des Autors an oder kontaktieren Sie TiagoFort direkt.
TradingView empfiehlt NICHT, für die Nutzung eines Scripts zu bezahlen, wenn Sie den Autor nicht als vertrauenswürdig halten und verstehen, wie das Script funktioniert. Sie können außerdem auch kostenlose Open-Source-Alternativen in unseren Community-Scripts finden.