equity-aggregator 0.1.1__py3-none-any.whl → 0.1.5__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 +49 -39
- 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.5.dist-info}/METADATA +205 -115
- equity_aggregator-0.1.5.dist-info/RECORD +103 -0
- {equity_aggregator-0.1.1.dist-info → equity_aggregator-0.1.5.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.5.dist-info}/entry_points.txt +0 -0
- {equity_aggregator-0.1.1.dist-info → equity_aggregator-0.1.5.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 Intrinio) and enriches it with supplementary data from Yahoo Finance and the Global LEI Foundation.
|
|
6
6
|
|
|
7
7
|
## Architecture & Design
|
|
8
8
|
|
|
@@ -15,6 +15,7 @@ src/equity_aggregator/
|
|
|
15
15
|
├── cli/ # Presentation Layer - User Interface
|
|
16
16
|
├── domain/ # Business Logic Layer - Core Domain
|
|
17
17
|
│ ├── pipeline/ # Aggregation pipeline orchestration
|
|
18
|
+
│ ├── retrieval/ # Canonical equity download and retrieval
|
|
18
19
|
│ └── _utils/ # Domain-specific utilities
|
|
19
20
|
├── adapters/ # Infrastructure Layer - External Integrations
|
|
20
21
|
│ └── data_sources/ # Data source adapters
|
|
@@ -30,16 +31,16 @@ src/equity_aggregator/
|
|
|
30
31
|
The system processes equity data through a six-stage async streaming pipeline:
|
|
31
32
|
|
|
32
33
|
```
|
|
33
|
-
Raw Data Sources → Parse → Convert → Identify →
|
|
34
|
+
Raw Data Sources → Parse → Convert → Identify → Group → Enrich → Canonicalise → Storage
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
### Pipeline Stages
|
|
37
38
|
|
|
38
39
|
#### 1. **Resolve**
|
|
39
40
|
|
|
40
|
-
Orchestrates parallel data fetching from
|
|
41
|
+
Orchestrates parallel data fetching from discovery feeds:
|
|
41
42
|
|
|
42
|
-
- Fetches data from
|
|
43
|
+
- Fetches data from LSEG, SEC, Stock Analysis, TradingView, XETRA and Intrinio concurrently
|
|
43
44
|
- Combines all feed data into a single stream for processing
|
|
44
45
|
- Maintains feed source metadata for downstream processing
|
|
45
46
|
|
|
@@ -47,7 +48,7 @@ Orchestrates parallel data fetching from authoritative feeds:
|
|
|
47
48
|
|
|
48
49
|
Validates and structures raw feed data:
|
|
49
50
|
|
|
50
|
-
- Applies feed-specific schemas (`
|
|
51
|
+
- Applies feed-specific schemas (`LsegFeedData`, `SecFeedData`, etc.)
|
|
51
52
|
- Filters out invalid records early in the pipeline
|
|
52
53
|
- Normalises data formats across different sources
|
|
53
54
|
|
|
@@ -64,25 +65,25 @@ Standardises financial data to USD reference currency:
|
|
|
64
65
|
Enriches records with global identification metadata:
|
|
65
66
|
|
|
66
67
|
- Queries OpenFIGI API for FIGI identifiers
|
|
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
|
-
- Groups records with identical
|
|
75
|
-
-
|
|
76
|
-
-
|
|
74
|
+
- Groups records with identical Share Class FIGI values
|
|
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
|
-
- Fetches
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
- Applies controlled concurrency to respect API limits
|
|
83
|
+
- Fetches representative identifiers from discovery data sources
|
|
84
|
+
- Queries enrichment feeds (Yahoo Finance, Global LEI Foundation) using these identifiers
|
|
85
|
+
- Performs single merge of all sources (discovery + enrichment) for optimal data quality
|
|
86
|
+
- Applies controlled concurrency to enrichment feeds to respect API limits
|
|
86
87
|
|
|
87
88
|
#### 7. **Canonicalise**
|
|
88
89
|
|
|
@@ -100,7 +101,7 @@ The pipeline uses asynchronous operations to process thousands of equity records
|
|
|
100
101
|
|
|
101
102
|
**Parallel Data Fetching**
|
|
102
103
|
|
|
103
|
-
- All
|
|
104
|
+
- All discovery feeds (LSEG, SEC, Stock Analysis, TradingView, XETRA, Intrinio) are fetched simultaneously
|
|
104
105
|
|
|
105
106
|
**Streaming Pipeline**
|
|
106
107
|
|
|
@@ -108,7 +109,9 @@ The pipeline uses asynchronous operations to process thousands of equity records
|
|
|
108
109
|
|
|
109
110
|
**Controlled Concurrency**
|
|
110
111
|
|
|
111
|
-
- External API calls (OpenFIGI, Yahoo Finance) use semaphores to limit concurrent requests and respect rate limits
|
|
112
|
+
- External API calls (OpenFIGI, Yahoo Finance, GLEIF) use semaphores to limit concurrent requests and respect rate limits
|
|
113
|
+
- Each enrichment feed has a configurable concurrency limit
|
|
114
|
+
- Fetch operations include timeout protection to prevent indefinite blocking
|
|
112
115
|
|
|
113
116
|
**Non-blocking Operations**
|
|
114
117
|
|
|
@@ -119,12 +122,12 @@ Illustration of Pipeline Flow:
|
|
|
119
122
|
|
|
120
123
|
```python
|
|
121
124
|
async def aggregate_canonical_equities() -> list[CanonicalEquity]:
|
|
122
|
-
|
|
125
|
+
|
|
123
126
|
# Resolve creates an async stream from multiple sources
|
|
124
127
|
stream = resolve()
|
|
125
128
|
|
|
126
129
|
# Each transform receives and returns an async iterator
|
|
127
|
-
transforms = (parse, convert, identify,
|
|
130
|
+
transforms = (parse, convert, identify, group, enrich, canonicalise)
|
|
128
131
|
|
|
129
132
|
# Pipe stream through each transform sequentially
|
|
130
133
|
for stage in transforms:
|
|
@@ -143,17 +146,23 @@ schemas/
|
|
|
143
146
|
├── raw.py # RawEquity - intermediate pipeline format
|
|
144
147
|
├── canonical.py # CanonicalEquity - final standardised format
|
|
145
148
|
├── types.py # Type definitions and validators
|
|
149
|
+
├── validators.py # Reusable validators for identifiers and financial data
|
|
146
150
|
└── feeds/ # Feed-specific data models
|
|
147
|
-
├──
|
|
148
|
-
├── lse_feed_data.py
|
|
151
|
+
├── lseg_feed_data.py
|
|
149
152
|
├── sec_feed_data.py
|
|
153
|
+
├── stock_analysis_feed_data.py
|
|
154
|
+
├── tradingview_feed_data.py
|
|
155
|
+
├── gleif_feed_data.py
|
|
156
|
+
├── feed_validators.py
|
|
150
157
|
├── xetra_feed_data.py
|
|
151
|
-
|
|
158
|
+
├── yfinance_feed_data.py
|
|
159
|
+
└── intrinio_feed_data.py
|
|
152
160
|
```
|
|
153
161
|
|
|
154
162
|
### Critical Role of Schemas
|
|
155
163
|
|
|
156
164
|
#### 1. **Data Validation at Boundaries**
|
|
165
|
+
|
|
157
166
|
Each feed has a dedicated Pydantic schema that:
|
|
158
167
|
- Validates incoming data structure and types
|
|
159
168
|
- Normalises field names and formats
|
|
@@ -171,11 +180,11 @@ def parse(stream: AsyncIterable[FeedRecord]) -> AsyncIterator[RawEquity]:
|
|
|
171
180
|
|
|
172
181
|
#### 3. **Field Mapping & Normalisation**
|
|
173
182
|
```python
|
|
174
|
-
class
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
class LsegFeedData(BaseModel):
|
|
184
|
+
issuername: str = Field(..., description="Company name")
|
|
185
|
+
tidm: str = Field(..., description="Trading symbol")
|
|
177
186
|
isin: str = Field(..., description="ISIN identifier")
|
|
178
|
-
mics: list[str] = Field(..., description="Market identifiers")
|
|
187
|
+
mics: list[str] | None = Field(..., description="Market identifiers")
|
|
179
188
|
|
|
180
189
|
# Automatic field mapping from raw feed data
|
|
181
190
|
@field_validator('symbol')
|
|
@@ -191,31 +200,32 @@ class EuronextFeedData(BaseModel):
|
|
|
191
200
|
4. **Enriched RawEquity** → Final canonicalisation
|
|
192
201
|
5. **CanonicalEquity** → Database persistence
|
|
193
202
|
|
|
194
|
-
##
|
|
203
|
+
## Discovery vs Enrichment Feeds
|
|
195
204
|
|
|
196
|
-
###
|
|
205
|
+
### Discovery Feeds (Primary Sources)
|
|
197
206
|
|
|
198
|
-
- **
|
|
199
|
-
- **LSE**: London Stock Exchange
|
|
200
|
-
- **XETRA**: Deutsche Börse Stock Exchange
|
|
207
|
+
- **LSEG**: London Stock Exchange Group trading platform
|
|
201
208
|
- **SEC**: US Securities and Exchange Commission
|
|
209
|
+
- **Stock Analysis**: US equities with comprehensive financial metrics
|
|
210
|
+
- **TradingView**: US equities with comprehensive financial metrics
|
|
211
|
+
- **XETRA**: Deutsche Börse Stock Exchange
|
|
212
|
+
- **Intrinio**: US financial data API providing company, securities, and real-time quote data
|
|
202
213
|
|
|
203
214
|
**Characteristics**:
|
|
204
215
|
|
|
205
|
-
- Provide core equity data (names, symbols,
|
|
206
|
-
-
|
|
207
|
-
- Data from these feeds is **never** overwritten by enrichment
|
|
216
|
+
- Provide core equity identifier data (names, symbols, codes)
|
|
217
|
+
- Multiple discovery sources for the same equity are merged with enrichment data
|
|
208
218
|
|
|
209
219
|
### Enrichment Feeds (Supplementary Sources)
|
|
210
220
|
|
|
211
221
|
- **Yahoo Finance**: Market data and financial metrics
|
|
222
|
+
- **Global LEI Foundation**: ISIN->LEI mapping for Legal Entity Identifier enrichment
|
|
212
223
|
|
|
213
224
|
**Characteristics**
|
|
214
225
|
|
|
215
|
-
- Only supplements missing data; never overwrites authoritative values
|
|
216
226
|
- Provides additional financial metrics (market cap, analyst ratings, etc.)
|
|
217
|
-
-
|
|
218
|
-
- Applied after
|
|
227
|
+
- Uses representative identifiers from discovery sources for look-up
|
|
228
|
+
- Applied after grouping but before final merge
|
|
219
229
|
|
|
220
230
|
## Equity Aggregator Components
|
|
221
231
|
|
|
@@ -235,7 +245,7 @@ class EuronextFeedData(BaseModel):
|
|
|
235
245
|
|
|
236
246
|
### Adapters Layer
|
|
237
247
|
|
|
238
|
-
- **data_sources/
|
|
248
|
+
- **data_sources/discovery_feeds/**: Primary data source integrations
|
|
239
249
|
- **data_sources/enrichment_feeds/**: Supplementary data integrations
|
|
240
250
|
- **data_sources/reference_lookup/**: External API services (OpenFIGI, exchange rates)
|
|
241
251
|
|
|
@@ -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
|
+
}
|