aether-quant 0.0.1__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 (99) hide show
  1. aether_quant-0.0.1.dist-info/METADATA +7 -0
  2. aether_quant-0.0.1.dist-info/RECORD +99 -0
  3. aether_quant-0.0.1.dist-info/WHEEL +5 -0
  4. aether_quant-0.0.1.dist-info/entry_points.txt +2 -0
  5. aether_quant-0.0.1.dist-info/licenses/LICENSE +133 -0
  6. aether_quant-0.0.1.dist-info/top_level.txt +13 -0
  7. analyzer/README.md +69 -0
  8. analyzer/__init__.py +17 -0
  9. analyzer/market_analyzer.py +521 -0
  10. aq_cli.py +3633 -0
  11. audit/README.md +47 -0
  12. audit/__init__.py +38 -0
  13. audit/hash_chain.py +52 -0
  14. audit/postgres_audit.py +61 -0
  15. audit/postgres_worker.py +402 -0
  16. audit/redis_queue.py +124 -0
  17. audit/status_export.py +62 -0
  18. data_pipeline/README.md +257 -0
  19. data_pipeline/__init__.py +6 -0
  20. data_pipeline/bar_synthesis.py +97 -0
  21. data_pipeline/dividend_backfill.py +243 -0
  22. data_pipeline/factor_file_backfill.py +276 -0
  23. data_pipeline/fetch.py +206 -0
  24. data_pipeline/fred_backfill.py +435 -0
  25. data_pipeline/ib_backfill.py +287 -0
  26. data_pipeline/v2_manifest.py +75 -0
  27. data_pipeline/yfinance_backfill.py +501 -0
  28. evaluation/README.md +152 -0
  29. evaluation/__init__.py +73 -0
  30. evaluation/ablation.py +310 -0
  31. evaluation/benchmark_comparison.py +195 -0
  32. evaluation/confidence_threshold_calibration.py +157 -0
  33. evaluation/factor_neutralization_check.py +94 -0
  34. evaluation/feature_parity.py +321 -0
  35. evaluation/feature_reconciliation.py +286 -0
  36. evaluation/impact_model.py +77 -0
  37. evaluation/kill_switch_replay.py +186 -0
  38. evaluation/limit_fill_simulator.py +152 -0
  39. evaluation/model_predictions.py +192 -0
  40. evaluation/monte_carlo.py +161 -0
  41. evaluation/public_benchmarks.py +117 -0
  42. evaluation/rank_book_simulator.py +494 -0
  43. evaluation/rank_ic_core.py +204 -0
  44. evaluation/rank_signal_calibration.py +690 -0
  45. evaluation/rolling_ic_gate_calibration.py +190 -0
  46. evaluation/rolling_ic_gate_replay.py +138 -0
  47. execution/README.md +281 -0
  48. execution/__init__.py +80 -0
  49. execution/config_cache.py +48 -0
  50. execution/cost_model.py +213 -0
  51. execution/lean_config_render.py +131 -0
  52. execution/live_credentials.py +38 -0
  53. execution/live_credentials_io.py +52 -0
  54. execution/order_gate.py +388 -0
  55. execution/paper_readiness.py +181 -0
  56. execution/paper_readiness_io.py +54 -0
  57. execution/paper_readiness_report.py +103 -0
  58. execution/paper_readiness_scheduler.py +137 -0
  59. execution/reconciliation.py +160 -0
  60. execution/runtime_config_io.py +38 -0
  61. execution/secret_scan.py +73 -0
  62. features/README.md +77 -0
  63. features/__init__.py +137 -0
  64. features/alt_data_features.py +154 -0
  65. features/bond_features.py +275 -0
  66. features/cross_asset_sensitivity.py +79 -0
  67. features/derivatives_macro_features.py +72 -0
  68. features/macro_features.py +79 -0
  69. features/options_greeks.py +329 -0
  70. features/sector_map.py +53 -0
  71. features/technical_indicators.py +185 -0
  72. generate_backtest_report.py +311 -0
  73. generate_evaluation_report.py +712 -0
  74. inference/README.md +105 -0
  75. inference/__init__.py +36 -0
  76. inference/exported_model.py +929 -0
  77. inference/parallel_inference.py +160 -0
  78. inference/strategy_selector_inference.py +75 -0
  79. json_safety.py +130 -0
  80. portfolio/README.md +447 -0
  81. portfolio/__init__.py +41 -0
  82. portfolio/book_construction.py +786 -0
  83. portfolio/book_neutrality.py +178 -0
  84. portfolio/hrp_allocation.py +159 -0
  85. portfolio/options_arbitrage_detector.py +232 -0
  86. portfolio/options_assignment_risk.py +119 -0
  87. portfolio/options_margin_sizing.py +333 -0
  88. portfolio/options_strategy.py +1999 -0
  89. portfolio/rank_signal.py +219 -0
  90. portfolio/rolling_ic_gate.py +183 -0
  91. risk/README.md +275 -0
  92. risk/__init__.py +38 -0
  93. risk/asset_class_router.py +584 -0
  94. risk/forex_risk.py +158 -0
  95. risk/futures_risk.py +202 -0
  96. risk/kill_switch.py +261 -0
  97. risk/manual_override.py +118 -0
  98. risk/position_sizing.py +303 -0
  99. risk/rl_sizing.py +189 -0
