Comparing curves, swap values and quote risk across engines¶
An independent implementation can help assess curve and valuation results when contract conventions and risk inputs are aligned. This study uses ORE/CurveEngine to reconstruct four curves and price four swaps from the historical market dated 11 November 2025.
sensitivity_config.json contains the independent market inputs, and
qs_results.json contains recorded Rust outputs from that snapshot. We
compare USD SOFR, TermSOFR3m, CLP ICP and USD-collateralized CLP curves, then
match swap PVs and sensitivities by financial identifiers.
The contracts use NullCalendar schedules, Actual/360 accrual, USD reporting and spot of 935 CLP per USD. The historical vanilla swaps use period-forward coupons. Credit and funding adjustments are excluded.
Use ORE 1.8.14.1 and CurveEngine source revision
d7e000d,
which supports the input’s OvernightIborBasis helpers. The published
CurveEngine 1.1.6 release lacks that helper. Install the optional dependencies
with the commands in the setup guide
and keep both JSON files beside the notebook. The calculation
reads those local inputs and recorded outputs; it does not run the current
Rust binary. Package versions are printed below.
Computational study. Canonical notebook · Companion files and setup. Download this notebook.
This page renders saved outputs; the documentation build does not execute it. Run the canonical notebook from its own directory with the checkout-built binding to reproduce the computation. Each study records its market inputs and numerical assumptions.
import json
from pathlib import Path
import importlib.metadata
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import ORE as ore
from curveengine import CurveEngine
directory = Path.cwd()
configuration = json.loads((directory / "sensitivity_config.json").read_text())
recorded = json.loads((directory / "qs_results.json").read_text())
reference = ore.DateParser.parseISO(configuration["refDate"])
ore.Settings.instance().evaluationDate = reference
engine = CurveEngine(configuration)
curve_names = ["SOFR", "TermSOFR3m", "ICP", "Collateral(CLP/USD)"]
curves = {name: engine.getCurve(name) for name in curve_names}
indices = {name: engine.getIndex(name) for name in curve_names}
recorded_products = {product["label"]: product for product in recorded["products"]}
print("Historical reference date:", reference.ISO())
print("ORE version:", getattr(ore, "__version__", "not exposed"))
try:
print("CurveEngine version:", importlib.metadata.version("curveengine"))
except importlib.metadata.PackageNotFoundError:
print("CurveEngine version: source checkout, no installed distribution metadata")
Historical reference date: 2025-11-11
ORE version: 1.8.14.1
CurveEngine version: 1.1.6
Comparing curves at common dates¶
We query the independent curves at the recorded Rust node dates. Aligning by date allows each engine to retain its own internal node representation. The signed discount-factor residuals show the resulting construction differences across maturities.
curve_rows = []
for reference_curve in recorded["curves"]:
name = reference_curve["name"]
for node in reference_curve["nodes"]:
date = ore.DateParser.parseISO(node["date"])
independent_df = curves[name].discount(date)
curve_rows.append(
{
"curve": name,
"date": node["date"],
"recorded_df": node["discount_factor"],
"independent_df": independent_df,
"signed_difference": independent_df - node["discount_factor"],
}
)
curve_comparison = pd.DataFrame(curve_rows)
curve_comparison.groupby("curve")["signed_difference"].agg(["min", "max"]).assign(
max_absolute_difference=lambda frame: frame.abs().max(axis=1)
)
| min | max | max_absolute_difference | |
|---|---|---|---|
| curve | |||
| Collateral(CLP/USD) | -9.586776e-14 | 5.928591e-14 | 9.586776e-14 |
| ICP | -1.550982e-13 | 5.928591e-14 | 1.550982e-13 |
| SOFR | -7.505108e-14 | 1.508793e-13 | 1.508793e-13 |
| TermSOFR3m | -4.962697e-14 | 1.996736e-13 | 1.996736e-13 |
figure, axes = plt.subplots(1, 2, figsize=(11, 4))
for name, frame in curve_comparison.groupby("curve"):
dates = pd.to_datetime(frame["date"])
axes[0].plot(dates, frame["independent_df"], marker=".", label=name)
axes[1].plot(dates, frame["signed_difference"].abs(), marker=".", label=name)
axes[0].set(
title="Independently constructed discount factors", ylabel="Discount factor"
)
axes[1].set(
title="Absolute DF difference against recorded Rust", ylabel="Absolute difference"
)
for axis in axes:
axis.set_xlabel("Maturity date")
axis.legend(fontsize=8)
figure.autofmt_xdate()
figure.tight_layout()
plt.show()
Valuing the four positions¶
For a single-currency receive-fixed swap,
\(V=N[K\sum_i\tau_iD_i-\sum_jF_j\delta_jD_j]\).
ICP projects CLP coupons, while Collateral(CLP/USD) discounts them under USD
collateral. Dividing local CLP PV by 935 expresses it in USD.
The cross-currency swap receives CLP ICP plus 50 bp and pays USD SOFR. We match each valuation to the recorded position by its identifier.
calendar = ore.NullCalendar()
start = calendar.advance(reference, ore.Period("2D"))
maturity = calendar.advance(start, ore.Period("5Y"))
basis = ore.Actual360()
def schedule(frequency):
return ore.Schedule(
start,
maturity,
ore.Period(frequency),
calendar,
ore.Following,
ore.Following,
ore.DateGeneration.Forward,
False,
)
sofr_handle = ore.YieldTermStructureHandle(curves["SOFR"])
collateral_handle = ore.YieldTermStructureHandle(curves["Collateral(CLP/USD)"])
positions = {}
definitions = [
("SOFR OIS 5Y Swap", "SOFR", 10_000_000, 0.0378, sofr_handle, 1.0),
("TermSOFR3m 5Y Swap", "TermSOFR3m", 10_000_000, 0.04, sofr_handle, 1.0),
(
"CLP ICP OIS 5Y Swap (USD collateral)",
"ICP",
5_000_000_000,
0.0440,
collateral_handle,
1 / 935,
),
]
for label, index, notional, coupon, discount_handle, usd_conversion in definitions:
swap = ore.VanillaSwap(
ore.VanillaSwap.Receiver,
notional,
schedule(ore.Semiannual),
coupon,
basis,
schedule(ore.Quarterly),
indices[index],
0.0,
basis,
)
swap.setPricingEngine(ore.DiscountingSwapEngine(discount_handle))
positions[label] = {"instrument": swap, "usd_conversion": usd_conversion}
spot_clp_per_usd = 935.0
fx_clp_to_usd = ore.SimpleQuote(1 / spot_clp_per_usd)
xccy = ore.CrossCcyBasisSwap(
10_000_000,
ore.USDCurrency(),
schedule(ore.Quarterly),
indices["SOFR"],
0.0,
1.0,
10_000_000 * spot_clp_per_usd,
ore.CLPCurrency(),
schedule(ore.Quarterly),
indices["ICP"],
0.005,
1.0,
)
xccy.setPricingEngine(
ore.CrossCcySwapEngine(
ore.USDCurrency(),
sofr_handle,
ore.CLPCurrency(),
collateral_handle,
ore.QuoteHandle(fx_clp_to_usd),
)
)
xccy_label = "Cross-Currency Swap CLP/USD 5Y (receive CLP ICP, pay USD SOFR)"
positions[xccy_label] = {"instrument": xccy, "usd_conversion": 1.0}
def position_pv_usd(label):
position = positions[label]
return position["instrument"].NPV() * position["usd_conversion"]
valuation = pd.DataFrame(
[
{
"position": label,
"independent_pv_usd": position_pv_usd(label),
"recorded_pv_usd": recorded_products[label]["npv"],
}
for label in positions
]
).set_index("position")
valuation["difference_usd"] = (
valuation["independent_pv_usd"] - valuation["recorded_pv_usd"]
)
valuation
| independent_pv_usd | recorded_pv_usd | difference_usd | |
|---|---|---|---|
| position | |||
| SOFR OIS 5Y Swap | 342.595511 | 342.595511 | -7.479684e-09 |
| TermSOFR3m 5Y Swap | 88754.684511 | 88754.684511 | -1.411536e-09 |
| CLP ICP OIS 5Y Swap (USD collateral) | 4.440637 | 4.440638 | -8.615045e-07 |
| Cross-Currency Swap CLP/USD 5Y (receive CLP ICP, pay USD SOFR) | 158661.258109 | 158661.258108 | 1.702545e-06 |
Matching risk inputs and units¶
Calibration-helper metadata identifies the quote type and tenor. We use it to construct labels matching the recorded Rust sensitivities. Rate and spread quotes are decimals; FX-forward points are CLP per USD.
For quote \(q\), central revaluation estimates
\(\partial V/\partial q\approx[V(q+h)-V(q-h)]/(2h)\).
We use \(h=10^{-4}\) for rates and \(h=10^{-3}\) CLP per USD for forward points,
then report P&L for +1 bp or +1 CLP per USD respectively. A finally block
restores every quote after its bump.
quote_book = []
for specification in configuration["curves"]:
name = specification["curveName"]
helpers = specification["curveConfig"]["rateHelpers"]
native_quotes = engine.getQuotes(name)
for helper, native_quote in zip(helpers, native_quotes):
kind = helper["helperType"]
tenor = helper["helperConfig"]["tenor"]
if kind == "FxSwap":
label = f"FxForward_USDCLP_{tenor}_AnchorForwardPoints"
quote = native_quote["fxPoints"]
family, bump, report_move = "FX forward +1 CLP per USD", 1e-3, 1.0
else:
if kind == "Deposit":
currency = "CLP" if name == "ICP" else "USD"
label = f"FixedRateDeposit_{currency}_{name}_{tenor}"
elif kind == "OIS":
currency = "CLP" if name == "ICP" else "USD"
label = f"OIS_{currency}_{name}_{tenor}"
elif kind == "OvernightIborBasis":
label = f"BasisSwap_USD_SOFR_TermSOFR3m_{tenor}"
elif kind == "Xccy":
label = f"FixFloatCrossCurrencySwap_CLP_SOFR_USD_{tenor}"
else:
raise ValueError(f"Unmapped calibration helper: {kind}")
quote = (
native_quote["spread"]
if kind == "OvernightIborBasis"
else native_quote["rate"]
)
family, bump, report_move = "rate/spread +1 bp", 1e-4, 1e-4
quote_book.append(
{
"pillar": label,
"quote": quote,
"family": family,
"difference_step": bump,
"report_move": report_move,
}
)
pd.DataFrame(quote_book).drop(columns="quote").groupby("family").size().rename(
"quote_count"
)
family
FX forward +1 CLP per USD 3
rate/spread +1 bp 42
Name: quote_count, dtype: int64
risk_rows = []
for label in positions:
recorded_risk = {
row["pillar"]: row["exposure"]
for row in recorded_products[label]["sensitivities"]
}
for item in quote_book:
if item["pillar"] not in recorded_risk:
continue
quote, step = item["quote"], item["difference_step"]
original = quote.value()
try:
quote.setValue(original + step)
up = position_pv_usd(label)
quote.setValue(original - step)
down = position_pv_usd(label)
finally:
quote.setValue(original)
derivative = (up - down) / (2 * step)
risk_rows.append(
{
"position": label,
"pillar": item["pillar"],
"family": item["family"],
"independent_pnl_usd": derivative * item["report_move"],
"recorded_pnl_usd": recorded_risk[item["pillar"]] * item["report_move"],
}
)
risk_comparison = pd.DataFrame(risk_rows)
risk_comparison["difference_usd"] = (
risk_comparison["independent_pnl_usd"] - risk_comparison["recorded_pnl_usd"]
)
risk_comparison.groupby(["position", "family"])[
["independent_pnl_usd", "recorded_pnl_usd", "difference_usd"]
].sum()
| independent_pnl_usd | recorded_pnl_usd | difference_usd | ||
|---|---|---|---|---|
| position | family | |||
| CLP ICP OIS 5Y Swap (USD collateral) | FX forward +1 CLP per USD | 34.205693 | 34.205664 | 0.000029 |
| rate/spread +1 bp | -2388.545942 | -2388.545933 | -0.000009 | |
| Cross-Currency Swap CLP/USD 5Y (receive CLP ICP, pay USD SOFR) | FX forward +1 CLP per USD | 629.037427 | 629.037376 | 0.000051 |
| rate/spread +1 bp | -37.644554 | -37.644537 | -0.000017 | |
| SOFR OIS 5Y Swap | rate/spread +1 bp | -4567.243079 | -4567.243071 | -0.000008 |
| TermSOFR3m 5Y Swap | rate/spread +1 bp | -9185.547780 | -9185.547764 | -0.000016 |
FX spot orientation and propagation¶
The recorded USD/CLP pillar is spot in CLP per USD. ORE’s reporting conversion uses the reciprocal. We bump USD/CLP spot, update the collateral-helper inputs and set the conversion quote to its reciprocal, with contractual notionals fixed. The resulting derivative is USD per one CLP-per-USD spot move.
The recorded FX derivative equals the conversion-rescaling expression \(-V/S\). The independent experiment additionally propagates the spot move through collateral curves and the cross-currency engine. The table exposes this risk-definition discrepancy separately from the curve, PV and rate-risk comparisons.
spot_inputs = [
quote["fxSpot"]
for specification in configuration["curves"]
for quote in engine.getQuotes(specification["curveName"])
if "fxSpot" in quote
]
original_spots = [quote.value() for quote in spot_inputs]
spot_rows = []
for label in [definitions[2][0], xccy_label]:
original_conversion = positions[label]["usd_conversion"]
prices = {}
try:
for step in [-0.01, 0.01]:
shocked_spot = spot_clp_per_usd + step
for quote in spot_inputs:
quote.setValue(shocked_spot)
fx_clp_to_usd.setValue(1 / shocked_spot)
if label == definitions[2][0]:
positions[label]["usd_conversion"] = 1 / shocked_spot
prices[step] = position_pv_usd(label)
finally:
for quote, value in zip(spot_inputs, original_spots):
quote.setValue(value)
fx_clp_to_usd.setValue(1 / spot_clp_per_usd)
positions[label]["usd_conversion"] = original_conversion
independent = (prices[0.01] - prices[-0.01]) / 0.02
recorded_derivative = next(
row["exposure"]
for row in recorded_products[label]["sensitivities"]
if row["pillar"] == "USD/CLP"
)
spot_rows.append(
{
"position": label,
"independent_usd_per_1_clp_per_usd": independent,
"recorded_usd_per_1_clp_per_usd": recorded_derivative,
"difference": independent - recorded_derivative,
}
)
spot_comparison = pd.DataFrame(spot_rows).set_index("position")
# The historical oracle records a conversion-rescaling FX derivative.
rescaling = -valuation.loc[spot_comparison.index, "recorded_pv_usd"] / spot_clp_per_usd
spot_comparison["simple_rescaling_derivative"] = rescaling
spot_comparison["full_spot_risk_matches_record"] = np.isclose(
spot_comparison.iloc[:, 0], spot_comparison.iloc[:, 1], rtol=1e-5, atol=1e-5
)
spot_comparison
| independent_usd_per_1_clp_per_usd | recorded_usd_per_1_clp_per_usd | difference | simple_rescaling_derivative | full_spot_risk_matches_record | |
|---|---|---|---|---|---|
| position | |||||
| CLP ICP OIS 5Y Swap (USD collateral) | 0.084539 | -0.004749 | 0.089288 | -0.004749 | False |
| Cross-Currency Swap CLP/USD 5Y (receive CLP ICP, pay USD SOFR) | -170.849614 | -169.691185 | -1.158429 | -169.691185 | False |
sofr_risk = risk_comparison.loc[
risk_comparison["position"] == "SOFR OIS 5Y Swap"
].set_index("pillar")
sofr_risk = sofr_risk.loc[
sofr_risk[["independent_pnl_usd", "recorded_pnl_usd"]].abs().max(axis=1) > 0.01
]
axis = sofr_risk[["independent_pnl_usd", "recorded_pnl_usd"]].plot.barh(figsize=(9, 4))
axis.set(
title="SOFR swap quote risk: independent vs recorded Rust",
xlabel="USD P&L for +1 bp",
ylabel="",
)
plt.tight_layout()
plt.show()
print(f"Maximum PV difference: USD {valuation['difference_usd'].abs().max():.6g}.")
print(
f"Maximum rate/forward-point scaled-risk difference: USD {risk_comparison['difference_usd'].abs().max():.6g}."
)
Maximum PV difference: USD 1.70254e-06.
Maximum rate/forward-point scaled-risk difference: USD 0.000105289.
The comparison uses explicit tolerances for curves, PVs and scaled quote risks. Restoring bumped inputs reproduces the independent base values. The FX table retains the discrepancy between recorded conversion-rescaling risk and the propagated market-spot shock; resolving it requires reviewing the historical FX-risk calculation and its conventions.
These results apply to the recorded 2025 snapshot. For derivatives and revaluation using the current binding, see the portfolio-risk study.