Free · TradingView

One free indicator that puts paid signal services and courses to shame — a live trade HUD with a single alert, sent straight to your phone at exactly the right moment.

Open Legacy is a single free Pine Script v6 indicator that works on any market — forex, indices, metals, crypto, you name it. It calculates fixed take-profit and stop-loss levels the moment a signal fires, plots the matching BUY / SELL labels and SL / TP lines directly on price, keeps a heads-up display panel showing the live status of the current signal, and automatically detects the right inverse-correlation benchmark for whatever you're charting — no second script or second chart required. It fires alerts the moment a target is hit or a new signal appears.

Fixed TP / SL levels Live HUD status panel Auto-detected correlation confirmation One alert, straight to your phone
XAUUSD · 15m · one example of any market
2,436.00 2,429.00 2,422.00 2,415.00 2,408.00 2,401.00 BUY SELL TP 2,420.30 (+$6.20) SL 2,403.30 (-$10.80) Open Legacy BUY ACTIVE TP Hit Odds 64.2% Fixed Entry ($) 2,414.10 Take Profit ($) 2,420.30 Stop Loss ($) 2,403.30 DXY Structure Confirmed ✅
Free tool, real risk. Open Legacy is a free indicator for TradingView that works on any market, not a signal service or trading advice. All trading carries risk of loss. The HUD's "TP Hit Odds" figure is an internal heuristic built from price distance and correlation with the auto-detected benchmark — it is a reference estimate, not a guarantee of outcome.
Get set up

Three things to get right

Each card jumps to a full walkthrough further down the page.

01

How to Install

One script, one chart, any market. Open Legacy runs entirely on whichever chart you put it on — there's nothing else to add.

Open the chart for the market you want to trade

Forex, indices, metals, crypto — Open Legacy auto-detects what you're charting and adapts to it.

Open the Pine Editor

Click Pine Editor in the bottom panel of the chart window.

Clear the default template

Select all the placeholder code in the editor and delete it, leaving a blank file.

Paste the script

Copy the full source for Open Legacy from the Get the Scripts section below and paste it in.

Click "Add to Chart"

The indicator compiles and appears on your chart — BUY/SELL labels, TP/SL lines, and the HUD panel all show up together, in one shot.

HUD not staying visible? If the HUD table gets tucked behind chart drawings or other indicators, right-click Open Legacy's name in the top-left of the chart, choose Visual Order, then Bring to Front.
02

Key Settings

Everything — the plotted labels, the TP/SL lines, and the HUD table — comes from one script now, so there's no more matching settings across scripts. Here's what each input group actually controls.

Input
Default
What it controls
UT Key Value
1.0
The ATR multiplier behind the trailing stop that drives every BUY/SELL flip and updates the HUD table in the same instant.
UT ATR Period
9
The volatility lookback behind that same trailing stop.
Fast / Slow EMA Length
9 / 21
Used to detect pullbacks and gate whether scale-in trades 2–3 are allowed to fire.
Scale-In Toggles
Trade 2/3, both on
Controls how many additional BUY 2/3 or SELL 2/3 labels the script will add onto an existing trend.
Smart Correlation Engine
On, auto-detect, EMA 9
Feeds the "[Benchmark] Bias" and "[Benchmark] Confirm" rows into the HUD table, and gates whether scale-in trades 2–3 are allowed to fire. Leave "Manual Benchmark Override" off to let it auto-detect the right benchmark for your chart, or switch it on to pin a specific symbol yourself.
Auto-Adapt Risk to Asset Scale
On
When on, TP/SL bounds scale automatically as a percentage of price — roughly 0.08–0.25% for TP, 0.18–0.45% for SL — so the same script works whether you're on a low-priced forex pair or a five-figure crypto chart. Turn it off to set fixed manual TP/SL bounds instead.
Fixed Risk Engine (TP / SL multipliers)
1.3x / 2.2x ATR
Calculated once the instant a signal fires, then locked in place — see below.
Targets lock in and stay put. The moment a signal fires, Open Legacy calculates TP and SL once and locks them — they won't silently shift if volatility changes mid-trade. And because one script generates the plotted labels, the TP/SL lines, and the HUD numbers together, they can't fall out of sync the way separate scripts could.
Critical

Wait for Candle Close

A BUY or SELL label showing up on your chart isn't the moment to act — the candle it appeared on has to fully close first.

Labels on the still-forming candle can vanish just as fast as they appeared. Open Legacy checks for a trailing-stop crossover on every price tick, including the current, unfinished candle. If price crosses one way and then reverses before that candle closes, the label disappears — that's a fakeout, not a real signal. Enter on it and you're trading a setup that may not even exist a few seconds later.

See a label appear? Don't act yet

Note it, but treat it as provisional — it's still live and can be erased by the next tick.

Wait for that candle to close

Once the candle the label appeared on finishes and the next candle opens, the signal is locked in — no more repainting.

Still there after close? Now it's solidified

That's the moment to consider entering — a confirmed signal, not a mid-candle flicker.

Guidance

Reading the TP Hit Odds

The HUD's "TP Hit Odds" figure is a heuristic, not a guarantee — but it's still useful as a filter for when to stay cautious. A simple rule of thumb:

Below 60% before entry

If a signal is active but the HUD's TP Hit Odds reads below 60%, treat that as a caution flag. Consider holding off on entering that trade rather than taking every signal automatically.

40–50% while in a trade

If you're already in a trade and the odds drift down into the 40–50% range or lower, consider closing the position early rather than waiting for SL to be hit.

These are thresholds for caution, not rules for certainty. The odds figure is built from price distance to target and correlation with the auto-detected benchmark — it can shift quickly on volatile candles or when that benchmark flips. Use it as one input among your own risk management, not a substitute for it. All trading carries risk of loss regardless of what the HUD reads.
Advanced

