Level 5 — MasteryWhen to Trade — Timing & Sessions

When to Trade — Timing & Sessions

90 minutes60 marksprintable — key stays hidden on paper

Level 5 — Mastery Examination (Cross-Domain: Quantitative Finance + Statistics + Coding)

Time limit: 90 minutes Total marks: 60 Instructions: Answer ALL three questions. Show full derivations. Code may be written in Python/pseudo-code but must be logically complete. Use ...... for math.


Question 1 — Opening Range & Session Volatility Model (20 marks)

Intraday volatility is empirically U-shaped: high at the open (opening range, 09:15–09:45), low during the midday lull, high again in the power hour (closing session).

Model the instantaneous variance rate over a normalized trading session t[0,1]t \in [0,1] as:

σ2(t)=a(t12)2+b\sigma^2(t) = a\left(t - \tfrac{1}{2}\right)^2 + b

where a>0a>0 controls the U-curvature and b>0b>0 is the midday floor variance rate.

(a) The total session variance is V=01σ2(t)dtV=\int_0^1 \sigma^2(t)\,dt. Derive a closed-form expression for VV in terms of aa and bb. (4 marks)

(b) Given empirical measurements: the midday-lull minimum variance rate is b=0.0004b = 0.0004 (per unit tt) and the total session variance is V=0.0009V = 0.0009, solve for aa. (4 marks)

(c) Define the opening range variance fraction as the proportion of total session variance realized during the first 30 minutes. If the session is 375 minutes long (Indian equity cash session 09:15–15:30), the opening range [09:15,09:45][09:15,09:45] maps to t[0,0.08]t\in[0,\,0.08]. Compute the fraction f=1V00.08σ2(t)dtf = \dfrac{1}{V}\int_0^{0.08}\sigma^2(t)\,dt using the values from (b). (6 marks)

(d) Interpret the result in (c) for a trader: argue quantitatively why avoiding the first 30 minutes discards a disproportionate/ proportionate share of the day's realizable move, and connect it to the "times to avoid" principle. (6 marks)


Question 2 — Gap Risk, Expiry Volatility & Position Sizing (22 marks)

An overnight-held long position faces a gap return GG (close-to-open log return). Assume GN(μ,σG2)G \sim \mathcal{N}(\mu, \sigma_G^2) with μ=0\mu=0.

(a) On a normal night, σG=0.008\sigma_G = 0.008. On an earnings-announcement night, empirical evidence shows the gap standard deviation inflates by a factor k=3.5k=3.5. Compute the 1-day 99% overnight Value-at-Risk (VaR) as a fraction of position value for both cases. Use z0.99=2.326z_{0.99}=2.326. (6 marks)

(b) A trader caps overnight VaR at 2% of account equity. Position value is PP, account equity is E=10,00,000E=₹10{,}00{,}000. Derive the maximum PP allowed on a normal night and on an earnings night, and state the ratio. (6 marks)

(c) On monthly options expiry day, realized volatility clusters. Model expiry-day intraday returns as a mixture: with probability p=0.2p=0.2 (a "pin/unwind" regime) variance is 9σ029\sigma_0^2, otherwise variance is σ02\sigma_0^2, where σ02=0.0001\sigma_0^2=0.0001. Derive the effective (mixture) variance and the effective standard deviation, then compute the excess kurtosis contribution qualitatively (state whether the mixture is leptokurtic and why). (6 marks)

(d) Write a Python function expiry_var(P, sigma0, p, z) that returns the expiry-day 1-day VaR using the mixture variance from (c). Then state what it returns for P=100000P=100000, σ0=0.01\sigma_0=0.01, p=0.2p=0.2, z=2.326z=2.326. (4 marks)


Question 3 — Economic Calendar Event Study & Session Backtest (18 marks)

You are given a list of intraday 1-minute bars and an economic-calendar timestamp for a high-impact release (e.g., RBI policy at 10:00).

