quantvolt 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quantvolt-0.1.0 → quantvolt-0.2.0}/Cargo.lock +1 -1
- {quantvolt-0.1.0 → quantvolt-0.2.0}/Cargo.toml +1 -2
- {quantvolt-0.1.0 → quantvolt-0.2.0}/PKG-INFO +30 -4
- {quantvolt-0.1.0 → quantvolt-0.2.0}/README.md +26 -2
- {quantvolt-0.1.0 → quantvolt-0.2.0}/pyproject.toml +23 -5
- {quantvolt-0.1.0 → quantvolt-0.2.0}/rust/src/paths.rs +2 -2
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/__init__.py +73 -1
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/dispatch_sdp.py +8 -2
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/long_dated.py +16 -17
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/cli.py +45 -4
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/datasets.py +2 -1
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/netztransparenz.py +4 -12
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/hedging/ppa_nomination.py +4 -12
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/__init__.py +72 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/commodity.py +18 -3
- quantvolt-0.2.0/src/quantvolt/models/instruments.py +514 -0
- quantvolt-0.2.0/src/quantvolt/models/ppa.py +89 -0
- quantvolt-0.2.0/src/quantvolt/models/ppa_terms.py +444 -0
- quantvolt-0.2.0/src/quantvolt/models/units.py +224 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/__init__.py +4 -0
- quantvolt-0.2.0/src/quantvolt/numerics/_degenerate.py +75 -0
- quantvolt-0.2.0/src/quantvolt/numerics/bachelier.py +316 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/black76.py +6 -18
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/portfolio/model.py +31 -3
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/portfolio/settlement.py +2 -6
- quantvolt-0.2.0/src/quantvolt/portfolio/valuation.py +641 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/__init__.py +26 -0
- quantvolt-0.2.0/src/quantvolt/pricing/bachelier.py +105 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/power_hedge.py +1 -3
- quantvolt-0.2.0/src/quantvolt/pricing/ppa.py +855 -0
- quantvolt-0.2.0/src/quantvolt/pricing/ppa_valuation.py +372 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/cashflow_metrics.py +1 -3
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/scenarios.py +2 -1
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/testing.py +5 -1
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/workflow/modeling.py +8 -8
- quantvolt-0.1.0/src/quantvolt/models/instruments.py +0 -221
- quantvolt-0.1.0/src/quantvolt/models/ppa.py +0 -54
- quantvolt-0.1.0/src/quantvolt/portfolio/valuation.py +0 -231
- quantvolt-0.1.0/src/quantvolt/pricing/ppa.py +0 -377
- {quantvolt-0.1.0 → quantvolt-0.2.0}/.gitignore +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/LICENSE +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/rust/src/asian_mc.rs +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/rust/src/lib.rs +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/_core.pyi +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/_validation.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/_tolerance.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/dispatch_approx.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/dispatch_deterministic.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/plant.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/assets/storage.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/curvemodels/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/curvemodels/multifactor.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/curvemodels/schwartz_smith.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/curves/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/curves/arbitrage.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/curves/builder.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/base.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/commercial.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/dataset_catalog.json +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/entsoe.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/entsog.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/open_meteo.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/data/smard.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/exceptions.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/hedging/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/hedging/_conditioning.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/hedging/hybrid.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/hedging/mean_variance.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/hedging/ppa_walk_forward.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/hedging/variance_min.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/market/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/market/outages.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/market/transmission.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/market/weather.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/curve.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/discount_curve.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/greeks.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/interval.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/power_hedge.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/schedule.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/models/vol_surface.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/_normal.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/daycount.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/exotic.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/interpolation.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/monte_carlo.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/risk_adjustment.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/rootfind.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/numerics/spread_models.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/portfolio/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/_dates.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/exotic.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/futures.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/implied_vol.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/mark_to_market.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/spread_option.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/spreads.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/swap.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/tolling.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/transmission_right.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/pricing/vanilla.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/py.typed +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/_levels.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/aggregation.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/cfar.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/covariance.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/credit_var.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/engine.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/mc_var.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/risk/parametric_var.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/stats/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/stats/correlation.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/stats/descriptive.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/stats/mean_reversion.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/stats/normality.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/stats/stationarity.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/workflow/__init__.py +0 -0
- {quantvolt-0.1.0 → quantvolt-0.2.0}/src/quantvolt/workflow/criteria.py +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quantvolt
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
6
6
|
Classifier: Intended Audience :: Science/Research
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
8
9
|
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
11
|
Classifier: Programming Language :: Rust
|
|
10
12
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
11
13
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
@@ -28,7 +30,7 @@ Summary: Quantitative analysis of European power and energy markets
|
|
|
28
30
|
Keywords: energy,power,gas,carbon,quant,derivatives,risk-management,pricing,monte-carlo,forward-curve
|
|
29
31
|
Author-email: Nima Bahrami <nima@skillmix.nl>
|
|
30
32
|
License-Expression: MIT
|
|
31
|
-
Requires-Python: >=3.
|
|
33
|
+
Requires-Python: >=3.11
|
|
32
34
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
33
35
|
Project-URL: Documentation, https://nimabahrami.github.io/quantvolt/
|
|
34
36
|
Project-URL: Homepage, https://nimabahrami.github.io/quantvolt/
|
|
@@ -51,9 +53,24 @@ Covered markets: EEX Phelix DE/AT power, EPEX SPOT power (DE, FR, NL, BE, GB), T
|
|
|
51
53
|
natural gas (ICE Endex), and EUA carbon allowances — extensible by the caller without editing
|
|
52
54
|
library source. See [docs/european-markets.md](docs/european-markets.md).
|
|
53
55
|
|
|
56
|
+
## Why QuantVolt?
|
|
57
|
+
|
|
58
|
+
A typical desk assembles a curve spreadsheet, a standalone options pricer, a storage/dispatch
|
|
59
|
+
optimizer and a bolted-on VaR tool, each with its own units, sign conventions and delivery-period
|
|
60
|
+
identity — hand-reconciling between them is slow and a recurring source of quiet errors. QuantVolt
|
|
61
|
+
instead moves the same immutable `ForwardCurve` / `DeliveryPeriod` / `MarketData` / `Greeks` value
|
|
62
|
+
objects through curve construction, pricing, portfolio valuation, risk, realized settlement and
|
|
63
|
+
physical-asset valuation, with determinism, eager validation and serializability enforced
|
|
64
|
+
structurally rather than left to convention. It is equally explicit about scope: a PPA stays
|
|
65
|
+
`unpriced` until you register a forward-looking pricer, storage/dispatch results are brought into
|
|
66
|
+
a portfolio explicitly, no market data ships with the package, and approximations (Kirk,
|
|
67
|
+
Turnbull-Wakeman, `bang_bang`) are labelled, never hidden. See
|
|
68
|
+
[the "Why QuantVolt?" page](https://nimabahrami.github.io/quantvolt/#/guide/why) and the
|
|
69
|
+
[external validation evidence](docs/validation.md) for the checkable version of these claims.
|
|
70
|
+
|
|
54
71
|
## Install
|
|
55
72
|
|
|
56
|
-
Requires Python 3.
|
|
73
|
+
Requires Python 3.11+.
|
|
57
74
|
|
|
58
75
|
```bash
|
|
59
76
|
uv add quantvolt # or: pip install quantvolt
|
|
@@ -295,7 +312,7 @@ specialised names stay on their sub-package. Full reference with a runnable exam
|
|
|
295
312
|
| `quantvolt.stats` | Descriptive statistics, normality tests, ADF/KPSS stationarity with Samuelson-effect detection, correlation, Ornstein-Uhlenbeck mean reversion |
|
|
296
313
|
| `quantvolt.market` | Transmission cost, temperature/degree-day utilities, generation outage / reliability KPIs |
|
|
297
314
|
| `quantvolt.workflow` | 7-step model-selection workflow for structured products |
|
|
298
|
-
| `quantvolt.portfolio` | `Portfolio`/`Position` book assembly,
|
|
315
|
+
| `quantvolt.portfolio` | `Portfolio`/`Position` book assembly; `value_portfolio` natively prices futures, forwards, swaps, transmission/pipeline rights, vanilla options, spread options and tolling agreements via the caller-extensible `DEFAULT_PRICERS` registry (PPAs and realized hedges are valued via a caller-registered pricer, e.g. `make_ppa_pricer` — deliberately not a default); realized interval `settle_energy_portfolio` aggregation is kept structurally separate from that forward-looking NPV |
|
|
299
316
|
| `quantvolt.data` | Optional (`quantvolt[data]`): provider adapters behind a `DataSource` protocol — the only package that performs I/O |
|
|
300
317
|
| `quantvolt.testing` | `assert_input_unchanged` — shipped test utility for the no-mutation invariant |
|
|
301
318
|
| `quantvolt.exceptions` | The `EnergyQuantError` hierarchy (`ValidationError`, `ArbitrageError`, `MissingTenorError`, …) |
|
|
@@ -317,11 +334,20 @@ positions. See [docs/risk-and-assets.md](docs/risk-and-assets.md).
|
|
|
317
334
|
|
|
318
335
|
## Documentation
|
|
319
336
|
|
|
337
|
+
- [Why QuantVolt?](https://nimabahrami.github.io/quantvolt/#/guide/why) — the integration problem,
|
|
338
|
+
the shared computational model, the extensible pricer registry, and an honest "what QuantVolt is
|
|
339
|
+
not" section
|
|
320
340
|
- [docs/api.md](docs/api.md) — module-by-module API reference with runnable examples
|
|
321
341
|
- [docs/risk-and-assets.md](docs/risk-and-assets.md) — the VaR family, the physical-vs-risk-neutral
|
|
322
342
|
drift rule, incomplete-market hedging, dispatch & storage, and the long-dated / liquidity caveats
|
|
323
343
|
- [docs/european-markets.md](docs/european-markets.md) — market coverage, power-vs-gas
|
|
324
344
|
statistics, negative prices, Samuelson effect, carbon costs, data-source policy
|
|
345
|
+
- [docs/validation.md](docs/validation.md) — external validation evidence: QuantVolt's pricers
|
|
346
|
+
checked against QuantLib 1.43 golden fixtures, with honest caveats and regeneration instructions
|
|
347
|
+
- Three complete, runnable end-to-end tutorials (site, each backed by a `site/examples/
|
|
348
|
+
verify_tutorial_*.py` script): [spark spread to hedge](https://nimabahrami.github.io/quantvolt/#/guide/tutorial-spark),
|
|
349
|
+
[renewable PPA to CFaR](https://nimabahrami.github.io/quantvolt/#/guide/tutorial-ppa), and
|
|
350
|
+
[storage intrinsic to hedge](https://nimabahrami.github.io/quantvolt/#/guide/tutorial-storage)
|
|
325
351
|
- `.kiro/steering/` — product, tech, structure, and coding-style standards
|
|
326
352
|
- `.kiro/specs/power-energy-quant-analysis/` — requirements, design, and task breakdown
|
|
327
353
|
(the library is spec-driven; the design document is the source of truth for the maths)
|
|
@@ -14,9 +14,24 @@ Covered markets: EEX Phelix DE/AT power, EPEX SPOT power (DE, FR, NL, BE, GB), T
|
|
|
14
14
|
natural gas (ICE Endex), and EUA carbon allowances — extensible by the caller without editing
|
|
15
15
|
library source. See [docs/european-markets.md](docs/european-markets.md).
|
|
16
16
|
|
|
17
|
+
## Why QuantVolt?
|
|
18
|
+
|
|
19
|
+
A typical desk assembles a curve spreadsheet, a standalone options pricer, a storage/dispatch
|
|
20
|
+
optimizer and a bolted-on VaR tool, each with its own units, sign conventions and delivery-period
|
|
21
|
+
identity — hand-reconciling between them is slow and a recurring source of quiet errors. QuantVolt
|
|
22
|
+
instead moves the same immutable `ForwardCurve` / `DeliveryPeriod` / `MarketData` / `Greeks` value
|
|
23
|
+
objects through curve construction, pricing, portfolio valuation, risk, realized settlement and
|
|
24
|
+
physical-asset valuation, with determinism, eager validation and serializability enforced
|
|
25
|
+
structurally rather than left to convention. It is equally explicit about scope: a PPA stays
|
|
26
|
+
`unpriced` until you register a forward-looking pricer, storage/dispatch results are brought into
|
|
27
|
+
a portfolio explicitly, no market data ships with the package, and approximations (Kirk,
|
|
28
|
+
Turnbull-Wakeman, `bang_bang`) are labelled, never hidden. See
|
|
29
|
+
[the "Why QuantVolt?" page](https://nimabahrami.github.io/quantvolt/#/guide/why) and the
|
|
30
|
+
[external validation evidence](docs/validation.md) for the checkable version of these claims.
|
|
31
|
+
|
|
17
32
|
## Install
|
|
18
33
|
|
|
19
|
-
Requires Python 3.
|
|
34
|
+
Requires Python 3.11+.
|
|
20
35
|
|
|
21
36
|
```bash
|
|
22
37
|
uv add quantvolt # or: pip install quantvolt
|
|
@@ -258,7 +273,7 @@ specialised names stay on their sub-package. Full reference with a runnable exam
|
|
|
258
273
|
| `quantvolt.stats` | Descriptive statistics, normality tests, ADF/KPSS stationarity with Samuelson-effect detection, correlation, Ornstein-Uhlenbeck mean reversion |
|
|
259
274
|
| `quantvolt.market` | Transmission cost, temperature/degree-day utilities, generation outage / reliability KPIs |
|
|
260
275
|
| `quantvolt.workflow` | 7-step model-selection workflow for structured products |
|
|
261
|
-
| `quantvolt.portfolio` | `Portfolio`/`Position` book assembly,
|
|
276
|
+
| `quantvolt.portfolio` | `Portfolio`/`Position` book assembly; `value_portfolio` natively prices futures, forwards, swaps, transmission/pipeline rights, vanilla options, spread options and tolling agreements via the caller-extensible `DEFAULT_PRICERS` registry (PPAs and realized hedges are valued via a caller-registered pricer, e.g. `make_ppa_pricer` — deliberately not a default); realized interval `settle_energy_portfolio` aggregation is kept structurally separate from that forward-looking NPV |
|
|
262
277
|
| `quantvolt.data` | Optional (`quantvolt[data]`): provider adapters behind a `DataSource` protocol — the only package that performs I/O |
|
|
263
278
|
| `quantvolt.testing` | `assert_input_unchanged` — shipped test utility for the no-mutation invariant |
|
|
264
279
|
| `quantvolt.exceptions` | The `EnergyQuantError` hierarchy (`ValidationError`, `ArbitrageError`, `MissingTenorError`, …) |
|
|
@@ -280,11 +295,20 @@ positions. See [docs/risk-and-assets.md](docs/risk-and-assets.md).
|
|
|
280
295
|
|
|
281
296
|
## Documentation
|
|
282
297
|
|
|
298
|
+
- [Why QuantVolt?](https://nimabahrami.github.io/quantvolt/#/guide/why) — the integration problem,
|
|
299
|
+
the shared computational model, the extensible pricer registry, and an honest "what QuantVolt is
|
|
300
|
+
not" section
|
|
283
301
|
- [docs/api.md](docs/api.md) — module-by-module API reference with runnable examples
|
|
284
302
|
- [docs/risk-and-assets.md](docs/risk-and-assets.md) — the VaR family, the physical-vs-risk-neutral
|
|
285
303
|
drift rule, incomplete-market hedging, dispatch & storage, and the long-dated / liquidity caveats
|
|
286
304
|
- [docs/european-markets.md](docs/european-markets.md) — market coverage, power-vs-gas
|
|
287
305
|
statistics, negative prices, Samuelson effect, carbon costs, data-source policy
|
|
306
|
+
- [docs/validation.md](docs/validation.md) — external validation evidence: QuantVolt's pricers
|
|
307
|
+
checked against QuantLib 1.43 golden fixtures, with honest caveats and regeneration instructions
|
|
308
|
+
- Three complete, runnable end-to-end tutorials (site, each backed by a `site/examples/
|
|
309
|
+
verify_tutorial_*.py` script): [spark spread to hedge](https://nimabahrami.github.io/quantvolt/#/guide/tutorial-spark),
|
|
310
|
+
[renewable PPA to CFaR](https://nimabahrami.github.io/quantvolt/#/guide/tutorial-ppa), and
|
|
311
|
+
[storage intrinsic to hedge](https://nimabahrami.github.io/quantvolt/#/guide/tutorial-storage)
|
|
288
312
|
- `.kiro/steering/` — product, tech, structure, and coding-style standards
|
|
289
313
|
- `.kiro/specs/power-energy-quant-analysis/` — requirements, design, and task breakdown
|
|
290
314
|
(the library is spec-driven; the design document is the source of truth for the maths)
|
|
@@ -4,10 +4,10 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quantvolt"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Quantitative analysis of European power and energy markets"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [{ name = "Nima Bahrami", email = "nima@skillmix.nl" }]
|
|
@@ -28,7 +28,9 @@ classifiers = [
|
|
|
28
28
|
"Intended Audience :: Financial and Insurance Industry",
|
|
29
29
|
"Intended Audience :: Science/Research",
|
|
30
30
|
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
31
32
|
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
32
34
|
"Programming Language :: Rust",
|
|
33
35
|
"Topic :: Office/Business :: Financial :: Investment",
|
|
34
36
|
"Topic :: Scientific/Engineering :: Mathematics",
|
|
@@ -61,12 +63,20 @@ dev = [
|
|
|
61
63
|
"maturin>=1.7,<2.0",
|
|
62
64
|
]
|
|
63
65
|
|
|
66
|
+
# PEP 735 dependency group for OFFLINE external-validation fixture generation only.
|
|
67
|
+
# NOT a [project.optional-dependencies] extra and NOT a core/runtime dependency: the
|
|
68
|
+
# analytics core never imports QuantLib, CI never installs this group, and the generator
|
|
69
|
+
# scripts under scripts/ (wheel-excluded above) are the only consumers. See
|
|
70
|
+
# .kiro/specs/external-validation/ and docs/validation.md for the regeneration workflow.
|
|
71
|
+
[dependency-groups]
|
|
72
|
+
validation = ["QuantLib>=1.34"]
|
|
73
|
+
|
|
64
74
|
[tool.maturin]
|
|
65
75
|
python-source = "src"
|
|
66
76
|
module-name = "quantvolt._core"
|
|
67
|
-
# abi3-
|
|
77
|
+
# abi3-py311: build a single stable-ABI wheel that works on CPython 3.11+
|
|
68
78
|
# (one compile per platform instead of one per Python version).
|
|
69
|
-
features = ["pyo3/extension-module", "pyo3/abi3-
|
|
79
|
+
features = ["pyo3/extension-module", "pyo3/abi3-py311"]
|
|
70
80
|
# Keep the sdist to the buildable package only. The docs site, rendered docs,
|
|
71
81
|
# research data, helper scripts, and the logo are not part of the installable
|
|
72
82
|
# library and would only bloat the distribution.
|
|
@@ -90,12 +100,20 @@ exclude = [
|
|
|
90
100
|
[tool.ruff]
|
|
91
101
|
line-length = 100
|
|
92
102
|
src = ["src", "tests"]
|
|
103
|
+
target-version = "py311"
|
|
93
104
|
|
|
94
105
|
[tool.ruff.lint]
|
|
95
106
|
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
96
107
|
|
|
108
|
+
[tool.ruff.lint.per-file-ignores]
|
|
109
|
+
# Authored documentation text (AUTHORED_DOCS) carries long human-readable lines;
|
|
110
|
+
# RUF001 flags the en-dashes used deliberately in that prose.
|
|
111
|
+
"site/build_api.py" = ["E501", "RUF001"]
|
|
112
|
+
# One-off internal refactoring tool, not shipped code.
|
|
113
|
+
"site/tools/refactor_reference_design.py" = ["E501"]
|
|
114
|
+
|
|
97
115
|
[tool.mypy]
|
|
98
|
-
python_version = "3.
|
|
116
|
+
python_version = "3.11"
|
|
99
117
|
strict = true
|
|
100
118
|
files = ["src/quantvolt"]
|
|
101
119
|
|
|
@@ -490,11 +490,11 @@ pub fn simulate_correlated_forwards_term_core(
|
|
|
490
490
|
if antithetic {
|
|
491
491
|
write_record(&mut data, p_minus, 0, rec, dim, &minus);
|
|
492
492
|
}
|
|
493
|
-
for step in
|
|
493
|
+
for (step, factor) in factors.iter().enumerate().take(steps) {
|
|
494
494
|
for e in &mut eps {
|
|
495
495
|
*e = rng.sample::<f64, _>(StandardNormal);
|
|
496
496
|
}
|
|
497
|
-
matvec_lower(
|
|
497
|
+
matvec_lower(factor, &eps, dim, &mut incr);
|
|
498
498
|
let offset = step * dim;
|
|
499
499
|
for d in 0..dim {
|
|
500
500
|
if active_steps[offset + d] {
|
|
@@ -22,6 +22,7 @@ from .assets import (
|
|
|
22
22
|
PhysicalFactorMapping,
|
|
23
23
|
PlantModel,
|
|
24
24
|
StorageModel,
|
|
25
|
+
ValuationSource,
|
|
25
26
|
bang_bang,
|
|
26
27
|
dispatch_deterministic,
|
|
27
28
|
dispatch_value,
|
|
@@ -70,7 +71,12 @@ from .hedging import (
|
|
|
70
71
|
)
|
|
71
72
|
from .models import (
|
|
72
73
|
BUILT_IN_COMMODITIES,
|
|
74
|
+
CachedAssetValuation,
|
|
75
|
+
CapFloorStripContract,
|
|
76
|
+
CapFloorType,
|
|
77
|
+
ChangeInLawAllocation,
|
|
73
78
|
CommodityConfig,
|
|
79
|
+
CurtailmentTreatment,
|
|
74
80
|
CurveNode,
|
|
75
81
|
DeliveryPeriod,
|
|
76
82
|
DeliverySchedule,
|
|
@@ -81,24 +87,43 @@ from .models import (
|
|
|
81
87
|
Granularity,
|
|
82
88
|
Greeks,
|
|
83
89
|
Hub,
|
|
90
|
+
IndexationStep,
|
|
84
91
|
InstrumentPriceRecord,
|
|
85
92
|
Moneyness,
|
|
93
|
+
NegativePriceClause,
|
|
94
|
+
NegativePriceTreatment,
|
|
95
|
+
OptionSide,
|
|
96
|
+
OptionType,
|
|
86
97
|
PipelineRight,
|
|
87
98
|
PlantConfig,
|
|
88
99
|
PowerDeliveryInterval,
|
|
89
100
|
PowerHedgeContract,
|
|
90
101
|
PowerHedgePosition,
|
|
91
102
|
PowerHedgeType,
|
|
103
|
+
PpaAvailabilityGuarantee,
|
|
92
104
|
PpaContract,
|
|
105
|
+
PpaContractMetadata,
|
|
106
|
+
PpaCreditSupportType,
|
|
107
|
+
PpaPriceTerms,
|
|
108
|
+
PpaReconciliationPeriod,
|
|
109
|
+
PpaReconciliationTerms,
|
|
93
110
|
PpaSettlementType,
|
|
111
|
+
PpaTerms,
|
|
112
|
+
PpaToleranceBand,
|
|
94
113
|
PpaVolumeBasis,
|
|
114
|
+
PpaVolumeTerms,
|
|
115
|
+
PriceUnit,
|
|
95
116
|
RiskType,
|
|
96
117
|
SettlementType,
|
|
118
|
+
SpreadOptionContract,
|
|
97
119
|
SwapContract,
|
|
120
|
+
TollingAgreement,
|
|
98
121
|
TransmissionRight,
|
|
99
122
|
TransportDirection,
|
|
123
|
+
VanillaOptionContract,
|
|
100
124
|
VolatilitySurface,
|
|
101
125
|
VolatilityTenor,
|
|
126
|
+
convert_price,
|
|
102
127
|
)
|
|
103
128
|
from .portfolio import (
|
|
104
129
|
Instrument,
|
|
@@ -114,6 +139,8 @@ from .portfolio import (
|
|
|
114
139
|
)
|
|
115
140
|
from .pricing import (
|
|
116
141
|
AsianOptionRequest,
|
|
142
|
+
BachelierOptionRequest,
|
|
143
|
+
BachelierOptionResult,
|
|
117
144
|
BarrierOptionRequest,
|
|
118
145
|
CapFloorRequest,
|
|
119
146
|
CapFloorResult,
|
|
@@ -129,6 +156,11 @@ from .pricing import (
|
|
|
129
156
|
PowerHedgeSettlement,
|
|
130
157
|
PpaDataColumns,
|
|
131
158
|
PpaIntervalSettlement,
|
|
159
|
+
PpaPeriodValuation,
|
|
160
|
+
PpaPeriodVolume,
|
|
161
|
+
PpaReconciliationColumns,
|
|
162
|
+
PpaValuationResult,
|
|
163
|
+
PpaVolumeProfile,
|
|
132
164
|
SpreadOptionRequest,
|
|
133
165
|
SpreadOptionResult,
|
|
134
166
|
SwapPricingResult,
|
|
@@ -147,19 +179,23 @@ from .pricing import (
|
|
|
147
179
|
futures_delta,
|
|
148
180
|
implied_heat_rate,
|
|
149
181
|
implied_vol,
|
|
182
|
+
make_ppa_pricer,
|
|
150
183
|
mark_to_market,
|
|
151
184
|
power_cap_payoff,
|
|
152
185
|
power_floor_payoff,
|
|
153
186
|
price_asian,
|
|
187
|
+
price_bachelier_option,
|
|
154
188
|
price_barrier,
|
|
155
189
|
price_cap_floor,
|
|
156
190
|
price_futures,
|
|
157
191
|
price_lookback,
|
|
192
|
+
price_ppa,
|
|
158
193
|
price_spark_spread_option,
|
|
159
194
|
price_spread_option,
|
|
160
195
|
price_swap,
|
|
161
196
|
price_tolling_agreement,
|
|
162
197
|
price_vanilla_option,
|
|
198
|
+
reconcile_ppa_ledger,
|
|
163
199
|
settle_power_hedge_interval,
|
|
164
200
|
settle_power_hedges_frame,
|
|
165
201
|
settle_ppa_frame,
|
|
@@ -188,7 +224,7 @@ from .risk import (
|
|
|
188
224
|
parametric_var,
|
|
189
225
|
)
|
|
190
226
|
|
|
191
|
-
__version__ = "0.
|
|
227
|
+
__version__ = "0.2.0"
|
|
192
228
|
|
|
193
229
|
__all__ = [
|
|
194
230
|
"BUILT_IN_COMMODITIES",
|
|
@@ -197,13 +233,20 @@ __all__ = [
|
|
|
197
233
|
"ArbitrageWarning",
|
|
198
234
|
"AsianOptionRequest",
|
|
199
235
|
"AuthenticationError",
|
|
236
|
+
"BachelierOptionRequest",
|
|
237
|
+
"BachelierOptionResult",
|
|
200
238
|
"BangBangHedgeWarning",
|
|
201
239
|
"BarrierOptionRequest",
|
|
240
|
+
"CachedAssetValuation",
|
|
202
241
|
"CapFloorRequest",
|
|
203
242
|
"CapFloorResult",
|
|
243
|
+
"CapFloorStripContract",
|
|
244
|
+
"CapFloorType",
|
|
204
245
|
"CashflowStrategyComparison",
|
|
205
246
|
"CashflowStrategyMetrics",
|
|
247
|
+
"ChangeInLawAllocation",
|
|
206
248
|
"CommodityConfig",
|
|
249
|
+
"CurtailmentTreatment",
|
|
207
250
|
"CurveBuildResult",
|
|
208
251
|
"CurveBuilder",
|
|
209
252
|
"CurveNode",
|
|
@@ -229,6 +272,7 @@ __all__ = [
|
|
|
229
272
|
"Greeks",
|
|
230
273
|
"Hub",
|
|
231
274
|
"ImpliedVolResult",
|
|
275
|
+
"IndexationStep",
|
|
232
276
|
"Instrument",
|
|
233
277
|
"InstrumentPriceRecord",
|
|
234
278
|
"InsufficientDataError",
|
|
@@ -243,8 +287,12 @@ __all__ = [
|
|
|
243
287
|
"MtMResult",
|
|
244
288
|
"MultifactorForwardModel",
|
|
245
289
|
"NativeExtensionError",
|
|
290
|
+
"NegativePriceClause",
|
|
291
|
+
"NegativePriceTreatment",
|
|
246
292
|
"NoPricingDataError",
|
|
247
293
|
"NumericalError",
|
|
294
|
+
"OptionSide",
|
|
295
|
+
"OptionType",
|
|
248
296
|
"PhysicalFactorMapping",
|
|
249
297
|
"PipelineRight",
|
|
250
298
|
"PlantConfig",
|
|
@@ -259,16 +307,31 @@ __all__ = [
|
|
|
259
307
|
"PowerHedgePosition",
|
|
260
308
|
"PowerHedgeSettlement",
|
|
261
309
|
"PowerHedgeType",
|
|
310
|
+
"PpaAvailabilityGuarantee",
|
|
262
311
|
"PpaContract",
|
|
312
|
+
"PpaContractMetadata",
|
|
313
|
+
"PpaCreditSupportType",
|
|
263
314
|
"PpaDataColumns",
|
|
264
315
|
"PpaIntervalSettlement",
|
|
265
316
|
"PpaNominationCandidate",
|
|
266
317
|
"PpaNominationColumns",
|
|
267
318
|
"PpaNominationFit",
|
|
268
319
|
"PpaNominationObjective",
|
|
320
|
+
"PpaPeriodValuation",
|
|
321
|
+
"PpaPeriodVolume",
|
|
322
|
+
"PpaPriceTerms",
|
|
323
|
+
"PpaReconciliationColumns",
|
|
324
|
+
"PpaReconciliationPeriod",
|
|
325
|
+
"PpaReconciliationTerms",
|
|
269
326
|
"PpaSettlementType",
|
|
327
|
+
"PpaTerms",
|
|
328
|
+
"PpaToleranceBand",
|
|
329
|
+
"PpaValuationResult",
|
|
270
330
|
"PpaVolumeBasis",
|
|
331
|
+
"PpaVolumeProfile",
|
|
332
|
+
"PpaVolumeTerms",
|
|
271
333
|
"PpaWalkForwardResult",
|
|
334
|
+
"PriceUnit",
|
|
272
335
|
"PricedPosition",
|
|
273
336
|
"RateLimitError",
|
|
274
337
|
"RiskEngine",
|
|
@@ -281,16 +344,20 @@ __all__ = [
|
|
|
281
344
|
"SchwartzSmithParams",
|
|
282
345
|
"SettledPortfolioPosition",
|
|
283
346
|
"SettlementType",
|
|
347
|
+
"SpreadOptionContract",
|
|
284
348
|
"SpreadOptionRequest",
|
|
285
349
|
"SpreadOptionResult",
|
|
286
350
|
"StorageModel",
|
|
287
351
|
"SwapContract",
|
|
288
352
|
"SwapPricingResult",
|
|
353
|
+
"TollingAgreement",
|
|
289
354
|
"TollingResult",
|
|
290
355
|
"TransmissionRight",
|
|
291
356
|
"TransportDirection",
|
|
292
357
|
"TransportRightResult",
|
|
293
358
|
"ValidationError",
|
|
359
|
+
"ValuationSource",
|
|
360
|
+
"VanillaOptionContract",
|
|
294
361
|
"VanillaOptionRequest",
|
|
295
362
|
"VanillaOptionResult",
|
|
296
363
|
"VolatilitySurface",
|
|
@@ -307,6 +374,7 @@ __all__ = [
|
|
|
307
374
|
"classify_moneyness",
|
|
308
375
|
"clean_spread",
|
|
309
376
|
"compare_cashflow_strategies",
|
|
377
|
+
"convert_price",
|
|
310
378
|
"crack_spread",
|
|
311
379
|
"credit_var",
|
|
312
380
|
"dark_spread",
|
|
@@ -321,21 +389,25 @@ __all__ = [
|
|
|
321
389
|
"implied_heat_rate",
|
|
322
390
|
"implied_vol",
|
|
323
391
|
"linear_cross_hedge",
|
|
392
|
+
"make_ppa_pricer",
|
|
324
393
|
"mark_to_market",
|
|
325
394
|
"monte_carlo_var",
|
|
326
395
|
"parametric_var",
|
|
327
396
|
"power_cap_payoff",
|
|
328
397
|
"power_floor_payoff",
|
|
329
398
|
"price_asian",
|
|
399
|
+
"price_bachelier_option",
|
|
330
400
|
"price_barrier",
|
|
331
401
|
"price_cap_floor",
|
|
332
402
|
"price_futures",
|
|
333
403
|
"price_lookback",
|
|
404
|
+
"price_ppa",
|
|
334
405
|
"price_spark_spread_option",
|
|
335
406
|
"price_spread_option",
|
|
336
407
|
"price_swap",
|
|
337
408
|
"price_tolling_agreement",
|
|
338
409
|
"price_vanilla_option",
|
|
410
|
+
"reconcile_ppa_ledger",
|
|
339
411
|
"settle_energy_portfolio",
|
|
340
412
|
"settle_power_hedge_interval",
|
|
341
413
|
"settle_power_hedges_frame",
|
|
@@ -771,7 +771,13 @@ def _dispatch_lsm(
|
|
|
771
771
|
conditions.append(float(np.linalg.cond(basis)))
|
|
772
772
|
predicted = basis @ coeffs
|
|
773
773
|
continuation = {s: predicted[:, j] for j, s in enumerate(feasible_next)}
|
|
774
|
-
|
|
774
|
+
# .astype(np.float64) (not .copy()): numpy's lstsq stub types its first
|
|
775
|
+
# return value as floating[Any] on some numpy releases (narrower Vector =
|
|
776
|
+
# NDArray[float64] elsewhere) -- this pins the static (and, since the data
|
|
777
|
+
# is already float64, identical-at-runtime) dtype across numpy versions.
|
|
778
|
+
coefficients = {
|
|
779
|
+
s: coeffs[:, j].astype(np.float64) for j, s in enumerate(feasible_next)
|
|
780
|
+
}
|
|
775
781
|
else:
|
|
776
782
|
# A min-run-locked online state whose derated capacity has collapsed
|
|
777
783
|
# below every feasible level on *some* paths realizes -inf there (a
|
|
@@ -799,7 +805,7 @@ def _dispatch_lsm(
|
|
|
799
805
|
step_conditions.append(float(np.linalg.cond(design)))
|
|
800
806
|
predicted_s = basis @ coeffs_s
|
|
801
807
|
continuation[state] = np.where(finite, predicted_s, -np.inf)
|
|
802
|
-
coefficients[state] = coeffs_s.
|
|
808
|
+
coefficients[state] = coeffs_s.astype(np.float64)
|
|
803
809
|
conditions.append(max(step_conditions, default=1.0))
|
|
804
810
|
policy_coefficients.append(coefficients)
|
|
805
811
|
|
|
@@ -18,11 +18,15 @@ long-dated risk is never understated (Req 23):
|
|
|
18
18
|
|
|
19
19
|
Tagging semantics
|
|
20
20
|
-----------------
|
|
21
|
-
:class:`ValuationSource` is the single vocabulary of
|
|
22
|
-
values, ``"forward"`` and
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
the tag prominently in
|
|
21
|
+
:class:`ValuationSource` (``models/instruments.py``) is the single vocabulary of
|
|
22
|
+
provenance tags. This module produces two of its values, ``"forward"`` and
|
|
23
|
+
``"projected"`` -- exactly the strings a caller should propagate onto a
|
|
24
|
+
:class:`~quantvolt.portfolio.model.Position`'s ``tags`` so that downstream risk code
|
|
25
|
+
can tell the regimes apart. :class:`BenchmarkResult` carries the tag prominently in
|
|
26
|
+
its ``source`` field. A third value, ``"simulated"``, is produced elsewhere (the
|
|
27
|
+
portfolio-native-pricers spec's ``CachedAssetValuation`` wrapper, Req 19) for a
|
|
28
|
+
precomputed LSMC/dispatch cache -- a third regime this module does not itself
|
|
29
|
+
produce, tagged the same way for the same reason (Property-66 pattern).
|
|
26
30
|
|
|
27
31
|
Intended wiring (this module does not touch ``risk/``)
|
|
28
32
|
------------------------------------------------------
|
|
@@ -62,10 +66,10 @@ from __future__ import annotations
|
|
|
62
66
|
|
|
63
67
|
from collections.abc import Callable
|
|
64
68
|
from dataclasses import dataclass
|
|
65
|
-
from enum import StrEnum
|
|
66
69
|
|
|
67
70
|
from ..exceptions import MissingTenorError, ValidationError
|
|
68
71
|
from ..models.curve import ForwardCurve
|
|
72
|
+
from ..models.instruments import ValuationSource as ValuationSource # explicit re-export
|
|
69
73
|
from ..models.schedule import DeliveryPeriod
|
|
70
74
|
from ..numerics.risk_adjustment import PriceOfRiskKind
|
|
71
75
|
from ..portfolio.model import PricedPosition
|
|
@@ -73,17 +77,12 @@ from ..portfolio.model import PricedPosition
|
|
|
73
77
|
# A pure projection of a period to its model spot expectation (documented, never mutated).
|
|
74
78
|
SpotModel = Callable[[DeliveryPeriod], float]
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
value apart from a forward-based one.
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
FORWARD = "forward" # liquid forward curve covers the period (Req 23.1)
|
|
86
|
-
PROJECTED = "projected" # projected from a spot model + corporate premium (Req 23.2)
|
|
80
|
+
# ``ValuationSource`` is now defined in ``models/instruments.py`` (the portfolio-native-pricers
|
|
81
|
+
# spec, Req 19, reuses it for a THIRD regime -- a precomputed LSMC/dispatch cache -- and
|
|
82
|
+
# ``models/instruments.py`` is the leaf-ish module every instrument-bearing type can import
|
|
83
|
+
# without a cycle back through ``portfolio/model.py``, which this module already depends on).
|
|
84
|
+
# Imported here (not re-defined) so there remains exactly one provenance vocabulary; every
|
|
85
|
+
# existing ``from quantvolt.assets.long_dated import ValuationSource`` import keeps working.
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
@dataclass(frozen=True, slots=True)
|
|
@@ -6,11 +6,52 @@ import argparse
|
|
|
6
6
|
import json
|
|
7
7
|
from dataclasses import asdict
|
|
8
8
|
from importlib import import_module
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Protocol, cast
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
class _DatasetRecordLike(Protocol):
|
|
14
|
+
"""Structural stand-in for ``quantvolt.data.datasets.DatasetRecord``.
|
|
15
|
+
|
|
16
|
+
The core (including this CLI entry point) must never statically import
|
|
17
|
+
``quantvolt.data`` (Req 12.1; enforced by
|
|
18
|
+
``tests/unit/test_data_isolation.py::test_no_core_source_file_imports_the_data_layer``),
|
|
19
|
+
so this protocol names only the attributes the CLI itself reads rather than importing
|
|
20
|
+
the real dataclass.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
dataset_id: str
|
|
24
|
+
version: str
|
|
25
|
+
filename: str
|
|
26
|
+
size: int
|
|
27
|
+
sha256: str
|
|
28
|
+
format: str
|
|
29
|
+
license: str
|
|
30
|
+
source: str
|
|
31
|
+
url: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _DatasetsModule(Protocol):
|
|
35
|
+
"""Structural type for the ``quantvolt.data.datasets`` submodule.
|
|
36
|
+
|
|
37
|
+
``quantvolt.data.datasets`` is imported lazily by name (rather than with a normal
|
|
38
|
+
``import`` statement) so that CLI subcommands other than ``data`` never trigger the
|
|
39
|
+
optional ``quantvolt[data]`` extra's dependency (``httpx``, pulled in transitively via
|
|
40
|
+
``quantvolt.data.__init__``). This protocol lets mypy still check the call sites below
|
|
41
|
+
against the submodule's real signatures without a static import of the data layer.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def list_datasets(self) -> tuple[_DatasetRecordLike, ...]: ...
|
|
45
|
+
def info(self, dataset_id: str) -> _DatasetRecordLike: ...
|
|
46
|
+
def fetch(self, dataset_id: str, *, force: bool = ..., offline: bool = ...) -> Path: ...
|
|
47
|
+
def verify(self, dataset_id: str) -> bool: ...
|
|
48
|
+
def path(self, dataset_id: str) -> Path: ...
|
|
49
|
+
def remove(self, dataset_id: str) -> bool: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _datasets() -> _DatasetsModule:
|
|
12
53
|
"""Load the optional data layer only after the CLI is invoked."""
|
|
13
|
-
return import_module("quantvolt.data.datasets")
|
|
54
|
+
return cast("_DatasetsModule", import_module("quantvolt.data.datasets"))
|
|
14
55
|
|
|
15
56
|
|
|
16
57
|
def _parser() -> argparse.ArgumentParser:
|
|
@@ -36,14 +77,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
36
77
|
if args.operation == "list":
|
|
37
78
|
records = datasets.list_datasets()
|
|
38
79
|
if args.as_json:
|
|
39
|
-
print(json.dumps([asdict(record) for record in records], indent=2))
|
|
80
|
+
print(json.dumps([asdict(cast(Any, record)) for record in records], indent=2))
|
|
40
81
|
else:
|
|
41
82
|
for record in records:
|
|
42
83
|
print(f"{record.dataset_id:38} {record.version:8} {record.size:>10} bytes")
|
|
43
84
|
return 0
|
|
44
85
|
dataset_id = args.dataset_id
|
|
45
86
|
if args.operation == "info":
|
|
46
|
-
print(json.dumps(asdict(datasets.info(dataset_id)), indent=2))
|
|
87
|
+
print(json.dumps(asdict(cast(Any, datasets.info(dataset_id))), indent=2))
|
|
47
88
|
elif args.operation == "fetch":
|
|
48
89
|
print(datasets.fetch(dataset_id, force=args.force, offline=args.offline))
|
|
49
90
|
elif args.operation == "verify":
|
|
@@ -38,7 +38,8 @@ class DatasetRecord:
|
|
|
38
38
|
|
|
39
39
|
def _catalog() -> dict[str, Any]:
|
|
40
40
|
resource = files("quantvolt.data").joinpath(_CATALOG_RESOURCE)
|
|
41
|
-
|
|
41
|
+
catalog: dict[str, Any] = json.loads(resource.read_text(encoding="utf-8"))
|
|
42
|
+
return catalog
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
def _record(dataset_id: str) -> DatasetRecord:
|