The Correlation Confirmation Secret

A second, independent read on strength — checking your instrument against the benchmark that naturally moves opposite it — is what separates a confirmed trade from a rushed one. Open Legacy figures out the right benchmark for whatever you're charting automatically, and surfaces it right in its own HUD table — no second chart, no second script.

Buddha heart · demon hands — reading both sides
BUDDHA HEART Before the trade — Ready DEMON HANDS Once live — Confirmed / Invalid ANY MARKET · READ BOTH SIDES BEFORE YOU MOVE

Nothing to add — it's already built in

Open Legacy's HUD table includes a "[Benchmark] Structure" row automatically, right on whichever chart you put it on. No second chart, no separate script — and no manual symbol picking, unless you want to override it yourself.

Before a trade — the Buddha heart

While you're waiting for a signal, the Structure row simply reads Ready ⏳ — calm, steady, no verdict yet. Nothing to act on, just watching.

Once a trade is live — the demon hands

The instant a BUY or SELL becomes active, that same row flips decisively: Confirmed ✅ means correlation backs your trade — your green light. Invalid ❌ means the trend is running against the expected correlation — a caution flag. You may also see CLOSED 💤 — that shows up when the underlying market is shut, or (for crypto) during the scheduled window when forex markets — and DXY along with them — are offline. Open Legacy bypasses the correlation check automatically during that window rather than penalizing a trade for a benchmark that isn't trading.

Only treat a signal as fully confirmed on Confirmed ✅

When Open Legacy fires a BUY or SELL, check the Structure row before acting. Invalid doesn't mean don't trade — it means treat the signal with extra caution.

How the benchmark gets picked

What you're charting
Auto-matched benchmark
Why
USD pairs & metals (EURUSD, XAUUSD, BTCUSD...)
DXY
The Dollar Index is the standard inverse read against anything priced in USD.
Major indices (S&P 500, NAS100, DAX, UK100...)
VIX
The volatility index tends to spike when equities sell off — a natural inverse gauge.
Indian equities (NSE, BSE)
India VIX
Same volatility-index logic, using the regional benchmark instead.
USDJPY / USDCAD / USDCHF
Yen / CAD / CHF Index
For USD-base pairs, the quote currency's own strength index is the more precise read.
Cross pairs quoted in JPY, GBP, or EUR
Yen / Pound / Euro Index
Matches the benchmark to whichever currency the pair is actually quoted in.
No separate alerts for this one. The Structure row is meant to be read visually in the HUD table — it's not part of the alert message. The single alert covered in Setting Up Alerts below already tells you exactly when to go look.

"To flow in the markets you must have a Buddha heart and demon hands. The Buddha heart is the calm, steady wait for what plausibly is right. The demon hands are the decisive verdict the moment a trade goes live, stating where we are planning and already operating our next move — bullish or bearish."

Calm patience, decisive execution — the same row tells you both.

Session Timing · XAUUSD example

Best Times to Trade — a Gold Example

Signals mean more when they fire during hours where your market typically sees real liquidity and follow-through. Since Open Legacy now works across markets, exact windows vary by instrument — here's a concrete example using XAUUSD's two prime windows, both in Irish Standard Time (IST). Trading something else? Look up your own market's peak-liquidity hours; the same avoid-the-dead-zones principle applies everywhere.

XAUUSD session timeline — IST
XAUUSD · PRIME TRADING WINDOWS (IST) 6 7 8 9 10 11 12 13 14 15 16 17 18 07:00 – 11:30 13:00 – 16:00 12:00–13:00 Prime window — highest liquidity Midday lull — avoid (next section)

07:00 – 11:30 IST

The morning window. London liquidity is online and price action tends to be cleaner and more directional.

13:00 – 16:00 IST

The afternoon window. London and New York overlap here, which is typically where gold sees its strongest follow-through.

Stay inside these windows. Signals firing outside them are more likely to be low-liquidity noise, even if Open Legacy's HUD reads confidently. Treat the two windows above as default trading hours if you're on XAUUSD — for other markets, swap in that market's own high-liquidity hours instead.
Session Timing · XAUUSD example

Avoid the Midday Lull

Between the two prime XAUUSD windows above sits a gap worth respecting on purpose. (Other markets have their own version of this dead zone — check yours.)

12:00 – 13:00 IST — the midday lull, also known as "coffee hour." Volume thins out as the London morning session winds down and New York hasn't opened yet. Price action here tends to chop sideways rather than trend, which can produce signals that look valid but fade quickly. Sit this hour out and wait for the 13:00 IST window instead.
Risk Guidance

News Awareness

Every market is sensitive to its own macro surprises. Open Legacy isn't designed to trade through that kind of volatility, whatever you're charting.

Avoid trading around medium or high impact news events. Check an economic calendar for scheduled releases relevant to your market — CPI, NFP, and Fed decisions for USD pairs and gold, ECB decisions for EUR pairs, earnings and central bank guidance for indices, and so on — and stay out of the market from 30 minutes before to 30 minutes after any medium or high impact release, not just during the release itself. Spreads widen and price can spike through both TP and SL levels in seconds, which the HUD's odds engine isn't built to anticipate.
03

Setting Up Alerts

Alerts are configured on Open Legacy with a single alert. That one alert covers all four events — new buy trade, new sell trade, TP hit, and SL hit — automatically, so there's nothing to split into multiple alerts anymore.

Right-click the chart and choose "Add Alert"

Or click the alarm clock icon in the right-hand toolbar.

Set the condition to Open Legacy

In the "Condition" dropdown, select the Open Legacy indicator — it's the only one on your chart now.

Choose "Any alert() function call"

