Valuation, quotation, and model choice¶
Each product has its own quotation conventions and supported analytics.
Value requests monetary PV, while FairRate requests the product’s
breakeven rate or price. Bonds additionally use price or spread anchors to
specify the valuation.
The examples below reuse reference and context from
A position, a market, and a valuation. Enter the context again for each calculation; its
market objects are reconstructed as needed.
Build products explicitly¶
Makers retain construction parameters and expose build() and
build_using(context). Both return the same concrete product type.
maker.is_resolved() reports whether date inputs and required market
observations are already resolved. build() checks this first; an unresolved error identifies
the missing input and explains how to supply it or call build_using(context).
Contract validation still occurs when the maker is ready.
build() raises UnresolvedProductErr for unresolved dates or market
observations. Use build_using(context) for date tenors, including maturity
tenors paired with an explicit start date.
maker = qs.MakeSwap(
identifier="spot-5y", start_tenor="0D", tenor="5Y",
notional=1_000_000, fixed_rate=0.03, currency="USD", market_index="SOFR",
)
with context:
instrument = maker.build_using(context)
trade = qs.Trade(instrument, context.evaluation_date)
result = context.evaluate(trade)
The maker remains reusable. The built instrument retains its dates and
contractual principal across valuations and scenarios. evaluate accepts
concrete trades; it does not build products or supply execution dates.
MakeTrade(..., trade_tenor="0D").build_using(context) provides an
explicit construction path for a relative execution date.
Scheduled makers use start_tenor and tenor; options use expiry_tenor
and forwards use delivery_tenor. Swaptions use expiry_tenor and an
underlying tenor. A date and tenor for the same field are mutually exclusive.
PricingContext(anchor_fixings=True, ...) requires stored index fixings
strictly before evaluation. Today and future observations project, even when
today’s fixing is stored. Set context.anchor_fixings = False to project all
observations; the curve must support the observation interval.
Floating amortizing loans require an explicit amortization_rate or
MakeLoan(...).build_using(context). Construction obtains the initial
index observation, spread, and payoff before fixing the principal schedule.
Missing historical observations fail during construction. Python loan
serialization retains the initial amortization rate for context-free reconstruction.
Rates and direction¶
Vanilla swaps, basis swaps, both cross-currency swap forms, deposits, and floating-rate notes expose contractual schedules and rate conventions. Their projections require the appropriate index curves and any historical fixings. Each constructor defines its leg ordering and the meaning of receive/pay side. Use those definitions when assigning the position’s direction.
Trade.notional overrides the position size without modifying the shared
instrument. Cross-currency positions have domestic and foreign overrides;
futures use num_contracts. Position amounts are finite and nonnegative in
the Python interface. FX forwards, equity forwards, options, CDS, and caplets
have no contractual notional field in their Python product specification;
their size is carried by the trade, normally defaulting to one.
For rate futures, a price such as 95.25 implies an annual decimal rate of
0.0475. contract_size is currency per price point, not currency per
basis point. The default 2500 therefore corresponds to 25 currency units for a
0.01 price-point change before contract-count and side conventions. Do not
feed a futures price to an API expecting a decimal interest rate.
An InterestRate stores one rate and its quotation conventions, not a term
structure. Use its compound/discount factor operations for that stored
quotation and a constructed curve’s forward query for market-dependent forwards.
Straight bonds: price is a quotation convention¶
Bond terms describe coupon, principal, schedule, and currency. The position
owns the observed quote and valuation choices:
pricing_anchor, settlement_date, and spread_day_counter.
An explicit anchor is required; absence does not mean a zero spread.
The next calculation asks what continuously compounded Z-spread is consistent
with a clean quote of 99.25. Clean and dirty prices are quoted per the
instrument’s quotation units, normally 100; returned price is monetary PV.
Accrued interest distinguishes the two quotation anchors.
bond = qs.FixedRateBond(
"USD-BOND", reference, reference + "5Y", 1_000_000,
0.04, "USD", day_counter="Actual360",
compounding="Simple", rate_frequency="Annual",
payment_frequency="Semiannual",
)
bond_trade = qs.Trade(
bond, reference,
pricing_anchor=qs.BondPricingAnchor.clean_price(99.25),
settlement_date=reference,
)
with context:
bond_result = context.evaluate(bond_trade, [
"Value", "YieldToMaturity", "ModifiedDuration",
"ZSpread", "Sensitivities",
])
print(bond_result.price, bond_result.z_spread)
print(bond_result.yield_to_maturity, bond_result.modified_duration)
BondPricingAnchor.z_spread(0.0) instead asks for the unspread model value;
dirty_price(...) includes accrued interest. with_pricing_anchor creates
a new trade rather than mutating contractual cashflows. Curve risk and the
bond’s <identifier>.ZSpread risk are different derivative coordinates.
A model-implied spread from a price anchor is a calibration result, not a
second independently observed market quote.
The context’s fixed-income discount policy maps currency to curve. When
prefer_instrument_index=True, straight products with an explicit contractual
discount index can take precedence; setting it False enforces the mapping.
The pricer reports an error for an unmapped payment currency.
Issuer-callable stepped bonds¶
Use SteppedCouponBond for stepped coupons, amortization and issuer calls. Coupon
templates and outstanding-notional percentages are positional, and their last
entries repeat through subsequent periods. PayoffOps represents native
coupon algebra, for example a geared floating coupon with floor and cap.
Serialized coupon objects retain this expression tree.
Calls are issuer calls with explicit strikes and either a discrete date or an inclusive date window. On an exercise date, same-day contractual coupons and amortization are paid; the call strike caps the remaining value. A call strike is quoted in the bond’s units, not as a decimal interest rate.
Choose TreeBased or BackwardFourierEvolution through a
PricerConfiguration with an appropriate Hull–White model. For example, the
configuration below specifies a tree with fixed mean reversion and short-rate
volatility.
tree_configuration = qs.PricerConfiguration.from_dict({
"instrument_type": "SteppedCouponBond",
"pricer": {"TreeBased": {"time_grid": {
"steps_per_year": 48, "max_steps": 4096,
"day_counter": "Actual365",
}}},
"models": [{"market_index": "SOFR", "model": {"HullWhite": {
"alpha": 0.05, "parameter_source": {"Fixed": {"sigma": 0.01}},
}}}],
})
With calls present, use BondPricingAnchor.OAS(0.0) for a zero-OAS model
value, or a clean/dirty anchor to imply OAS. Without call entries use a
Z-spread anchor. Yield to maturity and modified duration are intentionally not
defined for this stepped/callable product. Tree/Fourier grid and model choices
are numerical assumptions; convergence and calibration diagnostics belong in
the valuation record. The
callable-bond study works through
these choices.
Loans: contractual principal and expected receipts¶
A loan separates the contractual repayment schedule from the valuation of
prepayment and default. Specify LoanRepaymentStructure and one or more
native coupon templates; select discounting through the context/pricer policy,
not a discount_index product field. French amortization fixes principal from
the initial amortization rate; later floating coupon changes do not silently
rewrite the original principal schedule.
Set accrual conventions on the coupon: floating coupons use their explicit
day_counter, or the index convention when omitted; fixed coupons use their
InterestRate day counter. The loan uses the same convention for principal
amortization. The former Loan(day_counter=...) argument has been removed;
move it to each coupon. French payments are level through the first reset when
amortization_rate matches that reset’s all-in fixing, including spread and
payoff bounds. Later resets can change payments without changing principal.
loan = qs.Loan(
"USD-LOAN", reference, reference + "2Y", 100_000,
[qs.FixedRateCoupon(rate=qs.InterestRate(
rate=0.06, day_counter="Actual360",
))],
"USD", repayment=qs.LoanRepaymentStructure.bullet(),
payment_frequency="Quarterly",
prepayment=qs.PrepaymentModelConfiguration.constant_annual_cpr(0.10),
)
original_terms = loan.to_dict()
with context:
loan_result = context.evaluate(
qs.Trade(loan, reference),
["Value", "Cashflows", "Sensitivities"],
)
print(loan_result.price)
assert loan.to_dict() == original_terms
Value is signed PV of future expected receipts, excluding the initial
advance. A lender’s origination NPV therefore needs the advance considered
separately. The loan cashflow report is pricer-specific: scheduled, prepayment,
and recovery receipts occupy legs 0, 1, and 2. Do not apply the generic swap
cashflow-table interpretation indiscriminately to this report.
Prepayment may be absent, constant annual CPR, or refinancing-incentive driven.
The latter requires refinancing_benchmark_index in the context’s native
PricerConfiguration.loan(...); the
benchmark is an index rate, excluding replacement-loan lending spreads and
fees. Payment frequency, reset frequency, fixing lag, and observation tenor are
independent contractual inputs. coupon_fixing_requests() identifies required
historical observations, not a promise that missing fixings will be projected.
Request.FairRate(index) selects a zero-based contiguous loan coupon/spread
segment and returns its scalar in fair_rate. It solves earlier segments
first, shortening the loan and repaying remaining capital at each segment end;
solved rates stay fixed when extending to the requested segment. Mixed and
stepped schedules follow this same rule. The original contract is unchanged.
Each evaluation accepts one target; invalid indices and bounded coupons fail.
The search interval for each segment is [-5%, 50%].
rebuild_amortization_for_fair_rate=False preserves principal during trial rates; True reconstructs trial
amortization without changing the original contract. Choose the setting that
matches whether the proposed new coupon should also change contractual
principal repayments. Set these options through native pricer configuration
and keep using context.evaluate(...):
context.pricer_configurations = [qs.PricerConfiguration.loan(
rebuild_amortization_for_fair_rate=True,
refinancing_benchmark_index=qs.MarketIndex.SOFR,
)]
with context:
par = context.evaluate(qs.Trade(loan, reference), [qs.Request.FairRate(0)])
The context dispatches ordinary qs.Trade loan positions directly to the
core Rust LoanPricer, including portfolio members and scenarios. Discounting
follows context.fixed_income_policy. Omitting the loan configuration uses
native defaults: no refinancing benchmark and preserved amortization during
fair-rate trials. The loan study reconciles the settings.
Options, calibration, and simulation¶
Caps/floors, caplets/floorlets, European equity/FX options, and European
swaptions expose distinct pricers and market requirements. The rate cap
interfaces select Black pricing; an equity option needs its spot fixing and
forward/dividend conventions; a swaption needs an explicit
EuropeanSwaption/HullWhiteClosedForm configuration.
This swaption calculation reuses the transparent curve market, with a fixed absolute short-rate sigma. It is a model illustration rather than a fit to a quoted volatility surface.
model_configuration = qs.PricerConfiguration.from_dict({
"instrument_type": "EuropeanSwaption",
"pricer": "HullWhiteClosedForm",
"models": [{"market_index": "SOFR", "model": {"HullWhite": {
"alpha": 0.1, "parameter_source": {"Fixed": {"sigma": 0.01}},
}}}],
})
context.pricer_configurations = [model_configuration]
swaption = qs.EuropeanSwaption(
"PAYER-1Y5Y", reference + "1Y", reference + "6Y",
0.03, 1_000_000, "USD", "SOFR", swaption_type="Payer",
)
with context:
option_result = context.evaluate(
qs.Trade(swaption, reference), ["Value", "Sensitivities"],
)
print(option_result.price)
For calibration replace the fixed parameter source with Calibrated,
specifying Surface or Cube and an explicit calibration basket, and
supply that source’s quotes/configuration to the context. Cube baskets select
one tenor per expiry. Quote side and strike-domain policies apply during calibration as well as
later volatility queries.
Prefer absolute numeric option strikes, as in the example. A serialized
Relative strike does not have one library-wide financial interpretation:
the generic native Strike.resolve helper uses \(K=F+\mathrm{offset}\),
while Black and Hull–White cap/caplet pricers independently use
\(K=F(1+\mathrm{pct})\). Thus a caplet payload {"Relative": 0.10} turns a
4% forward into a 4.4% strike, not a 14% strike. The equity/FX helper convention
must not be transferred to a rate-option consumer. Volatility lookup
coordinates are also distinct from the payoff strike; inspect both before
comparing valuations.
Constructed Hull–White simulation diagnostics, when available, report
identifier, expiry, times t/big_t, market volatility, market/model prices,
calibrated sigma, forward, strike, and signed price_error (model minus
market). Fixed-parameter or other model simulations can return no diagnostics.
Generated simulation paths are materialized from numeric inputs; their storage
does not retain calibration pillars or imply differentiated spot/volatility
paths. This differs from model-backed LgmMarketModel computations used by
scripting and XVA. The standalone Hull–White path generator uses Euler state evolution and
requires positive alpha. Assess its discretization by refining the grid and
its sampling precision by comparing path counts and seeds. Direct LGM dynamics
support a separate zero-lambda limit.
FX, equity, and credit¶
Use MarketIndex.equity("SPX") for a typed equity identity and
MarketIndex.fx_pair("EUR", "USD") for a pair. A direct Python string is
accepted only where its documented native enum form is supported.
The equity forward needs an evaluation-date spot fixing and a forward curve;
absence of a dividend curve means zero dividends.
FX direction is quoted currency per base unit. A market-implied forward and a
contracted forward are different values. The current FX-forward pricer uses
spot * df_quote / df_base; the FX-option path uses its separately
implemented base/quote carry convention. Do not silently replace one formula
with the other in a parity claim. Check the actual instrument pricer, discount
policy, and returned currency before constructing a hedge.
CDS valuation additionally requires a survival/credit curve and recovery.
CreditCurveConfiguration bootstraps native credit quotes and can be supplied
as credit_curves to the context. Recovery and spread units are explicit
inputs; a flat-spread hazard approximation is not the same model as a
bootstrapped survival term structure.
Scripted payoffs¶
Python’s scripting bridge evaluates dated payoff events through the native engine. A script can retain state across events and declare payments that are discounted by its market model. The example below pays 100 USD in two years under a zero-volatility model and compares its value with the context curve.
script = {
"name": "SCRIPTED-PAYMENT", "currency": "USD",
"result_variable": "note",
"events": [{
"date": str(reference),
"code": 'note = 0; note pays 100 on "2028-02-24";',
}],
"models": [{"market_index": "SOFR", "model": {"Lgm": {
"lambda": 0.05, "parameter_source": {"Fixed": {"sigma": 0.0}},
}}}],
}
with context:
scripted_values = context.evaluate_script(script, n_paths=8, seed=42)
expected = 100 * context.curve("SOFR").discount_factor("2028-02-24")
assert abs(scripted_values["note"] - expected) < 1e-8
evaluate_script returns a plain dictionary of averaged numeric variables,
not Python sensitivity or cashflow tables. Models must be configured for the
discount and requested forward indices; there is no implicit zero-volatility
fallback. Context pricer assignments are overridden by simulation assignments,
then by inline script assignments. A script in a non-base currency requires
an explicit discount index. This bridge currently assembles rate models, not a
general correlated FX/equity-factor system.
With stochastic models, comparisons also depend on the estimator. Martingale controls can adjust the selected result variable, while native cashflow diagnostics retain raw sampled payment PVs. Their sum can therefore differ from the controlled result at finite path counts. Select a variable that accumulates the intended payments and assess differences using the sampling error and comparison tolerance.
Continue with Risk, netting, and extensibility for full-revaluation checks and exposure aggregation, or consult the official studies for larger product and calibration experiments.