(a) Define the event window [τw,τ+w][\tau-w, \tau+w] around release time τ\tau with half-width ww minutes. Explain, with reference to volatility windows, why ww should be asymmetric in practice (pre vs post). (4 marks)

(b) Write a function event_move(bars, tau, w) that computes the absolute price move Pτ+wPτw|P_{\tau+w} - P_{\tau-w}| and the average per-minute volume inside the window, given bars as a list of dicts {"t":int_minutes, "price":float, "vol":int}. Handle missing bars gracefully. (8 marks)

(c) Prove/argue: if the event-window absolute move exceeds NN average-day standard deviations, a rule to avoid entering positions in the 5 minutes before release strictly reduces expected slippage cost. State assumptions and give the inequality that must hold. (6 marks)


Answer keyMark scheme & solutions

Question 1

(a) Expand and integrate: V=01[a(t12)2+b]dt=a[(t12)33]01+b.V=\int_0^1\left[a\left(t-\tfrac12\right)^2+b\right]dt = a\left[\frac{(t-\frac12)^3}{3}\right]_0^1 + b. At t=1t=1: (12)3=18(\tfrac12)^3=\tfrac18; at t=0t=0: (12)3=18(-\tfrac12)^3=-\tfrac18. Difference =14=\tfrac{1}{4}. V=a12+b\boxed{V=\frac{a}{12}+b} Marks: integral setup (2), correct evaluation a/12+ba/12+b (2).

(b) 0.0009=a12+0.0004a12=0.0005a=0.006.0.0009 = \dfrac{a}{12}+0.0004 \Rightarrow \dfrac{a}{12}=0.0005 \Rightarrow a=0.006. Marks: substitution (2), a=0.006a=0.006 (2).

(c) Opening-range variance: 00.08 ⁣ ⁣[a(t12)2+b]dt=a[(t12)33]00.08+b(0.08).\int_0^{0.08}\!\!\left[a\left(t-\tfrac12\right)^2+b\right]dt = a\left[\frac{(t-\frac12)^3}{3}\right]_0^{0.08}+b(0.08). At t=0.08t=0.08: (0.080.5)=0.42(0.08-0.5)=-0.42, (0.42)3=0.074088(-0.42)^3=-0.074088; at t=0t=0: (0.5)3=0.125(-0.5)^3=-0.125. Difference =(0.074088(0.125))/3=0.050912/3=0.0169707=(-0.074088-(-0.125))/3 = 0.050912/3 = 0.0169707. Times a=0.006a=0.006: 0.006×0.0169707=1.01824×1040.006\times0.0169707 = 1.01824\times10^{-4}. Plus b0.08=0.0004×0.08=3.2×105b\cdot0.08 = 0.0004\times0.08 = 3.2\times10^{-5}. Numerator =1.01824×104+3.2×105=1.33824×104=1.01824\times10^{-4}+3.2\times10^{-5} = 1.33824\times10^{-4}. f=1.33824×1040.0009=0.1486914.9%.f = \frac{1.33824\times10^{-4}}{0.0009} = 0.14869 \approx 14.9\%. Marks: integral setup (2), cube evaluations (2), f0.149f\approx0.149 (2).

(d) The first 30 minutes is 8%8\% of clock time but carries 14.9%\approx14.9\% of the day's variance — nearly 1.9× its time-share. So it is a disproportionately high-move (and high-risk) window. A risk-averse or slippage-sensitive trader who "avoids the first 30 minutes" thus discards a large slice of realizable move; conversely a scalper seeking movement should trade it. This quantifies the "times to avoid" principle: avoidance decisions should be weighted by variance density, not clock minutes. Full marks require: (i) compare 8% vs 14.9% (2), (ii) compute the ~1.9× ratio (2), (iii) link to trader style / times-to-avoid (2).


Question 2

