Table of contents

Define the clocks first

For daily open OtO_t and close CtC_t, decompose the close-to-close log return into an overnight and intraday component:

rtovernight=logOtlogCt1,rtintraday=logCtlogOt.r_t^{\text{overnight}}=\log O_t-\log C_{t-1}, \qquad r_t^{\text{intraday}}=\log C_t-\log O_t.

The sum is exactly the close-to-close return. That identity is simple; producing trustworthy inputs is not. Corporate actions, exchange calendars, timestamp conventions, and the definition of “open” can all move the result.

A minimal calculation

Python
frame["overnight"] = np.log(frame["open"] / frame["close"].shift())
frame["intraday"] = np.log(frame["close"] / frame["open"])
frame["close_to_close"] = np.log(
    frame["close"] / frame["close"].shift()
)

check = frame["overnight"] + frame["intraday"]
np.testing.assert_allclose(check.dropna(), frame["close_to_close"].dropna())

The assertion is more than housekeeping. It protects the economic interpretation by ensuring the two return windows tile the full day without overlap or omission.

Aggregate facts are not strategies

If one component has a larger historical mean, that does not establish a tradeable anomaly. Execution prices, financing, survivorship, changing market structure, and the choice of sample all intervene.

The decomposition is still useful. It tells us when a return was earned and creates a sharper starting point for questions about information arrival and risk transfer.