alpha-engine-lib 0.47.0__tar.gz → 0.48.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.
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/PKG-INFO +6 -2
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/README.md +4 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/pyproject.toml +2 -2
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/__init__.py +1 -1
- alpha_engine_lib-0.48.0/src/alpha_engine_lib/http_retry.py +199 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/PKG-INFO +6 -2
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/SOURCES.txt +2 -0
- alpha_engine_lib-0.48.0/tests/test_http_retry.py +199 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/setup.cfg +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/agent_schemas.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/alerts.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/anthropic_payload.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/arcticdb.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/artifact_freshness.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/collector_results.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/cost.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/dates.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/decision_capture.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/ec2_spot.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/email_sender.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/eval_artifacts.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/locks.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/logging.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/model_pricing.yaml +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pillars.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pipeline_status/__init__.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pipeline_status/read.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pipeline_status/registry.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pipeline_status/templates.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/preflight.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/__init__.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/attribution.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/factor_risk.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/factor_risk_xs.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/returns.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/risk_measures.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/riskstats.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/rag/__init__.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/rag/db.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/rag/embeddings.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/rag/migrations/0001_content_tsv.sql +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/rag/rerank.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/rag/retrieval.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/rag/schema.sql +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/reconcile.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/secrets.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/sources/__init__.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/sources/protocols.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/ssm_dispatcher.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/ssm_log_capture.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/telegram.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/trading_calendar.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/transparency.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/transparency_inventory.yaml +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/universe.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/dependency_links.txt +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/requires.txt +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/top_level.txt +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_agent_schemas.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_alerts.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_anthropic_payload.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_arcticdb.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_artifact_freshness.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_collector_results.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_cost.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_dates.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_decision_capture.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_ec2_spot.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_email_sender.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_eval_artifacts.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_locks.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_logging.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_pillars.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_pipeline_status_read.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_pipeline_status_registry.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_pipeline_status_templates.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_preflight.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_quant_attribution.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_quant_factor_risk.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_quant_factor_risk_xs.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_quant_returns.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_quant_risk_measures.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_quant_riskstats.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_rag.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_rag_rerank.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_rag_retrieval_hybrid.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_reconcile.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_secrets.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_sources_protocols.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_ssm_dispatcher.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_ssm_log_capture.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_telegram.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_trading_calendar.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_transparency.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_universe.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_version_bump_workflow.py +0 -0
- {alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/tests/test_version_pin.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-engine-lib
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Shared utilities for the Alpha Engine modules: preflight, logging, ArcticDB, dates, decision capture, cost telemetry, Anthropic payload chokepoint, artifact freshness, RAG, agent schemas, SSM secrets, Telegram + SNS alerts, EC2 spot resilience, SSM log-capture, SSM dispatcher, Step-Functions execution-state projection,
|
|
3
|
+
Version: 0.48.0
|
|
4
|
+
Summary: Shared utilities for the Alpha Engine modules: preflight, logging, ArcticDB, dates, decision capture, cost telemetry, Anthropic payload chokepoint, artifact freshness, RAG, agent schemas, SSM secrets, Telegram + SNS alerts, EC2 spot resilience, SSM log-capture, SSM dispatcher, Step-Functions execution-state projection, S3-conditional-PUT writer locks, and bounded-backoff HTTP retry. Full surface documented in README.
|
|
5
5
|
Author: Brian McMahon
|
|
6
6
|
License: Proprietary
|
|
7
7
|
Requires-Python: >=3.9
|
|
@@ -265,6 +265,10 @@ The shared institutional-analytics engine: pure, front-end- and data-source-agno
|
|
|
265
265
|
- **`quant.returns`** — `xirr` (money-weighted, Newton + bisection), `time_weighted_return` (GIPS), `cumulative_return`, `annualize` (stdlib).
|
|
266
266
|
- **`quant.attribution`** — single-period Brinson-Fachler decomposition (`brinson_fachler`) + multi-period Cariño linking (`link_periods`) (stdlib).
|
|
267
267
|
|
|
268
|
+
### `http_retry` — bounded-backoff transient-API retry chokepoint
|
|
269
|
+
|
|
270
|
+
`request_with_retry(url, *, params, session, transient_status, ...)` returns the final `requests.Response` after retrying the transient class — 429 + 5xx responses (honoring `Retry-After`) and `Timeout`/`ConnectionError` network errors — with exponential backoff + full jitter; an exhausted network error raises `HttpRetryError` (api-key-scrubbed), while a persistent transient-status response is returned for the caller to interpret (so a 403, not in the transient set, is handed back for e.g. polygon's `PolygonForbiddenError` conversion). Also exposes the low-level `backoff_delay(attempt, *, base, cap, retry_after)` and `scrub_api_keys(msg)` (masks `api_key=`/`apiKey=` querystring values) for consumers with bespoke loops (the rate-limited `polygon_client` keeps its own loop + 403 + JSON parse and reuses just the delay math + scrubber). Consolidates the four mirrored alpha-engine-data retry sites (FRED fetch, polygon client, preflight reachability, FRED repair) into one policy so they stop drifting (L4499). Stdlib + `requests` only.
|
|
271
|
+
|
|
268
272
|
```python
|
|
269
273
|
from alpha_engine_lib.quant.risk_measures import historical_cvar
|
|
270
274
|
from alpha_engine_lib.quant.factor_risk import estimate_factor_model, portfolio_risk
|
|
@@ -230,6 +230,10 @@ The shared institutional-analytics engine: pure, front-end- and data-source-agno
|
|
|
230
230
|
- **`quant.returns`** — `xirr` (money-weighted, Newton + bisection), `time_weighted_return` (GIPS), `cumulative_return`, `annualize` (stdlib).
|
|
231
231
|
- **`quant.attribution`** — single-period Brinson-Fachler decomposition (`brinson_fachler`) + multi-period Cariño linking (`link_periods`) (stdlib).
|
|
232
232
|
|
|
233
|
+
### `http_retry` — bounded-backoff transient-API retry chokepoint
|
|
234
|
+
|
|
235
|
+
`request_with_retry(url, *, params, session, transient_status, ...)` returns the final `requests.Response` after retrying the transient class — 429 + 5xx responses (honoring `Retry-After`) and `Timeout`/`ConnectionError` network errors — with exponential backoff + full jitter; an exhausted network error raises `HttpRetryError` (api-key-scrubbed), while a persistent transient-status response is returned for the caller to interpret (so a 403, not in the transient set, is handed back for e.g. polygon's `PolygonForbiddenError` conversion). Also exposes the low-level `backoff_delay(attempt, *, base, cap, retry_after)` and `scrub_api_keys(msg)` (masks `api_key=`/`apiKey=` querystring values) for consumers with bespoke loops (the rate-limited `polygon_client` keeps its own loop + 403 + JSON parse and reuses just the delay math + scrubber). Consolidates the four mirrored alpha-engine-data retry sites (FRED fetch, polygon client, preflight reachability, FRED repair) into one policy so they stop drifting (L4499). Stdlib + `requests` only.
|
|
236
|
+
|
|
233
237
|
```python
|
|
234
238
|
from alpha_engine_lib.quant.risk_measures import historical_cvar
|
|
235
239
|
from alpha_engine_lib.quant.factor_risk import estimate_factor_model, portfolio_risk
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "alpha-engine-lib"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Shared utilities for the Alpha Engine modules: preflight, logging, ArcticDB, dates, decision capture, cost telemetry, Anthropic payload chokepoint, artifact freshness, RAG, agent schemas, SSM secrets, Telegram + SNS alerts, EC2 spot resilience, SSM log-capture, SSM dispatcher, Step-Functions execution-state projection,
|
|
7
|
+
version = "0.48.0"
|
|
8
|
+
description = "Shared utilities for the Alpha Engine modules: preflight, logging, ArcticDB, dates, decision capture, cost telemetry, Anthropic payload chokepoint, artifact freshness, RAG, agent schemas, SSM secrets, Telegram + SNS alerts, EC2 spot resilience, SSM log-capture, SSM dispatcher, Step-Functions execution-state projection, S3-conditional-PUT writer locks, and bounded-backoff HTTP retry. Full surface documented in README."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# EC2 still runs Python 3.9 on the always-on micro instance (boto3 drops
|
|
11
11
|
# 3.9 support 2026-04-29, so upgrade is on the near-term roadmap). All
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Bounded-backoff HTTP retry primitive — the transient external-API
|
|
2
|
+
resilience chokepoint (L4499).
|
|
3
|
+
|
|
4
|
+
Consolidates the backoff + full-jitter + ``Retry-After`` + api-key-scrub
|
|
5
|
+
retry idiom that was mirrored across four alpha-engine-data sites:
|
|
6
|
+
|
|
7
|
+
* ``collectors/daily_closes.py::_fred_get_with_retry`` (L4480)
|
|
8
|
+
* ``polygon_client.py::_get`` / ``_backoff`` (L4496)
|
|
9
|
+
* ``preflight.py::_reachability_get`` (L4494)
|
|
10
|
+
* ``collectors/daily_closes_fred_repair.py::_fetch_fred_range``
|
|
11
|
+
|
|
12
|
+
Each had its own copy of "exponential backoff + full jitter, honor
|
|
13
|
+
``Retry-After``, retry the transient class, scrub the api-key from the
|
|
14
|
+
error before logging/raising, then fail loud." This module is the single
|
|
15
|
+
source of truth for that policy so the four callsites stop drifting.
|
|
16
|
+
|
|
17
|
+
Two layers are exported:
|
|
18
|
+
|
|
19
|
+
* :func:`request_with_retry` — the full GET-with-retry for the plain
|
|
20
|
+
callsites (FRED fetch, preflight probe, FRED repair). Returns the final
|
|
21
|
+
``requests.Response``; the caller still owns status interpretation
|
|
22
|
+
(``raise_for_status`` / special-casing a 403), so genuinely different
|
|
23
|
+
consumers compose it without a leaky mega-config.
|
|
24
|
+
* :func:`backoff_delay` + :func:`scrub_api_keys` — the low-level pieces for
|
|
25
|
+
a consumer with bespoke control flow (the rate-limited ``polygon_client``
|
|
26
|
+
keeps its own loop + 403 handling + JSON parse + rate limiter, but shares
|
|
27
|
+
the delay math and the scrubber).
|
|
28
|
+
|
|
29
|
+
Design note (anti-over-engineering): this is deliberately NOT a
|
|
30
|
+
pluggable-everything HTTP framework. It captures the one invariant the four
|
|
31
|
+
sites share; consumers whose semantics diverge (polygon's 403 + rate limiter)
|
|
32
|
+
reuse the primitives rather than being forced through a generic loop.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import logging as _logging
|
|
38
|
+
import random as _random
|
|
39
|
+
import re
|
|
40
|
+
import time as _time
|
|
41
|
+
from typing import Callable, Iterable
|
|
42
|
+
|
|
43
|
+
import requests
|
|
44
|
+
|
|
45
|
+
_DEFAULT_LOGGER = _logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
# Transient HTTP status class: 429 (rate limit) + the retryable 5xx. A 4xx
|
|
48
|
+
# other than 429 is a deterministic client error — retrying it is pointless,
|
|
49
|
+
# so it is NOT in the default set and is returned to the caller as-is.
|
|
50
|
+
DEFAULT_TRANSIENT_STATUS: "frozenset[int]" = frozenset({429, 500, 502, 503, 504})
|
|
51
|
+
|
|
52
|
+
# Mask FRED ``api_key=`` (snake) and polygon ``apiKey=`` (camel) querystring
|
|
53
|
+
# VALUES — both leak via ``requests`` exception ``str()`` (the effective URL)
|
|
54
|
+
# and via hand-built error strings. Mirrors the per-repo scrubbers this module
|
|
55
|
+
# replaces; complements ``alpha_engine_lib.logging.SecretsRedactingFilter``
|
|
56
|
+
# (which catches token-shaped secrets, not query-param api keys).
|
|
57
|
+
_API_KEY_RE = re.compile(r"(?:api_key|apiKey)=[^&\s]+")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def scrub_api_keys(msg: object) -> str:
|
|
61
|
+
"""Mask ``api_key=...`` / ``apiKey=...`` querystring values in a string.
|
|
62
|
+
|
|
63
|
+
Preserves the key NAME (so logs still show *which* param) and the value
|
|
64
|
+
delimiter, replacing only the secret value with ``***``. Idempotent.
|
|
65
|
+
"""
|
|
66
|
+
return _API_KEY_RE.sub(lambda m: m.group(0).split("=", 1)[0] + "=***", str(msg))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class HttpRetryError(RuntimeError):
|
|
70
|
+
"""Raised when all attempts are exhausted on a transient NETWORK error
|
|
71
|
+
(``requests.Timeout`` / ``requests.ConnectionError``) or a non-transient
|
|
72
|
+
``RequestException``.
|
|
73
|
+
|
|
74
|
+
The message is api-key-scrubbed. The originating exception is preserved
|
|
75
|
+
as ``__cause__`` (and on ``.last_exc``); ``.label`` / ``.attempts`` carry
|
|
76
|
+
context for callers that want to re-wrap (e.g. preflight's
|
|
77
|
+
``RuntimeError(... unreachable ...)``).
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, label: str, attempts: int, last_exc: BaseException) -> None:
|
|
81
|
+
self.label = label
|
|
82
|
+
self.attempts = attempts
|
|
83
|
+
self.last_exc = last_exc
|
|
84
|
+
super().__init__(
|
|
85
|
+
scrub_api_keys(
|
|
86
|
+
f"{label or 'request'} failed after {attempts} attempt(s): {last_exc}"
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def backoff_delay(
|
|
92
|
+
attempt: int,
|
|
93
|
+
*,
|
|
94
|
+
base: float = 1.0,
|
|
95
|
+
cap: float = 30.0,
|
|
96
|
+
retry_after: "str | float | None" = None,
|
|
97
|
+
rng: "_random.Random | None" = None,
|
|
98
|
+
) -> float:
|
|
99
|
+
"""Full-jitter exponential backoff: ``min(base*2**attempt + U(0, base), cap)``.
|
|
100
|
+
|
|
101
|
+
``attempt`` is 0-indexed. Honors a server ``Retry-After`` (seconds, str or
|
|
102
|
+
float) when supplied — a numeric value replaces the exponential term (still
|
|
103
|
+
+ jitter, still capped); a non-numeric ``Retry-After`` (HTTP-date form)
|
|
104
|
+
falls back to the exponential term. ``rng`` is injectable for deterministic
|
|
105
|
+
tests.
|
|
106
|
+
"""
|
|
107
|
+
wait: "float | None" = None
|
|
108
|
+
if retry_after is not None:
|
|
109
|
+
try:
|
|
110
|
+
wait = float(retry_after)
|
|
111
|
+
except (TypeError, ValueError):
|
|
112
|
+
wait = None
|
|
113
|
+
if wait is None:
|
|
114
|
+
wait = base * (2 ** attempt)
|
|
115
|
+
jitter = (rng or _random).uniform(0, base)
|
|
116
|
+
return min(wait + jitter, cap)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def request_with_retry(
|
|
120
|
+
url: str,
|
|
121
|
+
*,
|
|
122
|
+
method: str = "GET",
|
|
123
|
+
params: "dict | None" = None,
|
|
124
|
+
session: "requests.Session | None" = None,
|
|
125
|
+
timeout: float = 15.0,
|
|
126
|
+
max_attempts: int = 3,
|
|
127
|
+
backoff_base: float = 1.0,
|
|
128
|
+
backoff_cap: float = 30.0,
|
|
129
|
+
transient_status: Iterable[int] = DEFAULT_TRANSIENT_STATUS,
|
|
130
|
+
retry_network: bool = True,
|
|
131
|
+
honor_retry_after: bool = True,
|
|
132
|
+
scrub: Callable[[object], str] = scrub_api_keys,
|
|
133
|
+
logger: "_logging.Logger | None" = None,
|
|
134
|
+
label: str = "",
|
|
135
|
+
sleep: Callable[[float], None] = _time.sleep,
|
|
136
|
+
) -> requests.Response:
|
|
137
|
+
"""``method`` ``url`` with bounded backoff + full jitter on the transient
|
|
138
|
+
class, returning the final :class:`requests.Response`.
|
|
139
|
+
|
|
140
|
+
Retries:
|
|
141
|
+
* responses whose status is in ``transient_status`` (default 429 + 5xx),
|
|
142
|
+
honoring ``Retry-After`` when ``honor_retry_after``; and
|
|
143
|
+
* (when ``retry_network``) ``requests.Timeout`` / ``ConnectionError``.
|
|
144
|
+
|
|
145
|
+
Terminal behavior:
|
|
146
|
+
* a transient-status response that survives ``max_attempts`` is
|
|
147
|
+
**returned** — the caller decides whether to ``raise_for_status`` or
|
|
148
|
+
special-case it (e.g. a 403, which is NOT in the transient set, is
|
|
149
|
+
returned immediately for the caller to convert); and
|
|
150
|
+
* an exhausted NETWORK error (or a non-transient ``RequestException``
|
|
151
|
+
such as a bad URL) raises :class:`HttpRetryError` (scrubbed).
|
|
152
|
+
|
|
153
|
+
``scrub`` is applied to every error string logged or raised. ``session``
|
|
154
|
+
lets a caller reuse a session (e.g. one carrying auth query params).
|
|
155
|
+
``sleep`` is injectable for tests. ``max_attempts`` must be >= 1.
|
|
156
|
+
"""
|
|
157
|
+
if max_attempts < 1:
|
|
158
|
+
raise ValueError(f"max_attempts must be >= 1, got {max_attempts}")
|
|
159
|
+
log = logger or _DEFAULT_LOGGER
|
|
160
|
+
transient = frozenset(transient_status)
|
|
161
|
+
requester = (session or requests).request
|
|
162
|
+
resp: "requests.Response | None" = None
|
|
163
|
+
for attempt in range(max_attempts):
|
|
164
|
+
last = attempt == max_attempts - 1
|
|
165
|
+
try:
|
|
166
|
+
resp = requester(method, url, params=params or {}, timeout=timeout)
|
|
167
|
+
except (requests.Timeout, requests.ConnectionError) as exc:
|
|
168
|
+
if not retry_network or last:
|
|
169
|
+
raise HttpRetryError(label, attempt + 1, exc) from exc
|
|
170
|
+
delay = backoff_delay(attempt, base=backoff_base, cap=backoff_cap)
|
|
171
|
+
log.warning(
|
|
172
|
+
"%s transient %s — backing off %.1fs (attempt %d/%d)",
|
|
173
|
+
label or url, type(exc).__name__, delay, attempt + 1, max_attempts,
|
|
174
|
+
)
|
|
175
|
+
sleep(delay)
|
|
176
|
+
continue
|
|
177
|
+
except requests.RequestException as exc:
|
|
178
|
+
# Non-transient (bad URL / too many redirects / invalid schema) —
|
|
179
|
+
# retrying a deterministic error is pointless; fail loud now.
|
|
180
|
+
raise HttpRetryError(label, attempt + 1, exc) from exc
|
|
181
|
+
|
|
182
|
+
if resp.status_code in transient and not last:
|
|
183
|
+
retry_after = resp.headers.get("Retry-After") if honor_retry_after else None
|
|
184
|
+
delay = backoff_delay(
|
|
185
|
+
attempt, base=backoff_base, cap=backoff_cap, retry_after=retry_after,
|
|
186
|
+
)
|
|
187
|
+
log.warning(
|
|
188
|
+
"%s HTTP %d — backing off %.1fs (attempt %d/%d)",
|
|
189
|
+
label or url, resp.status_code, delay, attempt + 1, max_attempts,
|
|
190
|
+
)
|
|
191
|
+
sleep(delay)
|
|
192
|
+
continue
|
|
193
|
+
return resp
|
|
194
|
+
|
|
195
|
+
# Loop exhausted on transient-status responses: return the last one for the
|
|
196
|
+
# caller to interpret (network exhaustion already raised above). resp is
|
|
197
|
+
# non-None because max_attempts >= 1 guarantees at least one assignment.
|
|
198
|
+
assert resp is not None
|
|
199
|
+
return resp
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-engine-lib
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Shared utilities for the Alpha Engine modules: preflight, logging, ArcticDB, dates, decision capture, cost telemetry, Anthropic payload chokepoint, artifact freshness, RAG, agent schemas, SSM secrets, Telegram + SNS alerts, EC2 spot resilience, SSM log-capture, SSM dispatcher, Step-Functions execution-state projection,
|
|
3
|
+
Version: 0.48.0
|
|
4
|
+
Summary: Shared utilities for the Alpha Engine modules: preflight, logging, ArcticDB, dates, decision capture, cost telemetry, Anthropic payload chokepoint, artifact freshness, RAG, agent schemas, SSM secrets, Telegram + SNS alerts, EC2 spot resilience, SSM log-capture, SSM dispatcher, Step-Functions execution-state projection, S3-conditional-PUT writer locks, and bounded-backoff HTTP retry. Full surface documented in README.
|
|
5
5
|
Author: Brian McMahon
|
|
6
6
|
License: Proprietary
|
|
7
7
|
Requires-Python: >=3.9
|
|
@@ -265,6 +265,10 @@ The shared institutional-analytics engine: pure, front-end- and data-source-agno
|
|
|
265
265
|
- **`quant.returns`** — `xirr` (money-weighted, Newton + bisection), `time_weighted_return` (GIPS), `cumulative_return`, `annualize` (stdlib).
|
|
266
266
|
- **`quant.attribution`** — single-period Brinson-Fachler decomposition (`brinson_fachler`) + multi-period Cariño linking (`link_periods`) (stdlib).
|
|
267
267
|
|
|
268
|
+
### `http_retry` — bounded-backoff transient-API retry chokepoint
|
|
269
|
+
|
|
270
|
+
`request_with_retry(url, *, params, session, transient_status, ...)` returns the final `requests.Response` after retrying the transient class — 429 + 5xx responses (honoring `Retry-After`) and `Timeout`/`ConnectionError` network errors — with exponential backoff + full jitter; an exhausted network error raises `HttpRetryError` (api-key-scrubbed), while a persistent transient-status response is returned for the caller to interpret (so a 403, not in the transient set, is handed back for e.g. polygon's `PolygonForbiddenError` conversion). Also exposes the low-level `backoff_delay(attempt, *, base, cap, retry_after)` and `scrub_api_keys(msg)` (masks `api_key=`/`apiKey=` querystring values) for consumers with bespoke loops (the rate-limited `polygon_client` keeps its own loop + 403 + JSON parse and reuses just the delay math + scrubber). Consolidates the four mirrored alpha-engine-data retry sites (FRED fetch, polygon client, preflight reachability, FRED repair) into one policy so they stop drifting (L4499). Stdlib + `requests` only.
|
|
271
|
+
|
|
268
272
|
```python
|
|
269
273
|
from alpha_engine_lib.quant.risk_measures import historical_cvar
|
|
270
274
|
from alpha_engine_lib.quant.factor_risk import estimate_factor_model, portfolio_risk
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/SOURCES.txt
RENAMED
|
@@ -13,6 +13,7 @@ src/alpha_engine_lib/decision_capture.py
|
|
|
13
13
|
src/alpha_engine_lib/ec2_spot.py
|
|
14
14
|
src/alpha_engine_lib/email_sender.py
|
|
15
15
|
src/alpha_engine_lib/eval_artifacts.py
|
|
16
|
+
src/alpha_engine_lib/http_retry.py
|
|
16
17
|
src/alpha_engine_lib/locks.py
|
|
17
18
|
src/alpha_engine_lib/logging.py
|
|
18
19
|
src/alpha_engine_lib/model_pricing.yaml
|
|
@@ -64,6 +65,7 @@ tests/test_decision_capture.py
|
|
|
64
65
|
tests/test_ec2_spot.py
|
|
65
66
|
tests/test_email_sender.py
|
|
66
67
|
tests/test_eval_artifacts.py
|
|
68
|
+
tests/test_http_retry.py
|
|
67
69
|
tests/test_locks.py
|
|
68
70
|
tests/test_logging.py
|
|
69
71
|
tests/test_pillars.py
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Tests for ``alpha_engine_lib.http_retry`` — the consolidated transient
|
|
2
|
+
external-API retry primitive (L4499)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
from unittest.mock import MagicMock, patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from alpha_engine_lib import http_retry
|
|
13
|
+
from alpha_engine_lib.http_retry import (
|
|
14
|
+
HttpRetryError,
|
|
15
|
+
backoff_delay,
|
|
16
|
+
request_with_retry,
|
|
17
|
+
scrub_api_keys,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ── scrub_api_keys ───────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_scrub_masks_both_styles():
|
|
25
|
+
assert scrub_api_keys("x?api_key=SECRET&y=1") == "x?api_key=***&y=1"
|
|
26
|
+
assert scrub_api_keys("x?apiKey=SECRET&y=1") == "x?apiKey=***&y=1"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_scrub_terminates_at_ampersand():
|
|
30
|
+
assert scrub_api_keys("u?apiKey=SECRET&file_type=json") == "u?apiKey=***&file_type=json"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_scrub_passthrough_and_idempotent():
|
|
34
|
+
assert scrub_api_keys("no key here") == "no key here"
|
|
35
|
+
once = scrub_api_keys("?api_key=SECRET")
|
|
36
|
+
assert scrub_api_keys(once) == once == "?api_key=***"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_scrub_accepts_exception_object():
|
|
40
|
+
exc = requests.HTTPError("500 for url: https://x/?apiKey=LEAKED&a=1")
|
|
41
|
+
out = scrub_api_keys(exc)
|
|
42
|
+
assert "LEAKED" not in out and "apiKey=***" in out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ── backoff_delay ────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_backoff_grows_exponentially_and_caps():
|
|
49
|
+
rng = MagicMock()
|
|
50
|
+
rng.uniform.return_value = 0.0 # zero jitter → deterministic
|
|
51
|
+
assert backoff_delay(0, base=1.0, cap=30.0, rng=rng) == 1.0
|
|
52
|
+
assert backoff_delay(1, base=1.0, cap=30.0, rng=rng) == 2.0
|
|
53
|
+
assert backoff_delay(2, base=1.0, cap=30.0, rng=rng) == 4.0
|
|
54
|
+
assert backoff_delay(10, base=1.0, cap=30.0, rng=rng) == 30.0 # capped
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_backoff_jitter_is_bounded():
|
|
58
|
+
for attempt in range(4):
|
|
59
|
+
d = backoff_delay(attempt, base=1.0, cap=100.0, rng=random.Random(attempt))
|
|
60
|
+
base_term = 1.0 * (2 ** attempt)
|
|
61
|
+
assert base_term <= d <= base_term + 1.0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_backoff_honors_numeric_retry_after():
|
|
65
|
+
rng = MagicMock(); rng.uniform.return_value = 0.0
|
|
66
|
+
# Retry-After replaces the exponential term.
|
|
67
|
+
assert backoff_delay(3, base=1.0, cap=100.0, retry_after="12", rng=rng) == 12.0
|
|
68
|
+
assert backoff_delay(3, base=1.0, cap=100.0, retry_after=7.5, rng=rng) == 7.5
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_backoff_non_numeric_retry_after_falls_back():
|
|
72
|
+
rng = MagicMock(); rng.uniform.return_value = 0.0
|
|
73
|
+
# HTTP-date form → not parseable → exponential term (2**1 = 2).
|
|
74
|
+
assert backoff_delay(1, base=1.0, cap=100.0, retry_after="Wed, 21 Oct 2026 07:28:00 GMT", rng=rng) == 2.0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ── request_with_retry ───────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _resp(status: int, headers: "dict | None" = None) -> MagicMock:
|
|
81
|
+
r = MagicMock(spec=requests.Response)
|
|
82
|
+
r.status_code = status
|
|
83
|
+
r.headers = headers or {}
|
|
84
|
+
return r
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _session(*side_effect):
|
|
88
|
+
s = MagicMock()
|
|
89
|
+
s.request.side_effect = list(side_effect)
|
|
90
|
+
return s
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
_NOSLEEP = lambda *_a, **_k: None # noqa: E731
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_success_first_try_returns_response():
|
|
97
|
+
s = _session(_resp(200))
|
|
98
|
+
out = request_with_retry("https://x", session=s, sleep=_NOSLEEP, label="x")
|
|
99
|
+
assert out.status_code == 200
|
|
100
|
+
assert s.request.call_count == 1
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_5xx_retries_then_succeeds():
|
|
104
|
+
s = _session(_resp(500), _resp(200))
|
|
105
|
+
out = request_with_retry("https://x", session=s, max_attempts=3, sleep=_NOSLEEP)
|
|
106
|
+
assert out.status_code == 200
|
|
107
|
+
assert s.request.call_count == 2
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_5xx_exhausted_returns_last_response():
|
|
111
|
+
# A persistent transient status is RETURNED (caller does raise_for_status).
|
|
112
|
+
s = _session(_resp(503), _resp(503), _resp(503))
|
|
113
|
+
out = request_with_retry("https://x", session=s, max_attempts=3, sleep=_NOSLEEP)
|
|
114
|
+
assert out.status_code == 503
|
|
115
|
+
assert s.request.call_count == 3
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_429_honors_retry_after_header():
|
|
119
|
+
delays = []
|
|
120
|
+
s = _session(_resp(429, {"Retry-After": "5"}), _resp(200))
|
|
121
|
+
request_with_retry(
|
|
122
|
+
"https://x", session=s, max_attempts=3,
|
|
123
|
+
sleep=lambda d: delays.append(d),
|
|
124
|
+
)
|
|
125
|
+
# The single backoff used Retry-After=5 (+ jitter in [0,1)).
|
|
126
|
+
assert len(delays) == 1 and 5.0 <= delays[0] < 6.0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_non_transient_status_returned_immediately():
|
|
130
|
+
# 403 is not in the transient set → returned at once for the caller (e.g.
|
|
131
|
+
# polygon's PolygonForbiddenError conversion), no retry.
|
|
132
|
+
s = _session(_resp(403))
|
|
133
|
+
out = request_with_retry("https://x", session=s, max_attempts=3, sleep=_NOSLEEP)
|
|
134
|
+
assert out.status_code == 403
|
|
135
|
+
assert s.request.call_count == 1
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_network_error_retries_then_succeeds():
|
|
139
|
+
s = _session(requests.ConnectionError("boom"), _resp(200))
|
|
140
|
+
out = request_with_retry("https://x", session=s, max_attempts=3, sleep=_NOSLEEP)
|
|
141
|
+
assert out.status_code == 200
|
|
142
|
+
assert s.request.call_count == 2
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_network_error_exhausted_raises_scrubbed():
|
|
146
|
+
s = _session(requests.Timeout("read timed out ?apiKey=LEAKED"),
|
|
147
|
+
requests.Timeout("read timed out ?apiKey=LEAKED"))
|
|
148
|
+
with pytest.raises(HttpRetryError) as ei:
|
|
149
|
+
request_with_retry("https://x", session=s, max_attempts=2, sleep=_NOSLEEP, label="polygon")
|
|
150
|
+
assert "LEAKED" not in str(ei.value)
|
|
151
|
+
assert ei.value.attempts == 2
|
|
152
|
+
assert isinstance(ei.value.last_exc, requests.Timeout)
|
|
153
|
+
assert "polygon" in str(ei.value)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_retry_network_false_raises_on_first_network_error():
|
|
157
|
+
s = _session(requests.ConnectionError("boom"))
|
|
158
|
+
with pytest.raises(HttpRetryError):
|
|
159
|
+
request_with_retry("https://x", session=s, retry_network=False, sleep=_NOSLEEP)
|
|
160
|
+
assert s.request.call_count == 1
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_non_transient_request_exception_raises_immediately():
|
|
164
|
+
s = _session(requests.TooManyRedirects("loop"))
|
|
165
|
+
with pytest.raises(HttpRetryError):
|
|
166
|
+
request_with_retry("https://x", session=s, max_attempts=3, sleep=_NOSLEEP)
|
|
167
|
+
assert s.request.call_count == 1 # no retry on a deterministic error
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_max_attempts_must_be_positive():
|
|
171
|
+
with pytest.raises(ValueError, match="max_attempts must be >= 1"):
|
|
172
|
+
request_with_retry("https://x", session=_session(_resp(200)), max_attempts=0)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_default_session_uses_requests_module():
|
|
176
|
+
# session=None path uses the module-level requests.request.
|
|
177
|
+
with patch.object(http_retry.requests, "request", return_value=_resp(200)) as m:
|
|
178
|
+
out = request_with_retry("https://x", sleep=_NOSLEEP)
|
|
179
|
+
assert out.status_code == 200
|
|
180
|
+
m.assert_called_once()
|
|
181
|
+
# method + url threaded positionally; params/timeout as kwargs.
|
|
182
|
+
args, kwargs = m.call_args
|
|
183
|
+
assert args[0] == "GET" and args[1] == "https://x"
|
|
184
|
+
assert "timeout" in kwargs
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_custom_transient_status_set():
|
|
188
|
+
# Caller can widen/narrow the retry class; 418 retried here, 500 not.
|
|
189
|
+
s = _session(_resp(418), _resp(200))
|
|
190
|
+
out = request_with_retry(
|
|
191
|
+
"https://x", session=s, transient_status={418}, max_attempts=3, sleep=_NOSLEEP,
|
|
192
|
+
)
|
|
193
|
+
assert out.status_code == 200 and s.request.call_count == 2
|
|
194
|
+
|
|
195
|
+
s2 = _session(_resp(500))
|
|
196
|
+
out2 = request_with_retry(
|
|
197
|
+
"https://x", session=s2, transient_status={418}, max_attempts=3, sleep=_NOSLEEP,
|
|
198
|
+
)
|
|
199
|
+
assert out2.status_code == 500 and s2.request.call_count == 1 # 500 not transient here
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/anthropic_payload.py
RENAMED
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/artifact_freshness.py
RENAMED
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/collector_results.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/decision_capture.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pipeline_status/__init__.py
RENAMED
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pipeline_status/read.py
RENAMED
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/pipeline_status/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/attribution.py
RENAMED
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/factor_risk.py
RENAMED
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/factor_risk_xs.py
RENAMED
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/quant/risk_measures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/sources/__init__.py
RENAMED
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/sources/protocols.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/trading_calendar.py
RENAMED
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib/transparency_inventory.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/requires.txt
RENAMED
|
File without changes
|
{alpha_engine_lib-0.47.0 → alpha_engine_lib-0.48.0}/src/alpha_engine_lib.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|