(a) VaR (fraction) =z0.99σG= z_{0.99}\,\sigma_G (since μ=0\mu=0).

  • Normal: 2.326×0.008=0.0186081.861%2.326\times0.008 = 0.018608 \approx 1.861\%.
  • Earnings: σG=3.5×0.008=0.028\sigma_G'=3.5\times0.008=0.028; VaR =2.326×0.028=0.0651286.513%=2.326\times0.028=0.065128\approx6.513\%. Marks: formula (2), normal value (2), earnings value (2).

(b) Constraint: VaRP0.02E=20,000\text{VaR}\cdot P \le 0.02E = ₹20{,}000.

  • Normal: Pmax=200000.018608=1,074,806P_{max}=\dfrac{20000}{0.018608}=₹1{,}074{,}806.
  • Earnings: Pmax=200000.065128=307,087P_{max}=\dfrac{20000}{0.065128}=₹307{,}087.
  • Ratio =PnormPearn=3.5=\dfrac{P_{norm}}{P_{earn}} = 3.5 (equals kk, since VaR scales linearly with σG\sigma_G). Marks: constraint (2), both PmaxP_{max} (2), ratio = 3.5 with reasoning (2).

(c) Mixture variance (mean return 00 in each regime): σeff2=p(9σ02)+(1p)σ02=σ02[9p+(1p)]=σ02(1+8p).\sigma^2_{eff}=p(9\sigma_0^2)+(1-p)\sigma_0^2 = \sigma_0^2[9p+(1-p)] = \sigma_0^2(1+8p). With p=0.2p=0.2: 1+8(0.2)=2.61+8(0.2)=2.6, so σeff2=2.6σ02=2.6×0.0001=2.6×104\sigma^2_{eff}=2.6\sigma_0^2 = 2.6\times0.0001=2.6\times10^{-4}. σeff=2.6×104=0.016125.\sigma_{eff}=\sqrt{2.6\times10^{-4}}=0.016125. The mixture of two zero-mean normals with different variances is leptokurtic (excess kurtosis >0>0): the high-variance regime fattens the tails and the low-variance regime peaks the center — hence "fat tails" on expiry, justifying wider stops / smaller size. Marks: mixture formula (2), σeff2=2.6σ02\sigma^2_{eff}=2.6\sigma_0^2 & σeff0.0161\sigma_{eff}\approx0.0161 (2), leptokurtic argument (2).

(d)

import math
def expiry_var(P, sigma0, p, z):
    var_eff = sigma0**2 * (1 + 8*p)   # mixture variance
    sigma_eff = math.sqrt(var_eff)
    return z * sigma_eff * P

For P=100000, σ0=0.01, p=0.2, z=2.326P=100000,\ \sigma_0=0.01,\ p=0.2,\ z=2.326: σeff=0.012.6=0.0161245\sigma_{eff}=0.01\sqrt{2.6}=0.0161245; VaR =2.326×0.0161245×100000=3750.55=2.326\times0.0161245\times100000 = ₹3750.55. Marks: correct mixture var in code (2), returned value 3750.6\approx₹3750.6 (2).


Question 3

(a) Event window [τw,τ+w][\tau-w,\tau+w]. Asymmetry rationale: pre-release volatility is suppressed (traders flatten/hedge, spreads widen but moves are small — "calm before"), while post-release volatility spikes as the surprise is priced in and can persist for many minutes. So the effective volatility window is longer after τ\tau; a symmetric ww either wastes the quiet pre-window or truncates the post-move. Practically use wpre<wpostw_{pre}<w_{post}. Marks: window definition (2), asymmetry pre/post reasoning (2).

(b)

