Use standard 1D candles for an equity with verified regular-session data. The indicator requests one explicit split-adjusted regular-session context and processes completed bars only. It shows gross wealth indices, two cost sensitivity curves, per-session returns, elapsed closure hours and the identity residual in the Data Window. It sends no orders and reports no Strategy Tester profit.
Pine v6 syntax checked with the Pineify MCP tool. Syntax success does not validate data coverage, session semantics or executable fills.pine
//@version=6
indicator("Overnight vs intraday: paired price attribution", overlay = false)
int startAt = input.time(timestamp("01 Jan 2020 00:00 +0000"), "First session (inclusive)")
int endAt = input.time(timestamp("01 Jan 2026 00:00 +0000"), "Last boundary (exclusive)")
float sideBps = input.float(5.0, "Illustrative cost per side (bps)", minval = 0, maxval = 100)
if not timeframe.isdaily or timeframe.multiplier != 1
runtime.error("Use standard 1D equity candles and audit the regular session.")
if startAt >= endAt
runtime.error("Start must precede end.")
string regularTicker = ticker.new(syminfo.prefix, syminfo.ticker, session.regular, adjustment.splits)
[o, c, previousClose, sessionTime, previousEnd] = request.security(regularTicker, "1D", [open, close, close[1], time, time_close[1]], gaps = barmerge.gaps_on, lookahead = barmerge.lookahead_off)
bool inRange = not na(sessionTime) and sessionTime >= startAt and sessionTime < endAt
bool valid = not na(o) and not na(c) and not na(previousClose) and o > 0 and c > 0 and previousClose > 0
var float overnightWealth = 1.0
var float intradayWealth = 1.0
var float fullWealth = 1.0
var float overnightCostWealth = 1.0
var float intradayCostWealth = 1.0
var int pairs = 0
var int rejected = 0
var int lastSession = na
float overnightReturn = na
float intradayReturn = na
float closureHours = na
float costFactor = (1 - sideBps / 10000) / (1 + sideBps / 10000)
if barstate.isconfirmed and inRange and (na(lastSession) or sessionTime != lastSession)
lastSession := sessionTime
if valid
float overnightFactor = o / previousClose
float intradayFactor = c / o
overnightReturn := 100 * (overnightFactor - 1)
intradayReturn := 100 * (intradayFactor - 1)
closureHours := (sessionTime - previousEnd) / 3600000.0
overnightWealth *= overnightFactor
intradayWealth *= intradayFactor
fullWealth *= c / previousClose
overnightCostWealth *= overnightFactor * costFactor
intradayCostWealth *= intradayFactor * costFactor
pairs += 1
else
rejected += 1
plot(pairs > 0 ? overnightWealth : na, "Close-to-open gross wealth", color = color.blue)
plot(pairs > 0 ? intradayWealth : na, "Open-to-close gross wealth", color = color.orange)
plot(pairs > 0 ? fullWealth : na, "Close-to-close gross wealth", color = color.gray)
plot(pairs > 0 ? overnightCostWealth : na, "Close-to-open cost scenario", color = color.aqua)
plot(pairs > 0 ? intradayCostWealth : na, "Open-to-close cost scenario", color = color.red)
plot(overnightReturn, "Paired overnight return (%)", display = display.data_window)
plot(intradayReturn, "Paired intraday return (%)", display = display.data_window)
plot(closureHours, "Hours between sessions", display = display.data_window)
plot(overnightWealth * intradayWealth - fullWealth, "Gross identity residual", display = display.data_window)
plot(pairs, "Accepted pairs", display = display.data_window)
plot(rejected, "Invalid observed pairs", display = display.data_window)
var table audit = table.new(position.top_right, 2, 4)
if barstate.islast
table.cell(audit, 0, 0, "Accepted pairs")
table.cell(audit, 1, 0, str.tostring(pairs))
table.cell(audit, 0, 1, "Invalid observed pairs")
table.cell(audit, 1, 1, str.tostring(rejected))
table.cell(audit, 0, 2, "Gross residual")
table.cell(audit, 1, 2, str.tostring(overnightWealth * intradayWealth - fullWealth, "#.########"))
table.cell(audit, 0, 3, "Basis / idle cash")
table.cell(audit, 1, 3, "Split-adjusted price / 0%")