Open Legacy now fires everything through one built-in alert() call, so this is the only option you need — no more picking between separate conditions.

Set your preferred interval

Pick whichever timeframe matches what you're trading. If you're not sure, 10 minutes is a solid default — frequent enough to catch signals early without flooding you with noise.

Name it and customize the message if you like

Give the alert any name you want — e.g. "Open Legacy Alert". The actual notification content (entry, TP, SL, or a hit confirmation) is generated automatically by the script, so the message field is just for your own reference.

Connect a notification channel

Turn on app push, email, SMS, or webhook delivery in the "Notifications" tab of the alert dialog, then click Create.

One alert does it all. Open Legacy's alert() call sends a different, detailed message depending on what just happened — new buy trade, new sell trade, TP hit, or SL hit — all four covered by the one alert you set up. Nothing else to configure.

All four events, one alert

BUY

New Buy Trade — a new BUY becomes active, with entry, TP, and SL included.

"🚀 {{ticker}} New BUY Active! 📌 Entry: ... 🎯 TP: ... 🛡️ SL: ..."

SELL

New Sell Trade — a new SELL becomes active, with entry, TP, and SL included.

"🚀 {{ticker}} New SELL Active! 📌 Entry: ... 🎯 TP: ... 🛡️ SL: ..."

TP

Take Profit Hit — price reaches the locked TP level.

"🎯 {{ticker}} Take Profit Hit! ✨ Reached Target (+X pts)"

SL

Stop Loss Hit — price reaches the locked SL level.

"🛑 {{ticker}} Stop Loss Hit! ⚠️ Breached (-X pts)"

Copy & paste

Get the Scripts

Full Pine Script v6 source for both. Copy each into its own Pine Editor tab as described in How to Install.

Open Legacy Any market
//@version=6
indicator("Open Legacy", overlay = true)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ⚙️ INPUTS & CONTROLS
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

replayMode      = input.bool(true, "Bar Replay / Backtest Mode", group = "Mode Controls", tooltip = "Disables real-time session closure checks so HUD functions during Bar Replay")

keyValue        = input.float(1.0, "UT Key Value", minval = 0.1, step = 0.1, group = "UT Bot Core")
atrPeriod       = input.int(9, "UT ATR Period", minval = 1, group = "UT Bot Core")

enableTrade2    = input.bool(true, "Enable Trade 2 Signal", group = "Scale-In Toggles")
enableTrade3    = input.bool(true, "Enable Trade 3 Signal", group = "Scale-In Toggles")

minBarsDelay2   = input.int(3, "Min Bars: Trade 1 ➔ Trade 2", minval = 1, group = "Scale-In Timing")
minBarsDelay3   = input.int(3, "Min Bars: Trade 2 ➔ Trade 3", minval = 1, group = "Scale-In Timing")

fastEmaLen      = input.int(9, "Fast EMA Length", minval = 1, group = "Trend Filters")
slowEmaLen      = input.int(21, "Slow EMA Length", minval = 2, group = "Trend Filters")

useCorrFilter   = input.bool(true, "Require Correct Correlation", group = "Smart Correlation Engine")
overrideBench   = input.bool(false, "Enable Manual Benchmark Override", group = "Smart Correlation Engine")
manualSymbol    = input.symbol("TVC:DXY", "Manual Benchmark Symbol", group = "Smart Correlation Engine")
manualInverse   = input.bool(true, "Manual Benchmark Is Inverse Correlation", group = "Smart Correlation Engine", tooltip = "ON: benchmark moving toward asset's SL direction = Invalid. OFF: benchmark moving toward asset's SL direction (same direction as asset) = Invalid.")
corrEmaLen      = input.int(9, "Benchmark EMA Length", minval = 1, group = "Smart Correlation Engine")
corrConfBars    = input.int(3, "Benchmark Confirmation Bars", minval = 1, tooltip="Bars required to sustain cross before validating structure", group = "Smart Correlation Engine")

gracePeriodBars = input.int(6, "Structure Grace Period (Bars)", minval = 1, tooltip = "Brief misalignment within this window does not penalize the score or flip the HUD label", group = "Scoring Persistence Engine")
maxRampBars     = input.int(12, "Bars To Reach Max Bonus / Penalty", minval = 1, tooltip = "How long sustained alignment/misalignment must persist to reach full bonus/penalty", group = "Scoring Persistence Engine")

autoScaleRisk   = input.bool(true, "Auto-Adapt TP/SL Bounds to Asset Scale", group = "Fixed Dynamic Risk Engine")
manualMinTp     = input.float(3.0, "Manual Min TP Target", minval = 0.1, group = "Fixed Dynamic Risk Engine")
manualMaxTp     = input.float(8.0, "Manual Max TP Target", minval = 0.1, group = "Fixed Dynamic Risk Engine")
manualMinSl     = input.float(8.0, "Manual Min SL Target", minval = 0.1, group = "Fixed Dynamic Risk Engine")
manualMaxSl     = input.float(15.0, "Manual Max SL Target", minval = 0.1, group = "Fixed Dynamic Risk Engine")

tpAtrMult       = input.float(1.3, "TP ATR Multiplier", minval = 0.5, step = 0.1, group = "Fixed Dynamic Risk Engine")
slAtrMult       = input.float(2.2, "SL ATR Multiplier", minval = 0.5, step = 0.1, group = "Fixed Dynamic Risk Engine")
riskRewardRatio = input.float(1.0, "Risk-to-Reward Ratio", minval = 0.1, group = "Fixed Dynamic Risk Engine")

