equity-aggregator 0.1.1__py3-none-any.whl → 0.1.4__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.
- equity_aggregator/README.md +40 -36
- equity_aggregator/adapters/__init__.py +13 -7
- equity_aggregator/adapters/data_sources/__init__.py +4 -6
- equity_aggregator/adapters/data_sources/_utils/_client.py +1 -1
- equity_aggregator/adapters/data_sources/{authoritative_feeds → _utils}/_record_types.py +1 -1
- equity_aggregator/adapters/data_sources/discovery_feeds/__init__.py +17 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/__init__.py +7 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/_utils/__init__.py +10 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/_utils/backoff.py +33 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/_utils/parser.py +107 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/intrinio.py +305 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/session.py +197 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/lseg/__init__.py +7 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/lseg/_utils/__init__.py +9 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/lseg/_utils/backoff.py +33 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/lseg/_utils/parser.py +120 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/lseg/lseg.py +239 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/lseg/session.py +162 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/sec/__init__.py +7 -0
- equity_aggregator/adapters/data_sources/{authoritative_feeds → discovery_feeds/sec}/sec.py +4 -5
- equity_aggregator/adapters/data_sources/discovery_feeds/stock_analysis/__init__.py +7 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/stock_analysis/stock_analysis.py +150 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/tradingview/__init__.py +5 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/tradingview/tradingview.py +275 -0
- equity_aggregator/adapters/data_sources/discovery_feeds/xetra/__init__.py +7 -0
- equity_aggregator/adapters/data_sources/{authoritative_feeds → discovery_feeds/xetra}/xetra.py +9 -12
- equity_aggregator/adapters/data_sources/enrichment_feeds/__init__.py +6 -1
- equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/__init__.py +5 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/api.py +71 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/download.py +109 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/gleif.py +195 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/parser.py +75 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/__init__.py +1 -1
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/_utils/__init__.py +11 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/{utils → _utils}/backoff.py +1 -1
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/{utils → _utils}/fuzzy.py +28 -26
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/_utils/json.py +36 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/api/__init__.py +1 -1
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/api/{summary.py → quote_summary.py} +44 -30
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/api/search.py +10 -5
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/auth.py +130 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/config.py +3 -3
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/ranking.py +97 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/session.py +85 -218
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/transport.py +191 -0
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/yfinance.py +413 -0
- equity_aggregator/adapters/data_sources/reference_lookup/exchange_rate_api.py +6 -13
- equity_aggregator/adapters/data_sources/reference_lookup/openfigi.py +23 -7
- equity_aggregator/cli/dispatcher.py +11 -8
- equity_aggregator/cli/main.py +14 -5
- equity_aggregator/cli/parser.py +1 -1
- equity_aggregator/cli/signals.py +32 -0
- equity_aggregator/domain/_utils/__init__.py +2 -2
- equity_aggregator/domain/_utils/_load_converter.py +30 -21
- equity_aggregator/domain/_utils/_merge.py +221 -368
- equity_aggregator/domain/_utils/_merge_config.py +205 -0
- equity_aggregator/domain/_utils/_strategies.py +180 -0
- equity_aggregator/domain/pipeline/resolve.py +17 -11
- equity_aggregator/domain/pipeline/runner.py +4 -4
- equity_aggregator/domain/pipeline/seed.py +5 -1
- equity_aggregator/domain/pipeline/transforms/__init__.py +2 -2
- equity_aggregator/domain/pipeline/transforms/canonicalise.py +1 -1
- equity_aggregator/domain/pipeline/transforms/enrich.py +328 -285
- equity_aggregator/domain/pipeline/transforms/group.py +48 -0
- equity_aggregator/logging_config.py +4 -1
- equity_aggregator/schemas/__init__.py +11 -5
- equity_aggregator/schemas/canonical.py +11 -6
- equity_aggregator/schemas/feeds/__init__.py +11 -5
- equity_aggregator/schemas/feeds/gleif_feed_data.py +35 -0
- equity_aggregator/schemas/feeds/intrinio_feed_data.py +142 -0
- equity_aggregator/schemas/feeds/{lse_feed_data.py → lseg_feed_data.py} +85 -52
- equity_aggregator/schemas/feeds/sec_feed_data.py +36 -6
- equity_aggregator/schemas/feeds/stock_analysis_feed_data.py +107 -0
- equity_aggregator/schemas/feeds/tradingview_feed_data.py +144 -0
- equity_aggregator/schemas/feeds/xetra_feed_data.py +1 -1
- equity_aggregator/schemas/feeds/yfinance_feed_data.py +47 -35
- equity_aggregator/schemas/raw.py +5 -3
- equity_aggregator/schemas/types.py +7 -0
- equity_aggregator/schemas/validators.py +81 -27
- equity_aggregator/storage/data_store.py +5 -3
- {equity_aggregator-0.1.1.dist-info → equity_aggregator-0.1.4.dist-info}/METADATA +205 -115
- equity_aggregator-0.1.4.dist-info/RECORD +103 -0
- {equity_aggregator-0.1.1.dist-info → equity_aggregator-0.1.4.dist-info}/WHEEL +1 -1
- equity_aggregator/adapters/data_sources/authoritative_feeds/__init__.py +0 -13
- equity_aggregator/adapters/data_sources/authoritative_feeds/euronext.py +0 -420
- equity_aggregator/adapters/data_sources/authoritative_feeds/lse.py +0 -352
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/feed.py +0 -350
- equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/utils/__init__.py +0 -9
- equity_aggregator/domain/pipeline/transforms/deduplicate.py +0 -54
- equity_aggregator/schemas/feeds/euronext_feed_data.py +0 -59
- equity_aggregator-0.1.1.dist-info/RECORD +0 -72
- {equity_aggregator-0.1.1.dist-info → equity_aggregator-0.1.4.dist-info}/entry_points.txt +0 -0
- {equity_aggregator-0.1.1.dist-info → equity_aggregator-0.1.4.dist-info}/licenses/LICENCE.txt +0 -0
equity_aggregator/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The equity aggregator is a sophisticated financial data processing system that aggregates equity information from multiple
|
|
5
|
+
The equity aggregator is a sophisticated financial data processing system that aggregates equity information from multiple discovery sources (LSEG, SEC, Stock Analysis, TradingView, XETRA) and enriches it with supplementary data from Yahoo Finance and Intrinio.
|
|
6
6
|
|
|
7
7
|
## Architecture & Design
|
|
8
8
|
|
|
@@ -30,16 +30,16 @@ src/equity_aggregator/
|
|
|
30
30
|
The system processes equity data through a six-stage async streaming pipeline:
|
|
31
31
|
|
|
32
32
|
```
|
|
33
|
-
Raw Data Sources → Parse → Convert → Identify →
|
|
33
|
+
Raw Data Sources → Parse → Convert → Identify → Group → Enrich → Canonicalise → Storage
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
### Pipeline Stages
|
|
37
37
|
|
|
38
38
|
#### 1. **Resolve**
|
|
39
39
|
|
|
40
|
-
Orchestrates parallel data fetching from
|
|
40
|
+
Orchestrates parallel data fetching from discovery feeds:
|
|
41
41
|
|
|
42
|
-
- Fetches data from
|
|
42
|
+
- Fetches data from LSEG, SEC, Stock Analysis, TradingView, and XETRA concurrently
|
|
43
43
|
- Combines all feed data into a single stream for processing
|
|
44
44
|
- Maintains feed source metadata for downstream processing
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ Orchestrates parallel data fetching from authoritative feeds:
|
|
|
47
47
|
|
|
48
48
|
Validates and structures raw feed data:
|
|
49
49
|
|
|
50
|
-
- Applies feed-specific schemas (`
|
|
50
|
+
- Applies feed-specific schemas (`LsegFeedData`, `SecFeedData`, etc.)
|
|
51
51
|
- Filters out invalid records early in the pipeline
|
|
52
52
|
- Normalises data formats across different sources
|
|
53
53
|
|
|
@@ -67,21 +67,23 @@ Enriches records with global identification metadata:
|
|
|
67
67
|
- Adds CUSIP, ISIN, and other standard identifiers
|
|
68
68
|
- Creates globally unique equity identities
|
|
69
69
|
|
|
70
|
-
#### 5. **
|
|
70
|
+
#### 5. **Group**
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
Groups equities by Share Class FIGI:
|
|
73
73
|
|
|
74
74
|
- Groups records with identical share_class_figi values
|
|
75
|
-
-
|
|
76
|
-
-
|
|
75
|
+
- Preserves all discovery feed source data for later merging
|
|
76
|
+
- Each group represents the same equity from multiple discovery sources
|
|
77
|
+
- Yields groups as `list[RawEquity]` for enrichment processing
|
|
77
78
|
|
|
78
79
|
#### 6. **Enrich**
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
Fetches enrichment data and performs comprehensive single merge:
|
|
81
82
|
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
83
|
+
- Extracts representative identifiers from discovery sources using merge algorithms
|
|
84
|
+
- Queries enrichment feeds (Yahoo Finance, Intrinio) with these identifiers
|
|
85
|
+
- Performs single merge of all sources (discovery + enrichment) for optimal data quality
|
|
86
|
+
- Uses same merge logic for identifiers and final merge (mode for IDs, fuzzy clustering for names, frequency for symbols)
|
|
85
87
|
- Applies controlled concurrency to respect API limits
|
|
86
88
|
|
|
87
89
|
#### 7. **Canonicalise**
|
|
@@ -100,7 +102,7 @@ The pipeline uses asynchronous operations to process thousands of equity records
|
|
|
100
102
|
|
|
101
103
|
**Parallel Data Fetching**
|
|
102
104
|
|
|
103
|
-
- All
|
|
105
|
+
- All discovery feeds (LSEG, SEC, Stock Analysis, TradingView, XETRA) are fetched simultaneously
|
|
104
106
|
|
|
105
107
|
**Streaming Pipeline**
|
|
106
108
|
|
|
@@ -108,7 +110,7 @@ The pipeline uses asynchronous operations to process thousands of equity records
|
|
|
108
110
|
|
|
109
111
|
**Controlled Concurrency**
|
|
110
112
|
|
|
111
|
-
- External API calls (OpenFIGI, Yahoo Finance) use semaphores to limit concurrent requests and respect rate limits
|
|
113
|
+
- External API calls (OpenFIGI, Yahoo Finance, Intrinio) use semaphores to limit concurrent requests and respect rate limits
|
|
112
114
|
|
|
113
115
|
**Non-blocking Operations**
|
|
114
116
|
|
|
@@ -119,12 +121,12 @@ Illustration of Pipeline Flow:
|
|
|
119
121
|
|
|
120
122
|
```python
|
|
121
123
|
async def aggregate_canonical_equities() -> list[CanonicalEquity]:
|
|
122
|
-
|
|
124
|
+
|
|
123
125
|
# Resolve creates an async stream from multiple sources
|
|
124
126
|
stream = resolve()
|
|
125
127
|
|
|
126
128
|
# Each transform receives and returns an async iterator
|
|
127
|
-
transforms = (parse, convert, identify,
|
|
129
|
+
transforms = (parse, convert, identify, group, enrich, canonicalise)
|
|
128
130
|
|
|
129
131
|
# Pipe stream through each transform sequentially
|
|
130
132
|
for stage in transforms:
|
|
@@ -144,11 +146,13 @@ schemas/
|
|
|
144
146
|
├── canonical.py # CanonicalEquity - final standardised format
|
|
145
147
|
├── types.py # Type definitions and validators
|
|
146
148
|
└── feeds/ # Feed-specific data models
|
|
147
|
-
├──
|
|
148
|
-
├── lse_feed_data.py
|
|
149
|
+
├── lseg_feed_data.py
|
|
149
150
|
├── sec_feed_data.py
|
|
151
|
+
├── stock_analysis_feed_data.py
|
|
152
|
+
├── tradingview_feed_data.py
|
|
150
153
|
├── xetra_feed_data.py
|
|
151
|
-
|
|
154
|
+
├── yfinance_feed_data.py
|
|
155
|
+
└── intrinio_feed_data.py
|
|
152
156
|
```
|
|
153
157
|
|
|
154
158
|
### Critical Role of Schemas
|
|
@@ -171,11 +175,11 @@ def parse(stream: AsyncIterable[FeedRecord]) -> AsyncIterator[RawEquity]:
|
|
|
171
175
|
|
|
172
176
|
#### 3. **Field Mapping & Normalisation**
|
|
173
177
|
```python
|
|
174
|
-
class
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
class LsegFeedData(BaseModel):
|
|
179
|
+
issuername: str = Field(..., description="Company name")
|
|
180
|
+
tidm: str = Field(..., description="Trading symbol")
|
|
177
181
|
isin: str = Field(..., description="ISIN identifier")
|
|
178
|
-
mics: list[str] = Field(..., description="Market identifiers")
|
|
182
|
+
mics: list[str] | None = Field(..., description="Market identifiers")
|
|
179
183
|
|
|
180
184
|
# Automatic field mapping from raw feed data
|
|
181
185
|
@field_validator('symbol')
|
|
@@ -191,31 +195,31 @@ class EuronextFeedData(BaseModel):
|
|
|
191
195
|
4. **Enriched RawEquity** → Final canonicalisation
|
|
192
196
|
5. **CanonicalEquity** → Database persistence
|
|
193
197
|
|
|
194
|
-
##
|
|
198
|
+
## Discovery vs Enrichment Feeds
|
|
195
199
|
|
|
196
|
-
###
|
|
200
|
+
### Discovery Feeds (Primary Sources)
|
|
197
201
|
|
|
198
|
-
- **
|
|
199
|
-
- **LSE**: London Stock Exchange
|
|
200
|
-
- **XETRA**: Deutsche Börse Stock Exchange
|
|
202
|
+
- **LSEG**: London Stock Exchange Group trading platform
|
|
201
203
|
- **SEC**: US Securities and Exchange Commission
|
|
204
|
+
- **Stock Analysis**: US equities with comprehensive financial metrics
|
|
205
|
+
- **TradingView**: US equities with comprehensive financial metrics
|
|
206
|
+
- **XETRA**: Deutsche Börse Stock Exchange
|
|
202
207
|
|
|
203
208
|
**Characteristics**:
|
|
204
209
|
|
|
205
|
-
- Provide core equity data (names, symbols,
|
|
206
|
-
-
|
|
207
|
-
- Data from these feeds is **never** overwritten by enrichment
|
|
210
|
+
- Provide core equity identifier data (names, symbols, codes)
|
|
211
|
+
- Multiple discovery sources for the same equity are merged with enrichment data
|
|
208
212
|
|
|
209
213
|
### Enrichment Feeds (Supplementary Sources)
|
|
210
214
|
|
|
211
215
|
- **Yahoo Finance**: Market data and financial metrics
|
|
216
|
+
- **Intrinio**: Market data and financial metrics
|
|
212
217
|
|
|
213
218
|
**Characteristics**
|
|
214
219
|
|
|
215
|
-
- Only supplements missing data; never overwrites authoritative values
|
|
216
220
|
- Provides additional financial metrics (market cap, analyst ratings, etc.)
|
|
217
|
-
-
|
|
218
|
-
- Applied after
|
|
221
|
+
- Uses representative identifiers from discovery sources for lookup
|
|
222
|
+
- Applied after grouping but before final merge
|
|
219
223
|
|
|
220
224
|
## Equity Aggregator Components
|
|
221
225
|
|
|
@@ -235,7 +239,7 @@ class EuronextFeedData(BaseModel):
|
|
|
235
239
|
|
|
236
240
|
### Adapters Layer
|
|
237
241
|
|
|
238
|
-
- **data_sources/
|
|
242
|
+
- **data_sources/discovery_feeds/**: Primary data source integrations
|
|
239
243
|
- **data_sources/enrichment_feeds/**: Supplementary data integrations
|
|
240
244
|
- **data_sources/reference_lookup/**: External API services (OpenFIGI, exchange rates)
|
|
241
245
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# adapters/__init__.py
|
|
2
2
|
|
|
3
|
-
from .data_sources.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
from .data_sources.discovery_feeds import (
|
|
4
|
+
fetch_equity_records_intrinio,
|
|
5
|
+
fetch_equity_records_lseg,
|
|
6
6
|
fetch_equity_records_sec,
|
|
7
|
+
fetch_equity_records_stock_analysis,
|
|
8
|
+
fetch_equity_records_tradingview,
|
|
7
9
|
fetch_equity_records_xetra,
|
|
8
10
|
)
|
|
9
11
|
from .data_sources.enrichment_feeds import (
|
|
12
|
+
open_gleif_feed,
|
|
10
13
|
open_yfinance_feed,
|
|
11
14
|
)
|
|
12
15
|
from .data_sources.reference_lookup import (
|
|
@@ -15,12 +18,15 @@ from .data_sources.reference_lookup import (
|
|
|
15
18
|
)
|
|
16
19
|
|
|
17
20
|
__all__ = [
|
|
18
|
-
#
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"fetch_equity_records_xetra",
|
|
21
|
+
# discovery feeds
|
|
22
|
+
"fetch_equity_records_intrinio",
|
|
23
|
+
"fetch_equity_records_lseg",
|
|
22
24
|
"fetch_equity_records_sec",
|
|
25
|
+
"fetch_equity_records_stock_analysis",
|
|
26
|
+
"fetch_equity_records_tradingview",
|
|
27
|
+
"fetch_equity_records_xetra",
|
|
23
28
|
# enrichment feeds
|
|
29
|
+
"open_gleif_feed",
|
|
24
30
|
"open_yfinance_feed",
|
|
25
31
|
# reference lookup
|
|
26
32
|
"fetch_equity_identification",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# data_sources/__init__.py
|
|
2
2
|
|
|
3
|
-
from .
|
|
4
|
-
|
|
5
|
-
fetch_equity_records_lse,
|
|
3
|
+
from .discovery_feeds import (
|
|
4
|
+
fetch_equity_records_lseg,
|
|
6
5
|
fetch_equity_records_sec,
|
|
7
6
|
fetch_equity_records_xetra,
|
|
8
7
|
)
|
|
@@ -15,9 +14,8 @@ from .reference_lookup import (
|
|
|
15
14
|
)
|
|
16
15
|
|
|
17
16
|
__all__ = [
|
|
18
|
-
#
|
|
19
|
-
"
|
|
20
|
-
"fetch_equity_records_lse",
|
|
17
|
+
# discovery feeds
|
|
18
|
+
"fetch_equity_records_lseg",
|
|
21
19
|
"fetch_equity_records_sec",
|
|
22
20
|
"fetch_equity_records_xetra",
|
|
23
21
|
# enrichment feeds
|
|
@@ -33,7 +33,7 @@ def make_client(**overrides: object) -> AsyncClient:
|
|
|
33
33
|
# Set default timeouts for connections, reading, and writing
|
|
34
34
|
timeout = Timeout(
|
|
35
35
|
connect=3.0, # 3s to establish TLS
|
|
36
|
-
read=
|
|
36
|
+
read=300.0, # up to 5 minutes to read a response
|
|
37
37
|
write=5.0, # up to 5s to send a body
|
|
38
38
|
pool=None, # no pool timeout
|
|
39
39
|
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# discovery_feeds/__init__.py
|
|
2
|
+
|
|
3
|
+
from .intrinio import fetch_equity_records as fetch_equity_records_intrinio
|
|
4
|
+
from .lseg import fetch_equity_records as fetch_equity_records_lseg
|
|
5
|
+
from .sec import fetch_equity_records as fetch_equity_records_sec
|
|
6
|
+
from .stock_analysis import fetch_equity_records as fetch_equity_records_stock_analysis
|
|
7
|
+
from .tradingview import fetch_equity_records as fetch_equity_records_tradingview
|
|
8
|
+
from .xetra import fetch_equity_records as fetch_equity_records_xetra
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"fetch_equity_records_intrinio",
|
|
12
|
+
"fetch_equity_records_lseg",
|
|
13
|
+
"fetch_equity_records_sec",
|
|
14
|
+
"fetch_equity_records_stock_analysis",
|
|
15
|
+
"fetch_equity_records_tradingview",
|
|
16
|
+
"fetch_equity_records_xetra",
|
|
17
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# intrinio/_utils/backoff.py
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import random
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def backoff_delays(
|
|
9
|
+
*,
|
|
10
|
+
base: float = 5.0,
|
|
11
|
+
cap: float = 128.0,
|
|
12
|
+
jitter: float = 0.10,
|
|
13
|
+
attempts: int = 5,
|
|
14
|
+
) -> Iterator[float]:
|
|
15
|
+
"""
|
|
16
|
+
Yield an exponential backoff sequence with bounded jitter for retry delays.
|
|
17
|
+
|
|
18
|
+
Each delay is calculated as: delay * (1 ± jitter), doubling each time up to cap.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
base (float): Initial delay in seconds.
|
|
22
|
+
cap (float): Maximum delay in seconds.
|
|
23
|
+
jitter (float): Fractional jitter (+/-) applied to each delay.
|
|
24
|
+
attempts (int): Number of delay values to yield.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Iterator[float]: Sequence of delay values in seconds.
|
|
28
|
+
"""
|
|
29
|
+
delay: float = base
|
|
30
|
+
for _ in range(attempts):
|
|
31
|
+
delta: float = delay * jitter * (2 * random.random() - 1)
|
|
32
|
+
yield max(0.0, min(delay + delta, cap))
|
|
33
|
+
delay = min(delay * 2, cap)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# _utils/parser.py
|
|
2
|
+
|
|
3
|
+
from equity_aggregator.adapters.data_sources._utils._record_types import (
|
|
4
|
+
EquityRecord,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_companies_response(payload: dict) -> tuple[list[EquityRecord], str | None]:
|
|
9
|
+
"""
|
|
10
|
+
Parse Intrinio companies API response.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
payload (dict): The JSON response from Intrinio API.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
tuple[list[EquityRecord], str | None]: Tuple of (parsed records,
|
|
17
|
+
next_page token).
|
|
18
|
+
"""
|
|
19
|
+
companies = payload.get("companies", [])
|
|
20
|
+
next_page = payload.get("next_page")
|
|
21
|
+
valid_companies = [c for c in companies if _is_valid_company(c)]
|
|
22
|
+
records = [_extract_company_record(company) for company in valid_companies]
|
|
23
|
+
return records, next_page
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_securities_response(payload: dict) -> list[EquityRecord]:
|
|
27
|
+
"""
|
|
28
|
+
Parse Intrinio securities API response.
|
|
29
|
+
|
|
30
|
+
Uses the company data embedded in the response (which is authoritative
|
|
31
|
+
for these securities) rather than external company data, to avoid
|
|
32
|
+
ticker reassignment issues where stale company records have incorrect
|
|
33
|
+
identifiers.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
payload (dict): The JSON response from Intrinio API.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
list[EquityRecord]: List of security records with company data merged.
|
|
40
|
+
"""
|
|
41
|
+
securities = payload.get("securities", [])
|
|
42
|
+
company_data = payload.get("company", {})
|
|
43
|
+
company = _extract_company_record(company_data) if company_data else {}
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
_extract_security_record(security, company)
|
|
47
|
+
for security in securities
|
|
48
|
+
if security.get("share_class_figi")
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_valid_company(company: dict | None) -> bool:
|
|
53
|
+
"""
|
|
54
|
+
Check if a company record has required fields.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
company (dict | None): Raw company data from API.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
bool: True if company has ticker and name, False otherwise.
|
|
61
|
+
"""
|
|
62
|
+
return bool(company and company.get("ticker") and company.get("name"))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _extract_company_record(company: dict) -> EquityRecord:
|
|
66
|
+
"""
|
|
67
|
+
Extract a company record from raw API data.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
company (dict): Raw company data from Intrinio API.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
EquityRecord: Normalised company record.
|
|
74
|
+
"""
|
|
75
|
+
return {
|
|
76
|
+
"company_id": company.get("id"),
|
|
77
|
+
"company_ticker": company.get("ticker"),
|
|
78
|
+
"name": company.get("name"),
|
|
79
|
+
"lei": company.get("lei"),
|
|
80
|
+
"cik": company.get("cik"),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _extract_security_record(security: dict, company: EquityRecord) -> EquityRecord:
|
|
85
|
+
"""
|
|
86
|
+
Extract a security record merged with company data.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
security (dict): Raw security data from Intrinio API.
|
|
90
|
+
company (EquityRecord): Company record to merge with security data.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
EquityRecord: Security record with company data merged.
|
|
94
|
+
"""
|
|
95
|
+
return {
|
|
96
|
+
# Company data
|
|
97
|
+
"name": company.get("name"),
|
|
98
|
+
"lei": company.get("lei"),
|
|
99
|
+
"cik": company.get("cik"),
|
|
100
|
+
# Security data
|
|
101
|
+
"ticker": security.get("ticker"),
|
|
102
|
+
"share_class_figi": security.get("share_class_figi"),
|
|
103
|
+
"figi": security.get("figi"),
|
|
104
|
+
"composite_figi": security.get("composite_figi"),
|
|
105
|
+
"currency": security.get("currency"),
|
|
106
|
+
"exchange_mic": security.get("exchange_mic"),
|
|
107
|
+
}
|