import sys, numpy as np
sys.path.insert(0,'..')
from engine import ch03MFAA Chapter 3 Laboratory
State Prices and Pricing Bounds (book §3.9)
Build a finite-state traded market; watch the risk-neutral set appear, shrink, and vanish; compute no-arbitrage bounds with hedging portfolios. Makes incompleteness visible.
1. The trinomial market and its no-arbitrage interval (Exercise 3.9 anchor)
Bond R=1.02, stock (1.15, 1.00, 0.85); claim X=(0.15, 0, 0). Target interval (0.0196, 0.0833).
m = ch03.trinomial_market()
X = np.array([0.15, 0, 0])
audit = ch03.arbitrage_audit(m)
print('arbitrage-free:', audit['arbitrage_free'])
pb = ch03.price_bounds(m, X)
print(f"interval = ({pb['pi_low']:.4f}, {pb['pi_bar']:.4f})")
print('super-hedge portfolio (bond, stock):', [round(x,4) for x in pb['theta_super']])
print('duality gap:', pb['duality_gap_upper'])arbitrage-free: True
interval = (0.0196, 0.0833)
super-hedge portfolio (bond, stock): [-0.4167, 0.5]
duality gap: 0.0
2. E1 — Completeness as a knife edge
Add an Arrow security on the middle state; the interval collapses to a point.
m2 = ch03.add_asset(m, ch03.arrow_security(m,1), 0.4/m.R)
pb2 = ch03.price_bounds(m2, X)
print(f"width {pb2['width']:.6f}, value {pb2['pi_low']:.4f} (book 0.0539)")width 0.000000, value 0.0539 (book 0.0539)
3. E2 — Manufacture an arbitrage
Underprice the stock until the audit trips and read the Farkas certificate.
m3 = ch03.Market(A=m.A.copy(), price=np.array([1.0,0.80]), prob=m.prob.copy(), R=m.R)
aud = ch03.arbitrage_audit(m3)
print('arbitrage-free:', aud['arbitrage_free'])
if not aud['arbitrage_free']:
print('arbitrage portfolio:', [round(x,4) for x in aud['arbitrage_portfolio']])
print('cost:', round(aud['cost'],4), 'state payoff:', [round(x,4) for x in aud['state_payoff']])arbitrage-free: False
arbitrage portfolio: [-0.8333, 1.0]
cost: -0.0333 state payoff: [0.3, 0.15, -0.0]
4. E3 — Spanning decomposition
The residual (untraded) risk accounts for the persistent interval width.
sp = ch03.spanning_decomposition(m, X)
print(f"spanned fraction {sp['spanned_fraction']:.4f}, residual norm {sp['residual_norm']:.4f}")spanned fraction 0.9129, residual norm 0.0354
5. Validation checks
v = ch03.validation_checks()
for k,d in v.items():
if isinstance(d,dict): print(k, 'PASS' if d['pass_'] else 'FAIL')
print('ALL:', v['all_pass'])V1_trinomial_anchor PASS
V2_duality_gap PASS
V3_completeness PASS
V4_arbitrage_detect PASS
ALL: True