def event_move(bars, tau, w):
    lut = {b["t"]: b for b in bars}          # index by minute
    def nearest(target):                      # graceful missing-bar handling
        if target in lut: return lut[target]
        cands = [b for b in bars if abs(b["t"]-target) <= 2]
        if not cands: return None
        return min(cands, key=lambda b: abs(b["t"]-target))
    lo, hi = nearest(tau - w), nearest(tau + w)
    if lo is None or hi is None:
        return None
    abs_move = abs(hi["price"] - lo["price"])
    window = [b for b in bars if tau - w <= b["t"] <= tau + w]
    vols = [b["vol"] for b in window]
    avg_vol = sum(vols)/len(vols) if vols else 0.0
    return {"abs_move": abs_move, "avg_vol": avg_vol}

Marks: correct endpoint prices & abs move (3), average volume over window (2), missing-bar handling / empty guard (3).

(c) Assumptions: (i) slippage cost per trade cs\approx c\cdot s where ss = half-spread, monotone increasing in local volatility; (ii) in the pre-release minute spreads widen and depth thins, so spre>snormals_{pre}>s_{normal}; (iii) expected fill quality worsens as move magnitude grows. Let day standard deviation be σD\sigma_D. If the event window absolute move satisfies Pτ+wPτw>NσD,|P_{\tau+w}-P_{\tau-w}| > N\,\sigma_D, then expected adverse slippage of entering just before τ\tau is αNσD\ge \alpha N\sigma_D (partial fill at post-jump prices), while deferring entry to τ+w\tau+w caps slippage at the (lower) post-event spread cost cspostc\,s_{post}. Since spres_{pre} is elevated and jump exposure is avoided, the deferral rule gives E[costavoid]=cspost<αNσD+cspre=E[costenter],\mathbb{E}[\text{cost}_{avoid}] = c\,s_{post} < \alpha N\sigma_D + c\,s_{pre} = \mathbb{E}[\text{cost}_{enter}], which holds whenever αNσD>c(spostspre)\alpha N\sigma_D > c(s_{post}-s_{pre}); with spostspres_{post}\le s_{pre} (spreads normalize after the print) the RHS is 0\le 0, so the inequality holds for any N>0N>0. Hence avoidance strictly reduces expected slippage. Marks: assumptions (2), inequality construction (2), conclusion under stated condition (2).

[
  {"claim":"Q1a total variance V = a/12 + b via integration of a(t-1/2)^2+b on [0,1]","code":"t,a,b=symbols('t a b'); V=integrate(a*(t-Rational(1,2))**2+b,(t,0,1)); result= simplify(V-(a/12+b))==0"},
  {"claim":"Q1b solving 0.0009=a/12+0.0004 gives a=0.006","code":"a=symbols('a'); sol=solve(Eq(Rational(9,10000),a/12+Rational(4,10000)),a)[0]; result= sol==Rational(6,1000)"},
  {"claim":"Q1c opening range variance fraction f ~ 0.1487","code":"t=symbols('t'); a=Rational(6,1000); b=Rational(4,10000); num=integrate(a*(t-Rational(1,2))**2+b,(t,0,Rational(8,100))); f=num/Rational(9,10000); result= abs(float(f)-0.14869)<0.001"},
  {"claim":"Q2a earnings overnight VaR = 2.326*0.028 = 0.065128","code":"result= abs(2.326*0.028-0.065128)<1e-9"},
  {"claim":"Q2b position ratio normal/earnings equals 3.5","code":"pn=20000/(2.326*0.008); pe=20000/(2.326*0.028); result= abs(pn/pe-3.5)<1e-9"},
  {"claim":"Q2c effective mixture variance = 2.6*sigma0^2 for p=0.2","code":"s0,p=symbols('s0 p'); veff=p*9*s0**2+(1-p)*s0**2; result= simplify(veff.subs(p,Rational(2,10))-Rational(26,10)*s0**2)==0"},
  {"claim":"Q2d expiry VaR ~ 3750.55 for P=1e5,sigma0=0.01,p=0.2,z=2.326","code":"import math; var=(0.01**2)*(1+8*0.2); v=2.326*math.sqrt(var)*100000; result= abs(v-3750.55)<0.5"}
]