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,221 @@
|
|
|
1
|
+
"""Common objective builders for domain-agnostic optimization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Callable, Mapping, Optional, Sequence
|
|
7
|
+
|
|
8
|
+
from .result import ObjectiveResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
MetricMap = Mapping[str, float]
|
|
12
|
+
ConstraintBuilder = Callable[[MetricMap, Mapping[str, Any], Any], float]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MissingOptimizationMetricError(KeyError):
|
|
16
|
+
"""Raised when an objective/constraint metric is required but unavailable."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_METRIC_ALIASES = {
|
|
20
|
+
"trades": "num_trades",
|
|
21
|
+
"trade_count": "num_trades",
|
|
22
|
+
"max_drawdown": "max_drawdown_pct",
|
|
23
|
+
"mdd": "max_drawdown_pct",
|
|
24
|
+
"margin_util": "margin_utilization",
|
|
25
|
+
"rejections": "rejection_rate",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def normalize_metric_name(name: str) -> str:
|
|
30
|
+
"""Return the canonical QuantBT objective metric name."""
|
|
31
|
+
|
|
32
|
+
key = str(name).strip()
|
|
33
|
+
return _METRIC_ALIASES.get(key, key)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def result_full_report(result: Any, *, trading_days: int = 365, scope: str = "auto") -> dict[str, Any]:
|
|
37
|
+
"""Extract the standard metrics report from a QuantBT result-like object."""
|
|
38
|
+
|
|
39
|
+
if hasattr(result, "full_report") and callable(result.full_report):
|
|
40
|
+
return dict(result.full_report(trading_days=trading_days, scope=scope))
|
|
41
|
+
metadata = dict(getattr(result, "metadata", {}) or {})
|
|
42
|
+
for key in ("report", "full_report", "metrics"):
|
|
43
|
+
value = metadata.get(key)
|
|
44
|
+
if isinstance(value, Mapping):
|
|
45
|
+
return dict(value)
|
|
46
|
+
raise TypeError("result must expose full_report(...) or metadata report/metrics")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def metric_from_result(
|
|
50
|
+
result: Any,
|
|
51
|
+
name: str,
|
|
52
|
+
*,
|
|
53
|
+
trading_days: int = 365,
|
|
54
|
+
scope: str = "auto",
|
|
55
|
+
required: bool = True,
|
|
56
|
+
default: Optional[float] = None,
|
|
57
|
+
) -> float:
|
|
58
|
+
"""Read a common objective metric from report, diagnostics, or metadata."""
|
|
59
|
+
|
|
60
|
+
canonical = normalize_metric_name(name)
|
|
61
|
+
report = result_full_report(result, trading_days=trading_days, scope=scope)
|
|
62
|
+
if canonical in report:
|
|
63
|
+
return float(report[canonical])
|
|
64
|
+
metadata = dict(getattr(result, "metadata", {}) or {})
|
|
65
|
+
if canonical in metadata:
|
|
66
|
+
return float(metadata[canonical])
|
|
67
|
+
if canonical == "margin_utilization":
|
|
68
|
+
value = _margin_utilization(result)
|
|
69
|
+
if value is not None:
|
|
70
|
+
return value
|
|
71
|
+
if canonical == "rejection_rate":
|
|
72
|
+
value = _rejection_rate(result)
|
|
73
|
+
if value is not None:
|
|
74
|
+
return value
|
|
75
|
+
if required:
|
|
76
|
+
raise MissingOptimizationMetricError(f"missing required optimization metric: {canonical}")
|
|
77
|
+
return float(0.0 if default is None else default)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def metrics_from_result(
|
|
81
|
+
result: Any,
|
|
82
|
+
*,
|
|
83
|
+
names: Sequence[str] = ("sharpe", "max_drawdown_pct", "num_trades", "profit_factor"),
|
|
84
|
+
trading_days: int = 365,
|
|
85
|
+
scope: str = "auto",
|
|
86
|
+
) -> dict[str, float]:
|
|
87
|
+
"""Extract optional display metrics from a QuantBT result.
|
|
88
|
+
|
|
89
|
+
Missing display metrics are omitted. Metrics used as objective values or
|
|
90
|
+
formal constraints must be requested through `metric_from_result(...,
|
|
91
|
+
required=True)` or the constraint helper functions below.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
metrics: dict[str, float] = {}
|
|
95
|
+
report = result_full_report(result, trading_days=trading_days, scope=scope)
|
|
96
|
+
for name in names:
|
|
97
|
+
canonical = normalize_metric_name(name)
|
|
98
|
+
if canonical in report:
|
|
99
|
+
metrics[canonical] = float(report[canonical])
|
|
100
|
+
else:
|
|
101
|
+
try:
|
|
102
|
+
metrics[canonical] = metric_from_result(result, canonical, trading_days=trading_days, scope=scope, required=True)
|
|
103
|
+
except MissingOptimizationMetricError:
|
|
104
|
+
pass
|
|
105
|
+
return metrics
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def max_drawdown_constraint(max_drawdown_pct: float) -> ConstraintBuilder:
|
|
109
|
+
"""Constraint: realized max drawdown must be <= `max_drawdown_pct`."""
|
|
110
|
+
|
|
111
|
+
limit = float(max_drawdown_pct)
|
|
112
|
+
return lambda metrics, params, result: _required_metric(metrics, "max_drawdown_pct") - limit
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def min_trades_constraint(min_trades: float) -> ConstraintBuilder:
|
|
116
|
+
"""Constraint: realized number of trades must be >= `min_trades`."""
|
|
117
|
+
|
|
118
|
+
required = float(min_trades)
|
|
119
|
+
return lambda metrics, params, result: required - _required_metric(metrics, "num_trades")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def max_turnover_constraint(max_turnover: float) -> ConstraintBuilder:
|
|
123
|
+
"""Constraint: realized turnover must be <= `max_turnover`."""
|
|
124
|
+
|
|
125
|
+
limit = float(max_turnover)
|
|
126
|
+
return lambda metrics, params, result: _required_metric(metrics, "turnover") - limit
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def max_margin_utilization_constraint(max_margin_utilization: float) -> ConstraintBuilder:
|
|
130
|
+
"""Constraint: maximum margin utilization must be <= limit."""
|
|
131
|
+
|
|
132
|
+
limit = float(max_margin_utilization)
|
|
133
|
+
return lambda metrics, params, result: _required_metric(metrics, "margin_utilization") - limit
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def max_rejection_rate_constraint(max_rejection_rate: float) -> ConstraintBuilder:
|
|
137
|
+
"""Constraint: package/order rejection rate must be <= limit."""
|
|
138
|
+
|
|
139
|
+
limit = float(max_rejection_rate)
|
|
140
|
+
return lambda metrics, params, result: _required_metric(metrics, "rejection_rate") - limit
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(frozen=True)
|
|
144
|
+
class ReportMetricObjective:
|
|
145
|
+
"""Build an ObjectiveResult from QuantBT full-report metrics.
|
|
146
|
+
|
|
147
|
+
Formal constraints keep Optuna's convention: values `<= 0` are feasible.
|
|
148
|
+
The score itself is not polluted by arbitrary penalties when a constraint
|
|
149
|
+
can express the domain rule explicitly.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
value_metrics: Sequence[str] = ("sharpe",)
|
|
153
|
+
metric_names: Sequence[str] = (
|
|
154
|
+
"sharpe",
|
|
155
|
+
"max_drawdown_pct",
|
|
156
|
+
"num_trades",
|
|
157
|
+
"turnover",
|
|
158
|
+
"profit_factor",
|
|
159
|
+
"margin_utilization",
|
|
160
|
+
"rejection_rate",
|
|
161
|
+
)
|
|
162
|
+
trading_days: int = 365
|
|
163
|
+
scope: str = "auto"
|
|
164
|
+
constraints: Sequence[ConstraintBuilder] = field(default_factory=tuple)
|
|
165
|
+
metadata_builder: Optional[Callable[[Any, Mapping[str, Any], MetricMap], Mapping[str, Any]]] = None
|
|
166
|
+
|
|
167
|
+
def __call__(self, result: Any, params: Mapping[str, Any]) -> ObjectiveResult:
|
|
168
|
+
metrics = metrics_from_result(result, names=self.metric_names, trading_days=self.trading_days, scope=self.scope)
|
|
169
|
+
values = tuple(metric_from_result(result, name, trading_days=self.trading_days, scope=self.scope, required=True) for name in self.value_metrics)
|
|
170
|
+
constraints = tuple(float(builder(metrics, params, result)) for builder in self.constraints)
|
|
171
|
+
metadata = {} if self.metadata_builder is None else dict(self.metadata_builder(result, params, metrics))
|
|
172
|
+
return ObjectiveResult(values=values, metrics=metrics, constraints=constraints, metadata=metadata)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass(frozen=True)
|
|
176
|
+
class SharpeObjective(ReportMetricObjective):
|
|
177
|
+
"""Single-objective Sharpe score with optional formal constraints."""
|
|
178
|
+
|
|
179
|
+
value_metrics: Sequence[str] = ("sharpe",)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _required_metric(metrics: MetricMap, name: str) -> float:
|
|
183
|
+
canonical = normalize_metric_name(name)
|
|
184
|
+
if canonical not in metrics:
|
|
185
|
+
raise MissingOptimizationMetricError(f"missing required optimization metric: {canonical}")
|
|
186
|
+
return float(metrics[canonical])
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _margin_utilization(result: Any) -> Optional[float]:
|
|
190
|
+
margin = getattr(result, "margin", None)
|
|
191
|
+
equity = getattr(result, "equity", None)
|
|
192
|
+
try:
|
|
193
|
+
if margin is not None and equity is not None and len(margin) and len(equity):
|
|
194
|
+
initial = margin["initial_margin"] if "initial_margin" in margin else margin.iloc[:, 0]
|
|
195
|
+
util = (initial.astype(float) / equity.astype(float).replace(0.0, float("nan"))).max()
|
|
196
|
+
return float(0.0 if util != util else util)
|
|
197
|
+
except Exception:
|
|
198
|
+
pass
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _rejection_rate(result: Any) -> Optional[float]:
|
|
203
|
+
metadata = dict(getattr(result, "metadata", {}) or {})
|
|
204
|
+
for key in ("rejection_rate", "package_rejection_rate"):
|
|
205
|
+
if key in metadata:
|
|
206
|
+
return float(metadata[key])
|
|
207
|
+
rejected = metadata.get("rejected_count", metadata.get("rejections"))
|
|
208
|
+
fills = metadata.get("fill_count", metadata.get("fills_count"))
|
|
209
|
+
if rejected is not None and fills is not None:
|
|
210
|
+
denom = float(rejected) + float(fills)
|
|
211
|
+
return 0.0 if denom <= 0.0 else float(rejected) / denom
|
|
212
|
+
fills_obj = getattr(result, "fills", ())
|
|
213
|
+
try:
|
|
214
|
+
fill_count = len(fills_obj)
|
|
215
|
+
if "rejected_count" not in metadata:
|
|
216
|
+
return None
|
|
217
|
+
rejected_count = int(metadata["rejected_count"])
|
|
218
|
+
denom = fill_count + rejected_count
|
|
219
|
+
return 0.0 if denom <= 0 else float(rejected_count) / float(denom)
|
|
220
|
+
except Exception:
|
|
221
|
+
return None
|