bbg-fetch 1.0.31__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.
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.3
2
+ Name: bbg_fetch
3
+ Version: 1.0.31
4
+ Summary: Bloomberg fetching analytics wrapping xbbg package
5
+ License: LICENSE.txt
6
+ Keywords: quantitative,investing,portfolio optimization,systematic strategies,volatility
7
+ Author: Artur Sepp
8
+ Author-email: artursepp@gmail.com
9
+ Maintainer: Artur Sepp
10
+ Maintainer-email: artursepp@gmail.com
11
+ Requires-Python: >=3.8
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: License :: Other/Proprietary License
18
+ Classifier: Natural Language :: English
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Programming Language :: Python :: 3 :: Only
28
+ Classifier: Topic :: Office/Business :: Financial :: Investment
29
+ Requires-Dist: pandas (>=1.5.2)
30
+ Requires-Dist: xbbg (>=0.0.7)
31
+ Project-URL: Documentation, https://github.com/ArturSepp/BloombergFetch
32
+ Project-URL: Issues, https://github.com/ArturSepp/BloombergFetch/issues
33
+ Project-URL: Personal website, https://artursepp.com
34
+ Project-URL: Repository, https://github.com/ArturSepp/BloombergFetch
35
+ Description-Content-Type: text/markdown
36
+
37
+ Wrapper of xbbg package for bloomberg fetch
38
+
39
+ Install using
40
+ ```python
41
+ pip install bbg_fetch
42
+ ```
43
+
44
+ Upgrade using
45
+ ```python
46
+ pip install --upgrade bbg_fetch
47
+ ```
48
+
49
+ Close using
50
+ ```python
51
+ git clone https://github.com/ArturSepp/BloombergFetch.git
52
+ ```
53
+
54
+
55
+ Install blpapi package with:
56
+ ```python
57
+ pip install --index-url=https://blpapi.bloomberg.com/repository/releases/python/simple blpapi
58
+ ```
59
+
60
+ Configuration of rolls for futures contracts on Bloomberg: GFUT
61
+
@@ -0,0 +1,24 @@
1
+ Wrapper of xbbg package for bloomberg fetch
2
+
3
+ Install using
4
+ ```python
5
+ pip install bbg_fetch
6
+ ```
7
+
8
+ Upgrade using
9
+ ```python
10
+ pip install --upgrade bbg_fetch
11
+ ```
12
+
13
+ Close using
14
+ ```python
15
+ git clone https://github.com/ArturSepp/BloombergFetch.git
16
+ ```
17
+
18
+
19
+ Install blpapi package with:
20
+ ```python
21
+ pip install --index-url=https://blpapi.bloomberg.com/repository/releases/python/simple blpapi
22
+ ```
23
+
24
+ Configuration of rolls for futures contracts on Bloomberg: GFUT
@@ -0,0 +1,702 @@
1
+ """
2
+ to install blpapi use
3
+ pip install --index-url=https://blpapi.bloomberg.com/repository/releases/python/simple blpapi
4
+ https://github.com/alpha-xone/xbbg/tree/main
5
+ GFUT
6
+ """
7
+
8
+ # packages
9
+ import re
10
+ import warnings
11
+ import datetime
12
+ import numpy as np
13
+ import pandas as pd
14
+ from enum import Enum
15
+ from typing import List, Optional, Tuple, Dict, Union
16
+ from xbbg import blp
17
+
18
+ DEFAULT_START_DATE = pd.Timestamp('01Jan1959')
19
+ VOLS_START_DATE = pd.Timestamp('03Jan2005')
20
+
21
+ FX_DICT = {
22
+ 'EURUSD Curncy': 'EUR',
23
+ 'GBPUSD Curncy': 'GBP',
24
+ 'CHFUSD Curncy': 'CHF',
25
+ 'CADUSD Curncy': 'CAD',
26
+ 'JPYUSD Curncy': 'JPY',
27
+ 'AUDUSD Curncy': 'AUD',
28
+ 'NZDUSD Curncy': 'NZD',
29
+ 'MXNUSD Curncy': 'MXN',
30
+ 'HKDUSD Curncy': 'HKD',
31
+ 'SEKUSD Curncy': 'SEK',
32
+ 'PLNUSD Curncy': 'PLN',
33
+ 'KRWUSD Curncy': 'KRW',
34
+ 'TRYUSD Curncy': 'TRY',
35
+ 'SGDUSD Curncy': 'SGD',
36
+ 'ZARUSD Curncy': 'ZAR',
37
+ 'CNYUSD Curncy': 'CNY',
38
+ 'INRUSD Curncy': 'INR',
39
+ 'TWDUSD Curncy': 'TWD',
40
+ 'NOKUSD Curncy': 'NOK'
41
+ }
42
+
43
+
44
+ IMPVOL_FIELDS_MNY_30DAY = {'30DAY_IMPVOL_80%MNY_DF': '30d80.0',
45
+ '30DAY_IMPVOL_90.0%MNY_DF': '30d90.0',
46
+ '30DAY_IMPVOL_95.0%MNY_DF': '30d95.0',
47
+ '30DAY_IMPVOL_97.5%MNY_DF': '30d97.5',
48
+ '30DAY_IMPVOL_100.0%MNY_DF': '30d100.0',
49
+ '30DAY_IMPVOL_102.5%MNY_DF': '30d102.5',
50
+ '30DAY_IMPVOL_105.0%MNY_DF': '30d105.0',
51
+ '30DAY_IMPVOL_110.0%MNY_DF': '30d110.0',
52
+ '30DAY_IMPVOL_120%MNY_DF': '30d120.0'}
53
+
54
+ IMPVOL_FIELDS_MNY_60DAY = {'60DAY_IMPVOL_80%MNY_DF': '60d80.0',
55
+ '60DAY_IMPVOL_90.0%MNY_DF': '60d90.0',
56
+ '60DAY_IMPVOL_95.0%MNY_DF': '60d95.0',
57
+ '60DAY_IMPVOL_97.5%MNY_DF': '60d97.5',
58
+ '60DAY_IMPVOL_100.0%MNY_DF': '60d100.0',
59
+ '60DAY_IMPVOL_102.5%MNY_DF': '60d102.5',
60
+ '60DAY_IMPVOL_105.0%MNY_DF': '60d105.0',
61
+ '60DAY_IMPVOL_110.0%MNY_DF': '60d110.0',
62
+ '60DAY_IMPVOL_120%MNY_DF': '60d120.0'}
63
+
64
+ IMPVOL_FIELDS_MNY_3MTH = {'3MTH_IMPVOL_80%MNY_DF': '3m80.0',
65
+ '3MTH_IMPVOL_90.0%MNY_DF': '3m90.0',
66
+ '3MTH_IMPVOL_95.0%MNY_DF': '3m95.0',
67
+ '3MTH_IMPVOL_97.5%MNY_DF': '3m97.5',
68
+ '3MTH_IMPVOL_100.0%MNY_DF': '3m100.0',
69
+ '3MTH_IMPVOL_102.5%MNY_DF': '3m102.5',
70
+ '3MTH_IMPVOL_105.0%MNY_DF': '3m105.0',
71
+ '3MTH_IMPVOL_110.0%MNY_DF': '3m110.0',
72
+ '3MTH_IMPVOL_120%MNY_DF': '3m120.0'}
73
+
74
+ IMPVOL_FIELDS_MNY_6MTH = {'6MTH_IMPVOL_80%MNY_DF': '6m80.0',
75
+ '6MTH_IMPVOL_90.0%MNY_DF': '6m90.0',
76
+ '6MTH_IMPVOL_95.0%MNY_DF': '6m95.0',
77
+ '6MTH_IMPVOL_97.5%MNY_DF': '6m97.5',
78
+ '6MTH_IMPVOL_100.0%MNY_DF': '6m100.0',
79
+ '6MTH_IMPVOL_102.5%MNY_DF': '6m102.5',
80
+ '6MTH_IMPVOL_105.0%MNY_DF': '6m105.0',
81
+ '6MTH_IMPVOL_110.0%MNY_DF': '6m110.0',
82
+ '6MTH_IMPVOL_120%MNY_DF': '6m120.0'}
83
+
84
+ IMPVOL_FIELDS_MNY_12M = {'12MTH_IMPVOL_80%MNY_DF': '12m80.0',
85
+ '12MTH_IMPVOL_90.0%MNY_DF': '12m90.0',
86
+ '12MTH_IMPVOL_95.0%MNY_DF': '12m95.0',
87
+ '12MTH_IMPVOL_97.5%MNY_DF': '12m97.5',
88
+ '12MTH_IMPVOL_100.0%MNY_DF': '12m100.0',
89
+ '12MTH_IMPVOL_102.5%MNY_DF': '12m102.5',
90
+ '12MTH_IMPVOL_105.0%MNY_DF': '12m105.0',
91
+ '12MTH_IMPVOL_110.0%MNY_DF': '12m110.0',
92
+ '12MTH_IMPVOL_120%MNY_DF': '12m120.0'}
93
+
94
+ IMPVOL_FIELDS_DELTA = {'1M_CALL_IMP_VOL_10DELTA_DFLT': '1MC10D.0',
95
+ '1M_CALL_IMP_VOL_25DELTA_DFLT': '1MC25D.0',
96
+ '1M_CALL_IMP_VOL_40DELTA_DFLT': '1MC40D.0',
97
+ '1M_CALL_IMP_VOL_50DELTA_DFLT': '1MC50D.0',
98
+ '1M_PUT_IMP_VOL_50DELTA_DFLT': '1MP50D.0',
99
+ '1M_PUT_IMP_VOL_40DELTA_DFLT': '1MP40D.0',
100
+ '1M_PUT_IMP_VOL_25DELTA_DFLT': '1MP25D.0',
101
+ '1M_PUT_IMP_VOL_10DELTA_DFLT': '1MP10D.0',
102
+ '2M_CALL_IMP_VOL_10DELTA_DFLT': '2MC10D.0',
103
+ '2M_CALL_IMP_VOL_25DELTA_DFLT': '2MC25D.0',
104
+ '2M_CALL_IMP_VOL_40DELTA_DFLT': '2MC40D.0',
105
+ '2M_CALL_IMP_VOL_50DELTA_DFLT': '2MC50D.0',
106
+ '2M_PUT_IMP_VOL_50DELTA_DFLT': '2MP50D.0',
107
+ '2M_PUT_IMP_VOL_40DELTA_DFLT': '2MP40D.0',
108
+ '2M_PUT_IMP_VOL_25DELTA_DFLT': '2MP25D.0',
109
+ '2M_PUT_IMP_VOL_10DELTA_DFLT': '2MP10D.0'
110
+ }
111
+
112
+
113
+ def fetch_field_timeseries_per_tickers(tickers: Union[List[str], Tuple[str], Dict[str, str]],
114
+ field: str = 'PX_LAST',
115
+ CshAdjNormal: bool = True,
116
+ CshAdjAbnormal: bool = True,
117
+ CapChg: bool = True,
118
+ start_date: Optional[pd.Timestamp] = DEFAULT_START_DATE,
119
+ end_date: Optional[pd.Timestamp] = pd.Timestamp.now(),
120
+ freq: str = None
121
+ ) -> Optional[pd.DataFrame]:
122
+ """
123
+ get bloomberg field data adjusted for splits and divs for a list of tickers
124
+ tickers can be a dict {'ES1 Index': 'SPY', 'UXY1 Comdty': '10yUST'}, then df columns are renamed
125
+ """
126
+ if isinstance(tickers, list) or isinstance(tickers, Tuple):
127
+ tickers_ = tickers
128
+ elif isinstance(tickers, dict):
129
+ tickers_ = list(tickers.keys())
130
+ else:
131
+ raise NotImplemented(f"type={type(tickers)}")
132
+ field_data = blp.bdh(tickers_, field, start_date, end_date, CshAdjNormal=CshAdjNormal,
133
+ CshAdjAbnormal=CshAdjAbnormal, CapChg=CapChg)
134
+
135
+ try:
136
+ field_data.columns = field_data.columns.droplevel(1) # eliminate multiindex
137
+ except:
138
+ warnings.warn(f"something is wrong for field={field}")
139
+ return None
140
+
141
+ # make sure all columns are returned
142
+ field_data.index = pd.to_datetime(field_data.index)
143
+ if freq is not None:
144
+ field_data = field_data.asfreq(freq, method='ffill')
145
+
146
+ # align columns
147
+ field_data = field_data.reindex(columns=tickers_)
148
+ if isinstance(tickers, dict):
149
+ field_data = field_data.rename(tickers, axis=1)
150
+
151
+ field_data = field_data.sort_index()
152
+
153
+ return field_data
154
+
155
+
156
+ def fetch_fields_timeseries_per_ticker(ticker: str,
157
+ fields: List[str] = ('PX_OPEN', 'PX_HIGH', 'PX_LOW', 'PX_LAST',),
158
+ CshAdjNormal: bool = True,
159
+ CshAdjAbnormal: bool = True,
160
+ CapChg: bool = True,
161
+ start_date: pd.Timestamp = DEFAULT_START_DATE,
162
+ end_date: pd.Timestamp = pd.Timestamp.now()
163
+ ) -> Optional[pd.DataFrame]:
164
+ """
165
+ get bloomberg fields data adjusted for splits and divs for given ticker
166
+ """
167
+ try:
168
+ # get bloomberg data adjusted for splits and divs
169
+ field_data = blp.bdh(ticker, fields, start_date, end_date,
170
+ CshAdjNormal=CshAdjNormal, CshAdjAbnormal=CshAdjAbnormal, CapChg=CapChg)
171
+ except:
172
+ warnings.warn(f"could not get field_data for ticker={ticker}")
173
+ return None
174
+
175
+ try:
176
+ field_data.columns = field_data.columns.droplevel(0) # eliminate multiindex
177
+ except:
178
+ warnings.warn(f"something is wrong for ticker=r={ticker}")
179
+ print(field_data)
180
+ return None
181
+
182
+ if len(fields) > 1:
183
+ field_data = field_data.reindex(columns=fields) # rearrange columns
184
+ else:
185
+ pass
186
+
187
+ field_data.index = pd.to_datetime(field_data.index)
188
+ field_data.sort_index()
189
+ return field_data
190
+
191
+
192
+ def fetch_fundamentals(tickers: Union[List[str], Dict[str, str]],
193
+ fields: Union[List[str], Dict[str, str]] = ('security_name', 'gics_sector_name',)
194
+ ) -> pd.DataFrame:
195
+ if isinstance(tickers, list):
196
+ tickers_ = tickers
197
+ elif isinstance(tickers, dict):
198
+ tickers_ = list(tickers.keys())
199
+ else:
200
+ raise NotImplemented(f"tickers type={type(tickers)}")
201
+ if isinstance(fields, list):
202
+ fields_ = fields
203
+ elif isinstance(fields, dict):
204
+ fields_ = list(fields.keys())
205
+ else:
206
+ raise NotImplemented(f"fields type={type(fields)}")
207
+ df = blp.bdp(tickers=tickers_, flds=fields_)
208
+ # align with given order of tickers
209
+ df = df.reindex(index=tickers_).reindex(columns=fields)
210
+ if isinstance(tickers, dict):
211
+ df = df.rename(tickers, axis=0)
212
+ if isinstance(fields, dict):
213
+ df = df.rename(fields, axis=1)
214
+ return df
215
+
216
+
217
+ def fetch_active_futures(generic_ticker: str = 'ES1 Index',
218
+ first_gen: int = 1
219
+ ) -> Tuple[pd.Series, pd.Series]:
220
+ """
221
+ need to run with GFUT settings: roll = None
222
+ bloomberg often fails to get joint data for two adjacent futures
223
+ we need to split the index
224
+ """
225
+ atickers = [instrument_to_active_ticker(generic_ticker, num=first_gen),
226
+ instrument_to_active_ticker(generic_ticker, num=first_gen + 1)]
227
+
228
+ start_date = DEFAULT_START_DATE
229
+ end_date = pd.Timestamp.now()
230
+ price_datas = {}
231
+ for aticker in atickers:
232
+ price_data = fetch_fields_timeseries_per_ticker(ticker=aticker, fields=['PX_LAST'],
233
+ CshAdjNormal=False, CshAdjAbnormal=False, CapChg=False,
234
+ start_date=start_date, end_date=end_date)
235
+ if price_data is None or price_data.empty:
236
+ warnings.warn(f"second attempt to fetch data for {aticker}")
237
+ price_data = fetch_fields_timeseries_per_ticker(ticker=aticker, fields=['PX_LAST'],
238
+ CshAdjNormal=False, CshAdjAbnormal=False, CapChg=False,
239
+ start_date=start_date, end_date=end_date)
240
+ if price_data is None or price_data.empty:
241
+ warnings.warn(f"third attempt to fetch data for {aticker}")
242
+ price_data = fetch_fields_timeseries_per_ticker(ticker=aticker, fields=['PX_LAST'],
243
+ CshAdjNormal=False, CshAdjAbnormal=False, CapChg=False,
244
+ start_date=start_date, end_date=end_date)
245
+
246
+ price_datas[aticker] = price_data.iloc[:, 0]
247
+ start_date = price_data.index[0]
248
+ end_date = price_data.index[-1]
249
+ price_data = pd.DataFrame.from_dict(price_datas, orient='columns')
250
+
251
+ return price_data.iloc[:, 0], price_data.iloc[:, 1]
252
+
253
+
254
+ def fetch_futures_contract_table(ticker: str = "ESA Index",
255
+ flds: List[str] = ('name',
256
+ 'px_settle',
257
+ 'px_last',
258
+ 'px_bid', 'px_ask', 'bid_size', 'ask_size',
259
+ 'volume', 'volume_avg_5d', 'open_int',
260
+ 'fut_cont_size',
261
+ 'contract_value',
262
+ 'fut_val_pt',
263
+ 'quoted_crncy',
264
+ 'fut_days_expire',
265
+ 'px_settle_last_dt',
266
+ 'last_tradeable_dt',
267
+ 'last_update_dt',
268
+ 'last_update'),
269
+ add_timestamp: bool = True,
270
+ add_gen_number: bool = True,
271
+ add_carry: bool = True,
272
+ tz: Optional[str] = 'UTC'
273
+ ) -> pd.DataFrame:
274
+ """
275
+ fetch contract table for active futures
276
+ """
277
+ contracts = blp.bds(ticker, "FUT_CHAIN")
278
+ if contracts.empty:
279
+ contracts = blp.bds(ticker, "FUT_CHAIN")
280
+ if not contracts.empty:
281
+ tickers = contracts['security_description']
282
+ df = blp.bdp(tickers=tickers, flds=flds)
283
+ tradable_tickers = tickers[np.isin(tickers, df.index, assume_unique=True)]
284
+ good_columns = pd.Index(flds)[np.isin(flds, df.columns, assume_unique=True)]
285
+ df = df.loc[tradable_tickers, good_columns]
286
+
287
+ if add_timestamp:
288
+ timestamps = df['last_update_dt'].copy()
289
+ # last_update can be date.time
290
+ for idx, (x, y) in enumerate(zip(df['last_update_dt'], df['last_update'])):
291
+ if isinstance(y, datetime.time):
292
+ timestamps.iloc[idx] = pd.Timestamp.combine(x, y).tz_localize(tz='CET').tz_convert(tz)
293
+ elif isinstance(x, datetime.date):
294
+ timestamps.iloc[idx] = pd.Timestamp.combine(x, datetime.time(0,0,0)).tz_localize(tz)
295
+ df['update'] = timestamps
296
+ df['timestamp'] = pd.Timestamp.utcnow()
297
+ df = df.drop(['last_update_dt', 'last_update'], axis=1)
298
+
299
+ if add_gen_number:
300
+ df['gen_number'] = [n+1 for n in range(len(df.index))]
301
+
302
+ if add_carry and len(df.index) > 1:
303
+ n = len(df.index)
304
+ carry = np.full(n, np.nan)
305
+ bid_ask = df[['px_bid', 'px_ask']].to_numpy()
306
+ is_good = np.logical_and(pd.isna(bid_ask[:, 0])==False, pd.isna(bid_ask[:, 1])==False)
307
+ mid_price = np.where(is_good, 0.5*(bid_ask[:, 0]+bid_ask[:, 1]), np.nan)
308
+ an_days_to_mat = df['fut_days_expire'].to_numpy() / 365.0
309
+ for idx in range(n):
310
+ if idx > 0:
311
+ carry[idx] = - (mid_price[idx] - mid_price[idx-1]) / mid_price[idx-1] / (an_days_to_mat[idx]-an_days_to_mat[idx-1])
312
+ df['an_carry'] = carry
313
+ else:
314
+ print(f"no data for {ticker}")
315
+ df = pd.DataFrame()
316
+ df['ticker'] = ticker
317
+ return df
318
+
319
+
320
+ def fetch_vol_timeseries(ticker: str = 'SPX Index',
321
+ vol_fields: Union[Dict, List] = IMPVOL_FIELDS_DELTA,
322
+ start_date: pd.Timestamp = VOLS_START_DATE,
323
+ rate_index: str = 'usgg3m Index',
324
+ add_underlying: bool = True,
325
+ rename: bool = True,
326
+ scaler: Optional[float] = 0.01
327
+ ) -> pd.DataFrame:
328
+ """
329
+ fetch imlied vols specified in vol_fields
330
+ """
331
+ if isinstance(vol_fields, list):
332
+ df = fetch_fields_timeseries_per_ticker(ticker=ticker,
333
+ fields=vol_fields,
334
+ start_date=start_date)
335
+ else:
336
+ df = fetch_fields_timeseries_per_ticker(ticker=ticker,
337
+ fields=list(vol_fields.keys()),
338
+ start_date=start_date)
339
+ if rename:
340
+ df = df.rename(vol_fields, axis=1)
341
+ if scaler is not None:
342
+ df *= scaler
343
+
344
+ if add_underlying:
345
+ price = fetch_fields_timeseries_per_ticker(ticker=ticker,
346
+ fields=['PX_LAST', 'EQY_DVD_YLD_12M'],
347
+ start_date=start_date)
348
+ if scaler is not None:
349
+ price['EQY_DVD_YLD_12M'] *= scaler
350
+ price = price.rename({'PX_LAST': 'spot_price', 'EQY_DVD_YLD_12M': 'div_yield'}, axis=1)
351
+ rate_3m = fetch_fields_timeseries_per_ticker(ticker=rate_index,
352
+ fields=['PX_LAST'],
353
+ start_date=start_date)
354
+ if scaler is not None:
355
+ rate_3m *= scaler
356
+ rate_3m = rate_3m.rename({'PX_LAST': 'rf_rate'}, axis=1)
357
+ # drop row when vols are missing
358
+ df = pd.concat([price, rate_3m, df], axis=1)#.dropna(axis=0, subset=df.columns, how='all')
359
+ return df
360
+
361
+
362
+ def fetch_last_prices(tickers: Union[List, Dict] = FX_DICT) -> pd.Series:
363
+ """
364
+ fetch last prices of instruments in tickers
365
+ """
366
+ if isinstance(tickers, Dict):
367
+ tickers1 = list(tickers.keys())
368
+ else:
369
+ tickers1 = tickers
370
+ df = blp.bdp(tickers=tickers1, flds='px_last')
371
+ if isinstance(tickers, Dict):
372
+ df = df.rename(tickers, axis=0)
373
+ return df.iloc[:, 0]
374
+
375
+
376
+ def fetch_bonds_info(isins: List[str] = ['US03522AAJ97', 'US126650CZ11'],
377
+ fields: List[str] = ('id_bb', 'name', 'security_des',
378
+ 'ult_parent_ticker_exchange', 'crncy', 'amt_outstanding',
379
+ 'px_last',
380
+ 'yas_bond_yld', 'yas_oas_sprd', 'yas_mod_dur'),
381
+ END_DATE_OVERRIDE: Optional[str] = None
382
+ ) -> pd.DataFrame:
383
+ """
384
+ bonds are given by isins
385
+ fetch fileds data for bonds
386
+ """
387
+ issue_data = blp.bdp([f"{isin} corp" for isin in isins], fields, END_DATE_OVERRIDE=END_DATE_OVERRIDE)
388
+ # process US03522AAH32 corp to US03522AAH32
389
+ issue_data.insert(loc=0, column='isin', value=[x.split(' ')[0] for x in issue_data.index])
390
+ issue_data = issue_data.reset_index(names='isin_corp').set_index('isin')
391
+ issue_data = issue_data.reindex(index=isins)
392
+ return issue_data
393
+
394
+
395
+ def fetch_cds_info(equity_tickers: List[str] = ('ABI BB Equity', 'CVS US Equity'),
396
+ field: str = 'cds_spread_ticker_5y'
397
+ ) -> pd.DataFrame:
398
+ """
399
+ fetch cds info
400
+ """
401
+ cds_rate_tickers = blp.bdp(tickers=equity_tickers, flds=field)
402
+ cds_rate_tickers = cds_rate_tickers.reindex(index=equity_tickers)
403
+ return cds_rate_tickers
404
+
405
+
406
+ def fetch_balance_data(tickers: List[str] = ('ABI BB Equity', 'T US Equity', 'JPM US Equity'),
407
+ fields: List[str] = ('GICS_SECTOR_NAME', 'BB_ISSR_COMP_BSE_ON_RTGS', 'TOT_COMMON_EQY',
408
+ 'BS_LT_BORROW', 'BS_ST_BORROW', 'EQY_FUND_CRNCY',
409
+ 'EARN_YLD',
410
+ 'RETURN_ON_ASSETS_ADJUSTED',
411
+ 'NET_DEBT_TO_FFCF',
412
+ 'NET_DEBT_TO_CASHFLOW',
413
+ 'FREE_CASH_FLOW_MARGIN',
414
+ 'CFO_TO_SALES',
415
+ 'NET_DEBT_PCT_OF_TOT_CAPITAL',
416
+ 'INTEREST_COVERAGE_RATIO',
417
+ 'BS_LIQUIDITY_COVERAGE_RATIO',
418
+ 'NET_DEBT_TO_EBITDA',
419
+ 'T12_FCF_T12_EBITDA')
420
+ ) -> pd.DataFrame:
421
+ """
422
+ fundamentals data for tickers in tickers
423
+ """
424
+ issue_data = blp.bdp(tickers, fields)
425
+ issue_data = issue_data.rename({x: x.upper() for x in issue_data.columns}, axis=1)
426
+ issue_data = issue_data.reindex(index=tickers).reindex(columns=fields)
427
+
428
+ return issue_data
429
+
430
+
431
+ def fetch_tickers_from_isins(isins: List[str] = ['US88160R1014', 'IL0065100930']) -> List[str]:
432
+ """
433
+ =BDP("US4592001014 ISIN", "PARSEKYABLE_DES") => IBM XX Equity
434
+ where XX depends on your terminal settings, which you can check on CNDF <Go>.
435
+ get the main exchange composite ticker, or whatever suits your need (in A3):
436
+ =BDP(A2,"EQY_PRIM_SECURITY_COMP_EXCH") => US
437
+ """
438
+ tickers = {f"/ISIN/{x}": x for x in isins}
439
+ df = blp.bdp(list(tickers.keys()), ["parsekyable_des", "eqy_prim_security_comp_exch"])
440
+ df.index = df.index.map(tickers) # map back to isins need to sort back to isins order
441
+ df = df.reindex(index=isins)
442
+ # replace default country with exchange
443
+ tickers = []
444
+ for ticker_, exchange in zip(df["parsekyable_des"].to_list(), df["eqy_prim_security_comp_exch"].to_list()):
445
+ ticker_s = ticker_.split(' ')
446
+ tickers.append(f"{ticker_s[0]} {exchange} {ticker_s[-1]}")
447
+ return tickers
448
+
449
+
450
+ def fetch_dividend_history(ticker: str = 'TIP US Equity') -> pd.DataFrame:
451
+ """
452
+ df.columns = ['declared_date', 'ex_date', 'record_date', 'payable_date',
453
+ 'dividend_amount', 'dividend_frequency', 'dividend_type']
454
+ """
455
+ this = blp.bds(ticker, 'dvd_hist_all')
456
+ return this
457
+
458
+
459
+ def fetch_div_yields(tickers: Union[List[str], Dict[str, str]],
460
+ dividend_types: List[str] = ('Income', 'Distribution')
461
+ ) -> Tuple[pd.DataFrame, pd.DataFrame]:
462
+ """
463
+ dividend_types can include:
464
+ dividend_types: List[str] = ('Income', 'Distribution')
465
+ dividend_types: List[str] = ('Income', 'Distribution', 'Return of Capital', 'Accumulation')
466
+ """
467
+ if isinstance(tickers, list):
468
+ tickers_ = tickers
469
+ elif isinstance(tickers, dict):
470
+ tickers_ = list(tickers.keys())
471
+ else:
472
+ raise NotImplemented(f"type={type(tickers)}")
473
+ divs = {}
474
+ divs_1y = {}
475
+ for ticker in tickers_:
476
+ div = fetch_dividend_history(ticker=ticker)
477
+ if not div.empty:
478
+ valid_div_cond = div['dividend_type'].apply(lambda x: x in dividend_types)
479
+ valid_div = div.loc[valid_div_cond, :].set_index('ex_date') # set ex_date index
480
+ if np.any(valid_div.index.duplicated()): # aggregate dividend by sum of non-unique distributions
481
+ def sum_unique(s):
482
+ return s.unique().sum()
483
+ valid_div = valid_div.groupby('declared_date', sort=False, as_index=True).agg(
484
+ declared_date=('declared_date', 'first'),
485
+ record_date=('record_date', 'first'),
486
+ payable_date=('payable_date', 'first'),
487
+ dividend_amount=('dividend_amount', sum_unique),
488
+ dividend_frequency=('dividend_frequency', 'first'),
489
+ dividend_type=('dividend_type', 'first')
490
+ )
491
+
492
+ if not valid_div.empty and len(valid_div.index) > 0:
493
+ valid_div.index = pd.to_datetime(valid_div.index)
494
+ valid_div = valid_div.sort_index()
495
+ valid_div_amount = valid_div['dividend_amount']
496
+ divs[ticker] = valid_div_amount
497
+ divs_1y[ticker] = valid_div_amount.rolling("365D").sum() # assume 365 B days in year
498
+ divs = pd.DataFrame.from_dict(divs, orient='columns').reindex(columns=tickers)
499
+ divs_1y = pd.DataFrame.from_dict(divs_1y, orient='columns').reindex(columns=tickers)
500
+ if isinstance(tickers, dict):
501
+ divs = divs.rename(tickers, axis=1)
502
+ divs_1y = divs_1y.rename(tickers, axis=1)
503
+
504
+ return divs, divs_1y
505
+
506
+
507
+ def fetch_index_members_weights(index: str = 'SPCPGN Index',
508
+ END_DATE_OVERRIDE: Optional[str] = None
509
+ ) -> pd.DataFrame:
510
+ members = blp.bds(index, 'INDX_MWEIGHT', END_DATE_OVERRIDE=END_DATE_OVERRIDE)
511
+ if members is not None:
512
+ members = members.set_index('member_ticker_and_exchange_code', drop=True)
513
+ else:
514
+ raise ValueError(f"no data for {index}")
515
+ return members
516
+
517
+
518
+ #################### Helper functions ####################
519
+
520
+
521
+ def instrument_to_active_ticker(instrument: str = 'ES1 Index', num: int = 1) -> str:
522
+ """
523
+ ES1 Index to ES{num} Index
524
+ Z1 Index to Z 1 Index
525
+ """
526
+ head = contract_to_instrument(instrument)
527
+ ticker_split = instrument.split(' ')
528
+ mid = "" if len(ticker_split[0]) > 1 else " "
529
+ active_ticker = f"{head}{mid}{num} {ticker_split[-1]}"
530
+ return active_ticker
531
+
532
+
533
+ def contract_to_instrument(future: str) -> str:
534
+ """
535
+ ES1 Index to ES Index
536
+ """
537
+ ticker_split_wo_num = re.sub('\d+', '', future).split()
538
+ return ticker_split_wo_num[0]
539
+
540
+
541
+ """
542
+ def fetch_option_underlying_tickers_from_isins(isins: List[str] = ['DE000C77PRU9', 'YY0160552733']) -> pd.DataFrame:
543
+ tickers = {f"/cusip/{x} Corp": x for x in isins}
544
+ # tickers = {f"{x}@BGN Corp": x for x in isins}
545
+ df = blp.bdp(list(tickers.keys()), "PARSEKYABLE_DES")
546
+ print(df)
547
+ df = blp.bdp(list(tickers.keys()), "OPT_UNDL_TICKER")
548
+ print(df)
549
+ df.index = df.index.map(tickers) # map back to isins
550
+ df = df.reindex(index=isins)
551
+ return df
552
+
553
+ elif unit_test == UnitTests.OPTION_UNDERLYING_FROM_ISIN:
554
+ df = fetch_option_underlying_tickers_from_isins()
555
+ print(df)
556
+ """
557
+
558
+
559
+ class UnitTests(Enum):
560
+ FIELD_TIMESERIES_PER_TICKERS = 1
561
+ FIELDS_TIMESERIES_PER_TICKER = 2
562
+ FUNDAMENTALS = 3
563
+ ACTIVE_FUTURES = 4
564
+ CONTRACT_TABLE = 5
565
+ IMPLIED_VOL_TIME_SERIES = 6
566
+ BOND_INFO = 7
567
+ LAST_PRICES = 8
568
+ CDS_INFO = 9
569
+ BALANCE_DATA = 10
570
+ TICKERS_FROM_ISIN = 11
571
+ # OPTION_UNDERLYING_FROM_ISIN = 14
572
+ DIVIDEND = 12
573
+ MEMBERS = 14
574
+ OPTION_CHAIN = 15
575
+ YIELD_CURVE = 16
576
+
577
+
578
+ def run_unit_test(unit_test: UnitTests):
579
+
580
+ pd.set_option('display.max_columns', 500)
581
+
582
+ if unit_test == UnitTests.FIELD_TIMESERIES_PER_TICKERS:
583
+ #df = fetch_field_timeseries_per_tickers(tickers=['ES1 Index', 'ES2 Index', 'ES3 Index'], field='PX_LAST',
584
+ # CshAdjNormal=False, CshAdjAbnormal=False, CapChg=False)
585
+ # df = fetch_field_timeseries_per_tickers(tickers=['CGS1U5 CBGN Curncy', 'CGS1U5 DRSK Curncy', 'CGS1U5 BEST Curncy'], field='PX_LAST')
586
+ # df = fetch_field_timeseries_per_tickers(tickers=['EUR003M Index'], field='PX_LAST')
587
+ # df = fetch_field_timeseries_per_tickers(tickers=['TY1 Comdty'], field='FUT_EQV_DUR_NOTL')
588
+ # df = fetch_field_timeseries_per_tickers(tickers=['TY1 Comdty', 'UXY1 Comdty'], start_date=pd.Timestamp('01Jan2015'), field='FUT_EQV_DUR_NOTL')
589
+ df = fetch_field_timeseries_per_tickers(tickers=['ZS877681 corp'], field='PX_LAST')
590
+ print(df)
591
+
592
+ elif unit_test == UnitTests.FIELDS_TIMESERIES_PER_TICKER:
593
+ df = fetch_fields_timeseries_per_ticker(ticker='ES1 Index', fields=['PX_LAST', 'FUT_DAYS_EXP'])
594
+ print(df)
595
+
596
+ elif unit_test == UnitTests.FUNDAMENTALS:
597
+ # df = fetch_fundamentals(tickers=['AAPL US Equity', 'BAC US Equity'],
598
+ # fields=['Security_Name', 'GICS_Sector_Name', 'CRNCY'])
599
+ df = fetch_fundamentals(tickers=['HAHYIM2 HK Equity'],
600
+ fields=['name', 'front_load', 'back_load', 'fund_mgr_stated_fee',
601
+ 'fund_min_invest'])
602
+ print(df)
603
+
604
+ elif unit_test == UnitTests.ACTIVE_FUTURES:
605
+ field_data = fetch_active_futures(generic_ticker='ES1 Index')
606
+ print(field_data)
607
+
608
+ elif unit_test == UnitTests.CONTRACT_TABLE:
609
+ df = fetch_futures_contract_table(ticker="NK1 Index")
610
+ print(df)
611
+
612
+ elif unit_test == UnitTests.IMPLIED_VOL_TIME_SERIES:
613
+ # df = fetch_vol_timeseries(ticker='SPX Index', vol_fields=[IMPVOL_FIELDS_MNY_30DAY, IMPVOL_FIELDS_MNY_60DAY,
614
+ # IMPVOL_FIELDS_MNY_3MTH, IMPVOL_FIELDS_MNY_6MTH,
615
+ # IMPVOL_FIELDS_MNY_12M])
616
+ df = fetch_vol_timeseries(ticker='EURUSD Curncy', vol_fields=['1M_CALL_IMP_VOL_10DELTA_DFLT',
617
+ '1M_PUT_IMP_VOL_10DELTA_DFLT'])
618
+ print(df)
619
+
620
+ elif unit_test == UnitTests.LAST_PRICES:
621
+ fx_prices = fetch_last_prices()
622
+ print(fx_prices)
623
+
624
+ elif unit_test == UnitTests.BOND_INFO:
625
+ # data = fetch_bonds_info()
626
+ # print(data)
627
+
628
+ data = fetch_bonds_info(isins=['US404280BL25'],
629
+ fields=['id_bb', 'name', 'security_des',
630
+ 'ult_parent_ticker_exchange', 'crncy',
631
+ 'amt_outstanding',
632
+ 'px_last',
633
+ 'yas_bond_yld', 'yas_oas_sprd', 'yas_mod_dur'])
634
+ print(data)
635
+
636
+ elif unit_test == UnitTests.CDS_INFO:
637
+ data = fetch_cds_info()
638
+ print(data)
639
+
640
+ elif unit_test == UnitTests.BALANCE_DATA:
641
+ data = fetch_balance_data(tickers=['ABI BB Equity', 'T US Equity', 'JPM US Equity', 'BAC US Equity'])
642
+ print(data)
643
+
644
+ elif unit_test == UnitTests.TICKERS_FROM_ISIN:
645
+ df = fetch_tickers_from_isins()
646
+ print(df)
647
+
648
+ elif unit_test == UnitTests.DIVIDEND:
649
+ this = fetch_dividend_history(ticker='SDHA LN Equity')
650
+ print(this)
651
+ divs, divs_1y = fetch_div_yields(tickers=['AHYG SP Equity'])
652
+ print(divs_1y)
653
+
654
+ elif unit_test == UnitTests.MEMBERS:
655
+ # members = fetch_index_members_weights(index='SPCPGN Index')
656
+ members = fetch_index_members_weights('I31415US Index', END_DATE_OVERRIDE='20200101')
657
+ # members = fetch_index_members_weights(index='I00182US Index')
658
+ print(members)
659
+ """
660
+ df = fetch_bonds_info(isins=members['member_ticker_and_exchange_code'].to_list(),
661
+ fields=['id_bb', 'name', 'security_des',
662
+ 'px_last', 'amt_outstanding',
663
+ 'yas_bond_yld', 'yld_ytc_mid', 'cpn',
664
+ 'yas_yld_spread', 'flt_spread'])
665
+
666
+ print(df)
667
+ df.to_clipboard()
668
+ """
669
+
670
+ elif unit_test == UnitTests.OPTION_CHAIN:
671
+ df = blp.bds('TSLA US Equity',
672
+ 'CHAIN_TICKERS',
673
+ # CHAIN_EXP_DT_OVRD='20210917',
674
+ CHAIN_PUT_CALL_TYPE_OVRD='PUT', # 'Call'
675
+ CHAIN_POINTS_OVRD=1000
676
+ )
677
+
678
+ print(df)
679
+
680
+ elif unit_test == UnitTests.YIELD_CURVE:
681
+ from datetime import date
682
+ YC_US = blp.bds("YCGT0025 Index", "INDX_MEMBERS")
683
+ print(YC_US)
684
+ YC_US_VAL = blp.bdp(YC_US.member_ticker_and_exchange_code.tolist(),
685
+ ['YLD_YTM_ASK', 'SECURITY NAME', 'MATURITY'])
686
+ YC_US_VAL.maturity = pd.to_datetime(YC_US_VAL.maturity)
687
+ YC_US_VAL["Yr"] = (YC_US_VAL.maturity - pd.to_datetime(date.today())) / np.timedelta64(365, 'D')
688
+ YC_US_VAL = YC_US_VAL.sort_values(by=["Yr"])
689
+
690
+ print(YC_US_VAL)
691
+
692
+
693
+ if __name__ == '__main__':
694
+
695
+ unit_test = UnitTests.FIELD_TIMESERIES_PER_TICKERS
696
+
697
+ is_run_all_tests = False
698
+ if is_run_all_tests:
699
+ for unit_test in UnitTests:
700
+ run_unit_test(unit_test=unit_test)
701
+ else:
702
+ run_unit_test(unit_test=unit_test)
@@ -0,0 +1,40 @@
1
+ [tool.poetry]
2
+ name = "bbg_fetch"
3
+ version = "1.0.31"
4
+ description = "Bloomberg fetching analytics wrapping xbbg package"
5
+ license = "LICENSE.txt"
6
+ authors = ["Artur Sepp <artursepp@gmail.com>"]
7
+ maintainers = ["Artur Sepp <artursepp@gmail.com>"]
8
+ readme = "README.md"
9
+ repository = "https://github.com/ArturSepp/BloombergFetch"
10
+ documentation = "https://github.com/ArturSepp/BloombergFetch"
11
+ keywords= ["quantitative", "investing", "portfolio optimization", "systematic strategies", "volatility"]
12
+ classifiers=[
13
+ "Development Status :: 4 - Beta",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Financial and Insurance Industry",
16
+ "Intended Audience :: Science/Research",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Natural Language :: English",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Topic :: Office/Business :: Financial :: Investment",
26
+ ]
27
+ #packages = [ {include = "bloomberg_fetch"} ]
28
+
29
+ [tool.poetry.urls]
30
+ "Issues" = "https://github.com/ArturSepp/BloombergFetch/issues"
31
+ "Personal website" = "https://artursepp.com"
32
+
33
+ [tool.poetry.dependencies]
34
+ python = ">=3.8"
35
+ pandas = ">=1.5.2"
36
+ xbbg = ">=0.0.7"
37
+
38
+ [build-system]
39
+ requires = ["poetry-core>=1.0.0"]
40
+ build-backend = "poetry.core.masonry.api"