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.
Quick Start
Five steps to a working chart. Everything below this section is detail you can come back to.
Open Legacy — manual signals
Signals plus a live HUD showing entry, take profit and stop loss.
Europe/Dublin) into Timezone (under 3. Display). Get this wrong and the market-closed status is wrong.Get set upThree things to get right▼
Each card jumps to a full walkthrough further down the page.
How to Install
Copy the Pine Script into TradingView's Pine Editor and add Open Legacy to any chart you want to trade.
View stepsKey Settings
What each input group controls, and how the fixed risk engine locks TP/SL the moment a signal fires.
View settingsSetting Up Alerts
Set up one TradingView alert on Open Legacy — it covers every event, from new signals to TP/SL hits.
View alert setupDAY TO DAYCheat Sheet▼
Once it's set up, this is all you actually need. Everything else on this page is background you can come back to.
Every session
Four habits that matter more than any setting.
TWO PROVEN STRATEGIESEntry Methods▼
Two well-known strategies — the only ones that held up when tested on three months of real 1-minute data across gold, indices and BTC. One dropdown picks the strategy; one Sensitivity dial sets how picky it is. Open Legacy Bot uses exactly the same engine, so the same settings give the same signals.
01How 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.
Visual Order, then Bring to Front.One-Time SetupSet Your Timezone▼
One quick field makes the HUD run on your own clock instead of a default one.
America/New_York, Europe/London, Asia/Tokyo. That keeps the market-open and market-closed status accurate to wherever you are.02Key Settings▼
Ten settings in three groups. Everything else is handled for you.
GuidanceSetting Your Targets▼
TP and SL are set in pips. A pip is tiny on most markets, so the numbers look big — here's what they mean in price.
CriticalWait 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.
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.
GuidanceTrade Small, Trade Consistently▼
Size every trade from the stop loss, not from how good the setup looks.
GuidanceReading 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.
AdvancedThe 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.
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
"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 exampleBest 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 UTC+1. Trading something else? Look up your own market's peak-liquidity hours; the same avoid-the-dead-zones principle applies everywhere.
07:00 – 11:30 UTC+1
The morning window. London liquidity is online and price action tends to be cleaner and more directional.
13:00 – 16:00 UTC+1
The afternoon window. London and New York overlap here, which is typically where gold sees its strongest follow-through.
Session Timing · XAUUSD exampleAvoid 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.)
Risk GuidanceNews 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.
03Setting Up Alerts▼
Alerts are configured on Open Legacy with a single alert — one per pair. 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. Trading more than one pair? Each one needs its own chart and its own alert.
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.
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
Copy & pasteGet the Scripts▼
Full Pine Script v6 source for both. Copy each into its own Pine Editor tab as described in How to Install.
//@version=6
indicator("Open Legacy", overlay = true)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// OPEN LEGACY — manual signals with a live HUD.
// The same two strategies and Sensitivity dial as Open Legacy Bot, so the
// same settings give the same BUY / SELL signals.
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ── 1. STRATEGY ──────────────────────────────────────────────────────────
G1 = "1. Strategy"
entryMethod = input.string("FVG Pullback", "Entry Method", options = ["FVG Pullback", "EMA Trend"], group = G1,
tooltip = "FVG Pullback — price leaves a fair value gap (a candle so fast it leaves an untouched gap), then a signal fires when price pulls back into the gap and turns.\n\nEMA Trend — signals at the start of a new trend leg: fast EMA above slow EMA, price above the fast EMA and RSI above 50 (the reverse for sells).")
sensitivity = input.string("Balanced", "Sensitivity", options = ["Aggressive", "Balanced", "Conservative"], group = G1,
tooltip = "Aggressive — the most signals, any timeframe.\n\nBalanced — only signals that agree with the 50 EMA trend.\n\nConservative — also needs the trend on a timeframe 4x your chart to agree, and real trend strength (ADX 20+). The fewest signals, the highest quality.")
// ── 2. TARGETS ───────────────────────────────────────────────────────────
G2 = "2. Targets"
slPips = input.float(100, "Stop Loss (pips)", minval = 0.1, step = 0.1, group = G2)
tpPips = input.float(200, "Take Profit (pips)", minval = 0.1, step = 0.1, group = G2,
tooltip = "Keep Take Profit larger than Stop Loss. Use the same values as your bot so both show the same levels.")
pipPoints = input.int(10, "Points Per Pip", minval = 1, group = G2,
tooltip = "10 for gold, indices, BTC and 5-decimal FX.")
// ── 3. DISPLAY ───────────────────────────────────────────────────────────
G3 = "3. Display"
buyColor = input.color(#4CAF50, "BUY", group = G3, inline = "c")
sellColor = input.color(#FF5252, "SELL", group = G3, inline = "c")
hudPos = input.string("Top Right", "HUD Position", options = ["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group = G3)
sessionTz = input.string("Europe/Dublin", "Timezone", group = G3,
tooltip = "Your timezone, IANA format — used to show when the market is closed.")
replayMode = input.bool(false, "Bar Replay Mode", group = G3,
tooltip = "Turn on while using Bar Replay so the HUD keeps updating.")
//── Internal constants ──
useExchangeTz = false
useCorrFilter = true
buyBgColor = buyColor
buyTextColor = color.white
sellBgColor = sellColor
sellTextColor = color.white
corrEmaLen = 9
corrConfBars = 3
gracePeriodBars = 6
maxRampBars = 12
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 = routedBenchSymbol
bool activeBenchInverse = routedBenchInverse
string activeBenchName = getBenchmarkDisplayName(activeBenchSymbol)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🌙 MARKET-CLOSE DETECTOR — scheduled weekly close OR unscheduled outage
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
bool isCrypto = syminfo.type == "crypto" or str.contains(syminfo.ticker, "BTC") or str.contains(syminfo.ticker, "ETH")
// Scheduled weekly close, evaluated against the REAL current clock (timenow) in
// the chosen timezone. DST is handled automatically via IANA tz names.
// • Mon-Fri: closed 22:00-22:59 local (daily break), reopens 23:00
// • Fri 22:00 onward through all of Sat through Sun 22:59: weekend closed
// • Reopens Sunday 23:00 local
f_isScheduledClose(string tz) =>
int dow = dayofweek(timenow, tz) // Pine: 1 = Sunday ... 7 = Saturday
int hr = hour(timenow, tz)
bool weekdayBreak = (dow >= 2 and dow <= 6) and (hr == 22)
bool weekendClosed = (dow == 6 and hr >= 22) or (dow == 7) or (dow == 1 and hr < 23)
weekdayBreak or weekendClosed
bool scheduledClosedNow = f_isScheduledClose(useExchangeTz ? syminfo.timezone : sessionTz)
// Live "no fresh data" detector — catches an UNSCHEDULED outage or maintenance
// break of any length (not a fixed window): as long as no new bar has printed
// for 15+ minutes on a genuinely live last bar, it stays flagged closed for
// however long that gap continues, and clears the moment fresh data resumes.
// Only ever evaluated on barstate.isrealtime, so historical/replay bars never
// trigger this regardless of how old their timestamps are.
bool dataStaleNow = barstate.isrealtime and barstate.islast and (timenow - time_close > 900000)
// DXY/forex benchmark closes on the standard schedule even when crypto itself
// keeps trading — bypass the correlation filter during that window.
bool isCryptoDxyOffline = not replayMode and isCrypto and scheduledClosedNow
// Main HUD "market closed" status:
// • non-crypto: closed if EITHER the scheduled weekly window applies OR the
// feed has genuinely gone stale (unscheduled maintenance/outage)
// • crypto: trades 24/7, so the fixed weekly schedule doesn't apply to the
// asset itself — only genuine exchange downtime (stale feed) marks it closed
bool isMarketClosedRT = not replayMode and (dataStaleNow or (not isCrypto and scheduledClosedNow))
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 📊 SIGNAL ENGINE — identical to Open Legacy Bot
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Sensitivity presets (literal values keep lengths "simple" for ta.* calls)
fvgLife = sensitivity == "Aggressive" ? 12 : sensitivity == "Balanced" ? 24 : 40
fvgMin = sensitivity == "Aggressive" ? 0.10 : sensitivity == "Balanced" ? 0.25 : 0.40
emaFastL = sensitivity == "Aggressive" ? 8 : sensitivity == "Balanced" ? 13 : 21
emaSlowL = sensitivity == "Aggressive" ? 21 : sensitivity == "Balanced" ? 34 : 55
atrV = ta.atr(14)
ema50 = ta.ema(close, 50)
[diP, diM, adxV] = ta.dmi(14, 14)
// Higher-timeframe trend (4x chart), previous completed candle — never repaints.
htfTf = timeframe.from_seconds(timeframe.in_seconds() * 4)
htfC = request.security(syminfo.tickerid, htfTf, close[1], lookahead = barmerge.lookahead_on)
htfE = request.security(syminfo.tickerid, htfTf, ta.ema(close, 50)[1], lookahead = barmerge.lookahead_on)
bool htfUp = not na(htfE) and htfC > htfE
bool htfDn = not na(htfE) and htfC < htfE
// ── FVG Pullback ──
var float bullTop = na
var float bullBot = na
var int bullBar = na
var float bearTop = na
var float bearBot = na
var int bearBar = na
bool fvgBuy = not na(bullBar) and bar_index - bullBar > 0 and bar_index - bullBar <= fvgLife and low <= bullTop and close > open and close > bullBot
bool fvgSell = not na(bearBar) and bar_index - bearBar > 0 and bar_index - bearBar <= fvgLife and high >= bearBot and close < open and close < bearTop
if fvgBuy
bullBar := na
if fvgSell
bearBar := na
if low > high[2] and (low - high[2]) >= fvgMin * atrV
bullBot := high[2]
bullTop := low
bullBar := bar_index
if high < low[2] and (low[2] - high) >= fvgMin * atrV
bearTop := low[2]
bearBot := high
bearBar := bar_index
// ── EMA Trend ──
emaF = ta.ema(close, emaFastL)
emaS = ta.ema(close, emaSlowL)
rsiV = ta.rsi(close, 14)
bool trUp = emaF > emaS and close > emaF and rsiV > 50
bool trDn = emaF < emaS and close < emaF and rsiV < 50
bool trendBuy = trUp and not trUp[1]
bool trendSell = trDn and not trDn[1]
// ── Sensitivity filter ──
bool rawBuy = entryMethod == "FVG Pullback" ? fvgBuy : trendBuy
bool rawSell = entryMethod == "FVG Pullback" ? fvgSell : trendSell
bool qBuy = sensitivity == "Aggressive" or (close > ema50 and (sensitivity == "Balanced" or (htfUp and adxV >= 20)))
bool qSell = sensitivity == "Aggressive" or (close < ema50 and (sensitivity == "Balanced" or (htfDn and adxV >= 20)))
bool sigBuy = rawBuy and qBuy
bool sigSell = rawSell and qSell and not sigBuy
slowEma = ema50
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
// ── Signals ──
bool confirmedBar = barstate.isconfirmed or replayMode
bool buy1 = sigBuy
bool sell1 = sigSell
newBuySignal = buy1
newSellSignal = sell1
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🔒 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
var int tradeEntryBar = na
string masterAlertMsg = ""
if (newBuySignal or newSellSignal) and (barstate.isconfirmed or replayMode)
activeIsBuy := newBuySignal
finalTpDist = tpPips * pipPoints * syminfo.mintick
finalSlDist = slPips * pipPoints * syminfo.mintick
lockedEntry := close
lockedTpPrice := activeIsBuy ? close + finalTpDist : close - finalTpDist
lockedSlPrice := activeIsBuy ? close - finalSlDist : close + finalSlDist
tradeEntryBar := bar_index
string tag = newBuySignal ? "BUY" : "SELL"
activeSignalTag := tag + " ACTIVE"
tradeActive := true
lastTpText := "---"
lastSlText := "---"
benchConfirmStreak := 0
benchInvalidStreak := 0
trendConfirmStreak := 0
trendInvalidStreak := 0
masterAlertMsg := "🚀 " + syminfo.ticker + " (" + timeframe.period + ") 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) and bar_index > tradeEntryBar
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 + " (" + timeframe.period + ") 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 + " (" + timeframe.period + ") 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 💤" : (not tradeActive ? "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 = isMarketClosedRT ? "CLOSED 💤" : (hasHitTargetOrStop ? "Waiting ⏳" : (na(lockedEntry) ? "Waiting..." : str.tostring(lockedEntry, "#.##")))
string takeProfitVal = isMarketClosedRT ? "CLOSED 💤" : (hasHitTargetOrStop ? "Waiting ⏳" : (lastTpText != "---" ? lastTpText : (na(lockedTpPrice) ? "Waiting..." : str.tostring(lockedTpPrice, "#.##"))))
string stopLossVal = isMarketClosedRT ? "CLOSED 💤" : (hasHitTargetOrStop ? "Waiting ⏳" : (lastSlText != "---" ? lastSlText : (na(lockedSlPrice) ? "Waiting..." : str.tostring(lockedSlPrice, "#.##"))))
string benchStructVal = correlStatus
table.cell(hud, 0, 1, "🎯 Setup Score", 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=isMarketClosedRT ? color.rgb(150, 150, 160) : 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=isMarketClosedRT ? color.rgb(150, 150, 160) : 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=isMarketClosedRT ? color.rgb(150, 150, 160) : 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)
bool structTrendUp = close > slowEma
bool structTrendDown = close < slowEma
bool structCorrBull = activeBenchInverse ? benchBearish : benchBullish
bool structCorrBear = activeBenchInverse ? benchBullish : benchBearish
bool structAgree = (structTrendUp and structCorrBull) or (structTrendDown and structCorrBear)
bool structConflict = (structTrendUp and structCorrBear) or (structTrendDown and structCorrBull)
string structureLabel = not tradeActive ? "Waiting ⏳" : (structAgree ? "Strong" : (structConflict ? "Weak" : "Decent"))
color structureColor = not tradeActive ? color.rgb(150, 150, 160) : (structAgree ? color.rgb(46, 204, 113) : (structConflict ? color.rgb(231, 76, 60) : color.rgb(241, 196, 15)))
table.cell(hud, 0, 6, "Overall Structure", text_color=color.rgb(180, 185, 195), bgcolor=baseBg, text_size=size.small)
table.cell(hud, 1, 6, structureLabel, text_color=structureColor, bgcolor=baseBg, text_size=size.small)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 🎨 SIGNAL LABELS — only on a closed candle, so a label never vanishes.
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
plotshape(buy1 and confirmedBar, title = "BUY Label", style = shape.labelup, location = location.belowbar, color = buyBgColor, text = "BUY", textcolor = buyTextColor, size = size.small)
plotshape(sell1 and confirmedBar, title = "SELL Label", style = shape.labeldown, location = location.abovebar, color = sellBgColor, text = "SELL", textcolor = sellTextColor, size = size.small)
//@version=6
indicator("Open Legacy Scoreboard", overlay = true, max_labels_count = 500)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// OPEN LEGACY SCOREBOARD — for manual traders.
// Replays the exact signals of Open Legacy / Open Legacy Bot with your
// sessions, blackouts and pip TP/SL, and tallies how each trade ended:
// TP hit, SL hit, or closed early in profit or loss. Use the same settings
// as your indicator or bot. (Bot users don't need this — the bot has the
// scoreboard built in.)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ── 1. STRATEGY ───────────────────────────────────────────────────────────
G1 = "1. Strategy"
entryMethod = input.string("FVG Pullback", "Entry Method", options = ["FVG Pullback", "EMA Trend"], group = G1,
tooltip = "FVG Pullback — price leaves a fair value gap (a candle so fast it leaves an untouched gap), then the bot enters when price pulls back into the gap and turns.\n\nEMA Trend — enters at the start of a new trend leg: fast EMA above slow EMA, price above the fast EMA and RSI above 50 (the reverse for sells).")
sensitivity = input.string("Balanced", "Sensitivity", options = ["Aggressive", "Balanced", "Conservative"], group = G1,
tooltip = "Aggressive — the most trades. Every valid setup, works on any timeframe.\n\nBalanced — only setups that agree with the 50 EMA trend. Roughly half the trades of Aggressive.\n\nConservative — also needs the trend on a timeframe 4x your chart to agree, and real trend strength (ADX 20+). The fewest trades, the highest quality.")
tfLock = input.string("Off", "Only Trade On Timeframe", options = ["Off", "1", "3", "5", "15", "30", "60"], group = G1,
tooltip = "Off — trades on whatever timeframe the chart (and alert) uses. Pick a value to stop it trading on any other timeframe by mistake.")
// ── 2. SESSIONS ───────────────────────────────────────────────────────────
G2 = "2. Sessions"
s1Time = input.session("0700-1200", "Session 1", group = G2, inline = "s1")
s1Days = input.string("Mon-Fri", "", options = ["Mon-Fri", "Tue-Fri", "Mon-Thu", "Tue-Thu", "Mon-Wed", "Wed-Fri", "Sun-Fri", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat-Sun", "Sat", "Sun", "Every Day", "Off"], group = G2, inline = "s1",
tooltip = "📆 Pick the days each session trades. Off switches the session off.")
s2Time = input.session("1400-1800", "Session 2", group = G2, inline = "s2")
s2Days = input.string("Off", "", options = ["Mon-Fri", "Tue-Fri", "Mon-Thu", "Tue-Thu", "Mon-Wed", "Wed-Fri", "Sun-Fri", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat-Sun", "Sat", "Sun", "Every Day", "Off"], group = G2, inline = "s2")
sessionTz = input.string("Europe/Dublin", "Timezone", group = G2,
tooltip = "Your timezone, IANA format — e.g. Europe/Dublin, Europe/London, America/New_York.")
closeAtSessionEnd = input.bool(true, "Close Trades At Session End", group = G2)
// ── 3. NEWS BLACKOUTS ─────────────────────────────────────────────────────
G3 = "3. News Blackouts"
newsSide = input.int(30, "Minutes Either Side", minval = 1, group = G3,
tooltip = "No new trades from this many minutes before each event until this many minutes after. Any open trade is closed as the window starts.")
n1On = input.bool(false, "News 1", group = G3, inline = "n1")
n1Date = input.time(timestamp("2026-10-13T12:00:00"), "", group = G3, inline = "n1")
n1Hour = input.int(13, "at", minval = 0, maxval = 23, group = G3, inline = "n1")
n1Min = input.int(30, ":", minval = 0, maxval = 59, group = G3, inline = "n1")
n2On = input.bool(false, "News 2", group = G3, inline = "n2")
n2Date = input.time(timestamp("2026-10-14T12:00:00"), "", group = G3, inline = "n2")
n2Hour = input.int(13, "at", minval = 0, maxval = 23, group = G3, inline = "n2")
n2Min = input.int(30, ":", minval = 0, maxval = 59, group = G3, inline = "n2")
n3On = input.bool(false, "News 3", group = G3, inline = "n3")
n3Date = input.time(timestamp("2026-10-15T12:00:00"), "", group = G3, inline = "n3")
n3Hour = input.int(13, "at", minval = 0, maxval = 23, group = G3, inline = "n3")
n3Min = input.int(30, ":", minval = 0, maxval = 59, group = G3, inline = "n3")
// ── 4. TARGETS ───────────────────────────────────────────────────────────
G4 = "4. Targets"
slPips = input.float(100, "Stop Loss (pips)", minval = 0.1, step = 0.1, group = G4)
tpPips = input.float(200, "Take Profit (pips)", minval = 0.1, step = 0.1, group = G4)
pipPoints = input.int(10, "Points Per Pip", minval = 1, group = G4,
tooltip = "10 for gold, indices, BTC and 5-decimal FX.")
// ── 5. DISPLAY ────────────────────────────────────────────────────────────
G5 = "5. Display"
sbShow = input.bool(true, "📊 Show Scoreboard", group = G5)
sbPeriod = input.string("This Week", "📅 Show Results For", options = ["This Week", "Last Week", "2 Weeks Ago", "3 Weeks Ago", "4 Weeks Ago", "Last 2 Weeks", "Last 4 Weeks", "Last 8 Weeks", "Last 12 Weeks", "All Time"], group = G5,
tooltip = "📅 One week: This Week, Last Week, 2-4 Weeks Ago.\n📈 Several weeks added up: Last 2/4/8/12 Weeks (this week included).\n♾️ All Time: every trade on the chart, best for backtesting.")
showMarks = input.bool(true, "Show Trade Markers", group = G5,
tooltip = "Marks each counted entry, and where it settled.")
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ENGINE
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
bool tfOk = tfLock == "Off" or timeframe.period == tfLock
int tfMin = timeframe.in_seconds() / 60
// Sensitivity presets (literal values keep lengths "simple" for ta.* calls)
fvgLife = sensitivity == "Aggressive" ? 12 : sensitivity == "Balanced" ? 24 : 40
fvgMin = sensitivity == "Aggressive" ? 0.10 : sensitivity == "Balanced" ? 0.25 : 0.40
emaFastL = sensitivity == "Aggressive" ? 8 : sensitivity == "Balanced" ? 13 : 21
emaSlowL = sensitivity == "Aggressive" ? 21 : sensitivity == "Balanced" ? 34 : 55
atrV = ta.atr(14)
ema50 = ta.ema(close, 50)
[diP, diM, adxV] = ta.dmi(14, 14)
// Higher-timeframe trend (4x chart), previous completed candle — never repaints.
htfTf = timeframe.from_seconds(timeframe.in_seconds() * 4)
htfC = request.security(syminfo.tickerid, htfTf, close[1], lookahead = barmerge.lookahead_on)
htfE = request.security(syminfo.tickerid, htfTf, ta.ema(close, 50)[1], lookahead = barmerge.lookahead_on)
bool htfUp = not na(htfE) and htfC > htfE
bool htfDn = not na(htfE) and htfC < htfE
// ── FVG Pullback ──
var float bullTop = na
var float bullBot = na
var int bullBar = na
var float bearTop = na
var float bearBot = na
var int bearBar = na
bool fvgBuy = not na(bullBar) and bar_index - bullBar > 0 and bar_index - bullBar <= fvgLife and low <= bullTop and close > open and close > bullBot
bool fvgSell = not na(bearBar) and bar_index - bearBar > 0 and bar_index - bearBar <= fvgLife and high >= bearBot and close < open and close < bearTop
if fvgBuy
bullBar := na
if fvgSell
bearBar := na
if low > high[2] and (low - high[2]) >= fvgMin * atrV
bullBot := high[2]
bullTop := low
bullBar := bar_index
if high < low[2] and (low[2] - high) >= fvgMin * atrV
bearTop := low[2]
bearBot := high
bearBar := bar_index
// ── EMA Trend ──
emaF = ta.ema(close, emaFastL)
emaS = ta.ema(close, emaSlowL)
rsiV = ta.rsi(close, 14)
bool trUp = emaF > emaS and close > emaF and rsiV > 50
bool trDn = emaF < emaS and close < emaF and rsiV < 50
bool trendBuy = trUp and not trUp[1]
bool trendSell = trDn and not trDn[1]
// ── Sensitivity filter ──
bool rawBuy = entryMethod == "FVG Pullback" ? fvgBuy : trendBuy
bool rawSell = entryMethod == "FVG Pullback" ? fvgSell : trendSell
bool qBuy = sensitivity == "Aggressive" or (close > ema50 and (sensitivity == "Balanced" or (htfUp and adxV >= 20)))
bool qSell = sensitivity == "Aggressive" or (close < ema50 and (sensitivity == "Balanced" or (htfDn and adxV >= 20)))
bool sigBuy = rawBuy and qBuy
bool sigSell = rawSell and qSell and not sigBuy
// Scoreboard period -> weeks to add up (0 = all) and weeks back
int sbSpan = switch sbPeriod
"Last 2 Weeks" => 2
"Last 4 Weeks" => 4
"Last 8 Weeks" => 8
"Last 12 Weeks" => 12
"All Time" => 0
=> 1
int sbWeeksBack = switch sbPeriod
"Last Week" => 1
"2 Weeks Ago" => 2
"3 Weeks Ago" => 3
"4 Weeks Ago" => 4
=> 0
// ── Sessions ──
f_days(string opt) =>
switch opt
"Mon-Fri" => "23456"
"Tue-Fri" => "3456"
"Mon-Thu" => "2345"
"Tue-Thu" => "345"
"Mon-Wed" => "234"
"Wed-Fri" => "456"
"Sun-Fri" => "123456"
"Mon" => "2"
"Tue" => "3"
"Wed" => "4"
"Thu" => "5"
"Fri" => "6"
"Sat-Sun" => "17"
"Sat" => "7"
"Sun" => "1"
"Every Day" => "1234567"
=> ""
f_hm(string s, int p) =>
int(str.tonumber(str.substring(s, p, p + 2)))
f_mins(int t) =>
hour(t, sessionTz) * 60 + minute(t, sessionTz)
// true when time t falls inside the window on one of its days
f_inSess(int t, string sess, string days) =>
int st = f_hm(sess, 0) * 60 + f_hm(sess, 2)
int en = f_hm(sess, 5) * 60 + f_hm(sess, 7)
int m = f_mins(t)
bool dayOk = str.length(days) > 0 and str.contains(days, str.tostring(dayofweek(t, sessionTz)))
dayOk and (en > st ? (m >= st and m < en) : (m >= st or m < en))
// entries need the candle to CLOSE inside the window with a candle of room left
f_room(string sess) =>
int en = f_hm(sess, 5) * 60 + f_hm(sess, 7)
int st = f_hm(sess, 0) * 60 + f_hm(sess, 2)
int m = f_mins(time_close)
int left = en > m ? en - m : 1440 - m + en
bool inside = en > st ? (m > st and m < en) : (m > st or m < en)
not closeAtSessionEnd or (inside and left > tfMin)
string d1 = f_days(s1Days)
string d2 = f_days(s2Days)
bool inWin1 = f_inSess(time, s1Time, d1)
bool inWin2 = f_inSess(time, s2Time, d2)
bool inSession = inWin1 or inWin2
bool sessionStarted = inSession and not inSession[1]
bool sessionEnded = not inSession and inSession[1]
// true on the LAST candle of a session (its close lands on the session end),
// so trades close exactly at the end time, not one candle later.
bool sessionEndsNow = (inWin1 and not f_inSess(time_close, s1Time, d1)) or (inWin2 and not f_inSess(time_close, s2Time, d2))
bool entryWindow = (inWin1 and f_room(s1Time)) or (inWin2 and f_room(s2Time))
// ── News blackouts ──
f_newsTs(int pick, int hh, int mm) =>
timestamp(sessionTz, year(pick, sessionTz), month(pick, sessionTz), dayofmonth(pick, sessionTz), hh, mm)
int sideMs = newsSide * 60 * 1000
f_inNews(int t, bool on, int ts) =>
on and t >= ts - sideMs and t < ts + sideMs
bool inBlackout = f_inNews(time_close, n1On, f_newsTs(n1Date, n1Hour, n1Min)) or f_inNews(time_close, n2On, f_newsTs(n2Date, n2Hour, n2Min)) or f_inNews(time_close, n3On, f_newsTs(n3Date, n3Hour, n3Min))
bool blackoutStarted = inBlackout and not inBlackout[1]
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// TRADE REPLAY — same rules as the bot: one position at a time, an opposite
// signal reverses it, session end and blackouts close it, TP/SL in pips.
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
float tpDist = tpPips * pipPoints * syminfo.mintick
float slDist = slPips * pipPoints * syminfo.mintick
// 1-minute data decides which level came first when one candle hits both.
[m1H, m1L] = request.security_lower_tf(syminfo.tickerid, "1", [high, low])
// Per week: 14 slots (7 days x 2 sessions) x 4 outcomes
// (0 TP hit, 1 Partial Profit, 2 Partial Loss, 3 SL hit).
var array<int> sbOut = array.new_int(56, 0)
var int sbWeek = 0
f_dayIdx(int t) =>
int d = dayofweek(t, sessionTz)
d == dayofweek.sunday ? 6 : d - 2
wkNow = weekofyear(time, sessionTz)
if wkNow != wkNow[1] and bar_index > 0
sbWeek += 1
for i = 0 to 55
array.push(sbOut, 0)
f_record(int et, int kind) =>
int sIdx = f_inSess(et, s1Time, d1) ? 0 : 1
int slot = (sbWeek * 14 + f_dayIdx(et) * 2 + sIdx) * 4 + kind
array.set(sbOut, slot, array.get(sbOut, slot) + 1)
f_mark(bool won, string txt, bool atHigh) =>
if showMarks
label.new(bar_index, atHigh ? high : low, txt, style = atHigh ? label.style_label_down : label.style_label_up,
color = won ? color.new(color.green, 20) : color.new(color.red, 20), textcolor = color.white, size = size.tiny)
var int posDir = 0
var float posEnt = na
var int posEt = na
var int posBar = na
if barstate.isconfirmed
// 1. TP / SL on this candle (not the entry candle itself)
if posDir != 0 and bar_index > posBar
float tpL = posEnt + posDir * tpDist
float slL = posEnt - posDir * slDist
bool tpHit = posDir == 1 ? high >= tpL : low <= tpL
bool slHit = posDir == 1 ? low <= slL : high >= slL
if tpHit or slHit
bool won = tpHit and not slHit
if tpHit and slHit and array.size(m1H) > 0
bool done = false
for m = 0 to array.size(m1H) - 1
if not done
bool mTp = posDir == 1 ? array.get(m1H, m) >= tpL : array.get(m1L, m) <= tpL
bool mSl = posDir == 1 ? array.get(m1L, m) <= slL : array.get(m1H, m) >= slL
if mTp or mSl
won := mTp and not mSl
done := true
f_record(posEt, won ? 0 : 3)
f_mark(won, won ? "✓ TP" : "✗ SL", won == (posDir == 1))
posDir := 0
// 2. Session end / blackout close
if posDir != 0 and ((sessionEndsNow and closeAtSessionEnd) or blackoutStarted)
bool won2 = (close - posEnt) * posDir > 0
f_record(posEt, won2 ? 1 : 2)
f_mark(won2, won2 ? "◐ Partial +" : "◐ Partial -", true)
posDir := 0
// 3. Entries (an opposite signal closes the open trade first)
bool canTrade = tfOk and entryWindow and not inBlackout
int want = canTrade and sigBuy ? 1 : canTrade and sigSell ? -1 : 0
if want != 0 and want != posDir
if posDir != 0
bool won3 = (close - posEnt) * posDir > 0
f_record(posEt, won3 ? 1 : 2)
f_mark(won3, won3 ? "◐ Partial +" : "◐ Partial -", true)
posDir := want
posEnt := close
posEt := time
posBar := bar_index
if showMarks
label.new(bar_index, want == 1 ? low : high, want == 1 ? "BUY" : "SELL", style = want == 1 ? label.style_label_up : label.style_label_down,
color = want == 1 ? color.new(color.green, 0) : color.new(color.red, 0), textcolor = color.white, size = size.small)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 📊 SCOREBOARD
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var table sb = table.new(position.bottom_right, 5, 16, border_width = 1, frame_width = 2,
frame_color = color.new(#2A3350, 0), border_color = color.new(#1C2334, 0))
// Strategies only run when a candle closes, so on a live chart the newest
// candle isn't "last" until it closes. Drawing on the last historical candle
// too makes the board appear the moment the chart loads.
if (barstate.islastconfirmedhistory or barstate.isrealtime) and sbShow
bg = color.new(#0E1320, 0)
alt = color.new(#121829, 0)
head = color.new(#18203A, 0)
sub = color.new(#141B2E, 0)
green = color.rgb(47, 212, 128)
red = color.rgb(255, 93, 108)
teal = color.rgb(46, 196, 182)
amber = color.rgb(245, 185, 66)
dim = color.rgb(118, 126, 146)
ts = size.small
bool s2On = d2 != ""
int lastWk = sbWeek - sbWeeksBack
int firstWk = sbSpan == 0 ? 0 : math.max(0, lastWk - sbSpan + 1)
bool single = sbSpan == 1
string hdr = sbSpan == 0 ? "📅 All" : single ? (sbWeeksBack == 0 ? "📅 This Wk" : sbWeeksBack == 1 ? "📅 Last Wk" : "📅 " + str.tostring(sbWeeksBack) + " Wks Ago") : "📅 " + str.tostring(sbSpan) + " Wks"
// totals per day/session, and per outcome
array<int> win = array.new_int(14, 0)
array<int> loss = array.new_int(14, 0)
array<int> o1 = array.new_int(4, 0)
array<int> o2 = array.new_int(4, 0)
if lastWk >= 0
for w = firstWk to lastWk
for cell = 0 to 13
for k = 0 to 3
int v = array.get(sbOut, (w * 14 + cell) * 4 + k)
if v > 0
if k <= 1
array.set(win, cell, array.get(win, cell) + v)
else
array.set(loss, cell, array.get(loss, cell) + v)
if cell % 2 == 0
array.set(o1, k, array.get(o1, k) + v)
else
array.set(o2, k, array.get(o2, k) + v)
table.cell(sb, 0, 0, hdr, text_color = color.rgb(214, 220, 234), bgcolor = head, text_size = ts)
table.cell(sb, 1, 0, "✅ S1", text_color = green, bgcolor = head, text_size = ts)
table.cell(sb, 2, 0, "❌ S1", text_color = red, bgcolor = head, text_size = ts)
table.cell(sb, 3, 0, "✅ S2", text_color = green, bgcolor = head, text_size = ts)
table.cell(sb, 4, 0, "❌ S2", text_color = red, bgcolor = head, text_size = ts)
names = array.from("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
int today = f_dayIdx(time)
int t1 = 0
int l1 = 0
int t2 = 0
int l2 = 0
for d = 0 to 6
bool isToday = d == today and sbWeeksBack == 0 and single
color rbg = isToday ? color.new(#1A2440, 0) : (d % 2 == 0 ? bg : alt)
table.cell(sb, 0, d + 1, (isToday ? "▸ " : "") + array.get(names, d),
text_color = isToday ? color.rgb(77, 232, 255) : color.rgb(205, 212, 228), bgcolor = rbg, text_size = ts)
for s = 0 to 1
int wv = array.get(win, d * 2 + s)
int lv = array.get(loss, d * 2 + s)
bool off = s == 1 and not s2On
table.cell(sb, s * 2 + 1, d + 1, off ? "·" : wv == 0 ? "-" : str.tostring(wv), text_color = off or wv == 0 ? dim : green, bgcolor = rbg, text_size = ts)
table.cell(sb, s * 2 + 2, d + 1, off ? "·" : lv == 0 ? "-" : str.tostring(lv), text_color = off or lv == 0 ? dim : red, bgcolor = rbg, text_size = ts)
if s == 0
t1 += wv
l1 += lv
else
t2 += wv
l2 += lv
int totW = t1 + t2
int totN = totW + l1 + l2
float winPct = totN > 0 ? totW * 100.0 / totN : 0.0
color totCol = totN == 0 ? dim : winPct > 50 ? green : winPct < 50 ? red : amber
table.cell(sb, 0, 8, totN == 0 ? (single ? "🏁 Week" : "🏁 Total") : "🏁 " + str.tostring(winPct, "#") + "%", text_color = totCol, bgcolor = head, text_size = ts)
table.cell(sb, 1, 8, str.tostring(t1), text_color = green, bgcolor = head, text_size = ts)
table.cell(sb, 2, 8, str.tostring(l1), text_color = red, bgcolor = head, text_size = ts)
table.cell(sb, 3, 8, s2On ? str.tostring(t2) : "·", text_color = s2On ? green : dim, bgcolor = head, text_size = ts)
table.cell(sb, 4, 8, s2On ? str.tostring(l2) : "·", text_color = s2On ? red : dim, bgcolor = head, text_size = ts)
// Outcome breakdown
table.cell(sb, 0, 9, "📊 Outcome", text_color = color.rgb(214, 220, 234), bgcolor = head, text_size = ts, text_halign = text.align_left)
table.cell(sb, 1, 9, "S1", text_color = dim, bgcolor = head, text_size = ts)
table.cell(sb, 2, 9, "S2", text_color = dim, bgcolor = head, text_size = ts)
table.cell(sb, 3, 9, "All", text_color = dim, bgcolor = head, text_size = ts)
table.cell(sb, 4, 9, "Share", text_color = dim, bgcolor = head, text_size = ts)
oNames = array.from("🎯 TP Hit", "◐ Partial +", "◐ Partial −", "🛑 SL Hit")
oCols = array.from(green, teal, amber, red)
for k = 0 to 3
int v1 = array.get(o1, k)
int v2 = array.get(o2, k)
int vt = v1 + v2
float pct = totN > 0 ? vt * 100.0 / totN : 0.0
int nb = int(math.round(pct / 20.0))
string meter = ""
for q = 1 to 5
meter += q <= nb ? "▰" : "▱"
color cc = array.get(oCols, k)
color rbg = k % 2 == 0 ? bg : alt
table.cell(sb, 0, 10 + k, array.get(oNames, k), text_color = cc, bgcolor = rbg, text_size = ts, text_halign = text.align_left)
table.cell(sb, 1, 10 + k, v1 == 0 ? "-" : str.tostring(v1), text_color = v1 == 0 ? dim : cc, bgcolor = rbg, text_size = ts)
table.cell(sb, 2, 10 + k, not s2On ? "·" : v2 == 0 ? "-" : str.tostring(v2), text_color = not s2On or v2 == 0 ? dim : cc, bgcolor = rbg, text_size = ts)
table.cell(sb, 3, 10 + k, str.tostring(vt), text_color = vt == 0 ? dim : cc, bgcolor = sub, text_size = ts)
table.cell(sb, 4, 10 + k, totN == 0 ? "▱▱▱▱▱" : meter + " " + str.tostring(pct, "#") + "%", text_color = totN == 0 ? dim : cc, bgcolor = sub, text_size = ts, text_halign = text.align_left)
// spacer so the TradingView footer never covers the last row
for col = 0 to 4
table.cell(sb, col, 14, " ", bgcolor = color.new(color.black, 100), text_size = ts)
//@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)
Finish the look
Optional · CosmeticGalaxy 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.