buyBgColor      = input.color(#4CAF50, "BUY Background Color", group = "Visual Customization")
buyTextColor    = input.color(color.white, "BUY Text Color", group = "Visual Customization")
sellBgColor     = input.color(#FF5252, "SELL Background Color", group = "Visual Customization")
sellTextColor   = input.color(color.white, "SELL Text Color", group = "Visual Customization")
hudPos          = input.string("Bottom Right", "HUD Screen Position", options=["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group = "Visual Customization")

getTablePosition(string pos) =>
    switch pos
        "Top Left"     => position.top_left
        "Top Right"    => position.top_right
        "Bottom Left"  => position.bottom_left
        "Bottom Right" => position.bottom_right
        => position.bottom_right

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🌐 UNIVERSAL HANDS-FREE BENCHMARK ROUTER
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Returns [benchmarkSymbol, isInverseRelationship]
// isInverseRelationship = true  -> benchmark moving UP pushes asset toward SL if asset is a BUY
//                                  (i.e. benchmark should move OPPOSITE the asset to favor TP)
// isInverseRelationship = false -> benchmark should move WITH the asset to favor TP
getUniversalInverseBenchmark() =>
    string p     = syminfo.prefix
    string t     = str.upper(syminfo.ticker)
    string base  = syminfo.basecurrency
    string quote = syminfo.currency

    string symbol  = "TVC:DXY"
    bool   inverse = true

    if t == "USDX" or t == "DXY" or t == "USDOLLAR" or t == "DX"
        symbol := "TVC:GOLD"
        inverse := true
    else if p == "NSE" or p == "BSE" or quote == "INR"
        symbol := "NSE:INDIAVIX"
        inverse := true
    else if str.contains(t, "SPX") or str.contains(t, "US500") or str.contains(t, "SP500") or str.contains(t, "NDX") or str.contains(t, "US100") or str.contains(t, "NAS100") or str.contains(t, "US30") or str.contains(t, "DJI") or str.contains(t, "GER") or str.contains(t, "DAX") or str.contains(t, "UK100") or str.contains(t, "EU50") or p == "NASDAQ" or p == "NYSE"
        symbol := "TVC:VIX"
        inverse := true
    else if base == "USD"
        if quote == "JPY"
            symbol := "TVC:JXY"
            inverse := true
        else if quote == "CAD"
            symbol := "TVC:CXY"
            inverse := true
        else if quote == "CHF"
            symbol := "TVC:SXY"
            inverse := true
        else
            // USD is the BASE here (USDSEK, USDMXN, USDZAR, USDTRY, USDHKD, etc.)
            // DXY moves WITH these pairs, not against them — DIRECT correlation
            symbol := "TVC:DXY"
            inverse := false
    else if quote == "USD"
        symbol := "TVC:DXY"
        inverse := true
    else if quote == "JPY"
        symbol := "TVC:JXY"
        inverse := true
    else if quote == "GBP"
        symbol := "TVC:BXY"
        inverse := true
    else if quote == "EUR"
        symbol := "TVC:EXY"
        inverse := true

    [symbol, inverse]

getBenchmarkDisplayName(string symbol) =>
    switch symbol
        "NSE:INDIAVIX" => "India VIX"
        "TVC:VIX"      => "VIX"
        "TVC:DXY"      => "DXY"
        "TVC:GOLD"     => "Gold"
        "TVC:JXY"      => "Yen Index"
        "TVC:BXY"      => "Pound Index"
        "TVC:EXY"      => "Euro Index"
        "TVC:CXY"      => "CAD Index"
        "TVC:SXY"      => "CHF Index"
        => symbol

[routedBenchSymbol, routedBenchInverse] = getUniversalInverseBenchmark()

string activeBenchSymbol  = overrideBench ? manualSymbol : routedBenchSymbol
bool   activeBenchInverse = overrideBench ? manualInverse : routedBenchInverse
string activeBenchName    = getBenchmarkDisplayName(activeBenchSymbol)

float autoMinTp = close * 0.0008
float autoMaxTp = close * 0.0025
float autoMinSl = close * 0.0018
float autoMaxSl = close * 0.0045

float minTpPoints  = autoScaleRisk ? autoMinTp : manualMinTp
float maxTpPoints  = autoScaleRisk ? autoMaxTp : manualMaxTp
float minSlPoints  = autoScaleRisk ? autoMinSl : manualMinSl
float maxSlPoints  = autoScaleRisk ? autoMaxSl : manualMaxSl
float minScaleDist = autoScaleRisk ? (close * 0.0005) : 2.0

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🌙 CRYPTO DXY SCHEDULE & STANDARD STALL DETECTOR
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
bool isCrypto = syminfo.type == "crypto" or str.contains(syminfo.ticker, "BTC") or str.contains(syminfo.ticker, "ETH")

int istTime = time + 19800000
int dayOfWeekIST = dayofweek(istTime, timezone = "UTC")
int hourIST      = hour(istTime, timezone = "UTC")

bool isCryptoWeekdayOffline = (dayOfWeekIST >= 2 and dayOfWeekIST <= 6) and (hourIST >= 22 and hourIST < 23)
bool isCryptoWeekendOffline = (dayOfWeekIST == 6 and hourIST >= 22) or (dayOfWeekIST == 7) or (dayOfWeekIST == 1 and hourIST < 23)
bool isCryptoDxyOffline     = not replayMode and isCrypto and (isCryptoWeekdayOffline or isCryptoWeekendOffline)

bool isMarketClosedRT = not replayMode and not isCrypto and barstate.isrealtime and barstate.islast and (timenow > time_close + 900000)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 📊 CORE ENGINE & SUSTAINED BENCHMARK LOGIC
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

src           = close
atrVal        = ta.atr(atrPeriod)
atrAvg        = ta.sma(atrVal, 30)
atrLoss       = keyValue * atrVal

var float utStop = na
prevStop = nz(utStop[1], src)

if src > prevStop and src[1] > prevStop
    utStop := math.max(prevStop, src - atrLoss)
else if src < prevStop and src[1] < prevStop
    utStop := math.min(prevStop, src + atrLoss)
else
    utStop := src > prevStop ? src - atrLoss : src + atrLoss

signalEma = ta.ema(src, 1)
utBuy  = ta.crossover(signalEma, utStop) and src > utStop
utSell = ta.crossover(utStop, signalEma) and src < utStop

fastEma = ta.ema(close, fastEmaLen)
slowEma = ta.ema(close, slowEmaLen)

rawBenchClose = request.security(activeBenchSymbol, timeframe.period, close, barmerge.gaps_off, barmerge.lookahead_off)
rawBenchEma   = request.security(activeBenchSymbol, timeframe.period, ta.ema(close, corrEmaLen), barmerge.gaps_off, barmerge.lookahead_off)

benchClose = nz(rawBenchClose, close)
benchEma   = nz(rawBenchEma, ta.ema(close, corrEmaLen))

bool rawBenchBullish = benchClose > benchEma
bool rawBenchBearish = benchClose < benchEma
bool benchBullish = ta.lowest(rawBenchBullish ? 1 : 0, corrConfBars) == 1
bool benchBearish = ta.lowest(rawBenchBearish ? 1 : 0, corrConfBars) == 1

// benchFavorsBuy/Sell = benchmark is moving in the direction that supports price
// heading toward TP (not SL) for the current trade direction.
bool benchFavorsBuy  = activeBenchInverse ? benchBearish : benchBullish
bool benchFavorsSell = activeBenchInverse ? benchBullish : benchBearish

bool corrBuyConfirm  = not useCorrFilter or isCryptoDxyOffline or benchFavorsBuy
bool corrSellConfirm = not useCorrFilter or isCryptoDxyOffline or benchFavorsSell

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🧠 STATE MACHINE & MULTI-TRADE SCALE-IN LOGIC
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

var int activeTrend = 0
var int barTrade1   = na
var int barTrade2   = na
var int barTrade3   = na

var float priceTrade1 = na
var float priceTrade2 = na
var float priceTrade3 = na

var bool trade2Sent = false
var bool trade3Sent = false
var bool sawPullback2 = false
var bool sawPullback3 = false

if utBuy
    activeTrend   := 1
    barTrade1     := bar_index
    priceTrade1   := close
    barTrade2     := na
    barTrade3     := na
    trade2Sent    := false
    trade3Sent    := false
    sawPullback2  := false
    sawPullback3  := false

else if utSell
    activeTrend   := -1
    barTrade1     := bar_index
    priceTrade1   := close
    barTrade2     := na
    barTrade3     := na
    trade2Sent    := false
    trade3Sent    := false
    sawPullback2  := false
    sawPullback3  := false

if activeTrend == 1
    if not trade2Sent and (close < open or low <= fastEma)
        sawPullback2 := true
    if trade2Sent and not trade3Sent and (close < open or low <= fastEma)
        sawPullback3 := true

if activeTrend == -1
    if not trade2Sent and (close > open or high >= fastEma)
        sawPullback2 := true
    if trade2Sent and not trade3Sent and (close > open or high >= fastEma)
        sawPullback3 := true

readyTrade2 = not na(barTrade1) and (bar_index - barTrade1 >= minBarsDelay2)
readyTrade3 = not na(barTrade2) and (bar_index - barTrade2 >= minBarsDelay3)

distFromTrade1 = na(priceTrade1) ? 9999.0 : math.abs(close - priceTrade1)
distFromTrade2 = na(priceTrade2) ? 9999.0 : math.abs(close - priceTrade2)

buyTrigger2  = (sawPullback2 or bar_index - barTrade1 >= minBarsDelay2 + 2) and close > open and close > fastEma and distFromTrade1 >= minScaleDist and corrBuyConfirm
sellTrigger2 = (sawPullback2 or bar_index - barTrade1 >= minBarsDelay2 + 2) and close < open and close < fastEma and distFromTrade1 >= minScaleDist and corrSellConfirm

buyTrigger3  = (sawPullback3 or bar_index - barTrade2 >= minBarsDelay3 + 2) and close > open and close > fastEma and distFromTrade2 >= minScaleDist and corrBuyConfirm
sellTrigger3 = (sawPullback3 or bar_index - barTrade2 >= minBarsDelay3 + 2) and close < open and close < fastEma and distFromTrade2 >= minScaleDist and corrSellConfirm

buy1  = utBuy
sell1 = utSell

buy2Cond  = activeTrend == 1 and readyTrade2 and not trade2Sent and buyTrigger2
sell2Cond = activeTrend == -1 and readyTrade2 and not trade2Sent and sellTrigger2

if buy2Cond or sell2Cond
    trade2Sent  := true
    barTrade2   := bar_index
    priceTrade2 := close

buy2  = enableTrade2 and buy2Cond
sell2 = enableTrade2 and sell2Cond

buy3Cond  = activeTrend == 1 and trade2Sent and readyTrade3 and not trade3Sent and buyTrigger3
sell3Cond = activeTrend == -1 and trade2Sent and readyTrade3 and not trade3Sent and sellTrigger3

if buy3Cond or sell3Cond
    trade3Sent  := true
    barTrade3   := bar_index
    priceTrade3 := close

buy3  = enableTrade3 and buy3Cond
sell3 = enableTrade3 and sell3Cond

newBuySignal  = buy1 or buy2 or buy3
newSellSignal = sell1 or sell2 or sell3

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🔒 MULTI-TIER PERSISTENT FIXED RISK ENGINE
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

var bool   tradeActive     = false
var string activeSignalTag = "WAITING"
var float  lockedEntry     = na
var float  lockedTpPrice   = na
var float  lockedSlPrice   = na
var bool   activeIsBuy     = false
var string lastTpText      = "---"
var string lastSlText      = "---"

var int benchConfirmStreak = 0
var int benchInvalidStreak = 0
var int trendConfirmStreak = 0
var int trendInvalidStreak = 0

var line  lineSL  = na
var line  lineTP  = na
var label labelSL = na
var label labelTP = na

string masterAlertMsg = ""

if (newBuySignal or newSellSignal) and (barstate.isconfirmed or replayMode)
    activeIsBuy := newBuySignal

    volatilityRatio = atrVal / atrAvg
    rawTpDist       = atrVal * tpAtrMult * volatilityRatio
    rawSlDist       = atrVal * slAtrMult

    finalTpDist = math.min(math.max(rawTpDist, minTpPoints), maxTpPoints)
    finalSlDist = math.min(math.max(rawSlDist, minSlPoints), maxSlPoints)

    lockedEntry   := close
    lockedTpPrice := activeIsBuy ? close + finalTpDist : close - finalTpDist
    lockedSlPrice := activeIsBuy ? close - finalSlDist : close + finalSlDist

    string tag = buy1 ? "BUY" : (buy2 ? "BUY 2" : (buy3 ? "BUY 3" : (sell1 ? "SELL" : (sell2 ? "SELL 2" : (sell3 ? "SELL 3" : "")))))
    activeSignalTag := tag + " ACTIVE"
    tradeActive     := true

    lastTpText      := "---"
    lastSlText      := "---"

    benchConfirmStreak := 0
    benchInvalidStreak := 0
    trendConfirmStreak := 0
    trendInvalidStreak := 0

    masterAlertMsg  := "🚀 " + syminfo.ticker + " New " + tag + " Active!\n📌 Entry: " + str.tostring(lockedEntry, "#.##") + "\n🎯 TP: " + str.tostring(lockedTpPrice, "#.##") + "\n🛡️ SL: " + str.tostring(lockedSlPrice, "#.##")

    if not na(lineSL)
        line.delete(lineSL)
    if not na(lineTP)
        line.delete(lineTP)
    if not na(labelSL)
        label.delete(labelSL)
    if not na(labelTP)
        label.delete(labelTP)

    int endX = bar_index + 12
    lineSL  := line.new(bar_index, lockedSlPrice, endX, lockedSlPrice, color = color.red, width = 2, style = line.style_dashed)
    labelSL := label.new(endX, lockedSlPrice, tag + " SL: " + str.tostring(lockedSlPrice, "#.##"), style = label.style_label_left, color = color.red, textcolor = color.white, size = size.tiny)

    lineTP  := line.new(bar_index, lockedTpPrice, endX, lockedTpPrice, color = color.green, width = 2, style = line.style_dashed)
    labelTP := label.new(endX, lockedTpPrice, tag + " TP: " + str.tostring(lockedTpPrice, "#.##"), style = label.style_label_left, color = color.green, textcolor = color.white, size = size.tiny)

if tradeActive and not na(lockedTpPrice) and not na(lockedSlPrice)
    bool tpHit = activeIsBuy ? (high >= lockedTpPrice) : (low <= lockedTpPrice)
    bool slHit = activeIsBuy ? (low <= lockedSlPrice)  : (high >= lockedSlPrice)

    if tpHit or slHit
        if tpHit
            float tpDistance = math.abs(lockedTpPrice - lockedEntry)
            activeSignalTag := "TP HIT 🎉"
            lastTpText      := "Hit (+ " + str.tostring(tpDistance, "#.##") + ")"
            masterAlertMsg  := "🎯 " + syminfo.ticker + " Take Profit Hit!\n✨ Reached Target (+ " + str.tostring(tpDistance, "#.##") + " pts)"
        else if slHit
            float slDistance = math.abs(lockedSlPrice - lockedEntry)
            activeSignalTag := "SL HIT ❌"
            lastSlText      := "Hit (- " + str.tostring(slDistance, "#.##") + ")"
            masterAlertMsg  := "🛑 " + syminfo.ticker + " Stop Loss Hit!\n⚠️ Breached (- " + str.tostring(slDistance, "#.##") + " pts)"

        tradeActive := false
        if not na(lineSL)
            line.delete(lineSL)
            lineSL := na
        if not na(lineTP)
            line.delete(lineTP)
            lineTP := na
        if not na(labelSL)
            label.delete(labelSL)
            labelSL := na
        if not na(labelTP)
            label.delete(labelTP)
            labelTP := na

if masterAlertMsg != ""
    alert(masterAlertMsg, alert.freq_once_per_bar)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🧠 UPGRADED PROBABILITY & BENCHMARK SCORING ENGINE
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

bool isCorrelationConfirmedNow = (activeIsBuy and benchFavorsBuy) or (not activeIsBuy and benchFavorsSell)
bool isTrendAlignedNow         = (activeIsBuy and close > slowEma) or (not activeIsBuy and close < slowEma)

if tradeActive
    if isCorrelationConfirmedNow
        benchConfirmStreak := benchConfirmStreak + 1
        benchInvalidStreak := 0
    else
        benchInvalidStreak := benchInvalidStreak + 1
        benchConfirmStreak := 0

    if isTrendAlignedNow
        trendConfirmStreak := trendConfirmStreak + 1
        trendInvalidStreak := 0
    else
        trendInvalidStreak := trendInvalidStreak + 1
        trendConfirmStreak := 0

f_persistBonus(confirmStreak, invalidStreak, maxBonus, grace, rampBars) =>
    float bonus = 0.0
    if confirmStreak > 0
        float rampProgress = math.min(confirmStreak, rampBars) / rampBars
        bonus := (maxBonus * 0.5) + (maxBonus * 0.5) * rampProgress
    else if invalidStreak > grace
        float decayBars = invalidStreak - grace
        bonus := -math.min(maxBonus, maxBonus * (decayBars / rampBars))
    bonus

float rawBenchBonus = isCryptoDxyOffline ? 0.0 : f_persistBonus(benchConfirmStreak, benchInvalidStreak, 10.0, gracePeriodBars, maxRampBars)
float rawTrendBonus = f_persistBonus(trendConfirmStreak, trendInvalidStreak, 6.0, gracePeriodBars, maxRampBars)

float smoothTrendBonus = ta.sma(rawTrendBonus, 5)
float smoothBenchBonus = ta.sma(rawBenchBonus, 5)

float currentProgress = tradeActive and not na(lockedTpPrice) and not na(lockedSlPrice) ? (activeIsBuy ? ((close - lockedSlPrice) / (lockedTpPrice - lockedSlPrice)) : ((lockedSlPrice - close) / (lockedSlPrice - lockedTpPrice))) : 0.0
float smoothedProg    = ta.sma(currentProgress, 2)

float tpOdds = na
if activeSignalTag == "TP HIT 🎉"
    tpOdds := 100.0
else if activeSignalTag == "SL HIT ❌"
    tpOdds := 0.0
else if tradeActive and not na(lockedTpPrice) and not na(lockedSlPrice)
    float distOdds = math.min(100.0, math.max(0.0, smoothedProg * 100.0))
    tpOdds := math.min(99.0, math.max(1.0, (distOdds * 0.80) + 10.0 + smoothTrendBonus + smoothBenchBonus))

// HUD label uses the same streaks as the odds score — only flips to Invalid once
// the benchmark has pushed toward SL for longer than the grace period, and flips
// back to Confirmed as soon as it favors TP again.
bool isCorrelationConfirmedForLabel = benchConfirmStreak > 0 or benchInvalidStreak <= gracePeriodBars

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 📱 DYNAMIC HUD TABLE RENDERER
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

var table hud = table.new(position = getTablePosition(hudPos), columns = 2, rows = 7, bgcolor = color.rgb(18, 18, 24, 10), border_color = color.rgb(40, 40, 50), border_width = 1)

if barstate.islast or replayMode
    color headerBg = color.rgb(25, 30, 45)
    color rowBg1   = color.rgb(20, 20, 28)
    color rowBg2   = color.rgb(14, 14, 20)
    color baseBg   = color.rgb(8, 8, 12)

    bool hasHitTargetOrStop = (activeSignalTag == "TP HIT 🎉" or activeSignalTag == "SL HIT ❌")

    string topHeaderStatus = isMarketClosedRT ? "CLOSED 💤" : (hasHitTargetOrStop ? "Waiting ⏳" : activeSignalTag)
    color topHeaderColor   = isMarketClosedRT ? color.rgb(150, 150, 160) : (hasHitTargetOrStop ? color.rgb(241, 196, 15) : (str.contains(activeSignalTag, "BUY") ? color.rgb(46, 204, 113) : color.rgb(231, 76, 60)))

    string correlStatus = isCryptoDxyOffline ? "CLOSED 💤" : (isMarketClosedRT ? "CLOSED 💤" : (activeSignalTag == "WAITING" ? "Ready ⏳" : (isCorrelationConfirmedForLabel ? "Confirmed ✅" : "Invalid ❌")))

    table.cell(hud, 0, 0, "Open Legacy (" + syminfo.ticker + ")", text_color=color.rgb(220, 225, 235), bgcolor=headerBg, text_size=size.small)
    table.cell(hud, 1, 0, topHeaderStatus, text_color=topHeaderColor, bgcolor=headerBg, text_size=size.small)

    string statusOddsVal  = isMarketClosedRT ? "Market Closed 💤" : (hasHitTargetOrStop ? "Waiting ⏳" : (activeSignalTag == "WAITING" ? "Waiting..." : str.tostring(tpOdds, "#.##") + "%"))
    string fixedEntryVal  = hasHitTargetOrStop ? "Waiting ⏳" : (na(lockedEntry) ? "Waiting..." : str.tostring(lockedEntry, "#.##"))
    string takeProfitVal  = hasHitTargetOrStop ? "Waiting ⏳" : (lastTpText != "---" ? lastTpText : (na(lockedTpPrice) ? "Waiting..." : str.tostring(lockedTpPrice, "#.##")))
    string stopLossVal    = hasHitTargetOrStop ? "Waiting ⏳" : (lastSlText != "---" ? lastSlText : (na(lockedSlPrice) ? "Waiting..." : str.tostring(lockedSlPrice, "#.##")))
    string benchStructVal = correlStatus

    table.cell(hud, 0, 1, "🎯 Status / Odds", text_color=color.rgb(180, 185, 195), bgcolor=rowBg1, text_size=size.small)
    table.cell(hud, 1, 1, statusOddsVal, text_color=isMarketClosedRT ? color.rgb(150, 150, 160) : (hasHitTargetOrStop ? color.rgb(241, 196, 15) : color.rgb(241, 196, 15)), bgcolor=rowBg1, text_size=size.small)

    table.cell(hud, 0, 2, "📌 Fixed Entry", text_color=color.rgb(180, 185, 195), bgcolor=rowBg2, text_size=size.small)
    table.cell(hud, 1, 2, fixedEntryVal, text_color=color.white, bgcolor=rowBg2, text_size=size.small)

    table.cell(hud, 0, 3, "🚀 Take Profit", text_color=color.rgb(180, 185, 195), bgcolor=rowBg1, text_size=size.small)
    table.cell(hud, 1, 3, takeProfitVal, text_color=color.rgb(46, 204, 113), bgcolor=rowBg1, text_size=size.small)

    table.cell(hud, 0, 4, "🛡️ Stop Loss", text_color=color.rgb(180, 185, 195), bgcolor=rowBg2, text_size=size.small)
    table.cell(hud, 1, 4, stopLossVal, text_color=color.rgb(231, 76, 60), bgcolor=rowBg2, text_size=size.small)

    table.cell(hud, 0, 5, "🌐 " + activeBenchName + " Structure", text_color=color.rgb(200, 210, 230), bgcolor=rowBg1, text_size=size.small)
    table.cell(hud, 1, 5, benchStructVal, text_color=isCryptoDxyOffline ? color.rgb(150, 150, 160) : (isMarketClosedRT ? color.rgb(150, 150, 160) : (correlStatus == "Ready ⏳" ? color.rgb(150, 150, 160) : (isCorrelationConfirmedForLabel ? color.rgb(46, 204, 113) : color.rgb(231, 76, 60)))), bgcolor=rowBg1, text_size=size.small)

    table.cell(hud, 0, 6, "", text_color=color.rgb(0,0,0,100), bgcolor=baseBg, text_size=size.small)
    table.cell(hud, 1, 6, "", text_color=color.rgb(0,0,0,100), bgcolor=baseBg, text_size=size.small)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🎨 UNIFIED PLOTSHAPES
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

plotshape(buy1,  title = "BUY Label",    style = shape.labelup,   location = location.belowbar, color = buyBgColor, text = "BUY",    textcolor = buyTextColor, size = size.small)
plotshape(sell1, title = "SELL Label",   style = shape.labeldown, location = location.abovebar, color = sellBgColor, text = "SELL",   textcolor = sellTextColor, size = size.small)
plotshape(buy2,  title = "BUY 2 Label",  style = shape.labelup,   location = location.belowbar, color = buyBgColor, text = "BUY 2",  textcolor = buyTextColor, size = size.small)
plotshape(sell2, title = "SELL 2 Label", style = shape.labeldown, location = location.abovebar, color = sellBgColor, text = "SELL 2", textcolor = sellTextColor, size = size.small)
plotshape(buy3,  title = "BUY 3 Label",  style = shape.labelup,   location = location.belowbar, color = buyBgColor, text = "BUY 3",  textcolor = buyTextColor, size = size.small)
plotshape(sell3, title = "SELL 3 Label", style = shape.labeldown, location = location.abovebar, color = sellBgColor, text = "SELL 3", textcolor = sellTextColor, size = size.small)
Galaxy Theme Visual only
//@version=6
indicator("Galaxy Theme", overlay=true)

// Create a 10x10 grid. The table background is 100% transparent so your candles remain completely visible!
var table space = table.new(position.top_left, 10, 10, bgcolor=color.new(color.black, 100), border_width=0)

// Deterministic seeds so the stars stay perfectly still and don't flicker when the price moves
var int seed1 = 17
var int seed2 = 23

if barstate.islast
    for r = 0 to 9
        for c = 0 to 9
            // Complex pseudo-randomization for a highly natural scatter effect
            int starRand = (r * seed1 + c * seed2) % 12

            string stardust = ""
            color starColor = color.new(color.white, 100)
            string tSize = size.small

            // Organic placement using varied line breaks (\n) and spacing so it doesn't look like a grid
            if starRand == 0
                stardust := "✦"
                starColor := color.new(#babbff, 30) // Soft glowing purple
                tSize := size.normal
            else if starRand == 1
                stardust := " \n\n ★"
                starColor := color.new(color.white, 50)
            else if starRand == 2
                stardust := "      ·"
                starColor := color.new(#ffd1ff, 30) // Faint pink nebula dust
            else if starRand == 3
                stardust := " \n ✦ \n "
                starColor := color.new(#9bf6ff, 30) // Distant cyan twinkle
            else if starRand == 5
                stardust := "★\n\n"
                starColor := color.new(color.white, 60)
                tSize := size.tiny
            else if starRand == 7
                stardust := "   ·   "
                starColor := color.new(#c8b6ff, 40) // Lavender
            else
                // Leave empty cells to create the dark void of deep space
                stardust := ""

            // THE MAGIC TRICK: width=10.0 and height=10.0 forces each cell to stretch.
            // 10 cells x 10% = exactly 100% full screen coverage edge-to-edge!
            table.cell(space, c, r, text=stardust, text_color=starColor, text_size=tSize, width=10.0, height=10.0)
Open Legacy and Galaxy Theme are provided free, as-is, for use on TradingView. Nothing here is financial advice — always test on a paper account before trading live.

Finish the look

Galaxy Theme is visual only. It draws a transparent starfield table over your chart — no signals, no alerts, nothing to configure. For the full effect, pair it with a dark gradient chart background — see Galaxy Background Setup below.
Optional · Cosmetic

Galaxy Background Setup

Galaxy Theme draws the stars — this is the last step to get the deep-space look behind them, straight from TradingView's own chart settings.

Open chart settings

Click the gear icon (⚙️) at the top of your chart, or right-click anywhere on the chart and choose Settings.

Go to the "Canvas" tab

This is where TradingView controls the chart's overall background.

Set Background to "Gradient"

You'll see a background style dropdown — switch it from Solid to Gradient. Two color swatches will appear next to it.

Set the two gradient colors

Set the first color swatch to a dark purple, and the second to pure black. This gives the chart a deep-space fade that Galaxy Theme's stars sit beautifully on top of.

That's the full look. Dark purple-to-black gradient canvas, underneath Galaxy Theme's transparent starfield, underneath Open Legacy's signals and HUD. Three layers, zero clutter.