Guides

Market Context

A MarketContext is an immutable container that bundles all pricing inputs into a single object: evaluation date, named curves, historical fixings, FX rates, and business calendars. Instead of threading curves=, fixings=, and fx= through every pricing call, you pass a single ctx= argument.

This simplifies multi-curve workflows, makes serialization straightforward (one JSON blob captures the entire pricing environment), and eliminates a common source of errors where curves and fixings get out of sync.

For complete method signatures and parameter details, see the API reference.

Setup

Build a realistic USD SOFR discount curve via Solver calibration:

sofr_curve = DiscountCurve(
    {
        effective: 1.0,
        datetime.date(2025, 7, 16): 1.0,
        datetime.date(2025, 9, 16): 1.0,
        datetime.date(2025, 12, 16): 1.0,
        datetime.date(2026, 6, 16): 1.0,
        datetime.date(2027, 6, 16): 1.0,
        datetime.date(2028, 6, 16): 1.0,
        datetime.date(2030, 6, 16): 1.0,
        datetime.date(2035, 6, 16): 1.0,
    },
    interpolation="log_linear",
    convention="act360",
    id="sofr",
)

dep_1m = Deposit(effective=effective, termination="1m", rate=0.0, convention="act360")
fra_3m = FRA(effective=effective, termination="3m", fixed_rate=0.0, convention="act360")
fra_6m = FRA(effective=effective, termination="6m", fixed_rate=0.0, convention="act360")
irs_1y = IRS(effective=effective, termination="1y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")
irs_2y = IRS(effective=effective, termination="2y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")
irs_3y = IRS(effective=effective, termination="3y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")
irs_5y = IRS(effective=effective, termination="5y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")
irs_10y = IRS(effective=effective, termination="10y", frequency="a", fixed_rate=0.0, convention="act360", float_convention="act360")

instruments = [
    (dep_1m, 0.0430),
    (fra_3m, 0.0425),
    (fra_6m, 0.0415),
    (irs_1y, 4.00),
    (irs_2y, 3.85),
    (irs_3y, 3.75),
    (irs_5y, 3.70),
    (irs_10y, 3.85),
]

my_solver = Solver(curves=[sofr_curve], instruments=instruments)
result = my_solver.iterate()
assert result.converged
sofr_curve = my_solver.get_curve(0)

Creating a Context

Create a FixingStore with historical SOFR fixings, then bundle everything into a MarketContext:

store = FixingStore()
store.set("SOFR", {
    datetime.date(2025, 6, 13): 0.0432,
    datetime.date(2025, 6, 12): 0.0431,
    datetime.date(2025, 6, 11): 0.0430,
})

ctx = MarketContext(
    eval_date=effective,
    curves={"sofr": sofr_curve},
    fixings=store,
)

assert ctx.eval_date == effective
assert "sofr" in ctx.curves
assert ctx.fixings is store
assert ctx.curve("sofr") is not None

From Solver

The production onramp is MarketContext.from_solver(). It extracts all curves from a calibrated Solver by their string IDs (set via the id= parameter on each curve). This is the recommended way to build a context after calibration:

ctx2 = MarketContext.from_solver(my_solver, eval_date=effective, fixings=store)

# Curves are extracted by their id= parameter ("sofr" in this case)
assert "sofr" in ctx2.curves
retrieved = ctx2.curve("sofr")
assert retrieved is not None

Pricing with ctx=

The key user benefit: pass ctx= instead of explicit curves=. The instrument resolves its curves from the context by disc_curve_id:

irs = IRS(
    effective=effective,
    termination="5y",
    frequency="a",
    fixed_rate=0.037,
    convention="act360",
    float_convention="act360",
    disc_curve_id="sofr",
)

# Explicit curve passing
npv_explicit = irs.npv(curves=sofr_curve)

# Context-based pricing -- same result, cleaner API
npv_ctx = irs.npv(ctx=ctx2)

assert abs(float(npv_explicit) - float(npv_ctx)) < 1e-8

Copy-on-Modify

MarketContext is immutable. The with_* methods return new contexts, leaving the original unchanged:

ctx_bumped = ctx2.with_eval_date(datetime.date(2025, 7, 16))
assert ctx2.eval_date == effective
assert ctx_bumped.eval_date == datetime.date(2025, 7, 16)

ctx_extra = ctx2.with_curve("eur_curve", sofr_curve)
assert "eur_curve" in ctx_extra.curves
assert "eur_curve" not in ctx2.curves

Analytics

Convenience query methods let you interrogate the context directly, without extracting individual curves:

# Discount factor 1 year out
df = ctx2.discount_factor("sofr", datetime.date(2026, 6, 16))
assert 0.9 < float(df) < 1.0

# 6-month forward rate
fwd = ctx2.forward_rate("sofr", datetime.date(2025, 12, 16), datetime.date(2026, 6, 16))
assert 0.01 < float(fwd) < 0.08

# 2-year zero rate
zr = ctx2.zero_rate("sofr", effective, datetime.date(2027, 6, 16))
assert 0.01 < float(zr) < 0.08

Context Diffing

Compare two contexts to see what changed. The diff() method returns a dict summarizing differences across all fields:

ctx_shifted = ctx2.with_curve("sofr", sofr_curve.shift(10))
d = ctx_shifted.diff(ctx2)

assert "eval_date" in d
assert "curves" in d
assert d["eval_date"]["changed"] is False
assert "sofr" in d["curves"]["common"]

Serialization Round-Trip

Serialize a context to JSON and restore it. The restored context produces identical pricing results:

json_str = ctx2.to_json()
assert isinstance(json_str, str)
assert len(json_str) > 0

ctx_restored = MarketContext.from_json(json_str)
assert ctx_restored.eval_date == ctx2.eval_date
assert "sofr" in ctx_restored.curves

# Price the same IRS against the restored context
npv_restored = irs.npv(ctx=ctx_restored)
assert abs(float(npv_ctx) - float(npv_restored)) < 1e-8

Next Steps

On this page