quantbt-engine 1.0.7__py3-none-any.whl

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 (133) hide show
  1. quantbt/__init__.py +862 -0
  2. quantbt/adapters/__init__.py +5 -0
  3. quantbt/adapters/nautilus/__init__.py +42 -0
  4. quantbt/adapters/nautilus/_dependency.py +53 -0
  5. quantbt/adapters/nautilus/backend.py +639 -0
  6. quantbt/adapters/nautilus/instruments.py +275 -0
  7. quantbt/adapters/nautilus/options.py +465 -0
  8. quantbt/adapters/nautilus/reports.py +231 -0
  9. quantbt/backends/__init__.py +29 -0
  10. quantbt/backends/_native_event_rust.py +2168 -0
  11. quantbt/backends/native_event.py +5048 -0
  12. quantbt/backends/native_option.py +796 -0
  13. quantbt/backends/native_portfolio.py +928 -0
  14. quantbt/backends/native_vectorized.py +1299 -0
  15. quantbt/backtester.py +485 -0
  16. quantbt/benchmarks/__init__.py +1 -0
  17. quantbt/benchmarks/compare_phase9_parity.py +302 -0
  18. quantbt/benchmarks/gamma_scalping_backtestsample.py +802 -0
  19. quantbt/benchmarks/profile_phase7.py +415 -0
  20. quantbt/benchmarks/run_arbitrage_phase_e.py +160 -0
  21. quantbt/benchmarks/run_optimization_overhead.py +195 -0
  22. quantbt/benchmarks/run_options_engine.py +272 -0
  23. quantbt/benchmarks/run_pct_equity_nautilus_smoke.py +227 -0
  24. quantbt/benchmarks/run_phase12_arbitrage_cert.py +402 -0
  25. quantbt/benchmarks/run_phase12_benchmark_nautilus_cert.py +430 -0
  26. quantbt/benchmarks/run_phase13_portfolio_report.py +97 -0
  27. quantbt/benchmarks/run_phase13_wfo_cache.py +162 -0
  28. quantbt/benchmarks/run_phase14_service_loop.py +694 -0
  29. quantbt/benchmarks/run_phase15a_nautilus_certification.py +514 -0
  30. quantbt/benchmarks/run_phase15b_synthetic_depth.py +175 -0
  31. quantbt/benchmarks/run_phase16_performance_debt.py +389 -0
  32. quantbt/benchmarks/run_phase30e_reactive_runner.py +161 -0
  33. quantbt/benchmarks/run_phase31_intrabar.py +441 -0
  34. quantbt/benchmarks/run_phase34a_native_event_memory.py +179 -0
  35. quantbt/benchmarks/run_phase34b_native_event_prepared_score.py +173 -0
  36. quantbt/benchmarks/run_phase34c_native_event_single_pass.py +176 -0
  37. quantbt/benchmarks/run_phase7.py +654 -0
  38. quantbt/benchmarks/run_portfolio_real_parity.py +537 -0
  39. quantbt/core/__init__.py +323 -0
  40. quantbt/core/arbitrage.py +767 -0
  41. quantbt/core/basket.py +234 -0
  42. quantbt/core/certification.py +274 -0
  43. quantbt/core/constraints.py +155 -0
  44. quantbt/core/engine.py +1144 -0
  45. quantbt/core/event.py +869 -0
  46. quantbt/core/execution_contract.py +197 -0
  47. quantbt/core/execution_depth.py +613 -0
  48. quantbt/core/intrabar_kernel.py +1906 -0
  49. quantbt/core/intrabar_reference.py +907 -0
  50. quantbt/core/intrabar_session.py +156 -0
  51. quantbt/core/market_tape.py +480 -0
  52. quantbt/core/native_event_capabilities.py +121 -0
  53. quantbt/core/native_event_parity.py +336 -0
  54. quantbt/core/order_compiler.py +422 -0
  55. quantbt/core/orders.py +319 -0
  56. quantbt/core/portfolio.py +392 -0
  57. quantbt/core/preprocessor.py +256 -0
  58. quantbt/core/reactive.py +173 -0
  59. quantbt/core/results.py +341 -0
  60. quantbt/core/schema.py +211 -0
  61. quantbt/core/scopes.py +96 -0
  62. quantbt/core/structured_orders.py +378 -0
  63. quantbt/core/types.py +87 -0
  64. quantbt/core/vectorized.py +203 -0
  65. quantbt/endpoint.py +4465 -0
  66. quantbt/engines.py +1014 -0
  67. quantbt/metrics/__init__.py +45 -0
  68. quantbt/metrics/options_analytics.py +46 -0
  69. quantbt/metrics/performance.py +541 -0
  70. quantbt/optimization/__init__.py +94 -0
  71. quantbt/optimization/callbacks.py +116 -0
  72. quantbt/optimization/candidate_selection.py +397 -0
  73. quantbt/optimization/config.py +84 -0
  74. quantbt/optimization/constraints.py +22 -0
  75. quantbt/optimization/evaluator.py +21 -0
  76. quantbt/optimization/evaluators/__init__.py +32 -0
  77. quantbt/optimization/evaluators/arbitrage.py +22 -0
  78. quantbt/optimization/evaluators/generic.py +34 -0
  79. quantbt/optimization/evaluators/grid_dca.py +22 -0
  80. quantbt/optimization/evaluators/intrabar.py +54 -0
  81. quantbt/optimization/evaluators/native_event.py +51 -0
  82. quantbt/optimization/evaluators/options.py +22 -0
  83. quantbt/optimization/evaluators/portfolio.py +42 -0
  84. quantbt/optimization/evaluators/signal.py +43 -0
  85. quantbt/optimization/multiseed.py +176 -0
  86. quantbt/optimization/objectives.py +221 -0
  87. quantbt/optimization/optimizer.py +500 -0
  88. quantbt/optimization/result.py +80 -0
  89. quantbt/optimization/samplers.py +84 -0
  90. quantbt/optimization/space.py +223 -0
  91. quantbt/options/__init__.py +215 -0
  92. quantbt/options/cache.py +116 -0
  93. quantbt/options/conventions.py +158 -0
  94. quantbt/options/data.py +177 -0
  95. quantbt/options/execution.py +600 -0
  96. quantbt/options/fees.py +135 -0
  97. quantbt/options/greeks.py +173 -0
  98. quantbt/options/hedging.py +232 -0
  99. quantbt/options/iv.py +188 -0
  100. quantbt/options/ledger.py +263 -0
  101. quantbt/options/lifecycle.py +94 -0
  102. quantbt/options/margin.py +311 -0
  103. quantbt/options/packages.py +147 -0
  104. quantbt/options/pricing.py +191 -0
  105. quantbt/options/schema.py +227 -0
  106. quantbt/options/selectors.py +281 -0
  107. quantbt/options/strategy.py +249 -0
  108. quantbt/options/surface.py +142 -0
  109. quantbt/options/tape.py +226 -0
  110. quantbt/options/templates/__init__.py +33 -0
  111. quantbt/options/templates/packages.py +354 -0
  112. quantbt/portfolio.py +606 -0
  113. quantbt/py.typed +1 -0
  114. quantbt/reporting/__init__.py +38 -0
  115. quantbt/reporting/arbitrage_audit.py +211 -0
  116. quantbt/reporting/nautilus_bundle.py +758 -0
  117. quantbt/reporting/nautilus_certification.py +226 -0
  118. quantbt/reporting/nautilus_diagnostics.py +225 -0
  119. quantbt/reporting/parity.py +496 -0
  120. quantbt/reporting/portfolio_audit.py +206 -0
  121. quantbt/reporting/portfolio_nautilus.py +140 -0
  122. quantbt/sizing/__init__.py +3 -0
  123. quantbt/sizing/fast.py +67 -0
  124. quantbt/sizing/modes.py +162 -0
  125. quantbt/viz/__init__.py +4 -0
  126. quantbt/viz/plots.py +310 -0
  127. quantbt/viz/themes.py +102 -0
  128. quantbt/walkforward.py +3136 -0
  129. quantbt_engine-1.0.7.dist-info/METADATA +891 -0
  130. quantbt_engine-1.0.7.dist-info/RECORD +133 -0
  131. quantbt_engine-1.0.7.dist-info/WHEEL +5 -0
  132. quantbt_engine-1.0.7.dist-info/licenses/LICENSE +21 -0
  133. quantbt_engine-1.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,223 @@
