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.
- quantbt/__init__.py +862 -0
- quantbt/adapters/__init__.py +5 -0
- quantbt/adapters/nautilus/__init__.py +42 -0
- quantbt/adapters/nautilus/_dependency.py +53 -0
- quantbt/adapters/nautilus/backend.py +639 -0
- quantbt/adapters/nautilus/instruments.py +275 -0
- quantbt/adapters/nautilus/options.py +465 -0
- quantbt/adapters/nautilus/reports.py +231 -0
- quantbt/backends/__init__.py +29 -0
- quantbt/backends/_native_event_rust.py +2168 -0
- quantbt/backends/native_event.py +5048 -0
- quantbt/backends/native_option.py +796 -0
- quantbt/backends/native_portfolio.py +928 -0
- quantbt/backends/native_vectorized.py +1299 -0
- quantbt/backtester.py +485 -0
- quantbt/benchmarks/__init__.py +1 -0
- quantbt/benchmarks/compare_phase9_parity.py +302 -0
- quantbt/benchmarks/gamma_scalping_backtestsample.py +802 -0
- quantbt/benchmarks/profile_phase7.py +415 -0
- quantbt/benchmarks/run_arbitrage_phase_e.py +160 -0
- quantbt/benchmarks/run_optimization_overhead.py +195 -0
- quantbt/benchmarks/run_options_engine.py +272 -0
- quantbt/benchmarks/run_pct_equity_nautilus_smoke.py +227 -0
- quantbt/benchmarks/run_phase12_arbitrage_cert.py +402 -0
- quantbt/benchmarks/run_phase12_benchmark_nautilus_cert.py +430 -0
- quantbt/benchmarks/run_phase13_portfolio_report.py +97 -0
- quantbt/benchmarks/run_phase13_wfo_cache.py +162 -0
- quantbt/benchmarks/run_phase14_service_loop.py +694 -0
- quantbt/benchmarks/run_phase15a_nautilus_certification.py +514 -0
- quantbt/benchmarks/run_phase15b_synthetic_depth.py +175 -0
- quantbt/benchmarks/run_phase16_performance_debt.py +389 -0
- quantbt/benchmarks/run_phase30e_reactive_runner.py +161 -0
- quantbt/benchmarks/run_phase31_intrabar.py +441 -0
- quantbt/benchmarks/run_phase34a_native_event_memory.py +179 -0
- quantbt/benchmarks/run_phase34b_native_event_prepared_score.py +173 -0
- quantbt/benchmarks/run_phase34c_native_event_single_pass.py +176 -0
- quantbt/benchmarks/run_phase7.py +654 -0
- quantbt/benchmarks/run_portfolio_real_parity.py +537 -0
- quantbt/core/__init__.py +323 -0
- quantbt/core/arbitrage.py +767 -0
- quantbt/core/basket.py +234 -0
- quantbt/core/certification.py +274 -0
- quantbt/core/constraints.py +155 -0
- quantbt/core/engine.py +1144 -0
- quantbt/core/event.py +869 -0
- quantbt/core/execution_contract.py +197 -0
- quantbt/core/execution_depth.py +613 -0
- quantbt/core/intrabar_kernel.py +1906 -0
- quantbt/core/intrabar_reference.py +907 -0
- quantbt/core/intrabar_session.py +156 -0
- quantbt/core/market_tape.py +480 -0
- quantbt/core/native_event_capabilities.py +121 -0
- quantbt/core/native_event_parity.py +336 -0
- quantbt/core/order_compiler.py +422 -0
- quantbt/core/orders.py +319 -0
- quantbt/core/portfolio.py +392 -0
- quantbt/core/preprocessor.py +256 -0
- quantbt/core/reactive.py +173 -0
- quantbt/core/results.py +341 -0
- quantbt/core/schema.py +211 -0
- quantbt/core/scopes.py +96 -0
- quantbt/core/structured_orders.py +378 -0
- quantbt/core/types.py +87 -0
- quantbt/core/vectorized.py +203 -0
- quantbt/endpoint.py +4465 -0
- quantbt/engines.py +1014 -0
- quantbt/metrics/__init__.py +45 -0
- quantbt/metrics/options_analytics.py +46 -0
- quantbt/metrics/performance.py +541 -0
- quantbt/optimization/__init__.py +94 -0
- quantbt/optimization/callbacks.py +116 -0
- quantbt/optimization/candidate_selection.py +397 -0
- quantbt/optimization/config.py +84 -0
- quantbt/optimization/constraints.py +22 -0
- quantbt/optimization/evaluator.py +21 -0
- quantbt/optimization/evaluators/__init__.py +32 -0
- quantbt/optimization/evaluators/arbitrage.py +22 -0
- quantbt/optimization/evaluators/generic.py +34 -0
- quantbt/optimization/evaluators/grid_dca.py +22 -0
- quantbt/optimization/evaluators/intrabar.py +54 -0
- quantbt/optimization/evaluators/native_event.py +51 -0
- quantbt/optimization/evaluators/options.py +22 -0
- quantbt/optimization/evaluators/portfolio.py +42 -0
- quantbt/optimization/evaluators/signal.py +43 -0
- quantbt/optimization/multiseed.py +176 -0
- quantbt/optimization/objectives.py +221 -0
- quantbt/optimization/optimizer.py +500 -0
- quantbt/optimization/result.py +80 -0
- quantbt/optimization/samplers.py +84 -0
- quantbt/optimization/space.py +223 -0
- quantbt/options/__init__.py +215 -0
- quantbt/options/cache.py +116 -0
- quantbt/options/conventions.py +158 -0
- quantbt/options/data.py +177 -0
- quantbt/options/execution.py +600 -0
- quantbt/options/fees.py +135 -0
- quantbt/options/greeks.py +173 -0
- quantbt/options/hedging.py +232 -0
- quantbt/options/iv.py +188 -0
- quantbt/options/ledger.py +263 -0
- quantbt/options/lifecycle.py +94 -0
- quantbt/options/margin.py +311 -0
- quantbt/options/packages.py +147 -0
- quantbt/options/pricing.py +191 -0
- quantbt/options/schema.py +227 -0
- quantbt/options/selectors.py +281 -0
- quantbt/options/strategy.py +249 -0
- quantbt/options/surface.py +142 -0
- quantbt/options/tape.py +226 -0
- quantbt/options/templates/__init__.py +33 -0
- quantbt/options/templates/packages.py +354 -0
- quantbt/portfolio.py +606 -0
- quantbt/py.typed +1 -0
- quantbt/reporting/__init__.py +38 -0
- quantbt/reporting/arbitrage_audit.py +211 -0
- quantbt/reporting/nautilus_bundle.py +758 -0
- quantbt/reporting/nautilus_certification.py +226 -0
- quantbt/reporting/nautilus_diagnostics.py +225 -0
- quantbt/reporting/parity.py +496 -0
- quantbt/reporting/portfolio_audit.py +206 -0
- quantbt/reporting/portfolio_nautilus.py +140 -0
- quantbt/sizing/__init__.py +3 -0
- quantbt/sizing/fast.py +67 -0
- quantbt/sizing/modes.py +162 -0
- quantbt/viz/__init__.py +4 -0
- quantbt/viz/plots.py +310 -0
- quantbt/viz/themes.py +102 -0
- quantbt/walkforward.py +3136 -0
- quantbt_engine-1.0.7.dist-info/METADATA +891 -0
- quantbt_engine-1.0.7.dist-info/RECORD +133 -0
- quantbt_engine-1.0.7.dist-info/WHEEL +5 -0
- quantbt_engine-1.0.7.dist-info/licenses/LICENSE +21 -0
- 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
|
+
]
|
quantbt/options/cache.py
ADDED
|
@@ -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)
|