PYield 0.7.7__tar.gz → 0.7.9__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.
- {pyield-0.7.7 → pyield-0.7.9}/PKG-INFO +7 -7
- {pyield-0.7.7 → pyield-0.7.9}/README.md +5 -5
- pyield-0.7.9/pyield/__about__.py +1 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/__init__.py +6 -2
- {pyield-0.7.7 → pyield-0.7.9}/pyield/data_access.py +58 -24
- pyield-0.7.9/pyield/futures/__init__.py +4 -0
- pyield-0.7.7/pyield/futures/common.py → pyield-0.7.9/pyield/futures/historical.py +130 -78
- pyield-0.7.9/pyield/futures/intraday.py +107 -0
- pyield-0.7.9/pyield/spreads.py +56 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/treasuries.py +0 -52
- {pyield-0.7.7 → pyield-0.7.9}/pyproject.toml +2 -2
- pyield-0.7.9/tests/test_futures.py +38 -0
- pyield-0.7.7/pyield/futures/di_xml.py → pyield-0.7.9/tests/xml.py +4 -4
- pyield-0.7.7/pyield/__about__.py +0 -1
- pyield-0.7.7/pyield/data_analysis.py +0 -33
- pyield-0.7.7/pyield/futures/__init__.py +0 -13
- pyield-0.7.7/pyield/futures/ddi.py +0 -101
- pyield-0.7.7/pyield/futures/di.py +0 -160
- pyield-0.7.7/pyield/futures/frc.py +0 -61
- pyield-0.7.7/tests/test_futures.py +0 -80
- {pyield-0.7.7 → pyield-0.7.9}/LICENSE +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/bday.py +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/holidays/__init__.py +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/holidays/core.py +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/indicators.py +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/projections.py +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/py.typed +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/pyield/utils.py +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/tests/__init__.py +0 -0
- {pyield-0.7.7 → pyield-0.7.9}/tests/test_bday.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PYield
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.9
|
|
4
4
|
Summary: A Python library for analysis of fixed income instruments in Brazil
|
|
5
5
|
Keywords: fixed-income, brazil, finance, analysis, bonds
|
|
6
6
|
Author-Email: Carlos Carvalho <cr.cj@outlook.com>
|
|
@@ -32,13 +32,13 @@ Classifier: Intended Audience :: Financial and Insurance Industry
|
|
|
32
32
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
33
33
|
Project-URL: Source, https://github.com/crdcj/PYield
|
|
34
34
|
Requires-Python: >=3.11
|
|
35
|
-
Requires-Dist: requests
|
|
36
35
|
Requires-Dist: pandas>=2.0.0
|
|
37
36
|
Requires-Dist: numpy
|
|
38
37
|
Requires-Dist: beautifulsoup4
|
|
39
38
|
Requires-Dist: html5lib
|
|
40
39
|
Requires-Dist: lxml
|
|
41
40
|
Requires-Dist: python-calamine>=0.2.0
|
|
41
|
+
Requires-Dist: requests>=2.31.0
|
|
42
42
|
Description-Content-Type: text/markdown
|
|
43
43
|
|
|
44
44
|
[](https://pypi.python.org/pypi/pyield)
|
|
@@ -101,7 +101,7 @@ dtype: datetime64[ns]
|
|
|
101
101
|
```python
|
|
102
102
|
# Fetch current DI Futures data from B3 (15 minutes delay)
|
|
103
103
|
>>> yd.fetch_asset(asset_code="DI1")
|
|
104
|
-
TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate
|
|
104
|
+
TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate CurrentRate
|
|
105
105
|
2024-04-21 13:37:39 DI1K24 2024-05-02 7 ... 0.10660 0.10652 0.10660 0.10660
|
|
106
106
|
2024-04-21 13:37:39 DI1M24 2024-06-03 28 ... 0.10518 0.10510 0.10516 0.10518
|
|
107
107
|
2024-04-21 13:37:39 DI1N24 2024-07-01 48 ... 0.10480 0.10456 0.10462 0.10460
|
|
@@ -158,11 +158,11 @@ BondType ReferenceDate MaturityDate DISpread
|
|
|
158
158
|
```python
|
|
159
159
|
# Fetch the SELIC target rate from the Central Bank of Brazil
|
|
160
160
|
>>> yd.fetch_indicator(indicator_code="SELIC", reference_date='2024-04-12')
|
|
161
|
-
10.75
|
|
161
|
+
0.1075 # 10.75%
|
|
162
162
|
|
|
163
163
|
# Fetch the IPCA monthly inflation rate from IBGE
|
|
164
164
|
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-03-18')
|
|
165
|
-
0.16
|
|
165
|
+
0.16 # 0.16%
|
|
166
166
|
|
|
167
167
|
# If no data is yet available for the indicator, the function returns None
|
|
168
168
|
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-04-10')
|
|
@@ -178,9 +178,9 @@ IndicatorProjection(
|
|
|
178
178
|
last_updated=Timestamp('2024-04-19 18:55:00'),
|
|
179
179
|
reference_month_ts=Timestamp('2024-04-01 00:00:00'),
|
|
180
180
|
reference_month_br='ABR/2024',
|
|
181
|
-
projected_value=0.0035
|
|
181
|
+
projected_value=0.0035 # 0.35%
|
|
182
182
|
)
|
|
183
183
|
>>> ipca.projected_value
|
|
184
|
-
0.0035
|
|
184
|
+
0.0035 # 0.35%
|
|
185
185
|
```
|
|
186
186
|
|
|
@@ -58,7 +58,7 @@ dtype: datetime64[ns]
|
|
|
58
58
|
```python
|
|
59
59
|
# Fetch current DI Futures data from B3 (15 minutes delay)
|
|
60
60
|
>>> yd.fetch_asset(asset_code="DI1")
|
|
61
|
-
TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate
|
|
61
|
+
TradeTimestamp TickerSymbol ExpirationDate BDaysToExp ... MaxRate LastAskRate LastBidRate CurrentRate
|
|
62
62
|
2024-04-21 13:37:39 DI1K24 2024-05-02 7 ... 0.10660 0.10652 0.10660 0.10660
|
|
63
63
|
2024-04-21 13:37:39 DI1M24 2024-06-03 28 ... 0.10518 0.10510 0.10516 0.10518
|
|
64
64
|
2024-04-21 13:37:39 DI1N24 2024-07-01 48 ... 0.10480 0.10456 0.10462 0.10460
|
|
@@ -115,11 +115,11 @@ BondType ReferenceDate MaturityDate DISpread
|
|
|
115
115
|
```python
|
|
116
116
|
# Fetch the SELIC target rate from the Central Bank of Brazil
|
|
117
117
|
>>> yd.fetch_indicator(indicator_code="SELIC", reference_date='2024-04-12')
|
|
118
|
-
10.75
|
|
118
|
+
0.1075 # 10.75%
|
|
119
119
|
|
|
120
120
|
# Fetch the IPCA monthly inflation rate from IBGE
|
|
121
121
|
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-03-18')
|
|
122
|
-
0.16
|
|
122
|
+
0.16 # 0.16%
|
|
123
123
|
|
|
124
124
|
# If no data is yet available for the indicator, the function returns None
|
|
125
125
|
>>> yd.fetch_indicator(indicator_code="IPCA", reference_date='2024-04-10')
|
|
@@ -135,9 +135,9 @@ IndicatorProjection(
|
|
|
135
135
|
last_updated=Timestamp('2024-04-19 18:55:00'),
|
|
136
136
|
reference_month_ts=Timestamp('2024-04-01 00:00:00'),
|
|
137
137
|
reference_month_br='ABR/2024',
|
|
138
|
-
projected_value=0.0035
|
|
138
|
+
projected_value=0.0035 # 0.35%
|
|
139
139
|
)
|
|
140
140
|
>>> ipca.projected_value
|
|
141
|
-
0.0035
|
|
141
|
+
0.0035 # 0.35%
|
|
142
142
|
```
|
|
143
143
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.9"
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
from .__about__ import __version__
|
|
2
2
|
from .bday import count_bdays, generate_bdays, is_bday, offset_bdays
|
|
3
|
-
from .data_access import
|
|
4
|
-
|
|
3
|
+
from .data_access import (
|
|
4
|
+
calculate_spreads,
|
|
5
|
+
fetch_asset,
|
|
6
|
+
fetch_indicator,
|
|
7
|
+
fetch_projection,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
10
|
__all__ = [
|
|
7
11
|
"__version__",
|
|
@@ -3,14 +3,13 @@ import pandas as pd
|
|
|
3
3
|
from . import futures as ft
|
|
4
4
|
from . import indicators as it
|
|
5
5
|
from . import projections as pr
|
|
6
|
+
from . import spreads as sp
|
|
6
7
|
from . import treasuries as tr
|
|
7
8
|
from .utils import _normalize_date
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def fetch_asset(
|
|
11
|
-
asset_code: str,
|
|
12
|
-
reference_date: str | pd.Timestamp | None = None,
|
|
13
|
-
**kwargs,
|
|
12
|
+
asset_code: str, reference_date: str | pd.Timestamp | None = None
|
|
14
13
|
) -> pd.DataFrame:
|
|
15
14
|
"""
|
|
16
15
|
Fetches data for a specified asset based on type and reference date.
|
|
@@ -24,6 +23,11 @@ def fetch_asset(
|
|
|
24
23
|
- "DI1": One-day Interbank Deposit Futures (Futuro de DI) from B3.
|
|
25
24
|
- "DDI": DI x U.S. Dollar Spread Futures (Futuro de Cupom Cambial) from B3.
|
|
26
25
|
- "FRC": Forward Rate Agreement (FRA) from B3.
|
|
26
|
+
- "DAP": DI x IPCA Spread Futures.
|
|
27
|
+
- "DOL": U.S. Dollar Futures from B3.
|
|
28
|
+
- "WDO": Mini U.S. Dollar Futures from B3.
|
|
29
|
+
- "IND": Ibovespa Futures from B3.
|
|
30
|
+
- "WIN": Mini Ibovespa Futures from B3.
|
|
27
31
|
reference_date (str | pd.Timestamp | None): The reference date for which data is
|
|
28
32
|
fetched. Defaults to the last business day if None.
|
|
29
33
|
**kwargs: Additional keyword arguments, specifically:
|
|
@@ -40,27 +44,28 @@ def fetch_asset(
|
|
|
40
44
|
>>> fetch_asset('TRB', '2023-04-01')
|
|
41
45
|
>>> fetch_asset('DI1', '2023-04-01', return_raw=True)
|
|
42
46
|
"""
|
|
43
|
-
|
|
47
|
+
SUPPORTED_BONDS = ["LTN", "LFT", "NTN-F", "NTN-B"]
|
|
48
|
+
SUPPORTED_FUTURES = ["DI1", "DDI", "FRC", "DAP", "DOL", "WDO", "IND", "WIN"]
|
|
49
|
+
|
|
44
50
|
normalized_date = _normalize_date(reference_date)
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
today = pd.Timestamp.today().normalize()
|
|
53
|
+
if normalized_date == today:
|
|
54
|
+
return ft.fetch_intraday_df(future_code=asset_code.upper())
|
|
55
|
+
|
|
56
|
+
if asset_code.upper() == "TRB":
|
|
57
|
+
return tr.fetch_bonds(reference_date=normalized_date)
|
|
58
|
+
|
|
59
|
+
if asset_code.upper() in SUPPORTED_BONDS:
|
|
49
60
|
df = tr.fetch_bonds(reference_date=normalized_date)
|
|
50
61
|
return df.query(f"BondType == '{asset_code.upper()}'")
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
elif asset_code.lower() == "ddi":
|
|
59
|
-
return ft.fetch_past_ddi(trade_date=normalized_date, return_raw=return_raw)
|
|
60
|
-
elif asset_code.lower() == "frc":
|
|
61
|
-
return ft.fetch_past_frc(trade_date=normalized_date, return_raw=return_raw)
|
|
62
|
-
else:
|
|
63
|
-
raise ValueError("Asset type not supported.")
|
|
63
|
+
if asset_code.upper() in SUPPORTED_FUTURES:
|
|
64
|
+
return ft.fetch_historical_df(
|
|
65
|
+
asset_code=asset_code.upper(), trade_date=normalized_date
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
raise ValueError("Asset type not supported.")
|
|
64
69
|
|
|
65
70
|
|
|
66
71
|
def fetch_indicator(
|
|
@@ -99,13 +104,13 @@ def fetch_indicator(
|
|
|
99
104
|
"""
|
|
100
105
|
normalized_date = _normalize_date(reference_date)
|
|
101
106
|
|
|
102
|
-
if indicator_code.
|
|
107
|
+
if indicator_code.upper() == "SELIC":
|
|
103
108
|
return it.fetch_selic_target(reference_date=normalized_date)
|
|
104
|
-
elif indicator_code.
|
|
109
|
+
elif indicator_code.upper() == "IPCA":
|
|
105
110
|
return it.fetch_ipca_mr(reference_date=normalized_date)
|
|
106
|
-
elif indicator_code.
|
|
111
|
+
elif indicator_code.upper() == "DI":
|
|
107
112
|
return it.fetch_di(reference_date=normalized_date)
|
|
108
|
-
elif indicator_code.
|
|
113
|
+
elif indicator_code.upper() == "VNA_LFT":
|
|
109
114
|
return it.fetch_vna_selic(reference_date=normalized_date)
|
|
110
115
|
else:
|
|
111
116
|
raise ValueError("Indicator type not supported.")
|
|
@@ -142,7 +147,36 @@ def fetch_projection(projection_code: str) -> pr.IndicatorProjection:
|
|
|
142
147
|
)
|
|
143
148
|
"""
|
|
144
149
|
|
|
145
|
-
if projection_code.
|
|
150
|
+
if projection_code.upper() == "IPCA_CM":
|
|
146
151
|
return pr.fetch_current_month_ipca_projection()
|
|
147
152
|
else:
|
|
148
153
|
raise ValueError("Projection type not supported.")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def calculate_spreads(
|
|
157
|
+
spread_type: str, reference_date: str | pd.Timestamp | None = None
|
|
158
|
+
) -> pd.DataFrame:
|
|
159
|
+
"""
|
|
160
|
+
Calculates spreads between assets based on the specified spread type.
|
|
161
|
+
If no reference date is provided, the function uses the previous business day.
|
|
162
|
+
|
|
163
|
+
Parameters:
|
|
164
|
+
spread_type (str): The type of spread to calculate. Available options are:
|
|
165
|
+
- "DI_vs_PRE": the spread between DI Futures and Treasury Pre-Fixed bonds.
|
|
166
|
+
reference_date (str | pd.Timestamp, optional): The reference date for the
|
|
167
|
+
spread calculation. If None or not provided, defaults to the previous
|
|
168
|
+
business day according to the Brazilian calendar.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
pd.DataFrame: A DataFrame containing the calculated spread in basis points.
|
|
172
|
+
The data is sorted by asset type and maturity/expiration date.
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ValueError: If an invalid spread type is provided.
|
|
176
|
+
"""
|
|
177
|
+
# Normalize the reference date
|
|
178
|
+
normalized_date = _normalize_date(reference_date)
|
|
179
|
+
if spread_type.upper() == "DI_VS_PRE":
|
|
180
|
+
return sp.calculate_di_spreads(normalized_date)
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError("Invalid spread type.")
|
|
@@ -5,26 +5,15 @@ import requests
|
|
|
5
5
|
|
|
6
6
|
from .. import bday
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"AJUSTE CORRIG. (4)": "AdjSettlementRate",
|
|
18
|
-
"AJUSTE DE REF.": "SettlementRate", # FRC
|
|
19
|
-
"PREÇO MÍN.": "MinRate",
|
|
20
|
-
"PREÇO MÉD.": "AvgRate",
|
|
21
|
-
"PREÇO MÁX.": "MaxRate",
|
|
22
|
-
"PREÇO ABERTU.": "FirstRate",
|
|
23
|
-
"ÚLT. PREÇO": "CloseRate",
|
|
24
|
-
"VAR. PTOS.": "PointsVariation",
|
|
25
|
-
# Attention: bid/ask rates are inverted
|
|
26
|
-
"ÚLT.OF. COMPRA": "CloseAskRate",
|
|
27
|
-
"ÚLT.OF. VENDA": "CloseBidRate",
|
|
8
|
+
COUNT_CONVENTIONS = {
|
|
9
|
+
"DAP": 252,
|
|
10
|
+
"DI1": 252,
|
|
11
|
+
"DDI": 360,
|
|
12
|
+
"FRC": None,
|
|
13
|
+
"DOL": None,
|
|
14
|
+
"WDO": None,
|
|
15
|
+
"IND": None,
|
|
16
|
+
"WIN": None,
|
|
28
17
|
}
|
|
29
18
|
|
|
30
19
|
|
|
@@ -153,7 +142,33 @@ def get_old_expiration_date(
|
|
|
153
142
|
return pd.NaT # type: ignore
|
|
154
143
|
|
|
155
144
|
|
|
156
|
-
def
|
|
145
|
+
def convert_prices_to_rates(
|
|
146
|
+
prices: pd.Series, days_to_expiration: pd.Series, count_convention: int
|
|
147
|
+
) -> pd.Series:
|
|
148
|
+
"""
|
|
149
|
+
Internal function to convert DI futures prices to rates.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
prices (pd.Series): The futures prices to be converted.
|
|
153
|
+
days_to_expiration (pd.Series): The number of days to expiration for each price.
|
|
154
|
+
count_convention (int): The count convention for the DI futures contract.
|
|
155
|
+
Can be 252 business days or 360 calendar days.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
pd.Series: A pd.Series containing the futures rates.
|
|
159
|
+
"""
|
|
160
|
+
if count_convention == 252:
|
|
161
|
+
rates = (100_000 / prices) ** (252 / days_to_expiration) - 1
|
|
162
|
+
elif count_convention == 360:
|
|
163
|
+
rates = (100_000 / prices - 1) * (360 / days_to_expiration)
|
|
164
|
+
else:
|
|
165
|
+
raise ValueError("Invalid count_convention. Must be 252 or 360.")
|
|
166
|
+
|
|
167
|
+
# Round to 5 (3 in %) dec. places (contract's current max. precision)
|
|
168
|
+
return rates.round(5)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
157
172
|
"""
|
|
158
173
|
Fetch the historical futures data from B3 for a specific trade date. If the data is
|
|
159
174
|
not available, an empty DataFrame is returned.
|
|
@@ -199,60 +214,17 @@ def fetch_past_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame
|
|
|
199
214
|
return df
|
|
200
215
|
|
|
201
216
|
|
|
202
|
-
def
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
future_code (str): The future code to fetch data for.
|
|
208
|
-
|
|
209
|
-
Returns:
|
|
210
|
-
pd.DataFrame: A DataFrame containing the normalized and cleaned data from the API.
|
|
211
|
-
|
|
212
|
-
Raises:
|
|
213
|
-
Exception: An exception is raised if the data fetch operation fails.
|
|
214
|
-
"""
|
|
215
|
-
|
|
216
|
-
url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{future_code}"
|
|
217
|
-
|
|
218
|
-
try:
|
|
219
|
-
r = requests.get(url)
|
|
220
|
-
r.raise_for_status() # Check for HTTP request errors
|
|
221
|
-
except requests.exceptions.RequestException:
|
|
222
|
-
raise Exception(f"Failed to fetch data for {future_code}.") from None
|
|
223
|
-
|
|
224
|
-
r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
|
|
217
|
+
def _adjust_older_contracts_rates(df: pd.DataFrame, rate_cols: list) -> pd.DataFrame:
|
|
218
|
+
for col in rate_cols:
|
|
219
|
+
df[col] = convert_prices_to_rates(df[col], df["BDaysToExp"], 252)
|
|
225
220
|
|
|
226
|
-
#
|
|
227
|
-
df =
|
|
228
|
-
|
|
229
|
-
# Clean and reformat the DataFrame columns
|
|
230
|
-
df.columns = (df.columns
|
|
231
|
-
.str.replace("SctyQtn.", "")
|
|
232
|
-
.str.replace("asset.AsstSummry.", "")
|
|
233
|
-
) # fmt: skip
|
|
234
|
-
df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
|
|
235
|
-
|
|
236
|
-
# Convert maturity codes to datetime and drop rows with missing values
|
|
237
|
-
df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
|
|
238
|
-
df.dropna(subset=["mtrtyCode"], inplace=True)
|
|
239
|
-
|
|
240
|
-
# Sort the DataFrame by maturity code and reset the index
|
|
241
|
-
df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
|
|
242
|
-
|
|
243
|
-
# Get current date and time
|
|
244
|
-
now = pd.Timestamp.now().round("s")
|
|
245
|
-
# Subtract 15 minutes from the current time to account for API delay
|
|
246
|
-
trade_ts = now - pd.Timedelta(minutes=15)
|
|
247
|
-
df["TradeTimestamp"] = trade_ts
|
|
248
|
-
|
|
249
|
-
# Convert DataFrame to use nullable data types for better type consistency
|
|
250
|
-
df = df.convert_dtypes(dtype_backend="numpy_nullable")
|
|
221
|
+
# Invert low and high prices
|
|
222
|
+
df["MinRate"], df["MaxRate"] = df["MaxRate"], df["MinRate"]
|
|
251
223
|
|
|
252
224
|
return df
|
|
253
225
|
|
|
254
226
|
|
|
255
|
-
def rename_columns(df: pd.DataFrame):
|
|
227
|
+
def rename_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
256
228
|
all_columns = {
|
|
257
229
|
"VENCTO": "ExpirationCode",
|
|
258
230
|
"CONTR. ABERT.(1)": "OpenContracts", # At the start of the day
|
|
@@ -274,13 +246,68 @@ def rename_columns(df: pd.DataFrame):
|
|
|
274
246
|
"ÚLT.OF. COMPRA": "CloseAskRate",
|
|
275
247
|
"ÚLT.OF. VENDA": "CloseBidRate",
|
|
276
248
|
}
|
|
277
|
-
rename_dict = {}
|
|
278
|
-
for col in all_columns:
|
|
279
|
-
if col in df.columns:
|
|
280
|
-
rename_dict[col] = all_columns[col]
|
|
249
|
+
rename_dict = {c: all_columns[c] for c in all_columns if c in df.columns}
|
|
281
250
|
return df.rename(columns=rename_dict)
|
|
282
251
|
|
|
283
252
|
|
|
253
|
+
def process_df(
|
|
254
|
+
df: pd.DataFrame,
|
|
255
|
+
trade_date: pd.Timestamp,
|
|
256
|
+
asset_code: str,
|
|
257
|
+
) -> pd.DataFrame:
|
|
258
|
+
df["TradeDate"] = trade_date
|
|
259
|
+
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
260
|
+
df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
|
|
261
|
+
|
|
262
|
+
df["TickerSymbol"] = asset_code + df["ExpirationCode"]
|
|
263
|
+
|
|
264
|
+
# Contract code format was changed in 22/05/2006
|
|
265
|
+
if trade_date < pd.Timestamp("2006-05-22"):
|
|
266
|
+
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
267
|
+
get_old_expiration_date, args=(trade_date,)
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
df["ExpirationDate"] = df["ExpirationCode"].apply(get_expiration_date)
|
|
271
|
+
|
|
272
|
+
df["DaysToExp"] = (df["ExpirationDate"] - trade_date).dt.days
|
|
273
|
+
# Convert to nullable integer, since it is the default type in the library
|
|
274
|
+
df["DaysToExp"] = df["DaysToExp"].astype(pd.Int64Dtype())
|
|
275
|
+
# Remove expired contracts
|
|
276
|
+
df.query("DaysToExp > 0", inplace=True)
|
|
277
|
+
|
|
278
|
+
df["BDaysToExp"] = bday.count_bdays(trade_date, df["ExpirationDate"])
|
|
279
|
+
|
|
280
|
+
# Columns where 0 means NaN
|
|
281
|
+
cols_with_nan = [col for col in df.columns if "Rate" in col]
|
|
282
|
+
if "SettlementPrice" in df.columns:
|
|
283
|
+
cols_with_nan.append("SettlementPrice")
|
|
284
|
+
# Replace 0 with NaN in these columns
|
|
285
|
+
df[cols_with_nan] = df[cols_with_nan].replace(0, pd.NA)
|
|
286
|
+
|
|
287
|
+
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
288
|
+
# Prior to 17/01/2002 (inclusive), DI prices were not converted to rates
|
|
289
|
+
if trade_date <= pd.Timestamp("2002-01-17") and asset_code == "DI1":
|
|
290
|
+
df = _adjust_older_contracts_rates(df, rate_cols)
|
|
291
|
+
else:
|
|
292
|
+
# Remove % and round to 5 (3 in %) dec. places in rate columns
|
|
293
|
+
df[rate_cols] = df[rate_cols].div(100).round(5)
|
|
294
|
+
|
|
295
|
+
if COUNT_CONVENTIONS[asset_code] == 252:
|
|
296
|
+
df["SettlementRate"] = convert_prices_to_rates(
|
|
297
|
+
prices=df["SettlementPrice"],
|
|
298
|
+
days_to_expiration=df["BDaysToExp"],
|
|
299
|
+
count_convention=252,
|
|
300
|
+
)
|
|
301
|
+
elif COUNT_CONVENTIONS[asset_code] == 360:
|
|
302
|
+
df["SettlementRate"] = convert_prices_to_rates(
|
|
303
|
+
prices=df["SettlementPrice"],
|
|
304
|
+
days_to_expiration=df["DaysToExp"],
|
|
305
|
+
count_convention=360,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return df
|
|
309
|
+
|
|
310
|
+
|
|
284
311
|
def reorder_columns(df: pd.DataFrame):
|
|
285
312
|
all_columns = [
|
|
286
313
|
"TradeDate",
|
|
@@ -305,8 +332,33 @@ def reorder_columns(df: pd.DataFrame):
|
|
|
305
332
|
"CloseBidRate",
|
|
306
333
|
"CloseRate",
|
|
307
334
|
]
|
|
308
|
-
reordered_columns = []
|
|
309
|
-
for col in all_columns:
|
|
310
|
-
if col in df.columns:
|
|
311
|
-
reordered_columns.append(col)
|
|
335
|
+
reordered_columns = [col for col in all_columns if col in df.columns]
|
|
312
336
|
return df[reordered_columns]
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def fetch_historical_df(
|
|
340
|
+
asset_code: str,
|
|
341
|
+
trade_date: pd.Timestamp,
|
|
342
|
+
) -> pd.DataFrame:
|
|
343
|
+
"""
|
|
344
|
+
Fetchs the futures data for a given date from B3.
|
|
345
|
+
|
|
346
|
+
This function fetches and processes the futures data from B3 for a specific
|
|
347
|
+
trade date. It's the primary external interface for accessing futures data.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
asset_code (str): The asset code to fetch the futures data.
|
|
351
|
+
trade_date (pd.Timestamp): The trade date to fetch the futures data.
|
|
352
|
+
count_convention (int): The count convention for the DI futures contract.
|
|
353
|
+
Can be 252 business days or 360 calendar days.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
pd.DataFrame: A Pandas pd.DataFrame containing processed futures data.
|
|
357
|
+
"""
|
|
358
|
+
df_raw = fetch_raw_df(asset_code=asset_code, trade_date=trade_date)
|
|
359
|
+
if df_raw.empty:
|
|
360
|
+
return df_raw
|
|
361
|
+
df = rename_columns(df_raw)
|
|
362
|
+
df = process_df(df, trade_date, asset_code)
|
|
363
|
+
df = reorder_columns(df)
|
|
364
|
+
return df
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _fetch_raw_df(future_code: str) -> pd.DataFrame:
|
|
6
|
+
"""
|
|
7
|
+
Fetch the latest data for a given future code from B3 derivatives quotation API.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
future_code (str): The future code to fetch data for.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
pd.DataFrame: A DataFrame containing the normalized and cleaned data from the API.
|
|
14
|
+
|
|
15
|
+
Raises:
|
|
16
|
+
Exception: An exception is raised if the data fetch operation fails.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
url = f"https://cotacao.b3.com.br/mds/api/v1/DerivativeQuotation/{future_code}"
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
r = requests.get(url)
|
|
23
|
+
r.raise_for_status() # Check for HTTP request errors
|
|
24
|
+
except requests.exceptions.RequestException:
|
|
25
|
+
raise Exception(f"Failed to fetch data for {future_code}.") from None
|
|
26
|
+
|
|
27
|
+
r.encoding = "utf-8" # Explicitly set response encoding to utf-8 for consistency
|
|
28
|
+
|
|
29
|
+
if "Quotation not available" in r.text:
|
|
30
|
+
return pd.DataFrame()
|
|
31
|
+
|
|
32
|
+
# Normalize JSON response into a flat table
|
|
33
|
+
df = pd.json_normalize(r.json()["Scty"])
|
|
34
|
+
|
|
35
|
+
# Clean and reformat the DataFrame columns
|
|
36
|
+
df.columns = (df.columns
|
|
37
|
+
.str.replace("SctyQtn.", "")
|
|
38
|
+
.str.replace("asset.AsstSummry.", "")
|
|
39
|
+
) # fmt: skip
|
|
40
|
+
df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
|
|
41
|
+
|
|
42
|
+
# Convert maturity codes to datetime and drop rows with missing values
|
|
43
|
+
df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
|
|
44
|
+
df.dropna(subset=["mtrtyCode"], inplace=True)
|
|
45
|
+
|
|
46
|
+
# Sort the DataFrame by maturity code and reset the index
|
|
47
|
+
df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
|
|
48
|
+
|
|
49
|
+
# Get current date and time
|
|
50
|
+
now = pd.Timestamp.now().round("s")
|
|
51
|
+
# Subtract 15 minutes from the current time to account for API delay
|
|
52
|
+
trade_ts = now - pd.Timedelta(minutes=15)
|
|
53
|
+
df["TradeTimestamp"] = trade_ts
|
|
54
|
+
|
|
55
|
+
# Convert DataFrame to use nullable data types for better type consistency
|
|
56
|
+
df = df.convert_dtypes(dtype_backend="numpy_nullable")
|
|
57
|
+
|
|
58
|
+
return df
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _process_raw_df(raw_df: pd.DataFrame) -> pd.DataFrame:
|
|
62
|
+
df = raw_df.copy()
|
|
63
|
+
|
|
64
|
+
# Columns to be renamed
|
|
65
|
+
all_columns = {
|
|
66
|
+
"TradeTimestamp": "TradeTimestamp",
|
|
67
|
+
"symb": "TickerSymbol",
|
|
68
|
+
"mtrtyCode": "ExpirationDate",
|
|
69
|
+
"BDaysToExp": "BDaysToExp",
|
|
70
|
+
"opnCtrcts": "OpenContracts",
|
|
71
|
+
"tradQty": "TradeCount",
|
|
72
|
+
"traddCtrctsQty": "TradeVolume",
|
|
73
|
+
"grssAmt": "FinancialVolume",
|
|
74
|
+
"prvsDayAdjstmntPric": "PrevSettlementRate",
|
|
75
|
+
"bottomLmtPric": "MinLimitRate",
|
|
76
|
+
"topLmtPric": "MaxLimitRate",
|
|
77
|
+
"opngPric": "OpenRate",
|
|
78
|
+
"minPric": "MinRate",
|
|
79
|
+
"avrgPric": "AvgRate",
|
|
80
|
+
"maxPric": "MaxRate",
|
|
81
|
+
"buyOffer.price": "CurrentAskRate",
|
|
82
|
+
"sellOffer.price": "CurrentBidRate",
|
|
83
|
+
"curPrc": "CurrentRate",
|
|
84
|
+
}
|
|
85
|
+
# Check which columns are present in the DataFrame before renaming
|
|
86
|
+
rename_dict = {c: all_columns[c] for c in all_columns if c in df.columns}
|
|
87
|
+
df = df.rename(columns=rename_dict)
|
|
88
|
+
|
|
89
|
+
# df["BDaysToExp"] = bd.count_bdays(df["TradeTimestamp"], df["ExpirationDate"])
|
|
90
|
+
|
|
91
|
+
# Remove percentage in all rate columns
|
|
92
|
+
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
93
|
+
df[rate_cols] = df[rate_cols] / 100
|
|
94
|
+
|
|
95
|
+
# Reorder columns based on the order of the dictionary
|
|
96
|
+
return df[rename_dict.values()]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def fetch_intraday_df(future_code: str) -> pd.DataFrame:
|
|
100
|
+
"""
|
|
101
|
+
Fetch the latest DI futures data from B3.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
|
|
105
|
+
"""
|
|
106
|
+
raw_df = _fetch_raw_df(future_code)
|
|
107
|
+
return _process_raw_df(raw_df)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from . import treasuries as tr
|
|
4
|
+
from .futures import historical as fh
|
|
5
|
+
|
|
6
|
+
# Constant for conversion to basis points
|
|
7
|
+
BPS_CONVERSION_FACTOR = 10_000
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def calculate_di_spreads(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
11
|
+
"""
|
|
12
|
+
Calculates the DI spread for Brazilian treasury bonds (LTN and NTN-F) based on
|
|
13
|
+
ANBIMA's indicative rates.
|
|
14
|
+
|
|
15
|
+
This function fetches the indicative rates for Brazilian treasury securities (LTN
|
|
16
|
+
and NTN-F bonds) and the DI futures rates for a specified reference date,
|
|
17
|
+
calculating the spread between these rates in basis points. If no reference date is
|
|
18
|
+
provided, the function uses the previous business day.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
reference_date (str | pd.Timestamp, optional): The reference date for the DI
|
|
22
|
+
spread calculation.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
pd.DataFrame: A DataFrame containing the bond type, reference date, maturity
|
|
26
|
+
date, and the calculated DI spread in basis points. The data is sorted by
|
|
27
|
+
bond type and maturity date.
|
|
28
|
+
"""
|
|
29
|
+
# Fetch DI rates for the reference date
|
|
30
|
+
df_di = fh.fetch_historical_df(asset_code="DI1", trade_date=reference_date)[
|
|
31
|
+
["ExpirationDate", "SettlementRate"]
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# Renaming the columns to match the ANBIMA structure
|
|
35
|
+
df_di.rename(columns={"ExpirationDate": "MaturityDate"}, inplace=True)
|
|
36
|
+
|
|
37
|
+
# Adjusting maturity date to match bond data format
|
|
38
|
+
df_di["MaturityDate"] = df_di["MaturityDate"].dt.to_period("M").dt.to_timestamp()
|
|
39
|
+
|
|
40
|
+
# Fetch bond rates, filtering for LTN and NTN-F types
|
|
41
|
+
df_anbima = tr.fetch_bonds(reference_date, False)
|
|
42
|
+
df_anbima.query("BondType in ['LTN', 'NTN-F']", inplace=True)
|
|
43
|
+
|
|
44
|
+
# Merge bond and DI rates by maturity date to calculate spreads
|
|
45
|
+
df_final = pd.merge(df_anbima, df_di, how="left", on="MaturityDate")
|
|
46
|
+
|
|
47
|
+
# Calculate the DI spread as the difference between indicative and settlement rates
|
|
48
|
+
df_final["DISpread"] = df_final["IndicativeRate"] - df_final["SettlementRate"]
|
|
49
|
+
|
|
50
|
+
# Convert spread to basis points for clarity
|
|
51
|
+
df_final["DISpread"] = (BPS_CONVERSION_FACTOR * df_final["DISpread"]).round(2)
|
|
52
|
+
|
|
53
|
+
# Prepare and return the final sorted DataFrame
|
|
54
|
+
select_columns = ["BondType", "ReferenceDate", "MaturityDate", "DISpread"]
|
|
55
|
+
df_final = df_final[select_columns].copy()
|
|
56
|
+
return df_final.sort_values(["BondType", "MaturityDate"], ignore_index=True)
|
|
@@ -3,15 +3,10 @@ import io
|
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
|
-
from . import futures as ft
|
|
7
|
-
|
|
8
6
|
# URL Constants
|
|
9
7
|
ANBIMA_NON_MEMBER_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs/"
|
|
10
8
|
ANBIMA_MEMBER_URL = "http://www.anbima.associados.rtm/merc_sec/arqs/"
|
|
11
9
|
|
|
12
|
-
# Constant for conversion to basis points
|
|
13
|
-
BPS_CONVERSION_FACTOR = 10_000
|
|
14
|
-
|
|
15
10
|
|
|
16
11
|
def _get_anbima_content(reference_date: pd.Timestamp) -> str:
|
|
17
12
|
url_date = reference_date.strftime("%y%m%d")
|
|
@@ -128,50 +123,3 @@ def fetch_bonds(reference_date: pd.Timestamp, return_raw=False) -> pd.DataFrame:
|
|
|
128
123
|
df = _process_raw_df(df)
|
|
129
124
|
|
|
130
125
|
return df
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def calculate_di_spreads(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
134
|
-
"""
|
|
135
|
-
Calculates the DI spread for Brazilian treasury bonds (LTN and NTN-F) based on
|
|
136
|
-
ANBIMA's indicative rates.
|
|
137
|
-
|
|
138
|
-
This function fetches the indicative rates for Brazilian treasury securities (LTN
|
|
139
|
-
and NTN-F bonds) and the DI futures rates for a specified reference date,
|
|
140
|
-
calculating the spread between these rates in basis points. If no reference date is
|
|
141
|
-
provided, the function uses the previous business day.
|
|
142
|
-
|
|
143
|
-
Parameters:
|
|
144
|
-
reference_date (str | pd.Timestamp, optional): The reference date for the DI
|
|
145
|
-
spread calculation.
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
pd.DataFrame: A DataFrame containing the bond type, reference date, maturity
|
|
149
|
-
date, and the calculated DI spread in basis points. The data is sorted by
|
|
150
|
-
bond type and maturity date.
|
|
151
|
-
"""
|
|
152
|
-
# Fetch DI rates and adjust the maturity date format for compatibility
|
|
153
|
-
df_di = ft.fetch_past_di(reference_date)[["ExpirationDate", "SettlementRate"]]
|
|
154
|
-
|
|
155
|
-
# Renaming the columns to match the ANBIMA structure
|
|
156
|
-
df_di.rename(columns={"ExpirationDate": "MaturityDate"}, inplace=True)
|
|
157
|
-
|
|
158
|
-
# Adjusting maturity date to match bond data format
|
|
159
|
-
df_di["MaturityDate"] = df_di["MaturityDate"].dt.to_period("M").dt.to_timestamp()
|
|
160
|
-
|
|
161
|
-
# Fetch bond rates, filtering for LTN and NTN-F types
|
|
162
|
-
df_anbima = fetch_bonds(reference_date, False)
|
|
163
|
-
df_anbima.query("BondType in ['LTN', 'NTN-F']", inplace=True)
|
|
164
|
-
|
|
165
|
-
# Merge bond and DI rates by maturity date to calculate spreads
|
|
166
|
-
df_final = pd.merge(df_anbima, df_di, how="left", on="MaturityDate")
|
|
167
|
-
|
|
168
|
-
# Calculate the DI spread as the difference between indicative and settlement rates
|
|
169
|
-
df_final["DISpread"] = df_final["IndicativeRate"] - df_final["SettlementRate"]
|
|
170
|
-
|
|
171
|
-
# Convert spread to basis points for clarity
|
|
172
|
-
df_final["DISpread"] = (BPS_CONVERSION_FACTOR * df_final["DISpread"]).round(2)
|
|
173
|
-
|
|
174
|
-
# Prepare and return the final sorted DataFrame
|
|
175
|
-
select_columns = ["BondType", "ReferenceDate", "MaturityDate", "DISpread"]
|
|
176
|
-
df_final = df_final[select_columns].copy()
|
|
177
|
-
return df_final.sort_values(["BondType", "MaturityDate"], ignore_index=True)
|
|
@@ -23,16 +23,16 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Office/Business :: Financial :: Investment",
|
|
24
24
|
]
|
|
25
25
|
dependencies = [
|
|
26
|
-
"requests",
|
|
27
26
|
"pandas>=2.0.0",
|
|
28
27
|
"numpy",
|
|
29
28
|
"beautifulsoup4",
|
|
30
29
|
"html5lib",
|
|
31
30
|
"lxml",
|
|
32
31
|
"python-calamine>=0.2.0",
|
|
32
|
+
"requests>=2.31.0",
|
|
33
33
|
]
|
|
34
34
|
dynamic = []
|
|
35
|
-
version = "0.7.
|
|
35
|
+
version = "0.7.9"
|
|
36
36
|
|
|
37
37
|
[project.license]
|
|
38
38
|
file = "LICENSE"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pyield as yd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_settlement_rate_with_old_holiday_list():
|
|
5
|
+
settlement_rates = {
|
|
6
|
+
"DI1N27": 0.09809,
|
|
7
|
+
"DI1F33": 0.10368,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
# 22-12-2023 is before the new holiday calendar
|
|
11
|
+
df = yd.fetch_asset(asset_code="DI1", reference_date="2023-12-22")
|
|
12
|
+
tickers = list(settlement_rates.keys()) # noqa: F841
|
|
13
|
+
result = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
|
|
14
|
+
assert result == list(settlement_rates.values())
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_settlement_rates_with_current_holiday_list():
|
|
18
|
+
settlement_rates = {
|
|
19
|
+
"DI1F24": 0.11644,
|
|
20
|
+
"DI1J24": 0.11300,
|
|
21
|
+
"DI1N24": 0.10786,
|
|
22
|
+
"DI1V24": 0.10321,
|
|
23
|
+
"DI1F25": 0.10031,
|
|
24
|
+
"DI1J25": 0.09852,
|
|
25
|
+
"DI1N25": 0.09715,
|
|
26
|
+
"DI1V25": 0.09651,
|
|
27
|
+
"DI1F26": 0.09583,
|
|
28
|
+
"DI1N26": 0.09631,
|
|
29
|
+
"DI1F27": 0.09683,
|
|
30
|
+
"DI1N27": 0.09794,
|
|
31
|
+
"DI1F29": 0.10042,
|
|
32
|
+
"DI1F31": 0.10240,
|
|
33
|
+
"DI1F33": 0.10331,
|
|
34
|
+
}
|
|
35
|
+
df = yd.fetch_asset(asset_code="DI1", reference_date="2023-12-26")
|
|
36
|
+
tickers = list(settlement_rates.keys()) # noqa: F841
|
|
37
|
+
results = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
|
|
38
|
+
assert results == list(settlement_rates.values())
|
|
@@ -7,8 +7,8 @@ import requests
|
|
|
7
7
|
from lxml import etree
|
|
8
8
|
from pandas import DataFrame, Timestamp
|
|
9
9
|
|
|
10
|
-
from .. import bday
|
|
11
|
-
from . import
|
|
10
|
+
from ..pyield import bday
|
|
11
|
+
from ..pyield.futures import historical as ht
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _get_file_from_url(trade_date: Timestamp, source_type: str) -> io.BytesIO:
|
|
@@ -206,7 +206,7 @@ def _standardize_column_names(df: DataFrame) -> DataFrame:
|
|
|
206
206
|
"TradAvrgPric": "AvgRate",
|
|
207
207
|
"MaxPric": "MaxRate",
|
|
208
208
|
"FrstPric": "FirstRate",
|
|
209
|
-
"LastPric": "
|
|
209
|
+
"LastPric": "CloseRate",
|
|
210
210
|
"AdjstdQtTax": "SettlementRate",
|
|
211
211
|
# "RglrTxsQty"
|
|
212
212
|
# "RglrTraddCtrcts"
|
|
@@ -229,7 +229,7 @@ def _process_di_df(df_raw: DataFrame) -> DataFrame:
|
|
|
229
229
|
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
230
230
|
df["TradDt"] = df["TradDt"].astype("datetime64[ns]")
|
|
231
231
|
|
|
232
|
-
expiration = df["TckrSymb"].str[3:].apply(
|
|
232
|
+
expiration = df["TckrSymb"].str[3:].apply(ht.get_expiration_date)
|
|
233
233
|
df.insert(2, "ExpirationDate", expiration)
|
|
234
234
|
|
|
235
235
|
business_days = bday.count_bdays(df["TradDt"], df["ExpirationDate"])
|
pyield-0.7.7/pyield/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.7"
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
|
|
3
|
-
from . import treasuries as tr
|
|
4
|
-
from .utils import _normalize_date
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def calculate_spreads(
|
|
8
|
-
spread_type: str, reference_date: str | pd.Timestamp | None = None
|
|
9
|
-
) -> pd.DataFrame:
|
|
10
|
-
"""
|
|
11
|
-
Calculates spreads between assets based on the specified spread type.
|
|
12
|
-
If no reference date is provided, the function uses the previous business day.
|
|
13
|
-
|
|
14
|
-
Parameters:
|
|
15
|
-
spread_type (str): The type of spread to calculate. Available options are:
|
|
16
|
-
- "DI_vs_PRE": the spread between DI Futures and Treasury Pre-Fixed bonds.
|
|
17
|
-
reference_date (str | pd.Timestamp, optional): The reference date for the
|
|
18
|
-
spread calculation. If None or not provided, defaults to the previous
|
|
19
|
-
business day according to the Brazilian calendar.
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
pd.DataFrame: A DataFrame containing the calculated spread in basis points.
|
|
23
|
-
The data is sorted by asset type and maturity/expiration date.
|
|
24
|
-
|
|
25
|
-
Raises:
|
|
26
|
-
ValueError: If an invalid spread type is provided.
|
|
27
|
-
"""
|
|
28
|
-
# Normalize the reference date
|
|
29
|
-
normalized_date = _normalize_date(reference_date)
|
|
30
|
-
if spread_type.lower() == "di_vs_pre":
|
|
31
|
-
return tr.calculate_di_spreads(normalized_date)
|
|
32
|
-
else:
|
|
33
|
-
raise ValueError("Invalid spread type.")
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from .common import get_expiration_date, get_old_expiration_date
|
|
2
|
-
from .ddi import fetch_past_ddi
|
|
3
|
-
from .di import fetch_last_di, fetch_past_di
|
|
4
|
-
from .frc import fetch_past_frc
|
|
5
|
-
|
|
6
|
-
__all__ = [
|
|
7
|
-
"fetch_past_di",
|
|
8
|
-
"fetch_last_di",
|
|
9
|
-
"fetch_past_ddi",
|
|
10
|
-
"fetch_past_frc",
|
|
11
|
-
"get_expiration_date",
|
|
12
|
-
"get_old_expiration_date",
|
|
13
|
-
]
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
|
|
3
|
-
from . import common as cm
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def _convert_prices_to_rates(prices: pd.Series, n_days: pd.Series) -> pd.Series:
|
|
7
|
-
"""
|
|
8
|
-
Internal function to convert DDI futures prices to rates.
|
|
9
|
-
|
|
10
|
-
Args:
|
|
11
|
-
prices (pd.Series): A pd.Series containing DDI futures prices.
|
|
12
|
-
bd (pd.Series): A serie containing the number of days to expiration.
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
pd.Series: A pd.Series containing DDI futures rates.
|
|
16
|
-
"""
|
|
17
|
-
rates = (100_000 / prices - 1) * (360 / n_days)
|
|
18
|
-
# Round to 5 (3 in %) dec. places (contract's current max. precision)
|
|
19
|
-
return rates.round(5)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _process_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
23
|
-
"""
|
|
24
|
-
Internal function to process and transform raw DI futures data.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
df (pd.DataFrame): the raw DI DataFrame.
|
|
28
|
-
trade_date: a datetime-like object representing the trade date.
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
|
|
32
|
-
"""
|
|
33
|
-
df = cm.rename_columns(df)
|
|
34
|
-
|
|
35
|
-
df["TradeDate"] = trade_date
|
|
36
|
-
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
37
|
-
df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
|
|
38
|
-
|
|
39
|
-
# Contract code format was changed in 22/05/2006
|
|
40
|
-
if trade_date < pd.Timestamp("2006-05-22"):
|
|
41
|
-
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
42
|
-
cm.get_old_expiration_date, args=(trade_date,)
|
|
43
|
-
)
|
|
44
|
-
else:
|
|
45
|
-
df["ExpirationDate"] = df["ExpirationCode"].apply(cm.get_expiration_date)
|
|
46
|
-
|
|
47
|
-
df["DaysToExp"] = (df["ExpirationDate"] - trade_date).dt.days
|
|
48
|
-
# Convert to nullable integer, since other columns use this data type
|
|
49
|
-
df["DaysToExp"] = df["DaysToExp"].astype(pd.Int64Dtype())
|
|
50
|
-
|
|
51
|
-
# Remove expired contracts
|
|
52
|
-
df.query("DaysToExp > 0", inplace=True)
|
|
53
|
-
|
|
54
|
-
# Columns where 0 means NaN
|
|
55
|
-
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
56
|
-
cols_with_nan = rate_cols + ["SettlementPrice"]
|
|
57
|
-
# Replace 0 with NaN in these columns
|
|
58
|
-
df[cols_with_nan] = df[cols_with_nan].replace(0, pd.NA)
|
|
59
|
-
|
|
60
|
-
df[rate_cols] = df[rate_cols].div(100).round(5)
|
|
61
|
-
|
|
62
|
-
# Calculate SettlementRate
|
|
63
|
-
df["SettlementRate"] = _convert_prices_to_rates(
|
|
64
|
-
df["SettlementPrice"], df["DaysToExp"]
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
df["TickerSymbol"] = "DI1" + df["ExpirationCode"]
|
|
68
|
-
|
|
69
|
-
# Filter and order columns
|
|
70
|
-
df = cm.reorder_columns(df)
|
|
71
|
-
|
|
72
|
-
return df
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def fetch_past_ddi(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
|
|
76
|
-
"""
|
|
77
|
-
Fetchs the DDI futures data for a given date from B3.
|
|
78
|
-
|
|
79
|
-
This function fetches and processes the DI futures data from B3 for a specific
|
|
80
|
-
trade date. It's the primary external interface for accessing DI data.
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
trade_date (pd.Timestamp): The trade date to fetch the DI futures data.
|
|
84
|
-
raw (bool): If True, returns the raw data as a Pandas pd.DataFrame.
|
|
85
|
-
Defaults to False.
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
|
|
89
|
-
|
|
90
|
-
Examples:
|
|
91
|
-
>>> from pyield.futures import di
|
|
92
|
-
>>> di.fetch_ddi(pd.Timestamp("2021-01-04"))
|
|
93
|
-
|
|
94
|
-
Notes:
|
|
95
|
-
- DaysToExp: number of business days to ExpirationDate.
|
|
96
|
-
- OpenContracts: number of open contracts at the start of the trading day.
|
|
97
|
-
"""
|
|
98
|
-
df_raw = cm.fetch_past_raw_df(asset_code="DDI", trade_date=trade_date)
|
|
99
|
-
if return_raw or df_raw.empty:
|
|
100
|
-
return df_raw
|
|
101
|
-
return _process_raw_df(df_raw, trade_date)
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
|
|
3
|
-
from .. import bday as bd
|
|
4
|
-
from . import common as cm
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _convert_prices_to_rates(prices: pd.Series, bd: pd.Series) -> pd.Series:
|
|
8
|
-
"""
|
|
9
|
-
Internal function to convert DI futures prices to rates.
|
|
10
|
-
|
|
11
|
-
Args:
|
|
12
|
-
prices (pd.Series): A pd.Series containing DI futures prices.
|
|
13
|
-
bd (pd.Series): A serie containing the number of business days to expiration.
|
|
14
|
-
|
|
15
|
-
Returns:
|
|
16
|
-
pd.Series: A pd.Series containing DI futures rates.
|
|
17
|
-
"""
|
|
18
|
-
rates = (100_000 / prices) ** (252 / bd) - 1
|
|
19
|
-
|
|
20
|
-
# Round to 5 (3 in %) dec. places (contract's current max. precision)
|
|
21
|
-
return rates.round(5)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _adjust_older_contracts_rates(df: pd.DataFrame, rate_cols: list) -> pd.DataFrame:
|
|
25
|
-
for col in rate_cols:
|
|
26
|
-
df[col] = _convert_prices_to_rates(df[col], df["BDaysToExp"])
|
|
27
|
-
|
|
28
|
-
# Invert low and high prices
|
|
29
|
-
df["MinRate"], df["MaxRate"] = df["MaxRate"], df["MinRate"]
|
|
30
|
-
|
|
31
|
-
return df
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _process_past_raw_df(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
35
|
-
"""
|
|
36
|
-
Internal function to process and transform raw DI futures data.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
df (pd.DataFrame): the raw DI DataFrame.
|
|
40
|
-
trade_date: a datetime-like object representing the trade date.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
|
|
44
|
-
"""
|
|
45
|
-
df = cm.rename_columns(df)
|
|
46
|
-
|
|
47
|
-
df["TradeDate"] = trade_date
|
|
48
|
-
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
49
|
-
df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
|
|
50
|
-
|
|
51
|
-
# Contract code format was changed in 22/05/2006
|
|
52
|
-
if trade_date < pd.Timestamp("2006-05-22"):
|
|
53
|
-
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
54
|
-
cm.get_old_expiration_date, args=(trade_date,)
|
|
55
|
-
)
|
|
56
|
-
else:
|
|
57
|
-
df["ExpirationDate"] = df["ExpirationCode"].apply(cm.get_expiration_date)
|
|
58
|
-
|
|
59
|
-
df["BDaysToExp"] = bd.count_bdays(trade_date, df["ExpirationDate"])
|
|
60
|
-
|
|
61
|
-
# Remove expired contracts
|
|
62
|
-
df.query("BDaysToExp > 0", inplace=True)
|
|
63
|
-
|
|
64
|
-
# Columns where 0 means NaN
|
|
65
|
-
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
66
|
-
cols_with_nan = rate_cols + ["SettlementPrice"]
|
|
67
|
-
# Replace 0 with NaN in these columns
|
|
68
|
-
df[cols_with_nan] = df[cols_with_nan].replace(0, pd.NA)
|
|
69
|
-
|
|
70
|
-
# Prior to 17/01/2002 (inclusive), prices were not converted to rates
|
|
71
|
-
if trade_date > pd.Timestamp("2002-01-17"):
|
|
72
|
-
# Remove % and round to 5 (3 in %) dec. places in rate columns
|
|
73
|
-
df[rate_cols] = df[rate_cols].div(100).round(5)
|
|
74
|
-
else:
|
|
75
|
-
df = _adjust_older_contracts_rates(df, rate_cols)
|
|
76
|
-
|
|
77
|
-
# Calculate SettlementRate
|
|
78
|
-
df["SettlementRate"] = _convert_prices_to_rates(
|
|
79
|
-
df["SettlementPrice"], df["BDaysToExp"]
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
df["TickerSymbol"] = "DI1" + df["ExpirationCode"]
|
|
83
|
-
|
|
84
|
-
# Filter and order columns
|
|
85
|
-
df = cm.reorder_columns(df)
|
|
86
|
-
return df
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _process_last_raw_di_df(raw_df: pd.DataFrame) -> pd.DataFrame:
|
|
90
|
-
df = raw_df.copy()
|
|
91
|
-
|
|
92
|
-
# Columns to be renamed
|
|
93
|
-
rename_columns = {
|
|
94
|
-
"TradeTimestamp": "TradeTimestamp",
|
|
95
|
-
"symb": "TickerSymbol",
|
|
96
|
-
"mtrtyCode": "ExpirationDate",
|
|
97
|
-
"BDaysToExp": "BDaysToExp",
|
|
98
|
-
"opnCtrcts": "OpenContracts",
|
|
99
|
-
"tradQty": "TradeCount",
|
|
100
|
-
"traddCtrctsQty": "TradeVolume",
|
|
101
|
-
"grssAmt": "FinancialVolume",
|
|
102
|
-
"prvsDayAdjstmntPric": "PrevSettlementRate",
|
|
103
|
-
"bottomLmtPric": "MinLimitRate",
|
|
104
|
-
"topLmtPric": "MaxLimitRate",
|
|
105
|
-
"opngPric": "OpenRate",
|
|
106
|
-
"minPric": "MinRate",
|
|
107
|
-
"avrgPric": "AvgRate",
|
|
108
|
-
"maxPric": "MaxRate",
|
|
109
|
-
"buyOffer.price": "LastAskRate",
|
|
110
|
-
"sellOffer.price": "LastBidRate",
|
|
111
|
-
"curPrc": "LastRate",
|
|
112
|
-
}
|
|
113
|
-
# Rename columns
|
|
114
|
-
df = df.rename(columns=rename_columns)
|
|
115
|
-
|
|
116
|
-
df["BDaysToExp"] = bd.count_bdays(df["TradeTimestamp"], df["ExpirationDate"])
|
|
117
|
-
|
|
118
|
-
# Remove percentage in all rate columns
|
|
119
|
-
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
120
|
-
df[rate_cols] = df[rate_cols] / 100
|
|
121
|
-
|
|
122
|
-
# Reorder columns based on the order of the dictionary
|
|
123
|
-
return df[rename_columns.values()]
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def fetch_last_di() -> pd.DataFrame:
|
|
127
|
-
"""
|
|
128
|
-
Fetch the latest DI futures data from B3.
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
|
|
132
|
-
"""
|
|
133
|
-
raw_df = cm.fetch_last_raw_df(future_code="DI1")
|
|
134
|
-
return _process_last_raw_di_df(raw_df)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def fetch_past_di(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
|
|
138
|
-
"""
|
|
139
|
-
Fetchs the DI futures data for a given date from B3.
|
|
140
|
-
|
|
141
|
-
This function fetches and processes the DI futures data from B3 for a specific
|
|
142
|
-
trade date. It's the primary external interface for accessing DI data. If the data
|
|
143
|
-
is not available, an empty DataFrame is returned.
|
|
144
|
-
|
|
145
|
-
Args:
|
|
146
|
-
trade_date (pd.Timestamp): The trade date to fetch the DI futures data.
|
|
147
|
-
return_raw (bool): If True, returns the raw data as a Pandas pd.DataFrame.
|
|
148
|
-
Defaults to False.
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
|
|
152
|
-
|
|
153
|
-
Notes:
|
|
154
|
-
- BDaysToExp: number of business days to ExpirationDate.
|
|
155
|
-
- OpenContracts: number of open contracts at the start of the trading day.
|
|
156
|
-
"""
|
|
157
|
-
df_raw = cm.fetch_past_raw_df(asset_code="DI1", trade_date=trade_date)
|
|
158
|
-
if return_raw or df_raw.empty:
|
|
159
|
-
return df_raw
|
|
160
|
-
return _process_past_raw_df(df_raw, trade_date)
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
|
|
3
|
-
from . import common as cm
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def _process_past_data(df: pd.DataFrame, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
7
|
-
"""
|
|
8
|
-
Internal function to process and transform raw DI futures data.
|
|
9
|
-
|
|
10
|
-
Args:
|
|
11
|
-
df (pd.DataFrame): the raw DI DataFrame.
|
|
12
|
-
trade_date: a datetime-like object representing the trade date.
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
pd.DataFrame: Processed and transformed data as a Pandas pd.DataFrame.
|
|
16
|
-
"""
|
|
17
|
-
df = cm.rename_columns(df)
|
|
18
|
-
|
|
19
|
-
df["TradeDate"] = trade_date
|
|
20
|
-
# Convert to datetime64[ns] since it is pandas default type for timestamps
|
|
21
|
-
df["TradeDate"] = df["TradeDate"].astype("datetime64[ns]")
|
|
22
|
-
|
|
23
|
-
# Contract code format was changed in 22/05/2006
|
|
24
|
-
if trade_date < pd.Timestamp("2006-05-22"):
|
|
25
|
-
df["ExpirationDate"] = df["ExpirationCode"].apply(
|
|
26
|
-
cm.get_old_expiration_date, args=(trade_date,)
|
|
27
|
-
)
|
|
28
|
-
else:
|
|
29
|
-
df["ExpirationDate"] = df["ExpirationCode"].apply(cm.get_expiration_date)
|
|
30
|
-
|
|
31
|
-
# Replace 0 values in rate columns with pd.NA and remove percentage
|
|
32
|
-
rate_cols = [col for col in df.columns if "Rate" in col]
|
|
33
|
-
# Round to 5 decimal places (3 in %) since it is the contract's precision
|
|
34
|
-
df[rate_cols] = df[rate_cols].replace(0, pd.NA).div(100).round(5)
|
|
35
|
-
|
|
36
|
-
df["TickerSymbol"] = "FRC" + df["ExpirationCode"]
|
|
37
|
-
|
|
38
|
-
# Filter and order columns
|
|
39
|
-
df = cm.reorder_columns(df)
|
|
40
|
-
return df
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def fetch_past_frc(trade_date: pd.Timestamp, return_raw: bool = False) -> pd.DataFrame:
|
|
44
|
-
"""
|
|
45
|
-
Fetchs the DI futures data for a given date from B3.
|
|
46
|
-
|
|
47
|
-
This function fetches and processes the DI futures data from B3 for a specific
|
|
48
|
-
trade date. It's the primary external interface for accessing DI data.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
trade_date (pd.Timestamp): The trade date to fetch the DI futures data.
|
|
52
|
-
raw (bool): If True, returns the raw data as a Pandas pd.DataFrame.
|
|
53
|
-
Defaults to False.
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
pd.DataFrame: A Pandas pd.DataFrame containing processed DI futures data.
|
|
57
|
-
"""
|
|
58
|
-
df_raw = cm.fetch_past_raw_df(asset_code="FRC", trade_date=trade_date)
|
|
59
|
-
if return_raw or df_raw.empty:
|
|
60
|
-
return df_raw
|
|
61
|
-
return _process_past_data(df_raw, trade_date)
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
|
|
3
|
-
from pyield import futures as ft
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_valid_old_contract_code1():
|
|
7
|
-
expiration_code = "JAN3" # Valid contract code
|
|
8
|
-
trade_date = pd.Timestamp("2001-05-21")
|
|
9
|
-
result = ft.get_old_expiration_date(expiration_code, trade_date)
|
|
10
|
-
contract_expiration = pd.Timestamp("2003-01-02")
|
|
11
|
-
assert result == contract_expiration
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_valid_old_contract_code2():
|
|
15
|
-
expiration_code = "JAN3" # Valid contract code
|
|
16
|
-
trade_date = pd.Timestamp("1990-01-01")
|
|
17
|
-
result = ft.get_old_expiration_date(expiration_code, trade_date)
|
|
18
|
-
contract_expiration = pd.Timestamp("1993-01-04")
|
|
19
|
-
assert result == contract_expiration
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def test_invalid_old_contract_code():
|
|
23
|
-
expiration_code = "J3" # Invalid contract code
|
|
24
|
-
trade_date = pd.Timestamp("2001-01-02")
|
|
25
|
-
# Must return NaT
|
|
26
|
-
result = ft.get_old_expiration_date(expiration_code, trade_date)
|
|
27
|
-
assert pd.isnull(result)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def test_new_contract_code():
|
|
31
|
-
expiration_code = "F23" # Valid contract code
|
|
32
|
-
result = ft.get_expiration_date(expiration_code)
|
|
33
|
-
contract_expiration = pd.Timestamp("2023-01-02")
|
|
34
|
-
assert result == contract_expiration
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_settlement_rate_with_old_holiday_list():
|
|
38
|
-
settlement_rates = {
|
|
39
|
-
"DI1N27": 0.09809,
|
|
40
|
-
"DI1F33": 0.10368,
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# 22-12-2023 is before the new holiday calendar
|
|
44
|
-
test_date = pd.Timestamp("2023-12-22")
|
|
45
|
-
df = ft.fetch_past_di(trade_date=test_date)
|
|
46
|
-
tickers = list(settlement_rates.keys()) # noqa: F841
|
|
47
|
-
result = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
|
|
48
|
-
assert result == list(settlement_rates.values())
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def test_settlement_rates_with_current_holiday_list():
|
|
52
|
-
settlement_rates = {
|
|
53
|
-
"DI1F24": 0.11644,
|
|
54
|
-
"DI1J24": 0.11300,
|
|
55
|
-
"DI1N24": 0.10786,
|
|
56
|
-
"DI1V24": 0.10321,
|
|
57
|
-
"DI1F25": 0.10031,
|
|
58
|
-
"DI1J25": 0.09852,
|
|
59
|
-
"DI1N25": 0.09715,
|
|
60
|
-
"DI1V25": 0.09651,
|
|
61
|
-
"DI1F26": 0.09583,
|
|
62
|
-
"DI1N26": 0.09631,
|
|
63
|
-
"DI1F27": 0.09683,
|
|
64
|
-
"DI1N27": 0.09794,
|
|
65
|
-
"DI1F29": 0.10042,
|
|
66
|
-
"DI1F31": 0.10240,
|
|
67
|
-
"DI1F33": 0.10331,
|
|
68
|
-
}
|
|
69
|
-
test_date = pd.Timestamp("2023-12-26")
|
|
70
|
-
df = ft.fetch_past_di(trade_date=test_date)
|
|
71
|
-
tickers = list(settlement_rates.keys()) # noqa: F841
|
|
72
|
-
results = df.query("TickerSymbol in @tickers")["SettlementRate"].to_list()
|
|
73
|
-
assert results == list(settlement_rates.values())
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def test_non_business_day():
|
|
77
|
-
non_business_day = pd.Timestamp("2023-12-24")
|
|
78
|
-
# Test if it an empty DataFrame is returned
|
|
79
|
-
df = ft.fetch_past_di(trade_date=non_business_day)
|
|
80
|
-
assert df.empty
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|