1
+ """Search-space parsing shared by QuantBT optimization surfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import json
7
+ import math
8
+ from typing import Any, Mapping, Optional
9
+
10
+ import numpy as np
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class SearchSpaceInfo:
15
+ """Static facts used by sampler compatibility checks."""
16
+
17
+ has_categorical: bool
18
+ has_continuous: bool
19
+ has_dynamic_float: bool
20
+ variable_names: tuple[str, ...]
21
+ grid_size: Optional[int]
22
+
23
+
24
+ def suggest_parameter(trial, name: str, spec: Any) -> Any:
25
+ """Suggest one parameter from a QuantBT param range spec.
26
+
27
+ Supported specs are intentionally compatible with existing alpha notebooks:
28
+ numeric tuples, categorical lists/tuples, ranges, bool choices, and scalar
29
+ constants.
30
+ """
31
+
32
+ if _is_bool_choice(spec):
33
+ return trial.suggest_categorical(name, [True, False])
34
+ if isinstance(spec, tuple) and len(spec) in (2, 3) and all(_is_number(value) for value in spec):
35
+ low, high = spec[0], spec[1]
36
+ step = spec[2] if len(spec) == 3 else None
37
+ if _looks_int(low) and _looks_int(high) and (step is None or _looks_int(step)):
38
+ return trial.suggest_int(name, int(low), int(high), step=1 if step is None else int(step))
39
+ if step is None:
40
+ return trial.suggest_float(name, float(low), float(high))
41
+ return trial.suggest_float(name, float(low), float(high), step=float(step))
42
+ if isinstance(spec, range):
43
+ values = list(spec)
44
+ if not values:
45
+ raise ValueError(f"param_ranges[{name!r}] is empty")
46
+ return trial.suggest_categorical(name, values)
47
+ if isinstance(spec, (list, tuple)):
48
+ if not spec:
49
+ raise ValueError(f"param_ranges[{name!r}] is empty")
50
+ return trial.suggest_categorical(name, list(spec))
51
+ return spec
52
+
53
+
54
+ def suggest_params(trial, param_ranges: Mapping[str, Any], fixed_params: Optional[Mapping[str, Any]] = None) -> dict[str, Any]:
55
+ """Suggest params and merge fixed params.
56
+
57
+ Fixed params override `param_ranges` entries by name. Additional fixed
58
+ params are appended to the final parameter dict.
59
+ """
60
+
61
+ fixed = dict(fixed_params or {})
62
+ params: dict[str, Any] = {}
63
+ for name, spec in dict(param_ranges or {}).items():
64
+ if name in fixed:
65
+ params[name] = fixed[name]
66
+ else:
67
+ params[name] = suggest_parameter(trial, name, spec)
68
+ for name, value in fixed.items():
69
+ params.setdefault(name, value)
70
+ return params
71
+
72
+
73
+ def stable_params_key(params: Mapping[str, Any]) -> str:
74
+ """Return a deterministic key for duplicate-trial detection."""
75
+
76
+ return json.dumps(_jsonable(params), sort_keys=True, separators=(",", ":"))
77
+
78
+
79
+ def search_space_info(param_ranges: Mapping[str, Any], fixed_params: Optional[Mapping[str, Any]] = None) -> SearchSpaceInfo:
80
+ """Inspect a QuantBT search space for sampler compatibility."""
81
+
82
+ fixed = set(dict(fixed_params or {}))
83
+ has_categorical = False
84
+ has_continuous = False
85
+ has_dynamic_float = False
86
+ variable_names: list[str] = []
87
+ grid_size = 1
88
+ finite_grid = True
89
+ for name, spec in dict(param_ranges or {}).items():
90
+ if name in fixed:
91
+ continue
92
+ kind = _spec_kind(spec)
93
+ if kind == "constant":
94
+ continue
95
+ variable_names.append(name)
96
+ if kind == "categorical":
97
+ has_categorical = True
98
+ if kind in {"float", "int"}:
99
+ has_continuous = has_continuous or kind == "float"
100
+ values = _grid_values(name, spec, allow_dynamic=True)
101
+ if values is None:
102
+ finite_grid = False
103
+ has_dynamic_float = True
104
+ else:
105
+ grid_size *= len(values)
106
+ return SearchSpaceInfo(
107
+ has_categorical=has_categorical,
108
+ has_continuous=has_continuous,
109
+ has_dynamic_float=has_dynamic_float,
110
+ variable_names=tuple(variable_names),
111
+ grid_size=grid_size if finite_grid else None,
112
+ )
113
+
114
+
115
+ def build_grid_search_space(
116
+ param_ranges: Mapping[str, Any],
117
+ fixed_params: Optional[Mapping[str, Any]] = None,
118
+ *,
119
+ max_grid_size: int = 100_000,
120
+ ) -> dict[str, list[Any]]:
121
+ """Build an Optuna GridSampler search space from finite specs."""
122
+
123
+ fixed = set(dict(fixed_params or {}))
124
+ grid: dict[str, list[Any]] = {}
125
+ size = 1
126
+ for name, spec in dict(param_ranges or {}).items():
127
+ if name in fixed:
128
+ continue
129
+ values = _grid_values(name, spec, allow_dynamic=False)
130
+ if values is None:
131
+ raise ValueError(f"grid sampler requires finite values for {name!r}")
132
+ if len(values) == 1 and _spec_kind(spec) == "constant":
133
+ continue
134
+ grid[name] = values
135
+ size *= len(values)
136
+ if size > int(max_grid_size):
137
+ raise ValueError(f"grid search space has {size:,} combinations, above max_grid_size={int(max_grid_size):,}")
138
+ if not grid:
139
+ raise ValueError("grid sampler requires at least one non-fixed finite parameter")
140
+ return grid
141
+
142
+
143
+ def _grid_values(name: str, spec: Any, *, allow_dynamic: bool) -> Optional[list[Any]]:
144
+ if _is_bool_choice(spec):
145
+ return [True, False]
146
+ if isinstance(spec, tuple) and len(spec) in (2, 3) and all(_is_number(value) for value in spec):
147
+ low, high = spec[0], spec[1]
148
+ step = spec[2] if len(spec) == 3 else None
149
+ if _looks_int(low) and _looks_int(high) and (step is None or _looks_int(step)):
150
+ step_i = 1 if step is None else int(step)
151
+ if step_i <= 0:
152
+ raise ValueError(f"integer step for {name!r} must be positive")
153
+ return list(range(int(low), int(high) + 1, step_i))
154
+ if step is None:
155
+ if allow_dynamic:
156
+ return None
157
+ raise ValueError(f"grid sampler requires a float step for {name!r}")
158
+ return _float_grid(float(low), float(high), float(step), name)
159
+ if isinstance(spec, range):
160
+ values = list(spec)
161
+ if not values:
162
+ raise ValueError(f"param_ranges[{name!r}] is empty")
163
+ return values
164
+ if isinstance(spec, (list, tuple)):
165
+ if not spec:
166
+ raise ValueError(f"param_ranges[{name!r}] is empty")
167
+ return list(spec)
168
+ return [spec]
169
+
170
+
171
+ def _float_grid(low: float, high: float, step: float, name: str) -> list[float]:
172
+ if step <= 0.0:
173
+ raise ValueError(f"float step for {name!r} must be positive")
174
+ if high < low:
175
+ raise ValueError(f"high must be >= low for {name!r}")
176
+ count = int(math.floor((high - low) / step + 1e-12)) + 1
177
+ values = [float(low + i * step) for i in range(count)]
178
+ if values and values[-1] < high and math.isclose(values[-1] + step, high, rel_tol=1e-9, abs_tol=1e-12):
179
+ values.append(float(high))
180
+ return values
181
+
182
+
183
+ def _spec_kind(spec: Any) -> str:
184
+ if _is_bool_choice(spec):
185
+ return "categorical"
186
+ if isinstance(spec, range):
187
+ return "categorical"
188
+ if isinstance(spec, tuple) and len(spec) in (2, 3) and all(_is_number(value) for value in spec):
189
+ if _looks_int(spec[0]) and _looks_int(spec[1]) and (len(spec) == 2 or _looks_int(spec[2])):
190
+ return "int"
191
+ return "float"
192
+ if isinstance(spec, (list, tuple)):
193
+ return "categorical"
194
+ return "constant"
195
+
196
+
197
+ def _is_bool_choice(spec: Any) -> bool:
198
+ return (
199
+ isinstance(spec, (list, tuple))
200
+ and len(spec) == 2
201
+ and all(isinstance(value, bool) for value in spec)
202
+ and set(spec) == {True, False}
203
+ )
204
+
205
+
206
+ def _looks_int(value: Any) -> bool:
207
+ return isinstance(value, (int, np.integer)) and not isinstance(value, bool)
208
+
209
+
210
+ def _is_number(value: Any) -> bool:
211
+ return isinstance(value, (int, float, np.integer, np.floating)) and not isinstance(value, bool)
212
+
213
+
214
+ def _jsonable(value: Any) -> Any:
215
+ if isinstance(value, Mapping):
216
+ return {str(key): _jsonable(val) for key, val in value.items()}
217
+ if isinstance(value, (list, tuple)):
218
+ return [_jsonable(item) for item in value]
219
+ if isinstance(value, np.generic):
220
+ return value.item()
221
+ if isinstance(value, np.ndarray):
222
+ return [_jsonable(item) for item in value.tolist()]
223
+ return value
@@ -0,0 +1,215 @@
1
+ """
2
+ QuantBT options domain package.
3
+
4
+ Phase 1 exposes schema, convention, and canonical chain-data validation only.
5
+ Pricing, execution, ledger, margin, endpoint wiring, and Nautilus validation are
6
+ added in later phases.
7
+ """
8
+
9
+ from .conventions import (
10
+ OptionVenueConvention,
11
+ binance_european_options_convention,
12
+ deribit_inverse_option_convention,
13
+ deribit_linear_usdc_option_convention,
14
+ )
15
+ from .cache import OptionPreparedRunCache, option_package_cache_key
16
+ from .data import CANONICAL_OPTION_CHAIN_COLUMNS, validate_option_chain_frame
17
+ from .execution import (
18
+ OptionDepthFidelity,
19
+ OptionExecutionConfig,
20
+ OptionLimitFidelity,
21
+ OptionPackageExecutionResult,
22
+ execute_option_package,
23
+ )
24
+ from .fees import (
25
+ OptionFeeResult,
26
+ OptionFeeSchedule,
27
+ calculate_option_fee,
28
+ deribit_inverse_fee_schedule,
29
+ deribit_linear_usdc_fee_schedule,
30
+ )
31
+ from .greeks import (
32
+ OptionGreeks,
33
+ inverse_black76_greeks_base,
34
+ inverse_black76_greeks_quote,
35
+ linear_black76_greeks,
36
+ scale_greeks_to_reporting_currency,
37
+ )
38
+ from .hedging import (
39
+ HedgeDecision,
40
+ HedgePathResult,
41
+ OptionHedgeConfig,
42
+ OptionHedgePolicyType,
43
+ compute_net_option_delta,
44
+ hedge_decision,
45
+ run_delta_hedge_path,
46
+ )
47
+ from .iv import IVStatus, ImpliedVolResult, implied_vol_black76, implied_vol_inverse_black76_base
48
+ from .ledger import OptionLedger, OptionPosition
49
+ from .lifecycle import (
50
+ OptionSettlementRepresentation,
51
+ OptionSettlementResult,
52
+ option_expiry_payoff_per_unit,
53
+ settle_option_expiry,
54
+ )
55
+ from .margin import (
56
+ ExternalOptionMarginValidator,
57
+ OptionLiquidationAudit,
58
+ OptionMarginConfig,
59
+ OptionMarginModel,
60
+ OptionMarginRequirement,
61
+ calculate_option_margin,
62
+ liquidate_option_positions,
63
+ )
64
+ from .packages import (
65
+ OptionPackageExecutionPolicy,
66
+ OptionPackageIntent,
67
+ OptionPackageLeg,
68
+ compile_option_package_orders,
69
+ )
70
+ from .pricing import (
71
+ black76_intrinsic,
72
+ black76_parity_residual,
73
+ black76_parity_value,
74
+ black76_price,
75
+ inverse_black76_intrinsic_base,
76
+ inverse_black76_parity_residual_base,
77
+ inverse_black76_parity_value_base,
78
+ inverse_black76_price_base,
79
+ )
80
+ from .schema import (
81
+ ExerciseStyle,
82
+ InstrumentRegistrySignature,
83
+ OptionDecisionFillPolicy,
84
+ OptionInstrumentRegistry,
85
+ OptionInstrumentSpec,
86
+ OptionKind,
87
+ PremiumConvention,
88
+ SettlementStyle,
89
+ )
90
+ from .selectors import (
91
+ OptionSelection,
92
+ OptionSelectionFilters,
93
+ available_option_rows,
94
+ select_atm_option,
95
+ select_target_delta_option,
96
+ select_target_dte_option,
97
+ select_target_moneyness_option,
98
+ )
99
+ from .surface import SurfaceDiagnostics, TotalVarianceSurface
100
+ from .tape import YEAR_NS, OptionTapeSignature, PreparedOptionTape, prepare_option_tape
101
+ from .strategy import GammaScalpingConfig, OptionStrategyRun, build_gamma_scalping_strategy_run
102
+ from .templates import (
103
+ butterfly,
104
+ calendar,
105
+ collar,
106
+ condor,
107
+ covered_call,
108
+ long_call,
109
+ long_put,
110
+ risk_reversal,
111
+ short_call,
112
+ short_put,
113
+ straddle,
114
+ strangle,
115
+ vertical,
116
+ )
117
+
118
+ __all__ = [
119
+ "CANONICAL_OPTION_CHAIN_COLUMNS",
120
+ "ExerciseStyle",
121
+ "ExternalOptionMarginValidator",
122
+ "HedgeDecision",
123
+ "HedgePathResult",
124
+ "GammaScalpingConfig",
125
+ "InstrumentRegistrySignature",
126
+ "OptionDecisionFillPolicy",
127
+ "OptionDepthFidelity",
128
+ "OptionExecutionConfig",
129
+ "OptionFeeResult",
130
+ "OptionFeeSchedule",
131
+ "OptionHedgeConfig",
132
+ "OptionHedgePolicyType",
133
+ "OptionInstrumentRegistry",
134
+ "OptionInstrumentSpec",
135
+ "OptionKind",
136
+ "OptionGreeks",
137
+ "OptionLimitFidelity",
138
+ "OptionLiquidationAudit",
139
+ "OptionMarginConfig",
140
+ "OptionMarginModel",
141
+ "OptionMarginRequirement",
142
+ "OptionPackageExecutionPolicy",
143
+ "OptionPackageExecutionResult",
144
+ "OptionPackageIntent",
145
+ "OptionPackageLeg",
146
+ "OptionLedger",
147
+ "OptionVenueConvention",
148
+ "OptionSelection",
149
+ "OptionSelectionFilters",
150
+ "OptionSettlementRepresentation",
151
+ "OptionSettlementResult",
152
+ "OptionStrategyRun",
153
+ "OptionTapeSignature",
154
+ "OptionPosition",
155
+ "OptionPreparedRunCache",
156
+ "PremiumConvention",
157
+ "PreparedOptionTape",
158
+ "SettlementStyle",
159
+ "SurfaceDiagnostics",
160
+ "TotalVarianceSurface",
161
+ "YEAR_NS",
162
+ "binance_european_options_convention",
163
+ "black76_intrinsic",
164
+ "black76_parity_residual",
165
+ "black76_parity_value",
166
+ "black76_price",
167
+ "build_gamma_scalping_strategy_run",
168
+ "calculate_option_fee",
169
+ "calculate_option_margin",
170
+ "compile_option_package_orders",
171
+ "deribit_inverse_option_convention",
172
+ "deribit_inverse_fee_schedule",
173
+ "deribit_linear_usdc_option_convention",
174
+ "deribit_linear_usdc_fee_schedule",
175
+ "implied_vol_black76",
176
+ "implied_vol_inverse_black76_base",
177
+ "inverse_black76_greeks_base",
178
+ "inverse_black76_greeks_quote",
179
+ "inverse_black76_intrinsic_base",
180
+ "inverse_black76_parity_residual_base",
181
+ "inverse_black76_parity_value_base",
182
+ "inverse_black76_price_base",
183
+ "IVStatus",
184
+ "ImpliedVolResult",
185
+ "linear_black76_greeks",
186
+ "available_option_rows",
187
+ "compute_net_option_delta",
188
+ "execute_option_package",
189
+ "hedge_decision",
190
+ "liquidate_option_positions",
191
+ "option_expiry_payoff_per_unit",
192
+ "option_package_cache_key",
193
+ "prepare_option_tape",
194
+ "run_delta_hedge_path",
195
+ "scale_greeks_to_reporting_currency",
196
+ "select_atm_option",
197
+ "select_target_delta_option",
198
+ "select_target_dte_option",
199
+ "select_target_moneyness_option",
200
+ "settle_option_expiry",
201
+ "butterfly",
202
+ "calendar",
203
+ "collar",
204
+ "condor",
205
+ "covered_call",
206
+ "long_call",
207
+ "long_put",
208
+ "risk_reversal",
209
+ "short_call",
210
+ "short_put",
211
+ "straddle",
212
+ "strangle",
213
+ "vertical",
214
+ "validate_option_chain_frame",
215
+ ]
@@ -0,0 +1,116 @@
1
+ """
2
+ Prepared option run cache.
3
+
4
+ The cache is explicit and signature-checked. It is designed for service/WFO
5
+ loops where the same option chain tape is replayed with many package choices.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Dict, Optional, Tuple
12
+
13
+ import pandas as pd
14
+
15
+ from ..core.orders import OrderIntent
16
+ from .packages import OptionPackageIntent, compile_option_package_orders
17
+ from .schema import OptionInstrumentRegistry
18
+ from .tape import PreparedOptionTape, prepare_option_tape
19
+
20
+
21
+ @dataclass
22
+ class OptionPreparedRunCache:
23
+ tape: PreparedOptionTape
24
+ package_orders: Dict[Tuple, Tuple[OrderIntent, ...]] = field(default_factory=dict)
25
+ metadata: Dict = field(default_factory=dict)
26
+
27
+ @classmethod
28
+ def from_chain(
29
+ cls,
30
+ chain: pd.DataFrame,
31
+ registry: OptionInstrumentRegistry,
32
+ *,
33
+ max_spread_bps: Optional[float] = None,
34
+ max_source_latency_ns: Optional[int] = None,
35
+ convention_signature: Optional[Tuple] = None,
36
+ ) -> "OptionPreparedRunCache":
37
+ tape = prepare_option_tape(
38
+ chain,
39
+ registry,
40
+ max_spread_bps=max_spread_bps,
41
+ max_source_latency_ns=max_source_latency_ns,
42
+ convention_signature=convention_signature,
43
+ )
44
+ return cls(
45
+ tape=tape,
46
+ metadata={
47
+ "cache_type": "OptionPreparedRunCache",
48
+ "snapshot_count": int(tape.snapshot_count),
49
+ "row_count": int(tape.row_count),
50
+ "registry_symbols": tuple(registry.symbols),
51
+ "convention_signature": tape.signature.convention_signature,
52
+ },
53
+ )
54
+
55
+ def validate(
56
+ self,
57
+ registry: OptionInstrumentRegistry,
58
+ *,
59
+ timestamps_ns=None,
60
+ convention_signature: Optional[Tuple] = None,
61
+ ) -> None:
62
+ self.tape.validate_compatible(
63
+ registry_signature=registry.signature,
64
+ timestamps_ns=timestamps_ns,
65
+ convention_signature=convention_signature,
66
+ )
67
+
68
+ def compile_package(self, package: OptionPackageIntent) -> Tuple[OrderIntent, ...]:
69
+ key = option_package_cache_key(package)
70
+ cached = self.package_orders.get(key)
71
+ if cached is None:
72
+ cached = compile_option_package_orders(package)
73
+ self.package_orders[key] = cached
74
+ return cached
75
+
76
+ @property
77
+ def package_cache_size(self) -> int:
78
+ return len(self.package_orders)
79
+
80
+
81
+ def option_package_cache_key(package: OptionPackageIntent) -> Tuple:
82
+ """Return a deterministic key for compiled option package order leaves."""
83
+ return (
84
+ int(package.timestamp_ns),
85
+ str(package.package_id),
86
+ float(package.quantity),
87
+ package.execution_policy.value,
88
+ None if package.max_debit is None else float(package.max_debit),
89
+ None if package.min_credit is None else float(package.min_credit),
90
+ tuple(
91
+ (
92
+ leg.instrument_id,
93
+ leg.side.value,
94
+ float(leg.ratio),
95
+ leg.order_type.value,
96
+ None if leg.limit_price is None else float(leg.limit_price),
97
+ leg.tif.value,
98
+ leg.role,
99
+ leg.tag,
100
+ tuple(sorted((str(k), _stable_value(v)) for k, v in leg.metadata.items())),
101
+ )
102
+ for leg in package.legs
103
+ ),
104
+ package.tag,
105
+ tuple(sorted((str(k), _stable_value(v)) for k, v in package.metadata.items())),
106
+ )
107
+
108
+
109
+ def _stable_value(value):
110
+ if isinstance(value, (str, int, float, bool, type(None))):
111
+ return value
112
+ if isinstance(value, dict):
113
+ return tuple(sorted((str(k), _stable_value(v)) for k, v in value.items()))
114
+ if isinstance(value, (list, tuple)):
115
+ return tuple(_stable_value(item) for item in value)
116
+ return repr(value)