Future Valuation Date: pricing a portfolio at a future date with today's market data #334
mattmenefee
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi all,
I've been working on a feature that I think could be a useful addition to ORE, and I'd like to gauge interest before putting together a formal PR.
The Use Case
The question is simple: "What would this portfolio be worth on a future date, given today's market data?"
This is a common what-if analysis in risk management — you want to see projected P&L at a future horizon using your current yield curve, without shifting the entire evaluation date (which would break fixings, curve references, and cashflow filtering). Some concrete scenarios:
Key Finding: QuantLib Already Supports This
The
DiscountingSwapEnginealready acceptsnpvDateandsettlementDateconstructor parameters that correctly value a swap at a future date:Setting
settlementDateto the same date excludes cashflows that have already "occurred" relative to the what-if date. And becauseevaluationDatestays at asOf, all fixings between asOf and the future date are automatically forecast from the forwarding curve — no synthetic fixings needed.The gap is that ORE's engine builders never pass these parameters through, so the capability sits unused.
Proposed Implementation
The implementation adds a
valuationDateparameter to the<Setup>section ofore.xml, treating it as a global pricing context alongsideasofDate:Omitting the parameter preserves current behavior (valuation at asOf).
How it flows through the architecture
This uses the existing
globalParametersmechanism (already used forGenerateAdditionalResultsandRunType), so there are no changes to theEngineFactoryconstructor or theEngineBuilder::init()signature.Changes in the initial phase
Core wiring:
InputParameters— newvaluationDate_member with getter/setter, following theasof_patternoreapp.cpp— parsevaluationDatefromore.xml<Setup>sectionanalytic.cpp— inject intoEngineData::globalParameters()SwapEngineBuilder— read fromglobalParameters_, pass asnpvDateandsettlementDatetoDiscountingSwapEngineOptionlet time decay fix (caps/floors):
ImpliedOptionletVolatilityStructureclass in QuantExt — a header-only wrapper (analogous toImpliedVolTermStructurefor equity/FX vols) that presents an optionlet vol surface with a shifted reference date, so that σ√T correctly shrinks when the valuation date moves forwardCapFlooredIborLegEngineBuilder— wraps the market's optionlet vol surface with the future reference date whenValuationDateis setSwaption time decay fix (European swaptions):
ImpliedSwaptionVolatilityStructureclass in QuantExt — same pattern as the optionlet wrapper, but for the 2D swaption vol surface. TheoptionTimedimension is shifted bytimeOffset_(reducing time-to-exercise), whileswapLengthpasses through unchanged (the underlying swap duration is independent of the pricing date)EuropeanSwaptionEngineBuilder— wraps the market's swaption vol surface whenValuationDateis setValuationDateis set — LGM calibration is anchored toevaluationDateand forward-starting it is non-trivialStress testing support:
runStressTest()injectsValuationDateinto the stress engine'sglobalParameters, so stressed NPVs are computed at the future dateCashflowReportCalculatorpassesvaluationDatethrough togenerateCashflowReportData(), so stress cashflow DFs are correctly re-anchoredSafety net:
supportedGlobalParameters()onEngineBuilder(returns empty set by default). Builders that handleValuationDateoverride it.EngineFactory::builder()checks and emits aStructuredConfigurationWarningMessageifValuationDateis set but the builder doesn't support it — because unlike other global parameters, silently ignoringValuationDateproduces wrong NPVs rather than just missing output.Example and test:
FutureValuationDateexample with three USD SOFR trades (vanilla swap, interest rate cap, European swaption), run in four configurations (baseline pricing, future pricing, baseline stress, future stress) with real LSEG market datarun.pycovering NPV comparison, DF re-anchoring, optionlet time decay, swaption time decay, stress base NPV consistency, and stress cashflow cross-checksOREData/test/swapnpvdate.cppcovering baseline, future date, date = asOf, past date rejection, and cashflow exclusionInstrument coverage and phased rollout
Currently supported (Phase 1):
SwapEngineBuilder) — uses engine's nativenpvDate/settlementDateCapFlooredIborLegEngineBuilder) — wraps optionlet vol viaImpliedOptionletVolatilityStructureEuropeanSwaptionEngineBuilder) — wraps swaption vol viaImpliedSwaptionVolatilityStructureNot yet supported (Phase 2):
npvDate/settlementDatenatively — cross-currency swaps (DiscountingCurrencySwapEngine), FX forwards, equity forwards, commodity swaps, bonds. These would follow the same pattern of reading fromglobalParameters_.npvDatesupport inBlackCapFloorEngine/BachelierCapFloorEngineevaluationDate; forward-starting it is a separate, more complex effort. A runtime warning is emitted when this combination is attempted.What doesn't change
npvDate/settlementDatenativelyevaluationDateinstrument->NPV()which reflects the engine'snpvDatenpvDateDiscountandvaluationDateValuationDateis correctly preservedValuationDateis a global engine parameter, not per-trade; no cache key change neededQuestions for the Maintainers
ore.xml<Setup>section the right home for this parameter, or would you prefer it elsewhere?supportedGlobalParameters()virtual method a reasonable approach for the safety warnings, or would you prefer a different mechanism?ImpliedOptionletVolatilityStructure/ImpliedSwaptionVolatilityStructureapproach for handling option time decay?I have a working implementation with tests and an example that I'd be happy to clean up into a PR if there's interest. Feedback welcome!
Beta Was this translation helpful? Give feedback.
All reactions