quantra 0.0.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.
Files changed (32) hide show
  1. quantra-0.0.0/.claude/settings.local.json +21 -0
  2. quantra-0.0.0/.gitignore +13 -0
  3. quantra-0.0.0/.python-version +1 -0
  4. quantra-0.0.0/BUILDPLAN.md +451 -0
  5. quantra-0.0.0/PKG-INFO +49 -0
  6. quantra-0.0.0/README.md +0 -0
  7. quantra-0.0.0/docs/adr/ADR-0001-uv-package-manager.md +63 -0
  8. quantra-0.0.0/docs/adr/ADR-0002-src-layout.md +73 -0
  9. quantra-0.0.0/docs/adr/ADR-0003-solid-layer-separation.md +76 -0
  10. quantra-0.0.0/docs/adr/ADR-0004-protocol-over-abc.md +71 -0
  11. quantra-0.0.0/docs/adr/ADR-0005-result-monad.md +90 -0
  12. quantra-0.0.0/docs/adr/ADR-0006-decimal-for-money.md +86 -0
  13. quantra-0.0.0/docs/adr/ADR-0007-observable-pattern.md +82 -0
  14. quantra-0.0.0/docs/adr/ADR-0008-weakset-observers.md +87 -0
  15. quantra-0.0.0/docs/adr/ADR-0009-invalidatable-cache.md +113 -0
  16. quantra-0.0.0/docs/adr/ADR-0010-instrument-engine-decoupling.md +128 -0
  17. quantra-0.0.0/docs/adr/ADR-0011-pydantic-domain-models.md +86 -0
  18. quantra-0.0.0/docs/adr/ADR-0012-async-io-layer.md +83 -0
  19. quantra-0.0.0/docs/adr/ADR-0013-pluggable-interpolation.md +78 -0
  20. quantra-0.0.0/docs/adr/ADR-0014-event-driven-backtest.md +94 -0
  21. quantra-0.0.0/docs/adr/ADR-0015-no-circular-imports.md +97 -0
  22. quantra-0.0.0/docs/adr/ADR-0016-engine-registry.md +78 -0
  23. quantra-0.0.0/docs/adr/ADR-0017-stochastic-process-injection.md +99 -0
  24. quantra-0.0.0/docs/adr/ADR-0018-hypothesis-property-testing.md +92 -0
  25. quantra-0.0.0/docs/adr/ADR-0019-sphinxdocs-readthedocs.md +110 -0
  26. quantra-0.0.0/docs/adr/ADR-0020-newtype-scalar-aliases.md +101 -0
  27. quantra-0.0.0/docs/adr/README.md +39 -0
  28. quantra-0.0.0/docs/contributing/setup.rst +255 -0
  29. quantra-0.0.0/main.py +6 -0
  30. quantra-0.0.0/pyproject.toml +129 -0
  31. quantra-0.0.0/src/quantra/__init__.py +17 -0
  32. quantra-0.0.0/uv.lock +2036 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "mcp__visualize__read_me",
