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.
- bbg_fetch-1.0.31/PKG-INFO +61 -0
- bbg_fetch-1.0.31/README.md +24 -0
- bbg_fetch-1.0.31/bbg_fetch.py +702 -0
- bbg_fetch-1.0.31/pyproject.toml +40 -0
|
@@ -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"
|