Markets: A Rigorous Course

Exercise · m10_gold_parity.py

Import-parity pricing of Indian gold

The landed price of gold in India per 10 g, before GST, is

P_INR(10g) = (P_USD/oz + q) / G * 10 * X * (1 + d)                  (10.15)

with G = 31.1034768 g per troy ounce, q the local premium (+) or discount (-) in USD/oz, X = USDINR (rupees per dollar) and d = BCD + AIDC the customs duty rate. Retail adds GST g on top: multiply by (1 + g).

Implement every function. Check with cd exercises && .venv/bin/python -m pytest modules/M10-commodities/tests/test_m10_gold_parity.py

Tests (6)

  • test_unit_conversion
  • test_reproduces_r3_worked_example_2_nov_2026
  • test_gst_and_premium
  • test_duty_regimes
  • test_implied_duty_close_to_statutory
  • test_log_return_decomposition_is_exact

Theory: M10 notes. Run with Ctrl/⌘+Enter.

not run yet

Not saved: sign up to keep your code
"""M10 exercise: import-parity pricing of Indian gold (notes §10.5).

The landed price of gold in India per 10 g, before GST, is

    P_INR(10g) = (P_USD/oz + q) / G * 10 * X * (1 + d)                  (10.15)

with G = 31.1034768 g per troy ounce, q the local premium (+) or discount (-)
in USD/oz, X = USDINR (rupees per dollar) and d = BCD + AIDC the customs
duty rate. Retail adds GST g on top: multiply by (1 + g).

Implement every function. Check with
    cd exercises && .venv/bin/python -m pytest modules/M10-commodities/tests/test_m10_gold_parity.py
"""

from __future__ import annotations

import datetime as _dt
import math  # noqa: F401  (you will need it)

TROY_OUNCE_G = 31.1034768  # grams per troy ounce (exact by definition: 480 grains)

# (effective_from, BCD, AIDC). Totals: 15% -> 6% (24 Jul 2024) -> 15% (13 May 2026).
# The first row is the regime in force immediately before the July 2024 cut. Its
# start date (1 Jul 2022, when the total went to 15%) and its BCD/AIDC split over the
# whole period were not verified for this course; only the 15% total is relied on.
DUTY_REGIMES: list[tuple[_dt.date, float, float]] = [
    (_dt.date(2022, 7, 1), 0.10, 0.05),
    (_dt.date(2024, 7, 24), 0.05, 0.01),
    (_dt.date(2026, 5, 13), 0.10, 0.05),
]

GST_GOLD = 0.03  # GST on gold (as of Nov 2026)


def usd_oz_to_inr_10g(usd_per_oz: float, usdinr: float) -> float:
    """International price converted to rupees per 10 g, before any duty.

    Math: usd_per_oz / TROY_OUNCE_G * 10 * usdinr.
    Raise ValueError if usd_per_oz < 0 or usdinr <= 0.
    Example: (3975, 94.5) -> about 1,20,770.26.
    """
    raise NotImplementedError


def import_parity_inr_10g(
    usd_per_oz: float,
    usdinr: float,
    duty: float,
    premium_usd_oz: float = 0.0,
    gst: float = 0.0,
) -> float:
    """Landed price per 10 g, eq. (10.15).

    Math: usd_oz_to_inr_10g(usd_per_oz + premium_usd_oz, usdinr) * (1 + duty) * (1 + gst).
    Pass gst=0.03 for the retail price including GST. MCX futures quote ex-GST.
    """
    raise NotImplementedError


def duty_on(date: _dt.date) -> float:
    """Total customs duty BCD + AIDC on gold in force on `date`, from DUTY_REGIMES.

    The latest regime whose effective_from <= date applies.
    Raise ValueError for dates before the first tabulated regime.
    """
    raise NotImplementedError


def implied_premium(market_inr_10g: float, usd_per_oz: float, usdinr: float, duty: float) -> float:
    """Domestic premium (+) or discount (-) over parity: market / parity - 1 (parity with q = 0, no GST)."""
    raise NotImplementedError


def implied_duty(market_inr_10g: float, usd_per_oz: float, usdinr: float) -> float:
    """The duty d that makes parity equal the market price: market / usd_oz_to_inr_10g(...) - 1."""
    raise NotImplementedError


def decompose_log_return(
    usd0: float, usd1: float, fx0: float, fx1: float, d0: float, d1: float
) -> dict[str, float]:
    """Exact split of the INR gold log return (eq. 10.16):

        ln(P1/P0) = ln(usd1/usd0) + ln(fx1/fx0) + ln((1+d1)/(1+d0)).

    Return a dict with keys 'gold_usd', 'fx', 'duty' (the three terms) and
    'total' = ln(parity_1 / parity_0) computed from import_parity_inr_10g directly.
    """
    raise NotImplementedError

No runs yet

The stub's functions raise NotImplementedError until you write them. Fill in one, press Run tests, and watch its tests turn green.

Reference solution

Available after your first run, with an account approved for code runs.

Create a free account

Reading is open to everyone. An account keeps your progress, answers, notes and code drafts, and gives you the study plan, the journals and the simulator. Running code needs an account approved for it.