aponyx 0.1.18__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.
- aponyx/__init__.py +14 -0
- aponyx/backtest/__init__.py +31 -0
- aponyx/backtest/adapters.py +77 -0
- aponyx/backtest/config.py +84 -0
- aponyx/backtest/engine.py +560 -0
- aponyx/backtest/protocols.py +101 -0
- aponyx/backtest/registry.py +334 -0
- aponyx/backtest/strategy_catalog.json +50 -0
- aponyx/cli/__init__.py +5 -0
- aponyx/cli/commands/__init__.py +8 -0
- aponyx/cli/commands/clean.py +349 -0
- aponyx/cli/commands/list.py +302 -0
- aponyx/cli/commands/report.py +167 -0
- aponyx/cli/commands/run.py +377 -0
- aponyx/cli/main.py +125 -0
- aponyx/config/__init__.py +82 -0
- aponyx/data/__init__.py +99 -0
- aponyx/data/bloomberg_config.py +306 -0
- aponyx/data/bloomberg_instruments.json +26 -0
- aponyx/data/bloomberg_securities.json +42 -0
- aponyx/data/cache.py +294 -0
- aponyx/data/fetch.py +659 -0
- aponyx/data/fetch_registry.py +135 -0
- aponyx/data/loaders.py +205 -0
- aponyx/data/providers/__init__.py +13 -0
- aponyx/data/providers/bloomberg.py +383 -0
- aponyx/data/providers/file.py +111 -0
- aponyx/data/registry.py +500 -0
- aponyx/data/requirements.py +96 -0
- aponyx/data/sample_data.py +415 -0
- aponyx/data/schemas.py +60 -0
- aponyx/data/sources.py +171 -0
- aponyx/data/synthetic_params.json +46 -0
- aponyx/data/transforms.py +336 -0
- aponyx/data/validation.py +308 -0
- aponyx/docs/__init__.py +24 -0
- aponyx/docs/adding_data_providers.md +682 -0
- aponyx/docs/cdx_knowledge_base.md +455 -0
- aponyx/docs/cdx_overlay_strategy.md +135 -0
- aponyx/docs/cli_guide.md +607 -0
- aponyx/docs/governance_design.md +551 -0
- aponyx/docs/logging_design.md +251 -0
- aponyx/docs/performance_evaluation_design.md +265 -0
- aponyx/docs/python_guidelines.md +786 -0
- aponyx/docs/signal_registry_usage.md +369 -0
- aponyx/docs/signal_suitability_design.md +558 -0
- aponyx/docs/visualization_design.md +277 -0
- aponyx/evaluation/__init__.py +11 -0
- aponyx/evaluation/performance/__init__.py +24 -0
- aponyx/evaluation/performance/adapters.py +109 -0
- aponyx/evaluation/performance/analyzer.py +384 -0
- aponyx/evaluation/performance/config.py +320 -0
- aponyx/evaluation/performance/decomposition.py +304 -0
- aponyx/evaluation/performance/metrics.py +761 -0
- aponyx/evaluation/performance/registry.py +327 -0
- aponyx/evaluation/performance/report.py +541 -0
- aponyx/evaluation/suitability/__init__.py +67 -0
- aponyx/evaluation/suitability/config.py +143 -0
- aponyx/evaluation/suitability/evaluator.py +389 -0
- aponyx/evaluation/suitability/registry.py +328 -0
- aponyx/evaluation/suitability/report.py +398 -0
- aponyx/evaluation/suitability/scoring.py +367 -0
- aponyx/evaluation/suitability/tests.py +303 -0
- aponyx/examples/01_generate_synthetic_data.py +53 -0
- aponyx/examples/02_fetch_data_file.py +82 -0
- aponyx/examples/03_fetch_data_bloomberg.py +104 -0
- aponyx/examples/04_compute_signal.py +164 -0
- aponyx/examples/05_evaluate_suitability.py +224 -0
- aponyx/examples/06_run_backtest.py +242 -0
- aponyx/examples/07_analyze_performance.py +214 -0
- aponyx/examples/08_visualize_results.py +272 -0
- aponyx/main.py +7 -0
- aponyx/models/__init__.py +45 -0
- aponyx/models/config.py +83 -0
- aponyx/models/indicator_transformation.json +52 -0
- aponyx/models/indicators.py +292 -0
- aponyx/models/metadata.py +447 -0
- aponyx/models/orchestrator.py +213 -0
- aponyx/models/registry.py +860 -0
- aponyx/models/score_transformation.json +42 -0
- aponyx/models/signal_catalog.json +29 -0
- aponyx/models/signal_composer.py +513 -0
- aponyx/models/signal_transformation.json +29 -0
- aponyx/persistence/__init__.py +16 -0
- aponyx/persistence/json_io.py +132 -0
- aponyx/persistence/parquet_io.py +378 -0
- aponyx/py.typed +0 -0
- aponyx/reporting/__init__.py +10 -0
- aponyx/reporting/generator.py +517 -0
- aponyx/visualization/__init__.py +20 -0
- aponyx/visualization/app.py +37 -0
- aponyx/visualization/plots.py +309 -0
- aponyx/visualization/visualizer.py +242 -0
- aponyx/workflows/__init__.py +18 -0
- aponyx/workflows/concrete_steps.py +720 -0
- aponyx/workflows/config.py +122 -0
- aponyx/workflows/engine.py +279 -0
- aponyx/workflows/registry.py +116 -0
- aponyx/workflows/steps.py +180 -0
- aponyx-0.1.18.dist-info/METADATA +552 -0
- aponyx-0.1.18.dist-info/RECORD +104 -0
- aponyx-0.1.18.dist-info/WHEEL +4 -0
- aponyx-0.1.18.dist-info/entry_points.txt +2 -0
- aponyx-0.1.18.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""Bloomberg Terminal/API data provider.
|
|
2
|
+
|
|
3
|
+
Fetches market data using Bloomberg's Python API via xbbg wrapper.
|
|
4
|
+
Requires active Bloomberg Terminal session.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
from zoneinfo import ZoneInfo
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
from ..bloomberg_config import (
|
|
15
|
+
BloombergInstrumentSpec,
|
|
16
|
+
get_instrument_spec,
|
|
17
|
+
get_security_from_ticker,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def fetch_from_bloomberg(
|
|
24
|
+
ticker: str,
|
|
25
|
+
instrument: str,
|
|
26
|
+
start_date: str | None = None,
|
|
27
|
+
end_date: str | None = None,
|
|
28
|
+
security: str | None = None,
|
|
29
|
+
**params: Any,
|
|
30
|
+
) -> pd.DataFrame:
|
|
31
|
+
"""
|
|
32
|
+
Fetch historical data from Bloomberg Terminal via xbbg wrapper.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
ticker : str
|
|
37
|
+
Bloomberg ticker (e.g., 'CDX IG CDSI GEN 5Y Corp', 'VIX Index', 'HYG US Equity').
|
|
38
|
+
instrument : str
|
|
39
|
+
Instrument type for field mapping ('cdx', 'vix', 'etf').
|
|
40
|
+
start_date : str or None, default None
|
|
41
|
+
Start date in YYYY-MM-DD format. Defaults to 5 years ago.
|
|
42
|
+
end_date : str or None, default None
|
|
43
|
+
End date in YYYY-MM-DD format. Defaults to today.
|
|
44
|
+
security : str or None, default None
|
|
45
|
+
Internal security identifier (e.g., 'cdx_ig_5y', 'hyg').
|
|
46
|
+
If provided, used directly for metadata. Otherwise, reverse lookup from ticker.
|
|
47
|
+
**params : Any
|
|
48
|
+
Additional Bloomberg request parameters passed to xbbg.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
pd.DataFrame
|
|
53
|
+
Historical data with DatetimeIndex and schema-compatible columns.
|
|
54
|
+
|
|
55
|
+
Raises
|
|
56
|
+
------
|
|
57
|
+
ImportError
|
|
58
|
+
If xbbg is not installed.
|
|
59
|
+
ValueError
|
|
60
|
+
If ticker format is invalid or instrument type is unknown.
|
|
61
|
+
RuntimeError
|
|
62
|
+
If Bloomberg request fails or returns empty data.
|
|
63
|
+
|
|
64
|
+
Notes
|
|
65
|
+
-----
|
|
66
|
+
Requires active Bloomberg Terminal session. Connection is handled
|
|
67
|
+
automatically by xbbg wrapper.
|
|
68
|
+
|
|
69
|
+
Returned DataFrame columns are mapped to project schemas:
|
|
70
|
+
- CDX: spread, security
|
|
71
|
+
- VIX: level
|
|
72
|
+
- ETF: spread, security
|
|
73
|
+
|
|
74
|
+
Example tickers:
|
|
75
|
+
- CDX: 'CDX IG CDSI GEN 5Y Corp'
|
|
76
|
+
- VIX: 'VIX Index'
|
|
77
|
+
- ETFs: 'HYG US Equity', 'LQD US Equity'
|
|
78
|
+
"""
|
|
79
|
+
# Get instrument specification from registry
|
|
80
|
+
spec = get_instrument_spec(instrument)
|
|
81
|
+
|
|
82
|
+
# Default to 5-year lookback if dates not provided
|
|
83
|
+
if end_date is None:
|
|
84
|
+
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
85
|
+
if start_date is None:
|
|
86
|
+
start_dt = datetime.now() - timedelta(days=5 * 365)
|
|
87
|
+
start_date = start_dt.strftime("%Y-%m-%d")
|
|
88
|
+
|
|
89
|
+
# Convert dates to Bloomberg format (YYYYMMDD)
|
|
90
|
+
bbg_start = start_date.replace("-", "")
|
|
91
|
+
bbg_end = end_date.replace("-", "")
|
|
92
|
+
|
|
93
|
+
logger.info(
|
|
94
|
+
"Fetching %s from Bloomberg: ticker=%s, dates=%s to %s",
|
|
95
|
+
instrument,
|
|
96
|
+
ticker,
|
|
97
|
+
start_date,
|
|
98
|
+
end_date,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Import xbbg wrapper
|
|
102
|
+
# Note: Use BaseException to catch pytest.Skipped from xbbg's importorskip
|
|
103
|
+
try:
|
|
104
|
+
from xbbg import blp
|
|
105
|
+
except BaseException as e:
|
|
106
|
+
# Handle multiple error types:
|
|
107
|
+
# 1. Direct ImportError when xbbg not installed
|
|
108
|
+
# 2. ImportError with blpapi in message (nested import failure)
|
|
109
|
+
# 3. pytest.Skipped exception from xbbg's importorskip
|
|
110
|
+
error_msg = str(e)
|
|
111
|
+
|
|
112
|
+
if "blpapi" in error_msg.lower():
|
|
113
|
+
raise ImportError(
|
|
114
|
+
"Bloomberg API (blpapi) not installed. "
|
|
115
|
+
"Install with: uv pip install blpapi\n"
|
|
116
|
+
"Or install all Bloomberg dependencies: uv sync --extra bloomberg\n"
|
|
117
|
+
"Note: Requires active Bloomberg Terminal subscription."
|
|
118
|
+
) from e
|
|
119
|
+
elif isinstance(e, ImportError):
|
|
120
|
+
raise ImportError(
|
|
121
|
+
f"xbbg not installed: {error_msg}\n"
|
|
122
|
+
"Install with: uv pip install xbbg\n"
|
|
123
|
+
"Or install all Bloomberg dependencies: uv sync --extra bloomberg"
|
|
124
|
+
) from e
|
|
125
|
+
else:
|
|
126
|
+
# Re-raise other exceptions (KeyboardInterrupt, SystemExit, etc.)
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
# Fetch historical data using xbbg
|
|
130
|
+
try:
|
|
131
|
+
df = blp.bdh(
|
|
132
|
+
tickers=ticker,
|
|
133
|
+
flds=spec.bloomberg_fields,
|
|
134
|
+
start_date=bbg_start,
|
|
135
|
+
end_date=bbg_end,
|
|
136
|
+
**params,
|
|
137
|
+
)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error("Bloomberg request failed: %s", str(e))
|
|
140
|
+
raise RuntimeError(f"Failed to fetch data from Bloomberg: {e}") from e
|
|
141
|
+
|
|
142
|
+
# Check if response is empty
|
|
143
|
+
if df is None or df.empty:
|
|
144
|
+
raise RuntimeError(
|
|
145
|
+
f"Bloomberg returned empty data for {ticker}. "
|
|
146
|
+
"Check ticker format and data availability."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
logger.debug("Fetched %d rows from Bloomberg", len(df))
|
|
150
|
+
|
|
151
|
+
# Convert index to DatetimeIndex (xbbg returns object dtype)
|
|
152
|
+
df.index = pd.to_datetime(df.index)
|
|
153
|
+
logger.debug("Converted index to DatetimeIndex: %s", df.index.dtype)
|
|
154
|
+
|
|
155
|
+
# Map Bloomberg field names to schema columns
|
|
156
|
+
df = _map_bloomberg_fields(df, spec)
|
|
157
|
+
|
|
158
|
+
# Add metadata columns (security identifier)
|
|
159
|
+
if spec.requires_security_metadata:
|
|
160
|
+
df = _add_security_metadata(df, ticker, security)
|
|
161
|
+
|
|
162
|
+
logger.info(
|
|
163
|
+
"Successfully fetched %d rows with columns: %s", len(df), list(df.columns)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return df
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _map_bloomberg_fields(
|
|
170
|
+
df: pd.DataFrame,
|
|
171
|
+
spec: BloombergInstrumentSpec,
|
|
172
|
+
) -> pd.DataFrame:
|
|
173
|
+
"""
|
|
174
|
+
Map Bloomberg field names to schema-expected column names.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
df : pd.DataFrame
|
|
179
|
+
Raw DataFrame from xbbg with Bloomberg field names.
|
|
180
|
+
spec : BloombergInstrumentSpec
|
|
181
|
+
Instrument specification with field mappings.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
pd.DataFrame
|
|
186
|
+
DataFrame with renamed columns matching project schemas.
|
|
187
|
+
|
|
188
|
+
Notes
|
|
189
|
+
-----
|
|
190
|
+
BDH returns multi-index columns: (ticker, field) with uppercase fields.
|
|
191
|
+
BDP returns flat columns: fields with lowercase.
|
|
192
|
+
We normalize to uppercase before mapping.
|
|
193
|
+
"""
|
|
194
|
+
# Handle xbbg multi-index columns: (ticker, field) from BDH
|
|
195
|
+
if isinstance(df.columns, pd.MultiIndex):
|
|
196
|
+
# Flatten by taking second level (field names)
|
|
197
|
+
df.columns = df.columns.get_level_values(1)
|
|
198
|
+
logger.debug("Flattened multi-index columns from BDH")
|
|
199
|
+
else:
|
|
200
|
+
# BDP returns flat columns (single ticker)
|
|
201
|
+
# Normalize lowercase fields to uppercase for consistent mapping
|
|
202
|
+
df.columns = df.columns.str.upper()
|
|
203
|
+
logger.debug("Normalized BDP field names to uppercase")
|
|
204
|
+
|
|
205
|
+
# Rename columns according to mapping
|
|
206
|
+
df = df.rename(columns=spec.field_mapping)
|
|
207
|
+
|
|
208
|
+
logger.debug(
|
|
209
|
+
"Mapped fields: %s -> %s",
|
|
210
|
+
list(spec.field_mapping.keys()),
|
|
211
|
+
list(spec.field_mapping.values()),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return df
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _add_security_metadata(
|
|
218
|
+
df: pd.DataFrame,
|
|
219
|
+
ticker: str,
|
|
220
|
+
security: str | None = None,
|
|
221
|
+
) -> pd.DataFrame:
|
|
222
|
+
"""
|
|
223
|
+
Add security metadata column.
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
df : pd.DataFrame
|
|
228
|
+
DataFrame with mapped field columns.
|
|
229
|
+
ticker : str
|
|
230
|
+
Bloomberg ticker string.
|
|
231
|
+
security : str or None
|
|
232
|
+
Internal security identifier. If None, reverse lookup from ticker.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
pd.DataFrame
|
|
237
|
+
DataFrame with added 'security' column.
|
|
238
|
+
|
|
239
|
+
Raises
|
|
240
|
+
------
|
|
241
|
+
ValueError
|
|
242
|
+
If security not provided and ticker not found in registry.
|
|
243
|
+
"""
|
|
244
|
+
# Get security identifier from parameter or reverse lookup
|
|
245
|
+
if security is not None:
|
|
246
|
+
sec_id = security
|
|
247
|
+
logger.debug("Using provided security identifier: %s", sec_id)
|
|
248
|
+
else:
|
|
249
|
+
# Reverse lookup from Bloomberg ticker
|
|
250
|
+
try:
|
|
251
|
+
sec_id = get_security_from_ticker(ticker)
|
|
252
|
+
logger.debug("Reverse lookup: %s -> %s", ticker, sec_id)
|
|
253
|
+
except ValueError as e:
|
|
254
|
+
logger.error("Failed to resolve security from ticker: %s", ticker)
|
|
255
|
+
raise ValueError(
|
|
256
|
+
f"Cannot determine security identifier for ticker '{ticker}'. "
|
|
257
|
+
"Either provide 'security' parameter or ensure ticker is in registry."
|
|
258
|
+
) from e
|
|
259
|
+
|
|
260
|
+
df["security"] = sec_id
|
|
261
|
+
logger.debug("Added security metadata: %s", sec_id)
|
|
262
|
+
|
|
263
|
+
return df
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def fetch_current_from_bloomberg(
|
|
267
|
+
ticker: str,
|
|
268
|
+
instrument: str,
|
|
269
|
+
security: str | None = None,
|
|
270
|
+
**params: Any,
|
|
271
|
+
) -> pd.DataFrame | None:
|
|
272
|
+
"""
|
|
273
|
+
Fetch current/latest data point from Bloomberg using BDP.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
ticker : str
|
|
278
|
+
Bloomberg ticker.
|
|
279
|
+
instrument : str
|
|
280
|
+
Instrument type for field mapping ('cdx', 'vix', 'etf').
|
|
281
|
+
security : str or None
|
|
282
|
+
Internal security identifier.
|
|
283
|
+
**params : Any
|
|
284
|
+
Additional Bloomberg request parameters.
|
|
285
|
+
|
|
286
|
+
Returns
|
|
287
|
+
-------
|
|
288
|
+
pd.DataFrame or None
|
|
289
|
+
Single-row DataFrame with current data and today's date as index.
|
|
290
|
+
Returns None if no data available (e.g., non-trading day).
|
|
291
|
+
|
|
292
|
+
Raises
|
|
293
|
+
------
|
|
294
|
+
ImportError
|
|
295
|
+
If xbbg is not installed.
|
|
296
|
+
RuntimeError
|
|
297
|
+
If Bloomberg request fails due to connection/authentication issues.
|
|
298
|
+
|
|
299
|
+
Notes
|
|
300
|
+
-----
|
|
301
|
+
Uses Bloomberg's BDP (current data) instead of BDH (historical data).
|
|
302
|
+
Returns data with today's date (US/Eastern timezone) as the index.
|
|
303
|
+
Gracefully returns None on weekends/holidays instead of raising errors.
|
|
304
|
+
"""
|
|
305
|
+
spec = get_instrument_spec(instrument)
|
|
306
|
+
|
|
307
|
+
logger.info(
|
|
308
|
+
"Fetching current %s from Bloomberg: ticker=%s",
|
|
309
|
+
instrument,
|
|
310
|
+
ticker,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
from xbbg import blp
|
|
315
|
+
except BaseException as e:
|
|
316
|
+
# Handle multiple error types:
|
|
317
|
+
# 1. Direct ImportError when xbbg not installed
|
|
318
|
+
# 2. ImportError with blpapi in message (nested import failure)
|
|
319
|
+
# 3. pytest.Skipped exception from xbbg's importorskip
|
|
320
|
+
error_msg = str(e)
|
|
321
|
+
|
|
322
|
+
if "blpapi" in error_msg.lower():
|
|
323
|
+
raise ImportError(
|
|
324
|
+
"Bloomberg API (blpapi) not installed. "
|
|
325
|
+
"Install with: uv pip install blpapi\n"
|
|
326
|
+
"Or install all Bloomberg dependencies: uv sync --extra bloomberg\n"
|
|
327
|
+
"Note: Requires active Bloomberg Terminal subscription."
|
|
328
|
+
) from e
|
|
329
|
+
elif isinstance(e, ImportError):
|
|
330
|
+
raise ImportError(
|
|
331
|
+
f"xbbg not installed: {error_msg}\n"
|
|
332
|
+
"Install with: uv pip install xbbg\n"
|
|
333
|
+
"Or install all Bloomberg dependencies: uv sync --extra bloomberg"
|
|
334
|
+
) from e
|
|
335
|
+
else:
|
|
336
|
+
# Re-raise other exceptions (KeyboardInterrupt, SystemExit, etc.)
|
|
337
|
+
raise
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
# Use BDP for current data point
|
|
341
|
+
current_data = blp.bdp(
|
|
342
|
+
tickers=ticker,
|
|
343
|
+
flds=spec.bloomberg_fields,
|
|
344
|
+
**params,
|
|
345
|
+
)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
logger.error("Bloomberg BDP request failed: %s", str(e))
|
|
348
|
+
raise RuntimeError(f"Failed to fetch current data from Bloomberg: {e}") from e
|
|
349
|
+
|
|
350
|
+
if current_data is None or current_data.empty:
|
|
351
|
+
# Gracefully handle no data (weekends, holidays, market closed)
|
|
352
|
+
logger.warning(
|
|
353
|
+
"Bloomberg BDP returned empty data for %s (likely non-trading day)",
|
|
354
|
+
ticker,
|
|
355
|
+
)
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
logger.debug("Fetched current data from Bloomberg: %s", current_data.shape)
|
|
359
|
+
|
|
360
|
+
# Convert BDP format to time series format
|
|
361
|
+
# BDP returns: index=tickers, columns=fields (lowercase)
|
|
362
|
+
# Need: index=dates, columns=fields (to match BDH format)
|
|
363
|
+
eastern = ZoneInfo("America/New_York")
|
|
364
|
+
today = datetime.now(eastern).strftime("%Y-%m-%d")
|
|
365
|
+
|
|
366
|
+
# Extract single ticker row and reassign index to today's date
|
|
367
|
+
df = current_data.iloc[[0]].copy() # Keep as DataFrame with single row
|
|
368
|
+
df.index = pd.to_datetime([today])
|
|
369
|
+
df.index.name = "date"
|
|
370
|
+
|
|
371
|
+
# Map Bloomberg field names to schema columns
|
|
372
|
+
df = _map_bloomberg_fields(df, spec)
|
|
373
|
+
|
|
374
|
+
# Add security metadata if required
|
|
375
|
+
if spec.requires_security_metadata:
|
|
376
|
+
df = _add_security_metadata(df, ticker, security)
|
|
377
|
+
|
|
378
|
+
logger.info(
|
|
379
|
+
"Successfully fetched current data with columns: %s",
|
|
380
|
+
list(df.columns),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return df
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File-based data provider for Parquet and CSV files.
|
|
3
|
+
|
|
4
|
+
Handles local file loading with automatic format detection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from ...persistence.parquet_io import load_parquet
|
|
13
|
+
from ..sources import FileSource
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def fetch_from_file(
|
|
19
|
+
source: FileSource,
|
|
20
|
+
ticker: str,
|
|
21
|
+
instrument: str,
|
|
22
|
+
security: str,
|
|
23
|
+
start_date: str | None = None,
|
|
24
|
+
end_date: str | None = None,
|
|
25
|
+
**params: Any,
|
|
26
|
+
) -> pd.DataFrame:
|
|
27
|
+
"""
|
|
28
|
+
Fetch data from local Parquet or CSV file using security-based lookup.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
source : FileSource
|
|
33
|
+
File source configuration with base_dir and security_mapping.
|
|
34
|
+
ticker : str
|
|
35
|
+
Ticker identifier (unused for file provider, for signature compatibility).
|
|
36
|
+
instrument : str
|
|
37
|
+
Instrument type (cdx, vix, etf).
|
|
38
|
+
security : str
|
|
39
|
+
Security identifier to fetch (e.g., 'cdx_ig_5y', 'vix', 'hyg').
|
|
40
|
+
start_date : str or None
|
|
41
|
+
Optional start date filter (ISO format, unused for file provider).
|
|
42
|
+
end_date : str or None
|
|
43
|
+
Optional end date filter (ISO format, unused for file provider).
|
|
44
|
+
**params : Any
|
|
45
|
+
Additional parameters (unused for file provider).
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
pd.DataFrame
|
|
50
|
+
Raw data loaded from file (validation happens in fetch layer).
|
|
51
|
+
|
|
52
|
+
Raises
|
|
53
|
+
------
|
|
54
|
+
ValueError
|
|
55
|
+
If security not found in mapping or file format not supported.
|
|
56
|
+
FileNotFoundError
|
|
57
|
+
If file does not exist.
|
|
58
|
+
|
|
59
|
+
Notes
|
|
60
|
+
-----
|
|
61
|
+
- Uses security_mapping to resolve security ID to filename
|
|
62
|
+
- Automatically detects Parquet vs CSV from file extension
|
|
63
|
+
- Adds 'security' column if instrument requires it
|
|
64
|
+
- Date filtering not performed (files pre-filtered to match needs)
|
|
65
|
+
"""
|
|
66
|
+
# Resolve security to filename
|
|
67
|
+
if security not in source.security_mapping:
|
|
68
|
+
available = ", ".join(sorted(source.security_mapping.keys()))
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"Security '{security}' not found in registry. Available: {available}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
filename = source.security_mapping[security]
|
|
74
|
+
file_path = source.base_dir / filename
|
|
75
|
+
|
|
76
|
+
logger.info(
|
|
77
|
+
"Fetching %s (security=%s) from file: %s", instrument, security, file_path
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if not file_path.exists():
|
|
81
|
+
raise FileNotFoundError(f"Data file not found: {file_path}")
|
|
82
|
+
|
|
83
|
+
# Load based on file type
|
|
84
|
+
if file_path.suffix == ".parquet":
|
|
85
|
+
df = load_parquet(file_path)
|
|
86
|
+
elif file_path.suffix == ".csv":
|
|
87
|
+
df = pd.read_csv(file_path)
|
|
88
|
+
# Convert 'date' column to DatetimeIndex if present
|
|
89
|
+
if "date" in df.columns:
|
|
90
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
91
|
+
df = df.set_index("date")
|
|
92
|
+
logger.debug("Converted 'date' column to DatetimeIndex")
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError(f"Unsupported file format: {file_path.suffix}")
|
|
95
|
+
|
|
96
|
+
# Add security column if instrument requires it
|
|
97
|
+
from ..bloomberg_config import get_instrument_spec
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
inst_spec = get_instrument_spec(instrument)
|
|
101
|
+
if inst_spec.requires_security_metadata and "security" not in df.columns:
|
|
102
|
+
df["security"] = security
|
|
103
|
+
logger.debug("Added security column: %s", security)
|
|
104
|
+
except ValueError:
|
|
105
|
+
# Unknown instrument type, skip metadata enrichment
|
|
106
|
+
logger.debug(
|
|
107
|
+
"Unknown instrument type '%s', skipping metadata enrichment", instrument
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
logger.info("Loaded %d rows from file", len(df))
|
|
111
|
+
return df
|