// © 2026 HYPERSPY LLC. All rights reserved. // HYPERSPY Forecaster Lite is free for personal use. You may share this file UNMODIFIED with this notice intact. // Modified copies may not use the HYPERSPY name or logo. Terms: hypespy.ai/legal // Educational and informational software only. Not financial advice. Trading involves risk of loss. //@version=6 // ═══════════════════════════════════════════════════════════════════════════════ // HYPERSPY Forecaster Lite V1.0 // // A 10-day volatility cone for SPY: where price could reasonably travel over the // next N trading days, with 50% and 80% ranges and the expected (median) move. // // How it works (Lite engine): // • Volatility is measured close-to-close on the regular-session DAILY series, // with a fast (≈ 2 weeks) and a slow (≈ 6 months) exponential average. The // cone blends them: near days follow current volatility, far days drift // toward the long-run level. // • The centre line uses a long-run drift (annual Sharpe input), nothing more. // • Intraday charts use the volatility known at the last daily close (no // repaint); the cone is anchored at the current price. // // The full HYPERSPY Forecaster adds the AI factor model, P(up), Up/Down calls, // calibrated bands, alerts and a live scorecard: hypespy.ai // ═══════════════════════════════════════════════════════════════════════════════ indicator("HYPERSPY Forecaster Lite", shorttitle = "HSF Lite", overlay = true, max_lines_count = 10, max_labels_count = 80, max_polylines_count = 5) // ── Inputs ─────────────────────────────────────────────────────────────────── grpF = "Forecast" hzLen = input.int(10, "Horizon (trading days)", minval = 2, maxval = 30, group = grpF) coverage = input.int(80, "Outer range %", options = [68, 80, 90, 95], group = grpF) showIn = input.bool(true, "Inner 50% range", group = grpF) priorSR = input.float(0.5, "Long-run drift (annual Sharpe)", minval = -1.0, maxval = 1.5, step = 0.1, tooltip = "SPY's long-run Sharpe ratio is roughly 0.5. Use 0 for a driftless cone.", group = grpF) lockBar = input.bool(true, "Anchor on last closed bar (no repaint)", group = grpF) grpV = "Display" showDots = input.bool(true, "Daily forecast dots", group = grpV) showLines = input.bool(true, "Now / horizon lines", group = grpV) showPanel = input.bool(true, "Panel", group = grpV) tblPos = input.string("Bottom Right", "Panel position", options = ["Top Left", "Top Right", "Bottom Right", "Bottom Left"], group = grpV) grpC = "Colors" cFc = input.color(#F5A623, "Forecast", group = grpC) cUp = input.color(#26A69A, "Up", group = grpC) cDn = input.color(#EF5350, "Down / horizon", group = grpC) trOut = input.int(88, "Outer range transparency", minval = 50, maxval = 98, group = grpC) trIn = input.int(80, "Inner range transparency", minval = 40, maxval = 98, group = grpC) C_MUTED = #787B86 C_TEXT = #D1D4DC C_BG = #131722 C_EDGE = #2A2E39 f_pos(string s) => s == "Top Left" ? position.top_left : s == "Top Right" ? position.top_right : s == "Bottom Left" ? position.bottom_left : position.bottom_right f_pctTxt(float frac) => (frac >= 0 ? "+" : "") + str.tostring(100.0 * frac, "0.0") + "%" // Variance over tau steps with fast→slow mean reversion (ph per step) f_hVar(float f_, float s_, float tau, float ph) => math.max(tau * math.max(s_, 1e-12) + (f_ - s_) * (1 - math.pow(ph, tau)) / (1 - ph), 1e-12) // ── Daily volatility engine (runs on the regular-session daily series) ─────── f_vol() => int dps = timeframe.isweekly ? 5 * timeframe.multiplier : timeframe.ismonthly ? 21 * timeframe.multiplier : timeframe.isdaily ? timeframe.multiplier : 1 float lamF = math.pow(0.94, dps) float lamS = math.pow(0.995, dps) float r = nz(math.log(close / close[1])) var float vFn = 0.0 var float vFw = 0.0 var float vSn = 0.0 var float vSw = 0.0 var int n = 0 if bar_index > 0 vFn := lamF * vFn + (1 - lamF) * r * r vFw := lamF * vFw + (1 - lamF) vSn := lamS * vSn + (1 - lamS) * r * r vSw := lamS * vSw + (1 - lamS) n += 1 float vF = vFw > 0 ? vFn / vFw : na float vS = vSw > 0 ? vSn / vSw : na [vF, vS, n] // Lag 1 on intraday charts (yesterday's completed daily values, no repaint), lag 0 otherwise f_volOut(simple int lg) => [vF, vS, n] = f_vol() [vF[lg], vS[lg], n[lg]] bool intra = timeframe.isintraday int lagE = intra ? 1 : 0 string engTf = intra ? "D" : timeframe.period string dTk = ticker.new(syminfo.prefix, syminfo.ticker, session.regular) [vF, vS, nVol] = request.security(dTk, engTf, f_volOut(lagE), lookahead = barmerge.lookahead_on) // steps per trading day on the engine timeframe (1 on intraday/1D charts) int dpsC = timeframe.isweekly ? 5 * timeframe.multiplier : timeframe.ismonthly ? 21 * timeframe.multiplier : timeframe.isdaily ? timeframe.multiplier : 1 float phC = math.pow(0.97, dpsC) int hzS = math.max(1, int(math.round(hzLen * 1.0 / dpsC))) // horizon in engine steps float zOut = coverage == 68 ? 0.9945 : coverage == 80 ? 1.2816 : coverage == 90 ? 1.6449 : 1.96 float zIn = 0.6745 // ── Calendar (NYSE rules; crypto trades every day) ─────────────────────────── string TZ = syminfo.timezone bool isCrypto = syminfo.type == "crypto" bool usCal = not isCrypto and (syminfo.country == "US" or TZ == "America/New_York") f_easterKey(int Y) => int a = Y % 19 int b = int(math.floor(Y / 100.0)) int c = Y % 100 int d = int(math.floor(b / 4.0)) int e = b % 4 int f = int(math.floor((b + 8) / 25.0)) int g = int(math.floor((b - f + 1) / 3.0)) int h = (19 * a + b - d - g + 15) % 30 int i = int(math.floor(c / 4.0)) int k = c % 4 int l = (32 + 2 * e + 2 * i - h - k) % 7 int m = int(math.floor((a + 11 * h + 22 * l) / 451.0)) int mo = int(math.floor((h + l - 7 * m + 114) / 31.0)) int da = (h + l - 7 * m + 114) % 31 + 1 Y * 10000 + mo * 100 + da f_goodFridayKey(int Y) => int ek = f_easterKey(Y) int mo = int(math.floor((ek % 10000) / 100.0)) int da = ek % 100 - 2 if da < 1 mo := 3 da := 31 + da Y * 10000 + mo * 100 + da f_isUSHoliday(int t) => int Y = year(t, TZ) int mo = month(t, TZ) int d = dayofmonth(t, TZ) int dw = dayofweek(t, TZ) bool mon = dw == dayofweek.monday bool fri = dw == dayofweek.friday bool wkd = dw != dayofweek.saturday and dw != dayofweek.sunday bool h = (mo == 1 and d == 1) or (mo == 1 and d == 2 and mon) h := h or (mo == 1 and mon and d >= 15 and d <= 21) or (mo == 2 and mon and d >= 15 and d <= 21) h := h or Y * 10000 + mo * 100 + d == f_goodFridayKey(Y) h := h or (mo == 5 and mon and d >= 25) h := h or (Y >= 2022 and ((mo == 6 and d == 19) or (mo == 6 and d == 18 and fri) or (mo == 6 and d == 20 and mon))) h := h or (mo == 7 and d == 4) or (mo == 7 and d == 3 and fri) or (mo == 7 and d == 5 and mon) h := h or (mo == 9 and mon and d <= 7) h := h or (mo == 11 and dw == dayofweek.thursday and d >= 22 and d <= 28) h := h or (mo == 12 and d == 25) or (mo == 12 and d == 24 and fri) or (mo == 12 and d == 26 and mon) h and wkd f_isTradingDay(int t) => int dw = dayofweek(t, TZ) isCrypto ? true : (dw != dayofweek.saturday and dw != dayofweek.sunday and not (usCal and f_isUSHoliday(t))) // t shifted forward by n trading days, keeping hh:mm f_addTradingDays(int t, int n) => int hh = hour(t, TZ) int mm = minute(t, TZ) int noon = timestamp(TZ, year(t, TZ), month(t, TZ), dayofmonth(t, TZ), 12, 0, 0) int added = 0 int guard = 0 while added < n and guard < 120 noon += 86400000 guard += 1 if f_isTradingDay(noon) added += 1 timestamp(TZ, year(noon, TZ), month(noon, TZ), dayofmonth(noon, TZ), hh, mm, 0) // time of future step k (trading days on intraday/daily charts, bars on W/M) f_stepTime(int t0, int k) => timeframe.isweekly or timeframe.ismonthly ? t0 + k * timeframe.in_seconds() * 1000 : f_addTradingDays(t0, k * dpsC) f_fmt(int t) => str.format_time(t, "MMM dd", TZ) // ── Drawing ────────────────────────────────────────────────────────────────── var polyline outPoly = na var polyline inPoly = na var polyline medLine = na var label tgtLbl = na var label hiLbl = na var label loLbl = na var line nowLine = na var line endLine = na var label[] dots = array.new