@@ -0,0 +1,287 @@
1
+ """Interactive Brokers historical-data backfill for futures/options -
2
+ mirrors yfinance_backfill.py's shape (pure helpers + one deferred-import
3
+ boundary + orchestration), using `ib_insync` (add to requirements/dev.txt
4
+ only, never requirements.txt - same "dev-only dependency" convention as
5
+ yfinance).
6
+
7
+ This module is ONLY for the offline historical-bars-to-local-Lean-zip step.
8
+ It does NOT touch live/paper trading, which stays entirely on Lean's own
9
+ InteractiveBrokersBrokerage (lean.json's ib-account/ib-user-name/
10
+ ib-password/ib-trading-mode fields and the environments.live-interactive
11
+ block - both untouched by this module). Lean's backtesting environment
12
+ (environments.backtesting in lean.json) is hardcoded to FileSystemDataFeed +
13
+ SubscriptionDataReaderHistoryProvider - it never talks to IB no matter what
14
+ lean.json's ib-* fields hold - so historical futures/options bars still
15
+ need a separate, offline data-prep step to land as local Lean-format zip
16
+ files before any backtest can use them. That is this module's entire job.
17
+
18
+ Connects to the same local TWS/IB Gateway process using
19
+ config.json's phase_v2.ib.host/port/client_id (a second, independent
20
+ client-id socket connection alongside whatever Lean's own brokerage uses
21
+ when live-deployed - standard IB API pattern, Gateway supports multiple
22
+ simultaneous API clients). "IB ready" requires BOTH:
23
+ 1. config.json's phase_v2.ib.enabled == true (Aether's own app-level
24
+ feature flag), AND
25
+ 2. lean.json's ib-account/ib-user-name are both non-empty (Lean's own
26
+ credential fields - untouched, read-only from this module's
27
+ perspective).
28
+ Every public function below raises IBNotConfiguredError, never a raw
29
+ traceback, when either check fails - see aq_cli.py::cmd_fetch()/cmd_ib()
30
+ for how that's surfaced to the user.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import json
36
+ import logging
37
+ from pathlib import Path
38
+ from typing import Any
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ ROOT = Path(__file__).resolve().parents[1]
43
+ CONFIG_PATH = ROOT / "config.json"
44
+ LEAN_CONFIG_PATH = ROOT / "lean.json"
45
+ CONTRACT_SPECS_PATH = ROOT / "data" / "reference" / "futures_contract_specs.json"
46
+
47
+
48
+ class IBNotConfiguredError(Exception):
49
+ """Raised by every public function in this module when
50
+ phase_v2.ib.enabled is false in config.json, or lean.json's
51
+ ib-account/ib-user-name are missing - never a raw ConnectionError, so
52
+ callers (fetch.py's ASSET_CLASS_CONFIG dispatch, aq fetch's CLI) can
53
+ catch one specific, documented exception type and print a clear
54
+ message instead of a traceback."""
55
+
56
+
57
+ def ib_enabled(config: dict, lean_config: dict) -> bool:
58
+ """Pure, no I/O. True only when config.json's phase_v2.ib.enabled is
59
+ truthy AND lean.json's ib-account/ib-user-name are both non-empty -
60
+ the toggle and the credentials live in two different, purpose-
61
+ appropriate files, cross-checked here."""
62
+ ib_config = config.get("phase_v2", {}).get("ib", {})
63
+ if not ib_config.get("enabled", False):
64
+ return False
65
+ return bool(lean_config.get("ib-account")) and bool(lean_config.get("ib-user-name"))
66
+
67
+
68
+ def ib_readiness_status(config: dict, lean_config: dict) -> str:
69
+ """One of "disabled" / "enabled_but_lean_credentials_missing" /
70
+ "ready" - the three states aq_cli.py::cmd_ib() reports (a real
71
+ connect/disconnect round-trip is a separate, heavier check layered on
72
+ top of this pure classification, see attempt_connection() below)."""
73
+ ib_config = config.get("phase_v2", {}).get("ib", {})
74
+ if not ib_config.get("enabled", False):
75
+ return "disabled"
76
+ if not (lean_config.get("ib-account") and lean_config.get("ib-user-name")):
77
+ return "enabled_but_lean_credentials_missing"
78
+ return "ready"
79
+
80
+
81
+ def load_futures_contract_specs(path: Path = CONTRACT_SPECS_PATH) -> dict:
82
+ try:
83
+ with path.open("r", encoding="utf-8") as handle:
84
+ data = json.load(handle)
85
+ except (OSError, json.JSONDecodeError) as exc:
86
+ logger.warning("load_futures_contract_specs(%s): unreadable — %s", path, exc)
87
+ return {}
88
+ return {key: value for key, value in data.items() if not key.startswith("_")}
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # The only functions that import ib_insync - deferred, mirrors
93
+ # yfinance_backfill.fetch_yahoo_ohlcv()'s deferred `import yfinance`.
94
+ # ---------------------------------------------------------------------------
95
+
96
+
97
+ def connect_ib(config: dict, lean_config: dict) -> Any:
98
+ """Raises IBNotConfiguredError if not ib_enabled(config, lean_config).
99
+ Never called at import time - this module must stay importable with
100
+ ib_insync absent when IB is disabled, same convention as
101
+ yfinance_backfill.py."""
102
+ if not ib_enabled(config, lean_config):
103
+ raise IBNotConfiguredError(
104
+ "IB is not configured: run 'aq config set phase_v2.ib.enabled true' and fill in "
105
+ "lean.json's ib-account/ib-user-name first (see 'aq ib status')."
106
+ )
107
+
108
+ from ib_insync import IB # deferred - dev-only dependency, never in requirements.txt
109
+
110
+ ib_config = config["phase_v2"]["ib"]
111
+ ib = IB()
112
+ ib.connect(
113
+ str(ib_config.get("host", "127.0.0.1")),
114
+ int(ib_config.get("port", 7497)),
115
+ clientId=int(ib_config.get("client_id", 7)),
116
+ timeout=float(ib_config.get("connect_timeout_seconds", 10)),
117
+ readonly=True,
118
+ )
119
+ return ib
120
+
121
+
122
+ def disconnect_ib(ib: Any) -> None:
123
+ ib.disconnect()
124
+
125
+
126
+ def attempt_connection(config: dict, lean_config: dict) -> tuple[bool, str]:
127
+ """Best-effort connect/disconnect round-trip for aq_cli.py::cmd_ib()'s
128
+ "reachable" tri-state check. Returns (True, "reachable") or
129
+ (False, <reason>) - never raises (any connection failure is caught and
130
+ reported, since this is a diagnostic command, not a data operation)."""
131
+ try:
132
+ ib = connect_ib(config, lean_config)
133
+ except IBNotConfiguredError as exc:
134
+ return False, str(exc)
135
+ except Exception as exc: # pragma: no cover - real-network failure paths, not unit tested
136
+ return False, f"connection failed — {exc}"
137
+ else:
138
+ disconnect_ib(ib)
139
+ return True, "reachable"
140
+
141
+
142
+ def _bar_rows_from_ib(bars: list) -> list[dict]:
143
+ """Normalizes ib_insync's BarData objects (each with .date/.open/.high/
144
+ .low/.close/.volume) into the same row shape fetch_yahoo_ohlcv()
145
+ returns, so they slot into scale_for_lean()/write_lean_zip() unchanged."""
146
+ rows = []
147
+ for bar in bars:
148
+ bar_date = bar.date.date() if hasattr(bar.date, "date") else bar.date
149
+ rows.append(
150
+ {
151
+ "date": bar_date,
152
+ "open": float(bar.open),
153
+ "high": float(bar.high),
154
+ "low": float(bar.low),
155
+ "close": float(bar.close),
156
+ "volume": float(bar.volume),
157
+ }
158
+ )
159
+ return rows
160
+
161
+
162
+ def fetch_future_historical_bars(
163
+ ib: Any,
164
+ ticker: str,
165
+ contract_spec: dict,
166
+ start: str,
167
+ end: str,
168
+ contract_month: str | None = None,
169
+ ) -> list[dict]:
170
+ """Historical daily bars for `ticker`'s future via ib_insync +
171
+ reqHistoricalData. contract_month=None (default): the continuous
172
+ front-month contract (ContFuture) - correct for live/current sizing,
173
+ but each call only ever sees "whatever is currently front-month," not
174
+ a stable historical series. contract_month="YYYYMM": a SPECIFIC dated
175
+ Future(...) contract instead - lets a caller fetch e.g. both an
176
+ "ES_FRONT" and "ES_NEXT" ticker (aq_cli.py's --contract-month,
177
+ grouped via --family-ticker) for a real historical front/next-month
178
+ term-structure pair (train.py::build_derivatives_macro_features_by_date()),
179
+ which a single continuous series can't provide on its own.
180
+
181
+ Returns [] (never raises) on any IB-side failure or empty response,
182
+ matching fetch_yahoo_ohlcv()'s "one bad symbol never aborts a multi-
183
+ asset run" convention."""
184
+ from ib_insync import ContFuture, Future, util
185
+
186
+ try:
187
+ if contract_month:
188
+ contract = Future(
189
+ ticker, lastTradeDateOrContractMonth=contract_month,
190
+ exchange=contract_spec.get("exchange", "CME"), currency="USD",
191
+ )
192
+ else:
193
+ contract = ContFuture(ticker, exchange=contract_spec.get("exchange", "CME"), currency="USD")
194
+ ib.qualifyContracts(contract)
195
+ duration_days = max((util.parseIBDatetime(end) - util.parseIBDatetime(start)).days, 1)
196
+ bars = ib.reqHistoricalData(
197
+ contract,
198
+ endDateTime=end,
199
+ durationStr=f"{duration_days} D",
200
+ barSizeSetting="1 day",
201
+ whatToShow="TRADES",
202
+ useRTH=True,
203
+ formatDate=1,
204
+ )
205
+ except Exception as exc:
206
+ logger.warning("fetch_future_historical_bars(%s): fetch failed — %s", ticker, exc)
207
+ return []
208
+
209
+ return _bar_rows_from_ib(bars or [])
210
+
211
+
212
+ def fetch_option_chain_snapshot(ib: Any, underlying_ticker: str, expiry: str) -> list[dict]:
213
+ """One row per (strike, right) for underlying_ticker's chain at expiry
214
+ - {strike, right, expiry, bid, ask, last, volume, open_interest} -
215
+ the raw chain snapshot features/options_greeks.py's compute_greeks()
216
+ consumes to attach delta/gamma/theta/vega/iv before
217
+ portfolio/options_strategy.py selects a contract. Returns [] on any
218
+ failure - never raises."""
219
+ from ib_insync import Option, Stock
220
+
221
+ try:
222
+ underlying = Stock(underlying_ticker, "SMART", "USD")
223
+ ib.qualifyContracts(underlying)
224
+ chain_params = ib.reqSecDefOptParams(underlying.symbol, "", underlying.secType, underlying.conId)
225
+ strikes: set[float] = set()
226
+ for param in chain_params:
227
+ if expiry in param.expirations:
228
+ strikes.update(param.strikes)
229
+
230
+ rows: list[dict] = []
231
+ for strike in sorted(strikes):
232
+ for right in ("C", "P"):
233
+ option = Option(underlying_ticker, expiry, float(strike), right, "SMART")
234
+ ib.qualifyContracts(option)
235
+ ticker_data = ib.reqMktData(option, "", False, False)
236
+ ib.sleep(0.2)
237
+ rows.append(
238
+ {
239
+ "strike": float(strike),
240
+ "right": "call" if right == "C" else "put",
241
+ "expiry": expiry,
242
+ "bid": float(ticker_data.bid) if ticker_data.bid == ticker_data.bid else None,
243
+ "ask": float(ticker_data.ask) if ticker_data.ask == ticker_data.ask else None,
244
+ "last": float(ticker_data.last) if ticker_data.last == ticker_data.last else None,
245
+ "volume": float(ticker_data.volume) if ticker_data.volume == ticker_data.volume else None,
246
+ "open_interest": None,
247
+ }
248
+ )
249
+ return rows
250
+ except Exception as exc:
251
+ logger.warning("fetch_option_chain_snapshot(%s, %s): fetch failed — %s", underlying_ticker, expiry, exc)
252
+ return []
253
+
254
+
255
+ def fetch_option_historical_bars(
256
+ ib: Any,
257
+ underlying_ticker: str,
258
+ expiry: str,
259
+ strike: float,
260
+ right: str,
261
+ start: str,
262
+ end: str,
263
+ ) -> list[dict]:
264
+ """Historical daily bars for one specific option contract. Same
265
+ OHLCV row shape as futures/equities. Returns [] on any failure -
266
+ never raises."""
267
+ from ib_insync import Option, util
268
+
269
+ try:
270
+ ib_right = "C" if right.lower() == "call" else "P"
271
+ contract = Option(underlying_ticker, expiry, float(strike), ib_right, "SMART")
272
+ ib.qualifyContracts(contract)
273
+ duration_days = max((util.parseIBDatetime(end) - util.parseIBDatetime(start)).days, 1)
274
+ bars = ib.reqHistoricalData(
275
+ contract,
276
+ endDateTime=end,
277
+ durationStr=f"{duration_days} D",
278
+ barSizeSetting="1 day",
279
+ whatToShow="TRADES",
280
+ useRTH=True,
281
+ formatDate=1,
282
+ )
283
+ except Exception as exc:
284
+ logger.warning("fetch_option_historical_bars(%s %s %s%s): fetch failed — %s", underlying_ticker, expiry, strike, right, exc)
285
+ return []
286
+
287
+ return _bar_rows_from_ib(bars or [])
@@ -0,0 +1,75 @@
1
+ """V2 metadata contract for the existing Lean-data training pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+
8
+
9
+ ROOT = Path(__file__).resolve().parents[1]
10
+
11
+
12
+ def _asset_group_counts(assets: list[dict]) -> dict[str, int]:
13
+ counts: dict[str, int] = {}
14
+ for asset in assets:
15
+ security_type = str(asset.get("security_type", "unknown"))
16
+ counts[security_type] = counts.get(security_type, 0) + 1
17
+ return counts
18
+
19
+
20
+ def _pipeline_consumers() -> list[str]:
21
+ return [
22
+ "train.py baseline model training",
23
+ "Lean backtesting via main.py",
24
+ "moe expert datasets",
25
+ "regime detection features",
26
+ "topology snapshots",
27
+ "dynamic risk and volatility dashboard feeds",
28
+ ]
29
+
30
+
31
+ def build_v2_pipeline_manifest(config: dict, dataset_manifest: dict | None = None) -> dict:
32
+ phase1 = config["phase1"]
33
+ universe = phase1["universe"]
34
+ assets = list(universe["assets"])
35
+ dataset_manifest = dataset_manifest or {}
36
+
37
+ return {
38
+ "project": config.get("name", "Aether Quant"),
39
+ "version": "v2",
40
+ "created_at": datetime.now(timezone.utc).isoformat(),
41
+ "data_source": {
42
+ "type": "local_lean_data_folder",
43
+ "root": "data",
44
+ "absolute_root": str(ROOT / "data"),
45
+ "training_uses_lean_data_folder": True,
46
+ "backtesting_uses_lean_data_folder": True,
47
+ },
48
+ "universe": {
49
+ "name": universe.get("name"),
50
+ "resolution": universe.get("resolution"),
51
+ "asset_count": len(assets),
52
+ "asset_group_counts": _asset_group_counts(assets),
53
+ "tickers": [asset["ticker"] for asset in assets],
54
+ },
55
+ "windows": phase1["windows"],
56
+ "features": {
57
+ "base_features": phase1["features"]["input_set"],
58
+ "target": phase1["target"],
59
+ "normalization": phase1["features"].get("normalization"),
60
+ },
61
+ "quality": {
62
+ "training_eligible_assets": dataset_manifest.get("training_eligible_assets", []),
63
+ "trading_eligible_assets": dataset_manifest.get("trading_eligible_assets", []),
64
+ "observation_only_assets": dataset_manifest.get("observation_only_assets", []),
65
+ },
66
+ "consumers": _pipeline_consumers(),
67
+ "next_v2_dependencies": [
68
+ "regime vectors",
69
+ "expert-specific training slices",
70
+ "topology snapshots",
71
+ "volatility sizing inputs",
72
+ "experience observations",
73
+ ],
74
+ }
75
+