5
+ "mcp__visualize__show_widget",
6
+ "Bash(gh repo *)",
7
+ "Bash(git add *)",
8
+ "Bash(git commit -m ' *)",
9
+ "Bash(git push *)",
10
+ "Bash(New-Item -ItemType Directory -Force \"C:\\\\projects\\\\quantra\\\\docs\\\\adr\")",
11
+ "Bash(Out-Null)",
12
+ "Bash(Write-Output \"done\")",
13
+ "Bash(uv --version)",
14
+ "Bash(uv python *)",
15
+ "Bash(uv sync *)",
16
+ "Bash(git checkout *)",
17
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" https://pypi.org/pypi/quantra/json)",
18
+ "Bash(uv build *)"
19
+ ]
20
+ }
21
+ }
@@ -0,0 +1,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Build plan CSV (local tracking only)
13
+ BUILDPLAN.csv
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,451 @@
1
+ # Quantra — 200+ Step Build Plan
2
+
3
+ Each step has three mandatory sub-tasks:
4
+ 1. **Code** — the implementation artifact
5
+ 2. **Test** — at minimum one unit test (property-based where noted)
6
+ 3. **Docs** — update the corresponding ReadTheDocs page before moving to the next step
7
+
8
+ Steps are grouped into phases. Within a phase, steps are strictly ordered by the
9
+ dependency graph — no step depends on code introduced in a later step.
10
+
11
+ ---
12
+
13
+ ## PHASE 0 — Repository & Tooling Setup (Steps 1–12)
14
+
15
+ | # | Task | Docs Page |
16
+ |---|------|-----------|
17
+ | 1 | `uv init` the project; commit `pyproject.toml` and `.python-version` | `docs/contributing/setup.rst` — project bootstrap |
18
+ | 2 | Add `[project.optional-dependencies]` groups: `dev`, `data`, `reporting` | `docs/contributing/setup.rst` — dependency groups |
19
+ | 3 | Configure `ruff` in `pyproject.toml`; enforce `ANN`, `B`, `SIM`, `I` rule sets | `docs/contributing/code-style.rst` |
20
+ | 4 | Configure `mypy` in `mypy.ini`; strict mode, `pydantic.mypy` plugin | `docs/contributing/type-checking.rst` |
21
+ | 5 | Add `pytest` config: `asyncio_mode = "auto"`, `testpaths`, coverage thresholds | `docs/contributing/testing.rst` |
22
+ | 6 | Create `src/quantra/__init__.py`; expose package version via `importlib.metadata` | `docs/api/index.rst` |
23
+ | 7 | Create `docs/` directory; initialize Sphinx with `sphinx-quickstart`; add `conf.py` | `docs/index.rst` — first live ReadTheDocs deploy |
24
+ | 8 | Add `docs/requirements.txt`; configure `.readthedocs.yaml`; connect repo to RTD project | `docs/contributing/docs-workflow.rst` |
25
+ | 9 | Add `furo` Sphinx theme; configure `autodoc`, `napoleon`, `intersphinx` extensions | `docs/contributing/docs-workflow.rst` |
26
+ | 10 | Write `CONTRIBUTING.md`; define PR checklist (ruff + mypy + pytest + docs gate) | `docs/contributing/index.rst` |
27
+ | 11 | Add GitHub Actions CI skeleton: lint → typecheck → test → docs-build | `docs/contributing/ci.rst` |
28
+ | 12 | Write `docs/design/principles.rst`: SOLID map, layer diagram, Observable pattern rationale | `docs/design/principles.rst` |
29
+
30
+ ---
31
+
32
+ ## PHASE 1 — `_core` Infrastructure (Steps 13–28)
33
+
34
+ | # | Task | Docs Page |
35
+ |---|------|-----------|
36
+ | 13 | `_core/types.py` — `NewType` aliases: `Rate`, `Money`, `BPS`, `Periods`, `Percentage` | `docs/api/core/types.rst` |
37
+ | 14 | `_core/exceptions.py` — `QuantraError` base; `ConvergenceError`, `InvalidConventionError`, `InsufficientDataError`, `ScheduleGenerationError`, `RiskModelError`, `PricingError` | `docs/api/core/exceptions.rst` |
38
+ | 15 | `_core/result.py` — `Ok[T]` / `Err[E]` frozen dataclasses; `Result = Union[Ok, Err]` | `docs/api/core/result.rst` — explain monad pattern |
39
+ | 16 | Tests for `Result`: unwrap helpers, isinstance narrowing | `docs/api/core/result.rst` — usage examples |
40
+ | 17 | `_core/conventions.py` — `DayCount`, `CompoundingFreq`, `AmortizationType`, `BusinessDayConvention`, `Currency` enums using `StrEnum` | `docs/api/core/conventions.rst` |
41
+ | 18 | `_core/observable.py` — `Observer` ABC with `on_market_update(source)`; `Observable` with `WeakSet[Observer]`, `register_observer`, `deregister_observer`, `notify_observers` | `docs/design/observable.rst` |
42
+ | 19 | Tests for `Observable`: register, notify, deregister, verify weak-ref GC behavior | `docs/design/observable.rst` — GC section |
43
+ | 20 | `_core/cache.py` — `@invalidatable_cache` descriptor; `invalidate_all_caches(instance)` | `docs/design/observable.rst` — lazy eval section |
44
+ | 21 | Tests for `@invalidatable_cache`: cache hit, miss, invalidation, multi-method isolation | `docs/design/observable.rst` |
45
+ | 22 | `_core/math.py` — `sign`, `clamp`, `isclose_decimal`, `round_half_up` pure utilities | `docs/api/core/math.rst` |
46
+ | 23 | Integration test: `Observable` → `Observer.on_market_update` → `invalidate_all_caches` full cycle | `docs/design/observable.rst` — integration example |
47
+ | 24 | `_core/logging.py` — structured logger factory (`quantra.<module>` hierarchy); no third-party deps | `docs/contributing/logging.rst` |
48
+ | 25 | `_core/serialization.py` — `Decimal`↔`str`, `date`↔`ISO8601` Pydantic validators | `docs/api/core/serialization.rst` |
49
+ | 26 | Docs page `docs/design/layer-map.rst` — rendered dependency graph (ASCII + Mermaid) | `docs/design/layer-map.rst` |
50
+ | 27 | `_core/__init__.py` — re-export public API surface; keep private types private | `docs/api/core/index.rst` |
51
+ | 28 | Benchmark `_core` allocations with `tracemalloc`; add `tests/perf/test_core_memory.py` | `docs/contributing/performance.rst` |
52
+
53
+ ---
54
+
55
+ ## PHASE 2 — `_protocols` Interface Contracts (Steps 29–44)
56
+
57
+ | # | Task | Docs Page |
58
+ |---|------|-----------|
59
+ | 29 | `_protocols/day_counter.py` — `DayCounterProtocol` (`year_fraction`, `day_count`, `name`); `CalendarProtocol` (`is_business_day`, `adjust`, `advance`, `business_days_between`) | `docs/api/protocols/day-counter.rst` |
60
+ | 30 | `_protocols/term_structure.py` — `YieldCurveProtocol` (`discount_factor`, `zero_rate`, `forward_rate`, `par_rate`); `VolSurfaceProtocol` (`implied_vol`, `local_vol`) | `docs/api/protocols/term-structure.rst` |
61
+ | 31 | `_protocols/pricing.py` — `InstrumentProtocol` (`valuation_date`, `currency`, `to_pricing_request`); `PricingEngineProtocol` (`calculate`, `supported_instrument_types`) | `docs/api/protocols/pricing.rst` |
62
+ | 32 | `_protocols/solver.py` — `RootFinderProtocol` (`solve`); `StochasticProcessProtocol` (`generate_paths`) | `docs/api/protocols/solver.rst` |
63
+ | 33 | `_protocols/data_feed.py` — `PriceFeedProtocol` (async `fetch_ohlcv`, `fetch_quote`); `FundamentalFeedProtocol` (async `fetch_financials`); `YieldCurveFeedProtocol` (async `fetch_curve`) | `docs/api/protocols/data-feed.rst` |
64
+ | 34 | `_protocols/risk.py` — `VaRProtocol` (`compute_var`, `compute_es`); `CreditRiskProtocol` (`expected_loss`, `unexpected_loss`, `raroc`) | `docs/api/protocols/risk.rst` |
65
+ | 35 | `_protocols/reporter.py` — `FormatterProtocol` (`format`); `ReporterProtocol` (`render`) | `docs/api/protocols/reporter.rst` |
66
+ | 36 | `_protocols/engine.py` — generic `EngineProtocol[InputT, OutputT]` (`compute → Result`) | `docs/api/protocols/engine.rst` |
67
+ | 37 | Write conformance test helpers: `assert_implements_protocol(obj, proto)` using `isinstance` | `docs/contributing/testing.rst` — protocol conformance section |
68
+ | 38 | Tests: dummy implementations of every protocol; verify `isinstance` checks pass at runtime | `docs/api/protocols/index.rst` |
69
+ | 39 | `_protocols/__init__.py` — re-export all protocols under `quantra.protocols` namespace | `docs/api/protocols/index.rst` |
70
+ | 40 | Docs page `docs/design/protocols.rst` — rationale: why Protocols over ABCs for external impls | `docs/design/protocols.rst` |
71
+ | 41 | Add `mypy` plugin tests: verify that missing method on a fake implementation raises `error: ... incompatible type` | `docs/contributing/type-checking.rst` |
72
+ | 42 | `_protocols/feed_registry.py` — `FeedRegistry`: maps feed names to `PriceFeedProtocol` impls; singleton | `docs/api/protocols/feed-registry.rst` |
73
+ | 43 | `_protocols/engine_registry.py` — `PricingEngineRegistry`: maps instrument type strings to engine instances | `docs/api/protocols/engine-registry.rst` |
74
+ | 44 | Docs page `docs/design/registry-pattern.rst` — rationale for runtime dispatch via registry | `docs/design/registry-pattern.rst` |
75
+
76
+ ---
77
+
78
+ ## PHASE 3 — `conventions` Module (Steps 45–68)
79
+
80
+ ### Day Count Conventions
81
+
82
+ | # | Task | Docs Page |
83
+ |---|------|-----------|
84
+ | 45 | `conventions/day_count/_base.py` — `DayCounterBase` ABC; `__repr__`, `__eq__` by name | `docs/api/conventions/day-count.rst` |
85
+ | 46 | `conventions/day_count/act360.py` — `Actual360`; `year_fraction = days / 360` | `docs/api/conventions/day-count.rst` |
86
+ | 47 | Tests for `Actual360`: same-month, year-crossing, leap-year edge cases | `docs/api/conventions/day-count.rst` |
87
+ | 48 | `conventions/day_count/act365.py` — `Actual365Fixed`; `year_fraction = days / 365` | `docs/api/conventions/day-count.rst` |
88
+ | 49 | `conventions/day_count/thirty360.py` — `Thirty360` with sub-variants: `US`, `European`, `ISDA`; implement the 30E/360 ISDA spec exactly | `docs/api/conventions/day-count.rst` |
89
+ | 50 | Tests for `Thirty360`: ISDA spec test vectors (use published ISDA doc examples) | `docs/api/conventions/day-count.rst` |
90
+ | 51 | `conventions/day_count/actact.py` — `ActualActual(variant)` with `ISDA`, `ICMA`, `AFB`; implement two-term ISDA formula | `docs/api/conventions/day-count.rst` |
91
+ | 52 | Tests for `ActualActual(ISDA)`: published ISDA test vectors; hypothesis tests for `0 ≤ dcf ≤ 2` | `docs/api/conventions/day-count.rst` |
92
+ | 53 | `conventions/day_count/bus252.py` — `Business252`; requires injected `CalendarProtocol` | `docs/api/conventions/day-count.rst` |
93
+ | 54 | `conventions/day_count/__init__.py` — `DayCounterFactory.from_name(str) → DayCounterBase` | `docs/api/conventions/day-count.rst` |
94
+
95
+ ### Calendar Conventions
96
+
97
+ | # | Task | Docs Page |
98
+ |---|------|-----------|
99
+ | 55 | `conventions/calendar/_base.py` — `CalendarBase` ABC; `adjust`, `advance`, `business_days_between` implemented in terms of abstract `is_holiday` | `docs/api/conventions/calendar.rst` |
100
+ | 56 | `conventions/calendar/us_federal.py` — `USFederalCalendar`; hardcode holiday rule set (New Year, MLK, Presidents, Memorial, Juneteenth, Independence, Labor, Columbus, Veterans, Thanksgiving, Christmas) | `docs/api/conventions/calendar.rst` |
101
+ | 57 | Tests for `USFederalCalendar`: 2024 and 2025 full holiday lists; Monday substitution rules | `docs/api/conventions/calendar.rst` |
102
+ | 58 | `conventions/calendar/nyse.py` — `NYSECalendar`; wraps `exchange-calendars` library; implements `CalendarProtocol` | `docs/api/conventions/calendar.rst` |
103
+ | 59 | `conventions/calendar/target.py` — `TARGETCalendar`; wraps `exchange-calendars`; Good Friday / Easter computed via `dateutil.easter` | `docs/api/conventions/calendar.rst` |
104
+ | 60 | `conventions/calendar/joint.py` — `JointCalendar(calendars, mode)`; `mode = "union"` (holiday if any) or `"intersection"` (holiday if all) | `docs/api/conventions/calendar.rst` |
105
+ | 61 | Tests for `JointCalendar`: union vs intersection; verify holiday set arithmetic | `docs/api/conventions/calendar.rst` |
106
+
107
+ ### Date Schedule Generator
108
+
109
+ | # | Task | Docs Page |
110
+ |---|------|-----------|
111
+ | 62 | `conventions/schedule.py` — `BusinessDayConvention` enum; `DateGenerationRule` enum | `docs/api/conventions/schedule.rst` |
112
+ | 63 | `ScheduleBuilder.build()` — forward and backward date generation; IMM date support | `docs/api/conventions/schedule.rst` |
113
+ | 64 | `ScheduleBuilder` — end-of-month rule; stub period handling for bonds | `docs/api/conventions/schedule.rst` |
114
+ | 65 | Tests for `ScheduleBuilder`: 5Y quarterly bond backward schedule; EOM rule; Modified Following on month-end | `docs/api/conventions/schedule.rst` |
115
+ | 66 | `conventions/client.py` — `ConventionsClient`; factory methods `day_counter(name)`, `calendar(name)`, `schedule_builder(calendar)` | `docs/api/conventions/client.rst` |
116
+ | 67 | `conventions/__init__.py` — public re-exports | `docs/api/conventions/index.rst` |
117
+ | 68 | Docs page `docs/guides/conventions.rst` — narrative guide with bond schedule worked example | `docs/guides/conventions.rst` |
118
+
119
+ ---
120
+
121
+ ## PHASE 4 — `numerics` Module (Steps 69–93)
122
+
123
+ ### Root Finders
124
+
125
+ | # | Task | Docs Page |
126
+ |---|------|-----------|
127
+ | 69 | `numerics/solvers/_base.py` — `RootFinderBase` ABC; `solve` signature; iteration counter mixin | `docs/api/numerics/solvers.rst` |
128
+ | 70 | `numerics/solvers/bisection.py` — `BisectionSolver`; validates bracket `f(a)·f(b) < 0` | `docs/api/numerics/solvers.rst` |
129
+ | 71 | Tests for `BisectionSolver`: `f(x)=x²-2`, `f(x)=cos(x)-x`; convergence rate assertion | `docs/api/numerics/solvers.rst` |
130
+ | 72 | `numerics/solvers/brent.py` — `BrentSolver`; wraps `scipy.optimize.brentq`; raises `ConvergenceError` on failure | `docs/api/numerics/solvers.rst` |
131
+ | 73 | `numerics/solvers/newton_raphson.py` — `NewtonRaphson`; finite-difference fallback; step-clamp for bounded vars | `docs/api/numerics/solvers.rst` |
132
+ | 74 | Tests for `NewtonRaphson`: analytic deriv; FD deriv; zero-derivative guard; bounds clamping | `docs/api/numerics/solvers.rst` |
133
+ | 75 | `numerics/solvers/implied_vol.py` — `ImpliedVolSolver`; Newton primary, Brent fallback; intrinsic value guard | `docs/api/numerics/implied-vol.rst` |
134
+ | 76 | Tests for `ImpliedVolSolver`: round-trip BSM → market price → implied vol ≈ original vol | `docs/api/numerics/implied-vol.rst` |
135
+ | 77 | `numerics/solvers/yield_solver.py` — `YieldSolver`; solves bond yield-from-price | `docs/api/numerics/yield-solver.rst` |
136
+
137
+ ### Integration & Linear Algebra
138
+
139
+ | # | Task | Docs Page |
140
+ |---|------|-----------|
141
+ | 78 | `numerics/integration.py` — `GaussLegendre(n)`, `Simpson`, `Romberg`; typed `Integrator` protocol | `docs/api/numerics/integration.rst` |
142
+ | 79 | Tests for integrators: `∫₀¹ x² dx = 1/3`; `∫₀^π sin(x) dx = 2`; error bound assertions | `docs/api/numerics/integration.rst` |
143
+ | 80 | `numerics/linalg.py` — `cholesky_correlation(corr_matrix)`; `correlated_normals(L, Z)`; `nearest_psd(M)` (Higham algorithm) | `docs/api/numerics/linalg.rst` |
144
+ | 81 | Tests for `nearest_psd`: non-PSD input → PSD output; idempotent on PSD input | `docs/api/numerics/linalg.rst` |
145
+
146
+ ### Stochastic Processes
147
+
148
+ | # | Task | Docs Page |
149
+ |---|------|-----------|
150
+ | 82 | `numerics/stochastic/_base.py` — `StochasticProcessBase` ABC; `generate_paths` signature; antithetic variate mixin | `docs/api/numerics/stochastic.rst` |
151
+ | 83 | `numerics/stochastic/gbm.py` — `GBMProcess`; Euler-Maruyama; log-normal exact scheme | `docs/api/numerics/stochastic.rst` |
152
+ | 84 | Tests for `GBMProcess`: `E[S_T] ≈ S₀·e^{rT}` (MC convergence); `Var[S_T]` in tolerance | `docs/api/numerics/stochastic.rst` |
153
+ | 85 | `numerics/stochastic/heston.py` — `HestonProcess`; full-truncation Euler; Milstein correction on V; Feller condition warning | `docs/api/numerics/stochastic.rst` |
154
+ | 86 | Tests for `HestonProcess`: variance stays non-negative; spot mean convergence; Feller warning fires | `docs/api/numerics/stochastic.rst` |
155
+ | 87 | `numerics/stochastic/hull_white.py` — `HullWhiteProcess`; exact discretisation of dr = [θ(t)-a·r]dt + σ·dW | `docs/api/numerics/stochastic.rst` |
156
+ | 88 | `numerics/stochastic/jump_diffusion.py` — `MertonJumpDiffusion`; compound Poisson jump superimposed on GBM | `docs/api/numerics/stochastic.rst` |
157
+ | 89 | Tests for `MertonJumpDiffusion`: zero jump intensity → converges to GBM | `docs/api/numerics/stochastic.rst` |
158
+ | 90 | `numerics/stochastic/variance_reduction.py` — `AntitheticSampler`, `ControlVariateSampler`, `QuasiMonteCarlo` (Sobol sequences) | `docs/api/numerics/variance-reduction.rst` |
159
+ | 91 | Tests for variance reduction: antithetic halves MC std error vs crude; Sobol vs pseudo-random convergence rate | `docs/api/numerics/variance-reduction.rst` |
160
+ | 92 | `numerics/__init__.py` — re-export public API | `docs/api/numerics/index.rst` |
161
+ | 93 | Docs page `docs/guides/numerical-methods.rst` — narrative: when to use each solver and process | `docs/guides/numerical-methods.rst` |
162
+
163
+ ---
164
+
165
+ ## PHASE 5 — `term_structures` Module (Steps 94–118)
166
+
167
+ ### Rate Term Structures
168
+
169
+ | # | Task | Docs Page |
170
+ |---|------|-----------|
171
+ | 94 | `term_structures/rates/_base.py` — `TermStructureBase(Observable)`; `reference_date`; `update()` triggers `notify_observers()` | `docs/api/term-structures/rates.rst` |
172
+ | 95 | `term_structures/rates/interpolation.py` — `InterpolatorProtocol`; `LinearInterpolator`, `LogLinearInterpolator` | `docs/api/term-structures/interpolation.rst` |
173
+ | 96 | `term_structures/rates/interpolation.py` — `CubicSplineInterpolator` (C2, not-a-knot BC) | `docs/api/term-structures/interpolation.rst` |
174
+ | 97 | `term_structures/rates/interpolation.py` — `NelsonSiegelInterpolator`; parametric fit via `scipy.optimize.least_squares` | `docs/api/term-structures/interpolation.rst` |
175
+ | 98 | `term_structures/rates/interpolation.py` — `SvenssonInterpolator`; 6-parameter extension of Nelson-Siegel | `docs/api/term-structures/interpolation.rst` |
176
+ | 99 | Tests for interpolators: `LogLinear` → convexity of discount factors; `CubicSpline` → C2 continuity check; `NelsonSiegel` → fit to US Treasury 2024 data | `docs/api/term-structures/interpolation.rst` |
177
+ | 100 | `term_structures/rates/discount_curve.py` — `DiscountCurve(TermStructureBase)`; `fit()` triggers `update()`; `discount_factor`, `zero_rate`, `forward_rate`, `par_rate` | `docs/api/term-structures/discount-curve.rst` |
178
+ | 101 | Tests for `DiscountCurve`: `P(0,0)=1`; monotone DFs; forward rate consistency `f(t1,t2)·(t2-t1) = z(t2)·t2 - z(t1)·t1` | `docs/api/term-structures/discount-curve.rst` |
179
+ | 102 | Integration test: `DiscountCurve.fit()` → `notify_observers()` → registered `Observer.on_market_update()` called | `docs/design/observable.rst` |
180
+ | 103 | `term_structures/rates/forward_curve.py` — `ForwardRateCurve`; instantaneous forward from `DiscountCurve` via numerical derivative | `docs/api/term-structures/forward-curve.rst` |
181
+ | 104 | `term_structures/rates/bootstrap.py` — `CurveBootstrapper`; accepts list of `(instrument, market_price)` tuples; iterative pillar solving via injected `RootFinderProtocol` | `docs/api/term-structures/bootstrap.rst` |
182
+ | 105 | Supported bootstrap instruments: `DepositRate`, `FRA`, `FuturesContract`, `SwapRate` | `docs/api/term-structures/bootstrap.rst` |
183
+ | 106 | Tests for `CurveBootstrapper`: reproduce deposit + swap bootstrap from known Euribor quotes; verify zero-coupon prices reprice to par | `docs/api/term-structures/bootstrap.rst` |
184
+ | 107 | `term_structures/rates/ois_curve.py` — `OISCurve`; dual-curve bootstrap (OIS discounting, LIBOR/SOFR projection) | `docs/api/term-structures/ois-curve.rst` |
185
+
186
+ ### Volatility Surfaces
187
+
188
+ | # | Task | Docs Page |
189
+ |---|------|-----------|
190
+ | 108 | `term_structures/volatility/_base.py` — `VolSurfaceBase(TermStructureBase)`; `implied_vol(T, K)`, `local_vol(t, S)` | `docs/api/term-structures/vol-surface.rst` |
191
+ | 109 | `term_structures/volatility/flat.py` — `FlatVolSurface(sigma)`; `local_vol == implied_vol` | `docs/api/term-structures/vol-surface.rst` |
192
+ | 110 | `term_structures/volatility/sabr.py` — `SABRSurface(alpha, beta, rho, nu)`; Hagan 2002 approximation formula | `docs/api/term-structures/vol-surface.rst` |
193
+ | 111 | Tests for `SABRSurface`: smile shape (ITM < ATM > OTM for ρ=0); Hagan formula against known values | `docs/api/term-structures/vol-surface.rst` |
194
+ | 112 | `term_structures/volatility/svi.py` — `SVIParamSurface`; Raw SVI parametrisation: `w(k) = a + b(ρ(k-m) + √((k-m)²+σ²))` | `docs/api/term-structures/vol-surface.rst` |
195
+ | 113 | `term_structures/volatility/local_vol.py` — `DupireLocalVol`; numerical differentiation of call price surface; Dupire formula `σ²_loc = (∂C/∂T) / (½K²·∂²C/∂K²)` | `docs/api/term-structures/vol-surface.rst` |
196
+ | 114 | Tests for `DupireLocalVol`: flat implied vol → flat local vol (within numerical tolerance) | `docs/api/term-structures/vol-surface.rst` |
197
+ | 115 | `term_structures/volatility/interpolated.py` — `GridVolSurface`; bilinear and bicubic interpolation over (T, K) grid of market quotes | `docs/api/term-structures/vol-surface.rst` |
198
+ | 116 | `term_structures/client.py` — `TermStructureClient`; factory methods; manages lifecycle of curves and surfaces | `docs/api/term-structures/client.rst` |
199
+ | 117 | `term_structures/__init__.py` — public re-exports | `docs/api/term-structures/index.rst` |
200
+ | 118 | Docs page `docs/guides/term-structures.rst` — narrative: bootstrap walkthrough; vol surface calibration example | `docs/guides/term-structures.rst` |
201
+
202
+ ---
203
+
204
+ ## PHASE 6 — `instruments` Module (Steps 119–138)
205
+
206
+ | # | Task | Docs Page |
207
+ |---|------|-----------|
208
+ | 119 | `instruments/_base.py` — `InstrumentBase(Observer)`; `link(*observables)`, `unlink_all()`; `@invalidatable_cache` on `npv`, `delta`, `gamma`, `vega`; `swap_engine` | `docs/api/instruments/base.rst` |
209
+ | 120 | Tests for `InstrumentBase`: cache hit; `on_market_update` invalidates; `swap_engine` invalidates | `docs/api/instruments/base.rst` |
210
+ | 121 | `instruments/equity/stock.py` — `Stock`; `to_pricing_request()` with `instrument_type = "Stock"` | `docs/api/instruments/equity.rst` |
211
+ | 122 | `instruments/rates/bond.py` — `FixedRateBond`; `to_pricing_request()` serializes schedule, coupon, face | `docs/api/instruments/rates.rst` |
212
+ | 123 | `instruments/rates/bond.py` — `FloatingRateBond`; `index_name`, `spread`, `reset_frequency` | `docs/api/instruments/rates.rst` |
213
+ | 124 | `instruments/rates/bond.py` — `ZeroCouponBond`; `to_pricing_request()` | `docs/api/instruments/rates.rst` |
214
+ | 125 | `instruments/rates/swap.py` — `VanillaIRS`; fixed leg + floating leg; `to_pricing_request()` | `docs/api/instruments/rates.rst` |
215
+ | 126 | `instruments/rates/swap.py` — `OIS` (Overnight Index Swap); compounded floating leg | `docs/api/instruments/rates.rst` |
216
+ | 127 | `instruments/rates/fra.py` — `ForwardRateAgreement`; `to_pricing_request()` | `docs/api/instruments/rates.rst` |
217
+ | 128 | `instruments/options/vanilla.py` — `EuropeanOption`; `OptionRight`, `ExerciseStyle`; `time_to_expiry` property; `to_pricing_request()` | `docs/api/instruments/options.rst` |
218
+ | 129 | `instruments/options/vanilla.py` — `AmericanOption`; same contract terms; early exercise flag in request | `docs/api/instruments/options.rst` |
219
+ | 130 | Tests for `EuropeanOption`: observer link → update → cache miss; `to_pricing_request` schema validation | `docs/api/instruments/options.rst` |
220
+ | 131 | `instruments/options/asian.py` — `AsianOption`; `averaging_type` (arithmetic/geometric); `observation_dates` list | `docs/api/instruments/options.rst` |
221
+ | 132 | `instruments/options/barrier.py` — `BarrierOption`; `barrier_type` (up-in, up-out, down-in, down-out); `barrier_level`, `rebate` | `docs/api/instruments/options.rst` |
222
+ | 133 | `instruments/options/swaption.py` — `Swaption`; underlying `VanillaIRS`; `expiry`, `exercise_style` | `docs/api/instruments/options.rst` |
223
+ | 134 | `instruments/credit/cds.py` — `CreditDefaultSwap`; `reference_entity`, `spread_bps`, `notional`, `recovery_rate`, `payment_schedule` | `docs/api/instruments/credit.rst` |
224
+ | 135 | Tests for all instrument `to_pricing_request()` outputs: `instrument_type` key present; pydantic schema validates | `docs/api/instruments/index.rst` |
225
+ | 136 | `instruments/__init__.py` — public re-exports | `docs/api/instruments/index.rst` |
226
+ | 137 | Docs page `docs/guides/instruments.rst` — narrative: building an option, linking to a vol surface, swapping engine | `docs/guides/instruments.rst` |
227
+ | 138 | Docs page `docs/design/instrument-engine-decoupling.rst` — deep dive: why `to_pricing_request` returns a dict | `docs/design/instrument-engine-decoupling.rst` |
228
+
229
+ ---
230
+
231
+ ## PHASE 7 — `pricing_engines` Module (Steps 139–163)
232
+
233
+ ### Engine Base & Registry
234
+
235
+ | # | Task | Docs Page |
236
+ |---|------|-----------|
237
+ | 139 | `pricing_engines/_base.py` — `PricingEngineBase` ABC; `_assert_supported(request)`; standard result schema | `docs/api/pricing/base.rst` |
238
+ | 140 | `pricing_engines/client.py` — `PricingEngineRegistry`; `register(engine)`, `get(instrument_type) → PricingEngineBase`; priority ordering | `docs/api/pricing/registry.rst` |
239
+
240
+ ### Analytic Engines
241
+
242
+ | # | Task | Docs Page |
243
+ |---|------|-----------|
244
+ | 141 | `pricing_engines/analytic/black_scholes.py` — `BlackScholesMertonEngine`; all 5 Greeks; accepts `YieldCurveProtocol` + `VolSurfaceProtocol` | `docs/api/pricing/analytic/bsm.rst` |
245
+ | 142 | Tests for `BSMEngine`: put-call parity; `delta ∈ (0,1)` for call; `gamma > 0`; `vega > 0`; limiting cases T→0 | `docs/api/pricing/analytic/bsm.rst` |
246
+ | 143 | `pricing_engines/analytic/black76.py` — `Black76Engine`; for futures options and swaptions; forward-price based | `docs/api/pricing/analytic/black76.rst` |
247
+ | 144 | `pricing_engines/analytic/bachelier.py` — `BachelierEngine`; normal model for negative rate environments | `docs/api/pricing/analytic/bachelier.rst` |
248
+ | 145 | `pricing_engines/analytic/bond_analytic.py` — `BondAnalyticEngine`; `price_from_yield`, `yield_from_price` (via injected solver), `modified_duration`, `convexity`, `dv01` | `docs/api/pricing/analytic/bond.rst` |
249
+ | 146 | Tests for `BondAnalyticEngine`: price at par when yield = coupon; duration < maturity for coupon bond; convexity > 0; round-trip yield-price-yield | `docs/api/pricing/analytic/bond.rst` |
250
+ | 147 | `pricing_engines/analytic/swap_analytic.py` — `SwapAnalyticEngine`; NPV = PV(fixed) - PV(float); par swap rate solver | `docs/api/pricing/analytic/swap.rst` |
251
+ | 148 | Tests for `SwapAnalyticEngine`: at-market swap NPV ≈ 0; payer/receiver symmetry; DV01 sign check | `docs/api/pricing/analytic/swap.rst` |
252
+ | 149 | `pricing_engines/analytic/cds_analytic.py` — `CDSAnalyticEngine`; survival probability curve; par spread; upfront conversion | `docs/api/pricing/analytic/cds.rst` |
253
+
254
+ ### Finite Difference Engines
255
+
256
+ | # | Task | Docs Page |
257
+ |---|------|-----------|
258
+ | 150 | `pricing_engines/finite_difference/_grid.py` — `PDEGrid`; uniform and non-uniform S-grids; boundary condition applicators | `docs/api/pricing/fd/grid.rst` |
259
+ | 151 | `pricing_engines/finite_difference/implicit.py` — `CrankNicolsonEngine`; tridiagonal system solve via Thomas algorithm | `docs/api/pricing/fd/crank-nicolson.rst` |
260
+ | 152 | Tests for `CrankNicolsonEngine`: European call → matches BSM within 0.01%; American call → no early exercise for non-dividend-paying | `docs/api/pricing/fd/crank-nicolson.rst` |
261
+ | 153 | `pricing_engines/finite_difference/trinomial.py` — `TrinomialTreeEngine`; CRR parameterisation; early exercise at each node | `docs/api/pricing/fd/trinomial.rst` |
262
+ | 154 | Tests for `TrinomialTreeEngine`: American put > European put; put-call parity for European; convergence with N nodes | `docs/api/pricing/fd/trinomial.rst` |
263
+
264
+ ### Monte Carlo Engines
265
+
266
+ | # | Task | Docs Page |
267
+ |---|------|-----------|
268
+ | 155 | `pricing_engines/monte_carlo/_simulator.py` — `MonteCarloSimulator`; process injection; path caching keyed by `(seed, n_paths, n_steps)` | `docs/api/pricing/mc/simulator.rst` |
269
+ | 156 | `pricing_engines/monte_carlo/european.py` — `MonteCarloEuropeanEngine`; standard error output; confidence interval | `docs/api/pricing/mc/european.rst` |
270
+ | 157 | Tests for `MonteCarloEuropeanEngine` with `GBMProcess`: converges to BSM; std_err decreases with √N | `docs/api/pricing/mc/european.rst` |
271
+ | 158 | `pricing_engines/monte_carlo/path_dependent.py` — `MonteCarloAsianEngine`; `MonteCarloBarrierEngine`; separate payoff functions | `docs/api/pricing/mc/path-dependent.rst` |
272
+ | 159 | Tests for `MonteCarloAsianEngine`: geometric Asian → closed-form benchmark; arithmetic Asian > geometric | `docs/api/pricing/mc/path-dependent.rst` |
273
+ | 160 | `pricing_engines/monte_carlo/american.py` — `LSMEngine` (Longstaff-Schwartz); Laguerre basis; regression-based continuation value | `docs/api/pricing/mc/american.rst` |
274
+ | 161 | Tests for `LSMEngine`: American put value ≥ European put; converges toward trinomial tree price | `docs/api/pricing/mc/american.rst` |
275
+ | 162 | `pricing_engines/__init__.py` — public re-exports | `docs/api/pricing/index.rst` |
276
+ | 163 | Docs page `docs/guides/pricing-engines.rst` — decision matrix: analytic vs FD vs MC; engine swap example | `docs/guides/pricing-engines.rst` |
277
+
278
+ ---
279
+
280
+ ## PHASE 8 — `tvm` Module (Steps 164–172)
281
+
282
+ | # | Task | Docs Page |
283
+ |---|------|-----------|
284
+ | 164 | `tvm/_engine.py` — `pv`, `fv`, `pmt`, `nper`, `rate` pure functions using `Decimal` arithmetic | `docs/api/tvm/engine.rst` |
285
+ | 165 | Tests for TVM engine: all 5 variables; round-trip identities; zero-rate edge cases | `docs/api/tvm/engine.rst` |
286
+ | 166 | `tvm/_engine.py` — `npv(rate, cashflows)`, `irr(cashflows)` via Brent solver, `mirr`, `xnpv`, `xirr` | `docs/api/tvm/engine.rst` |
287
+ | 167 | Tests for `irr`: multiple IRR detection (raise `AmbiguousIRRError`); single sign change; all-negative guard | `docs/api/tvm/engine.rst` |
288
+ | 168 | `tvm/annuity.py` — `OrdinaryAnnuity`, `AnnuityDue`, `Perpetuity`, `GrowingPerpetuity` | `docs/api/tvm/annuity.rst` |
289
+ | 169 | `tvm/bond.py` — `BondPriceEngine`; accrued interest; clean vs dirty price; yield conventions | `docs/api/tvm/bond.rst` |
290
+ | 170 | `tvm/client.py` — `TVMClient`; wraps engine; exposes `npv`, `irr`, `pv`, `fv`, `annuity` | `docs/api/tvm/client.rst` |
291
+ | 171 | `tvm/__init__.py` — public re-exports | `docs/api/tvm/index.rst` |
292
+ | 172 | Docs page `docs/guides/tvm.rst` — DCF worked example; mortgage payment derivation | `docs/guides/tvm.rst` |
293
+
294
+ ---
295
+
296
+ ## PHASE 9 — `cashflow` Module (Steps 173–179)
297
+
298
+ | # | Task | Docs Page |
299
+ |---|------|-----------|
300
+ | 173 | `cashflow/_engine.py` — `CashFlowStream`; `payback_period`, `discounted_payback`, `profitability_index` | `docs/api/cashflow/engine.rst` |
301
+ | 174 | `cashflow/projections.py` — `StochasticProjection`; Monte Carlo CF distributions; percentile outputs | `docs/api/cashflow/projections.rst` |
302
+ | 175 | `cashflow/waterfall.py` — `WaterfallEngine`; priority-of-payments; tranche allocation; OC/IC test triggers | `docs/api/cashflow/waterfall.rst` |
303
+ | 176 | Tests for `WaterfallEngine`: senior paid before sub; OC test breach → redirect | `docs/api/cashflow/waterfall.rst` |
304
+ | 177 | `cashflow/client.py` — `CashFlowClient` | `docs/api/cashflow/client.rst` |
305
+ | 178 | `cashflow/__init__.py` | `docs/api/cashflow/index.rst` |
306
+ | 179 | Docs page `docs/guides/cashflow.rst` — project finance example; waterfall example | `docs/guides/cashflow.rst` |
307
+
308
+ ---
309
+
310
+ ## PHASE 10 — `financials` Module (Steps 180–187)
311
+
312
+ | # | Task | Docs Page |
313
+ |---|------|-----------|
314
+ | 180 | `financials/statements.py` — `BalanceSheet`, `IncomeStatement`, `CashFlowStatement` Pydantic models | `docs/api/financials/statements.rst` |
315
+ | 181 | `financials/ratios.py` — liquidity (current, quick, cash); solvency (D/E, interest coverage, debt-to-assets); profitability (ROE, ROA, EBITDA margin); efficiency (asset turnover, DSO, DIO) | `docs/api/financials/ratios.rst` |
316
+ | 182 | Tests for ratios: known balance sheet → validate each ratio; division-by-zero guards | `docs/api/financials/ratios.rst` |
317
+ | 183 | `financials/scoring.py` — `AltmanZScore` (public + private variants); `PiotroskiFScore` (9-point) | `docs/api/financials/scoring.rst` |
318
+ | 184 | Tests for `AltmanZScore`: Enron 2000 10-K → distress zone; Apple 2023 → safe zone | `docs/api/financials/scoring.rst` |
319
+ | 185 | `financials/client.py` — `FinancialsClient`; async `fetch_and_analyze(ticker)` via injected `FundamentalFeedProtocol` | `docs/api/financials/client.rst` |
320
+ | 186 | `financials/__init__.py` | `docs/api/financials/index.rst` |
321
+ | 187 | Docs page `docs/guides/financials.rst` — credit analysis walkthrough using ratio + Z-score | `docs/guides/financials.rst` |
322
+
323
+ ---
324
+
325
+ ## PHASE 11 — `credit` Module (Steps 188–204)
326
+
327
+ | # | Task | Docs Page |
328
+ |---|------|-----------|
329
+ | 188 | `credit/loan.py` — `LoanTerms` Pydantic model; field validators; `balloon_period` consistency check | `docs/api/credit/loan.rst` |
330
+ | 189 | `credit/loan.py` — `ScheduleRow` frozen dataclass; `AmortizationSchedule`; `total_interest`, `total_payments`, `weighted_average_life` properties | `docs/api/credit/loan.rst` |
331
+ | 190 | `credit/_engine.py` — `_periodic_rate`, `_level_payment` (PMT formula in `Decimal`); `loan_constant`, `effective_annual_rate` | `docs/api/credit/engine.rst` |
332
+ | 191 | `credit/_engine.py` — `build_level_payment_schedule`; I/O period support; curtailment map; final-period stub rounding; returns `Result[list[ScheduleRow], ScheduleGenerationError]` | `docs/api/credit/engine.rst` |
333
+ | 192 | Tests for level-payment schedule: 30Y mortgage exact payment; total interest check; extra principal curtailment reduces WAL | `docs/api/credit/engine.rst` |
334
+ | 193 | `credit/_engine.py` — `build_straight_line_schedule`; `build_interest_only_schedule`; `build_balloon_schedule` | `docs/api/credit/engine.rst` |
335
+ | 194 | Tests for alternate amortization types: straight-line equal principal; IO full outstanding at maturity; balloon payment in final row | `docs/api/credit/engine.rst` |
336
+ | 195 | `credit/prepayment.py` — `PSAModel(psa_speed)`; CPR ramp (0% → 6% over 30 months); `cpr_to_smm`, `smm_to_curtailment_map` | `docs/api/credit/prepayment.rst` |
337
+ | 196 | Tests for `PSAModel`: 100 PSA month-30+ → 6% CPR; 0 PSA → no prepayment | `docs/api/credit/prepayment.rst` |
338
+ | 197 | `credit/modification.py` — `LoanModification`; forbearance (deferred interest capitalisation); rate reduction; term extension; principal forgiveness | `docs/api/credit/modification.rst` |
339
+ | 198 | Tests for `LoanModification`: deferred interest adds to balance; modified schedule total payments ≠ original | `docs/api/credit/modification.rst` |
340
+ | 199 | `credit/pricing.py` — `LoanPricer`; OAS calculation via Monte Carlo rate paths (Hull-White); Z-spread solver | `docs/api/credit/pricing.rst` |
341
+ | 200 | `credit/client.py` — `CreditClient`; `amortize`, `prepayment_adjusted_schedule`, `modify`, `price`; async `fetch_rates` | `docs/api/credit/client.rst` |
342
+ | 201 | `credit/__init__.py` | `docs/api/credit/index.rst` |
343
+ | 202 | Docs page `docs/guides/credit/amortization.rst` — 30Y fixed-rate mortgage walkthrough | `docs/guides/credit/amortization.rst` |
344
+ | 203 | Docs page `docs/guides/credit/prepayment.rst` — PSA model and WAL sensitivity | `docs/guides/credit/prepayment.rst` |
345
+ | 204 | Docs page `docs/guides/credit/modification.rst` — TDR accounting and NPV test for loss mitigation | `docs/guides/credit/modification.rst` |
346
+
347
+ ---
348
+
349
+ ## PHASE 12 — `risk` Module (Steps 205–217)
350
+
351
+ | # | Task | Docs Page |
352
+ |---|------|-----------|
353
+ | 205 | `risk/market.py` — `HistoricalVaR`; filtered historical simulation; `ParametricVaR` (delta-normal) | `docs/api/risk/market.rst` |
354
+ | 206 | `risk/market.py` — `MonteCarloVaR`; uses injected `StochasticProcessProtocol`; `ExpectedShortfall` (CVaR) | `docs/api/risk/market.rst` |
355
+ | 207 | Tests for VaR: known return distribution → analytic VaR matches parametric; ES ≥ VaR always | `docs/api/risk/market.rst` |
356
+ | 208 | `risk/credit_risk.py` — `PD`, `LGD`, `EAD` data models; `ExpectedLoss`, `UnexpectedLoss` (Vasicek single-factor); `EconomicCapital`; `RAROC` | `docs/api/risk/credit-risk.rst` |
357
+ | 209 | Tests for credit risk: `EL = PD × LGD × EAD`; `UL > 0` for ρ > 0; RAROC interpretation | `docs/api/risk/credit-risk.rst` |
358
+ | 210 | `risk/liquidity.py` — `LCR` (Basel III: HQLA / net cash outflows); `NSFR`; `CumulativeGapReport` | `docs/api/risk/liquidity.rst` |
359
+ | 211 | `risk/stress.py` — `ScenarioRunner`; parallel scenarios via `asyncio.gather`; `ShockVector` (parallel shift, twist, fly) | `docs/api/risk/stress.rst` |
360
+ | 212 | Tests for `ScenarioRunner`: +100bps parallel shift → bond price decreases by approx DV01 | `docs/api/risk/stress.rst` |
361
+ | 213 | `risk/_engine.py` — Greeks aggregation across portfolio; `DV01Portfolio`, `CS01Portfolio` | `docs/api/risk/engine.rst` |
362
+ | 214 | `risk/client.py` — `RiskClient`; composes all sub-engines; `var_report`, `credit_risk_report`, `stress_test` | `docs/api/risk/client.rst` |
363
+ | 215 | `risk/__init__.py` | `docs/api/risk/index.rst` |
364
+ | 216 | Docs page `docs/guides/risk/var.rst` — three VaR methods compared on same portfolio | `docs/guides/risk/var.rst` |
365
+ | 217 | Docs page `docs/guides/risk/credit-risk.rst` — EL/UL/RAROC for a loan book | `docs/guides/risk/credit-risk.rst` |
366
+
367
+ ---
368
+
369
+ ## PHASE 13 — `portfolio` Module (Steps 218–228)
370
+
371
+ | # | Task | Docs Page |
372
+ |---|------|-----------|
373
+ | 218 | `portfolio/analytics.py` — `PortfolioAnalytics`; annualised return, vol, Sharpe, Sortino, Calmar, max drawdown, drawdown duration | `docs/api/portfolio/analytics.rst` |
374
+ | 219 | Tests for portfolio analytics: known return series → verified Sharpe; max drawdown on monotone series = 0 | `docs/api/portfolio/analytics.rst` |
375
+ | 220 | `portfolio/analytics.py` — `PerformanceAttribution`; Brinson-Hood-Beebower (BHB) allocation + selection + interaction | `docs/api/portfolio/analytics.rst` |
376
+ | 221 | `portfolio/optimization.py` — `MeanVarianceOptimizer`; `cvxpy` QP; long-only + sum-to-one; optional cardinality constraint | `docs/api/portfolio/optimization.rst` |
377
+ | 222 | `portfolio/optimization.py` — `EfficientFrontier`; parametric trace over target return range | `docs/api/portfolio/optimization.rst` |
378
+ | 223 | Tests for `MeanVarianceOptimizer`: 2-asset analytical solution matches optimizer; min-vol ≤ max-Sharpe vol | `docs/api/portfolio/optimization.rst` |
379
+ | 224 | `portfolio/optimization.py` — `BlackLittermanOptimizer`; prior from market cap weights; posterior via BL formula | `docs/api/portfolio/optimization.rst` |
380
+ | 225 | `portfolio/optimization.py` — `RiskParityOptimizer`; equal risk contribution via iterative ERC algorithm | `docs/api/portfolio/optimization.rst` |
381
+ | 226 | `portfolio/factors.py` — `FactorModel`; OLS regression on Fama-French 3/5 factors; `alpha`, `beta_vec`, `r_squared`, `t_stats` | `docs/api/portfolio/factors.rst` |
382
+ | 227 | `portfolio/client.py` — `PortfolioClient`; `analyze`, `optimize`, `factor_decompose` | `docs/api/portfolio/client.rst` |
383
+ | 228 | `portfolio/__init__.py`; docs page `docs/guides/portfolio.rst` | `docs/guides/portfolio.rst` |
384
+
385
+ ---
386
+
387
+ ## PHASE 14 — `backtest` Module (Steps 229–243)
388
+
389
+ | # | Task | Docs Page |
390
+ |---|------|-----------|
391
+ | 229 | `backtest/data_handler.py` — `MarketEvent` frozen dataclass; `HistoricalDataHandler`; async concurrent `fetch_all` via `asyncio.gather` | `docs/api/backtest/data-handler.rst` |
392
+ | 230 | `backtest/strategy.py` — `StrategyBase` ABC; `on_market_event → list[SignalEvent]`; `on_fill` | `docs/api/backtest/strategy.rst` |
393
+ | 231 | `backtest/execution.py` — `OrderSide`, `OrderType` enums; `SignalEvent`, `OrderEvent`, `FillEvent` frozen dataclasses | `docs/api/backtest/execution.rst` |
394
+ | 232 | `backtest/execution.py` — `SlippageModel` (fixed bps); `VolAdjustedSlippage`; `ExecutionHandler` (next-open fill) | `docs/api/backtest/execution.rst` |
395
+ | 233 | `backtest/portfolio_tracker.py` — `Position` dataclass; FIFO cost basis; `PortfolioTracker`; `size_order` (fixed fraction) | `docs/api/backtest/portfolio.rst` |
396
+ | 234 | `backtest/portfolio_tracker.py` — `snapshot()`; equity curve accumulation; commission accounting | `docs/api/backtest/portfolio.rst` |
397
+ | 235 | `backtest/performance.py` — `PerformanceRecord`; `total_return`, `annualised_return`, `annualised_volatility`, `sharpe`, `sortino`, `max_drawdown`, `calmar` | `docs/api/backtest/performance.rst` |
398
+ | 236 | Tests for `PerformanceRecord`: flat equity → 0 drawdown; monotone increasing → 0 max dd; known Sharpe | `docs/api/backtest/performance.rst` |
399
+ | 237 | `backtest/_engine.py` — `BacktestEngine`; event loop: update prices → strategy → size → execute → record → snapshot | `docs/api/backtest/engine.rst` |
400
+ | 238 | Tests for `BacktestEngine`: buy-and-hold strategy on synthetic price series → known final equity | `docs/api/backtest/engine.rst` |
401
+ | 239 | `backtest/_engine.py` — `on_bar_hook` callback; `transaction_log` output | `docs/api/backtest/engine.rst` |
402
+ | 240 | `backtest/client.py` — `BacktestClient`; wires all components; exposes `async run(strategy, tickers, start, end)` | `docs/api/backtest/client.rst` |
403
+ | 241 | `backtest/__init__.py` | `docs/api/backtest/index.rst` |
404
+ | 242 | Docs page `docs/guides/backtest/quickstart.rst` — SMA crossover strategy end-to-end | `docs/guides/backtest/quickstart.rst` |
405
+ | 243 | Docs page `docs/guides/backtest/strategy-authoring.rst` — `StrategyBase` contract; signal → order lifecycle | `docs/guides/backtest/strategy-authoring.rst` |
406
+
407
+ ---
408
+
409
+ ## PHASE 15 — `reporting` Module (Steps 244–251)
410
+
411
+ | # | Task | Docs Page |
412
+ |---|------|-----------|
413
+ | 244 | `reporting/serialization.py` — Pydantic v2 schemas for all domain models; `model_json_schema()` export | `docs/api/reporting/serialization.rst` |
414
+ | 245 | `reporting/formatters.py` — `ConsoleFormatter` (rich tables); `JSONFormatter`; `DataFrameFormatter` | `docs/api/reporting/formatters.rst` |
415
+ | 246 | `reporting/formatters.py` — `AmortizationScheduleFormatter`; `PerformanceRecordFormatter` | `docs/api/reporting/formatters.rst` |
416
+ | 247 | `reporting/tearsheet.py` — `TearsheetBuilder`; plotly subplots: equity curve, drawdown, monthly returns heatmap, rolling Sharpe | `docs/api/reporting/tearsheet.rst` |
417
+ | 248 | `reporting/tearsheet.py` — HTML export; PDF export via `weasyprint`; async render path | `docs/api/reporting/tearsheet.rst` |
418
+ | 249 | `reporting/__init__.py` | `docs/api/reporting/index.rst` |
419
+ | 250 | Docs page `docs/guides/reporting.rst` — generate tearsheet from backtest result | `docs/guides/reporting.rst` |
420
+ | 251 | Docs page `docs/guides/reporting.rst` — export amortization schedule to Excel and PDF | `docs/guides/reporting.rst` |
421
+
422
+ ---
423
+
424
+ ## PHASE 16 — Integration, Hardening, Release (Steps 252–265)
425
+
426
+ | # | Task | Docs Page |
427
+ |---|------|-----------|
428
+ | 252 | Integration test: `EuropeanOption.link(curve, vol)` → `curve.fit()` → `opt.npv()` recomputes; `opt.swap_engine(mc)` → recomputes via MC | `docs/design/observable.rst` |
429
+ | 253 | Integration test: `CurveBootstrapper` → `DiscountCurve` → `BondAnalyticEngine` → `FixedRateBond.npv()` → reprices to par | `docs/guides/term-structures.rst` |
430
+ | 254 | Integration test: `BacktestClient.run()` end-to-end with `yfinance` feed; verify no look-ahead bias (results identical regardless of row fetch order) | `docs/guides/backtest/quickstart.rst` |
431
+ | 255 | Integration test: `CreditClient.amortize` + `PSAModel` + `LoanPricer.oas` on a sample RMBS pool | `docs/guides/credit/prepayment.rst` |
432
+ | 256 | Integration test: `RiskClient.stress_test` on a bond portfolio with `ScenarioRunner` parallel scenarios | `docs/guides/risk/var.rst` |
433
+ | 257 | Add `hypothesis` property tests to `_engine.py` files: TVM identities; amortization balance non-negative throughout; IRR of a single cash flow | `docs/contributing/testing.rst` |
434
+ | 258 | Performance benchmark suite: `pytest-benchmark`; amortization schedule 360-period < 5ms; MC 100k paths < 2s | `docs/contributing/performance.rst` |
435
+ | 259 | Add `conftest.py` shared fixtures: `flat_curve`, `flat_surface`, `sample_loan_terms`, `sample_option` | `docs/contributing/testing.rst` |
436
+ | 260 | Add `tests/integration/test_no_circular_imports.py`: verify DAG constraint via `importlib` crawl | `docs/design/layer-map.rst` |
437
+ | 261 | Pin all dependencies in `uv.lock`; add `uv export --format requirements-txt` to CI for RTD build | `docs/contributing/setup.rst` |
438
+ | 262 | Write `CHANGELOG.md`; tag `v0.1.0-alpha`; publish to TestPyPI via `uv publish --index testpypi` | `docs/changelog.rst` |
439
+ | 263 | Docs page `docs/api/index.rst` — full auto-generated API reference via `sphinx-apidoc` | `docs/api/index.rst` |
440
+ | 264 | Docs page `docs/guides/index.rst` — narrative guides index with suggested reading order | `docs/guides/index.rst` |
441
+ | 265 | Final RTD deploy: verify all 30+ pages render; fix any broken cross-references; enable versioned docs (stable / latest) | `docs/index.rst` |
442
+
443
+ ---
444
+
445
+ ## Step Execution Rules
446
+
447
+ 1. **Never skip the docs sub-task.** A step is not complete until the corresponding RST page is updated and committed.
448
+ 2. **Green CI gate before advancing.** Each commit must pass `ruff + mypy + pytest`. The RTD build is a required CI check from Step 8 onward.
449
+ 3. **One step = one commit.** The commit message format is: `[step-NNN] <verb>: <what>`. Example: `[step-191] feat: build_level_payment_schedule with curtailment support`.
450
+ 4. **Test-first on math engines.** For steps tagged `_engine.py`, write the test stubs before the implementation.
451
+ 5. **No skipping phases.** Phase N+1 may not begin until all Phase N tests pass.
quantra-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: quantra
3
+ Version: 0.0.0
4
+ Summary: An open-source Python library for quantitative finance — covering time value of money, credit lifecycle, risk analytics, derivatives pricing, and portfolio optimization.
5
+ Project-URL: Homepage, https://github.com/prashant-fintech/quantra
6
+ Project-URL: Documentation, https://quantra.readthedocs.io
7
+ Project-URL: Repository, https://github.com/prashant-fintech/quantra
8
+ Project-URL: Bug Tracker, https://github.com/prashant-fintech/quantra/issues
9
+ Author-email: Prashant Singh <box_prashant@outlook.com>
10
+ License: MIT
11
+ Keywords: derivatives,finance,fixed-income,pricing,quantitative,risk
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.13
21
+ Requires-Dist: anyio>=4.4
22
+ Requires-Dist: cvxpy>=1.5
23
+ Requires-Dist: exchange-calendars>=4.5
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: numpy>=2.0
26
+ Requires-Dist: pandas>=2.2
27
+ Requires-Dist: pydantic>=2.7
28
+ Requires-Dist: python-dateutil>=2.9
29
+ Requires-Dist: scipy>=1.13
30
+ Provides-Extra: data
31
+ Requires-Dist: pandas-datareader>=0.10; extra == 'data'
32
+ Requires-Dist: yfinance>=0.2; extra == 'data'
33
+ Provides-Extra: dev
34
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
35
+ Requires-Dist: mypy>=1.10; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
37
+ Requires-Dist: pytest-benchmark>=4.0; extra == 'dev'
38
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
39
+ Requires-Dist: pytest>=8.2; extra == 'dev'
40
+ Requires-Dist: ruff>=0.5; extra == 'dev'
41
+ Provides-Extra: docs
42
+ Requires-Dist: furo>=2024.1.29; extra == 'docs'
43
+ Requires-Dist: myst-parser>=3.0; extra == 'docs'
44
+ Requires-Dist: sphinx-autodoc-typehints>=2.2; extra == 'docs'
45
+ Requires-Dist: sphinx>=7.4; extra == 'docs'
46
+ Provides-Extra: reporting
47
+ Requires-Dist: openpyxl>=3.1; extra == 'reporting'
48
+ Requires-Dist: plotly>=5.22; extra == 'reporting'
49
+ Requires-Dist: weasyprint>=62.0; extra == 'reporting'
File without changes
@@ -0,0 +1,63 @@
1
+ # ADR-0001 — Use `uv` as the Exclusive Package Manager
2
+
3
+ **Date:** 2026-06-27
4
+ **Status:** Accepted
5
+ **Deciders:** Prashant Singh
6
+
7
+ ---
8
+
9
+ ## Context
10
+
11
+ A finance library with scientific and numerical dependencies (NumPy, SciPy, CVXPY, Pydantic) must
12
+ have a reproducible, fast, and ergonomic dependency management story. The Python ecosystem offers
13
+ several options: `pip` + `venv`, `Poetry`, `Hatch`, `PDM`, and `uv`.
14
+
15
+ Key constraints:
16
+ - Dependency resolution must be deterministic and fast — `scipy` and `cvxpy` have deep dependency
17
+ trees that slow `pip`-based solvers by seconds to minutes.
18
+ - The tool must support optional dependency groups (`dev`, `data`, `reporting`) natively.
19
+ - Lock files must be committed to version control for reproducible CI builds.
20
+ - The tool must support publishing to PyPI without a separate tool.
21
+ - Python version management should be unified with the package manager.
22
+
23
+ ## Decision
24
+
25
+ Use **`uv`** exclusively for all package management operations. No `pip`, `pip-tools`, `Poetry`,
26
+ or `Hatch` invocations are permitted in CI or developer documentation.
27
+
28
+ Specific commitments:
29
+ - `pyproject.toml` is the single source of truth for all dependency declarations.
30
+ - `uv.lock` is committed to version control and must not be manually edited.
31
+ - `uv sync --extra dev` is the canonical developer bootstrap command.
32
+ - `uv run` is used to invoke all project tools (pytest, mypy, ruff) to guarantee they run
33
+ inside the managed virtual environment without activation.
34
+ - `uv publish` is used for PyPI releases.
35
+ - `.python-version` pins the interpreter version; `uv` respects it automatically.
36
+
37
+ ## Options Considered
38
+
39
+ | Tool | Resolution Speed | Lock File | Optional Groups | Publish | Decision |
40
+ |------|-----------------|-----------|-----------------|---------|----------|
41
+ | pip + venv | Slow | No (manual pip-compile) | No | No | Rejected |
42
+ | Poetry | Moderate | Yes | Yes | Yes | Rejected — slower resolver; separate venv model |
43
+ | Hatch | Fast | No | Yes (env-based) | Yes | Rejected — no lock file |
44
+ | PDM | Moderate | Yes | Yes | Yes | Rejected — smaller ecosystem, less momentum |
45
+ | **uv** | **10–100× faster** | **Yes** | **Yes** | **Yes** | **Accepted** |
46
+
47
+ ## Consequences
48
+
49
+ **Positive:**
50
+ - `uv sync` resolves the full dependency tree in under one second on warm cache.
51
+ - Lock file guarantees identical environments across developer machines and CI.
52
+ - Single tool replaces `pip`, `pip-compile`, `virtualenv`, `pyenv`, and `twine`.
53
+ - `uv run pytest` eliminates the "wrong environment" class of CI failures.
54
+
55
+ **Negative:**
56
+ - Developers unfamiliar with `uv` must learn new commands. Mitigated by `CONTRIBUTING.md`
57
+ cheat-sheet.
58
+ - `uv` is younger than Poetry; edge cases may require workarounds. Mitigated by pinning `uv`
59
+ version in CI.
60
+
61
+ **Required follow-on:**
62
+ - `.readthedocs.yaml` must specify `uv export --format requirements-txt` to produce a
63
+ `requirements.txt` for RTD's pip-based build system, since RTD does not natively support `uv`.