PYield 0.12.2__tar.gz → 0.13.0__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.12.2 → pyield-0.13.0}/PKG-INFO +1 -1
- pyield-0.13.0/pyield/__about__.py +1 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/bonds/lft.py +15 -15
- {pyield-0.12.2 → pyield-0.13.0}/pyield/bonds/ltn.py +12 -12
- {pyield-0.12.2 → pyield-0.13.0}/pyield/bonds/ntnb.py +62 -77
- {pyield-0.12.2 → pyield-0.13.0}/pyield/bonds/ntnf.py +101 -85
- {pyield-0.12.2 → pyield-0.13.0}/pyield/bonds/utils.py +3 -9
- {pyield-0.12.2 → pyield-0.13.0}/pyield/di.py +38 -31
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/anbima.py +2 -3
- {pyield-0.12.2 → pyield-0.13.0}/pyproject.toml +1 -1
- pyield-0.12.2/pyield/__about__.py +0 -1
- {pyield-0.12.2 → pyield-0.13.0}/LICENSE +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/README.md +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/__init__.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/bday.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/bonds/__init__.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/date_converter.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/__init__.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/futures_data/__init__.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/futures_data/common.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/futures_data/historical.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/futures_data/intraday.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/futures_data/xml.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/indicators.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/fetchers/projections.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/holidays/__init__.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/holidays/holidays_manager.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/interpolator.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/pyield/py.typed +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/tests/__init__.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/tests/data/SPRD231222.zip +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/tests/data/SPRD240426.zip +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/tests/test_bday.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/tests/test_futures.py +0 -0
- {pyield-0.12.2 → pyield-0.13.0}/tests/test_futures_xml.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.13.0"
|
|
@@ -32,7 +32,7 @@ def anbima_rates(reference_date: str | pd.Timestamp) -> pd.Series:
|
|
|
32
32
|
return an.get_anbima_rates(reference_date, "LFT")
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def anbima_historical_rates(
|
|
35
|
+
def anbima_historical_rates(maturity: str | pd.Timestamp) -> pd.Series:
|
|
36
36
|
"""
|
|
37
37
|
Fetch historical LFT Anbima indicative rates for the given maturity date.
|
|
38
38
|
|
|
@@ -42,21 +42,21 @@ def anbima_historical_rates(maturity_date: str | pd.Timestamp) -> pd.Series:
|
|
|
42
42
|
Returns:
|
|
43
43
|
pd.Series: A Series containing the rates indexed by reference date.
|
|
44
44
|
"""
|
|
45
|
-
return an.get_anbima_historical_rates("LFT",
|
|
45
|
+
return an.get_anbima_historical_rates("LFT", maturity)
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def quotation(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
settlement: str | pd.Timestamp,
|
|
50
|
+
maturity: str | pd.Timestamp,
|
|
51
|
+
rate: float,
|
|
52
52
|
) -> float:
|
|
53
53
|
"""
|
|
54
54
|
Calculate the quotation of a LFT bond using Anbima rules.
|
|
55
55
|
|
|
56
56
|
Args:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
settlement (str | pd.Timestamp): The settlement date of the bond.
|
|
58
|
+
maturity (str | pd.Timestamp): The maturity date of the bond.
|
|
59
|
+
rate (float): The annualized yield rate of the bond
|
|
60
60
|
|
|
61
61
|
Returns:
|
|
62
62
|
float: The quotation of the bond.
|
|
@@ -64,22 +64,22 @@ def quotation(
|
|
|
64
64
|
Examples:
|
|
65
65
|
Calculate the quotation of a LFT bond with a 0.02 yield rate:
|
|
66
66
|
>>> lft.quotation(
|
|
67
|
-
...
|
|
68
|
-
...
|
|
69
|
-
...
|
|
67
|
+
... settlement="24-07-2024",
|
|
68
|
+
... maturity="01-09-2030",
|
|
69
|
+
... rate=0.001717, # 0.1717%
|
|
70
70
|
... )
|
|
71
71
|
98.9645
|
|
72
72
|
"""
|
|
73
73
|
# Validate and normalize dates
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
settlement = dc.convert_date(settlement)
|
|
75
|
+
maturity = dc.convert_date(maturity)
|
|
76
76
|
|
|
77
77
|
# The number of bdays between settlement (inclusive) and the maturity (exclusive)
|
|
78
|
-
bdays = bday.count(
|
|
78
|
+
bdays = bday.count(settlement, maturity)
|
|
79
79
|
|
|
80
80
|
# Calculate the number of periods truncated as per Anbima rules
|
|
81
81
|
num_of_years = ut.truncate(bdays / 252, 14)
|
|
82
82
|
|
|
83
|
-
discount_factor = 1 / (1 +
|
|
83
|
+
discount_factor = 1 / (1 + rate) ** num_of_years
|
|
84
84
|
|
|
85
85
|
return ut.truncate(100 * discount_factor, 4)
|
|
@@ -34,7 +34,7 @@ def anbima_rates(reference_date: str | pd.Timestamp) -> pd.Series:
|
|
|
34
34
|
return an.get_anbima_rates(reference_date, "LTN")
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def anbima_historical_rates(
|
|
37
|
+
def anbima_historical_rates(maturity: str | pd.Timestamp) -> pd.Series:
|
|
38
38
|
"""
|
|
39
39
|
Fetch historical LTN Anbima indicative rates for the given maturity date.
|
|
40
40
|
|
|
@@ -44,23 +44,23 @@ def anbima_historical_rates(maturity_date: str | pd.Timestamp) -> pd.Series:
|
|
|
44
44
|
Returns:
|
|
45
45
|
pd.Series: A Series containing the rates for the given maturity date.
|
|
46
46
|
"""
|
|
47
|
-
return an.get_anbima_historical_rates("LTN",
|
|
47
|
+
return an.get_anbima_historical_rates("LTN", maturity)
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def price(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
settlement: str | pd.Timestamp,
|
|
52
|
+
maturity: str | pd.Timestamp,
|
|
53
|
+
rate: float,
|
|
54
54
|
) -> float:
|
|
55
55
|
"""
|
|
56
56
|
Calculate the LTN price using Anbima rules.
|
|
57
57
|
|
|
58
58
|
Args:
|
|
59
|
-
|
|
59
|
+
settlement (str | pd.Timestamp): The settlement date in 'DD-MM-YYYY' format
|
|
60
60
|
or a pandas Timestamp.
|
|
61
|
-
|
|
61
|
+
maturity (str | pd.Timestamp): The maturity date in 'DD-MM-YYYY' format or
|
|
62
62
|
a pandas Timestamp.
|
|
63
|
-
|
|
63
|
+
rate (float): The discount rate used to calculate the present value of
|
|
64
64
|
the cash flows, which is the yield to maturity (YTM) of the NTN-F.
|
|
65
65
|
|
|
66
66
|
Returns:
|
|
@@ -75,16 +75,16 @@ def price(
|
|
|
75
75
|
"""
|
|
76
76
|
|
|
77
77
|
# Validate and normalize dates
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
settlement = dc.convert_date(settlement)
|
|
79
|
+
maturity = dc.convert_date(maturity)
|
|
80
80
|
|
|
81
81
|
# Calculate the number of business days between settlement and cash flow dates
|
|
82
|
-
bdays = bday.count(
|
|
82
|
+
bdays = bday.count(settlement, maturity)
|
|
83
83
|
|
|
84
84
|
# Calculate the number of periods truncated as per Anbima rule
|
|
85
85
|
num_of_years = ut.truncate(bdays / 252, 14)
|
|
86
86
|
|
|
87
|
-
discount_factor = (1 +
|
|
87
|
+
discount_factor = (1 + rate) ** num_of_years
|
|
88
88
|
|
|
89
89
|
# Truncate the price to 6 decimal places as per Anbima rules
|
|
90
90
|
return ut.truncate(FACE_VALUE / discount_factor, 6)
|
|
@@ -47,7 +47,7 @@ def anbima_rates(reference_date: str | pd.Timestamp) -> pd.Series:
|
|
|
47
47
|
return an.get_anbima_rates(reference_date, "NTN-B")
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def anbima_historical_rates(
|
|
50
|
+
def anbima_historical_rates(maturity: str | pd.Timestamp) -> pd.Series:
|
|
51
51
|
"""
|
|
52
52
|
Fetch historical NTN-B Anbima indicative rates for the given maturity date.
|
|
53
53
|
|
|
@@ -57,7 +57,7 @@ def anbima_historical_rates(maturity_date: str | pd.Timestamp) -> pd.Series:
|
|
|
57
57
|
Returns:
|
|
58
58
|
pd.Series: A Series containing the rates for the given maturity date.
|
|
59
59
|
"""
|
|
60
|
-
return an.get_anbima_historical_rates("NTN-B",
|
|
60
|
+
return an.get_anbima_historical_rates("NTN-B", maturity)
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
def _coupon_dates_map(
|
|
@@ -96,8 +96,8 @@ def _coupon_dates_map(
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def coupon_dates(
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
start: str | pd.Timestamp,
|
|
100
|
+
maturity: str | pd.Timestamp,
|
|
101
101
|
) -> pd.Series:
|
|
102
102
|
"""
|
|
103
103
|
Generate all remaining coupon dates between a given date and the maturity date.
|
|
@@ -113,23 +113,23 @@ def coupon_dates(
|
|
|
113
113
|
list[pd.Timestamp]: List of coupon dates between start and maturity dates.
|
|
114
114
|
"""
|
|
115
115
|
# Validate and normalize dates
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
start = dc.convert_date(start)
|
|
117
|
+
maturity = dc.convert_date(maturity)
|
|
118
118
|
|
|
119
119
|
# Check if maturity date is after the start date
|
|
120
|
-
if
|
|
120
|
+
if maturity < start:
|
|
121
121
|
raise ValueError("Maturity date must be after the start date.")
|
|
122
122
|
|
|
123
123
|
# Check if the maturity date is a valid NTN-B maturity date
|
|
124
|
-
if
|
|
124
|
+
if maturity.day != COUPON_DAY or maturity.month not in COUPON_MONTHS:
|
|
125
125
|
raise ValueError("NTN-B maturity must be 15/02, 15/05, 15/08, or 15/11.")
|
|
126
126
|
|
|
127
127
|
# Initialize loop variables
|
|
128
|
-
cp_date =
|
|
128
|
+
cp_date = maturity
|
|
129
129
|
cp_dates = []
|
|
130
130
|
|
|
131
131
|
# Iterate backwards from the maturity date to the settlement date
|
|
132
|
-
while cp_date >=
|
|
132
|
+
while cp_date >= start:
|
|
133
133
|
cp_dates.append(cp_date)
|
|
134
134
|
# Move the coupon date back 6 months
|
|
135
135
|
cp_date -= pd.DateOffset(months=6)
|
|
@@ -138,19 +138,19 @@ def coupon_dates(
|
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
def quotation(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
settlement: str | pd.Timestamp,
|
|
142
|
+
maturity: str | pd.Timestamp,
|
|
143
|
+
rate: float,
|
|
144
144
|
) -> float:
|
|
145
145
|
"""
|
|
146
146
|
Calculate the NTN-B quotation in base 100 using Anbima rules.
|
|
147
147
|
|
|
148
148
|
Args:
|
|
149
|
-
|
|
149
|
+
settlement (str | pd.Timestamp): The settlement date in 'DD-MM-YYYY' format
|
|
150
150
|
or a pandas Timestamp.
|
|
151
|
-
|
|
151
|
+
maturity (str | pd.Timestamp): The maturity date in 'DD-MM-YYYY' format or
|
|
152
152
|
a pandas Timestamp.
|
|
153
|
-
|
|
153
|
+
rate (float): The discount rate used to calculate the present value of
|
|
154
154
|
the cash flows, which is the yield to maturity (YTM) of the NTN-B.
|
|
155
155
|
|
|
156
156
|
Returns:
|
|
@@ -169,25 +169,25 @@ def quotation(
|
|
|
169
169
|
99.5341
|
|
170
170
|
"""
|
|
171
171
|
# Validate and normalize dates
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
settlement = dc.convert_date(settlement)
|
|
173
|
+
maturity = dc.convert_date(maturity)
|
|
174
174
|
|
|
175
175
|
# Get the coupon dates between the settlement and maturity dates
|
|
176
|
-
payment_dates = coupon_dates(
|
|
176
|
+
payment_dates = coupon_dates(settlement, maturity)
|
|
177
177
|
|
|
178
178
|
# Coupon payment dates must be after the settlement date
|
|
179
|
-
payment_dates = payment_dates[payment_dates >
|
|
179
|
+
payment_dates = payment_dates[payment_dates > settlement]
|
|
180
180
|
|
|
181
181
|
# Calculate the number of business days between settlement and cash flow dates
|
|
182
|
-
bdays = bday.count(
|
|
182
|
+
bdays = bday.count(settlement, payment_dates)
|
|
183
183
|
|
|
184
184
|
# Set the cash flow at maturity to FINAL_PMT and the others to COUPON_PMT
|
|
185
|
-
cash_flows = np.where(payment_dates ==
|
|
185
|
+
cash_flows = np.where(payment_dates == maturity, FINAL_PMT, COUPON_PMT)
|
|
186
186
|
|
|
187
187
|
# Calculate the number of periods truncated as per Anbima rules
|
|
188
188
|
num_of_years = ut.truncate(bdays / 252, 14)
|
|
189
189
|
|
|
190
|
-
discount_factor = (1 +
|
|
190
|
+
discount_factor = (1 + rate) ** num_of_years
|
|
191
191
|
|
|
192
192
|
# Calculate the present value of each cash flow (DCF) rounded as per Anbima rules
|
|
193
193
|
discounted_cash_flows = (cash_flows / discount_factor).round(10)
|
|
@@ -222,26 +222,30 @@ def price(
|
|
|
222
222
|
|
|
223
223
|
def _calculate_coupons_pv(
|
|
224
224
|
bootstrap_df: pd.DataFrame,
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
settlement: pd.Timestamp,
|
|
226
|
+
maturity: pd.Timestamp,
|
|
227
227
|
) -> float:
|
|
228
228
|
# Create a subset DataFrame with only the coupon payments (without last payment)
|
|
229
|
-
cp_dates_wo_last = coupon_dates(
|
|
229
|
+
cp_dates_wo_last = coupon_dates(settlement, maturity)[:-1] # noqa
|
|
230
|
+
if len(cp_dates_wo_last) == 0:
|
|
231
|
+
return 0
|
|
232
|
+
|
|
230
233
|
df_coupons = bootstrap_df.query("MaturityDate in @cp_dates_wo_last").copy()
|
|
231
234
|
df_coupons["Coupon"] = COUPON_PMT
|
|
232
235
|
|
|
233
236
|
# Calculate the present value of the coupon payments
|
|
234
237
|
pv = ut.calculate_present_value(
|
|
235
238
|
cash_flows=df_coupons["Coupon"],
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
rates=df_coupons["SpotRate"],
|
|
240
|
+
periods=df_coupons["BDays"] / 252,
|
|
238
241
|
)
|
|
239
242
|
return pv
|
|
240
243
|
|
|
241
244
|
|
|
242
245
|
def spot_rates(
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
settlement: str | pd.Timestamp,
|
|
247
|
+
rates: list[float] | pd.Series,
|
|
248
|
+
maturities: list[str | pd.Timestamp],
|
|
245
249
|
) -> pd.DataFrame:
|
|
246
250
|
"""
|
|
247
251
|
Calculate the spot rates for NTN-B bonds using the bootstrap method.
|
|
@@ -269,24 +273,24 @@ def spot_rates(
|
|
|
269
273
|
- Calculate the real spot rates for each maturity date.
|
|
270
274
|
"""
|
|
271
275
|
# Process and validate the input data
|
|
272
|
-
|
|
273
|
-
|
|
276
|
+
settlement = dc.convert_date(settlement)
|
|
277
|
+
maturities = pd.to_datetime(maturities, errors="coerce", dayfirst=True)
|
|
274
278
|
|
|
275
279
|
# Create the interpolator object
|
|
276
280
|
ytm_rate_interpolator = it.Interpolator(
|
|
277
281
|
method="flat_forward",
|
|
278
|
-
known_bdays=bday.count(
|
|
279
|
-
known_rates=
|
|
282
|
+
known_bdays=bday.count(settlement, maturities),
|
|
283
|
+
known_rates=rates,
|
|
280
284
|
)
|
|
281
285
|
|
|
282
|
-
last_ntnb =
|
|
286
|
+
last_ntnb = maturities.max()
|
|
283
287
|
|
|
284
288
|
# Generate coupon dates up to the longest maturity date
|
|
285
|
-
all_coupon_dates = _coupon_dates_map(start=
|
|
289
|
+
all_coupon_dates = _coupon_dates_map(start=settlement, end=last_ntnb)
|
|
286
290
|
|
|
287
291
|
# Create a DataFrame with all coupon dates and the corresponding YTM
|
|
288
292
|
df_spot = pd.DataFrame(data=all_coupon_dates, columns=["MaturityDate"])
|
|
289
|
-
df_spot["BDays"] = bday.count(
|
|
293
|
+
df_spot["BDays"] = bday.count(settlement, df_spot["MaturityDate"])
|
|
290
294
|
df_spot["YTM"] = df_spot["BDays"].apply(ytm_rate_interpolator)
|
|
291
295
|
|
|
292
296
|
# The Bootstrap loop to calculate spot rates
|
|
@@ -297,45 +301,26 @@ def spot_rates(
|
|
|
297
301
|
ytm = df_spot.at[index, "YTM"]
|
|
298
302
|
|
|
299
303
|
# Get the coupon dates for the bond without the last one (principal + coupon)
|
|
300
|
-
cp_dates = coupon_dates(
|
|
304
|
+
cp_dates = coupon_dates(settlement, maturity)
|
|
301
305
|
|
|
302
306
|
# If there is only one coupon date and this date is the first maturity date
|
|
303
307
|
# of an existing bond, the ytm rate is also a spot rate.
|
|
304
|
-
if len(cp_dates) == 1 and cp_dates[0] ==
|
|
308
|
+
if len(cp_dates) == 1 and cp_dates[0] == rates.index[0]:
|
|
305
309
|
df_spot.at[index, "SpotRate"] = ytm
|
|
306
310
|
continue
|
|
307
311
|
|
|
308
312
|
# Calculate the real spot rate for the bond
|
|
309
|
-
coupons_pv = _calculate_coupons_pv(df_spot,
|
|
310
|
-
bond_price = quotation(
|
|
313
|
+
coupons_pv = _calculate_coupons_pv(df_spot, settlement, maturity)
|
|
314
|
+
bond_price = quotation(settlement, maturity, ytm)
|
|
311
315
|
spot_rate = (FINAL_PMT / (bond_price - coupons_pv)) ** (252 / bdays) - 1
|
|
312
316
|
df_spot.at[index, "SpotRate"] = spot_rate
|
|
313
317
|
|
|
314
318
|
df_spot.drop(columns=["BDays"], inplace=True)
|
|
315
319
|
# Return the result without the intermediate coupon dates (virtual bonds)
|
|
316
|
-
existing_maturities =
|
|
320
|
+
existing_maturities = rates.index # noqa
|
|
317
321
|
return df_spot.query("MaturityDate in @existing_maturities").reset_index(drop=True)
|
|
318
322
|
|
|
319
323
|
|
|
320
|
-
def anbima_spot_rates(
|
|
321
|
-
reference_date: str | pd.Timestamp,
|
|
322
|
-
settlement_date: str | pd.Timestamp,
|
|
323
|
-
) -> pd.DataFrame:
|
|
324
|
-
"""
|
|
325
|
-
Fetch the NTN-B Anbima indicative rates and calculate the spot rates for the bonds.
|
|
326
|
-
|
|
327
|
-
Args:
|
|
328
|
-
reference_date (str | pd.Timestamp): The reference date for fetching the data.
|
|
329
|
-
settlement_date (str | pd.Timestamp): The reference date for settlement.
|
|
330
|
-
|
|
331
|
-
Returns:
|
|
332
|
-
pd.DataFrame: DataFrame containing the maturity dates and corresponding real
|
|
333
|
-
spot rates.
|
|
334
|
-
"""
|
|
335
|
-
ytm_rates = anbima_rates(reference_date)
|
|
336
|
-
return spot_rates(settlement_date, ytm_rates)
|
|
337
|
-
|
|
338
|
-
|
|
339
324
|
def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
340
325
|
"""
|
|
341
326
|
Fetch the Nominal Interest Rate (NIR) data for NTN-B bonds.
|
|
@@ -355,8 +340,6 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
355
340
|
"TickerSymbol",
|
|
356
341
|
"ExpirationDate",
|
|
357
342
|
"BDaysToExp",
|
|
358
|
-
"CurrentAskRate",
|
|
359
|
-
"CurrentBidRate",
|
|
360
343
|
"NIR_DI",
|
|
361
344
|
]
|
|
362
345
|
elif "SettlementRate" in df.columns:
|
|
@@ -369,7 +352,7 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
369
352
|
"NIR_DI",
|
|
370
353
|
]
|
|
371
354
|
else:
|
|
372
|
-
raise ValueError("
|
|
355
|
+
raise ValueError("DI data not available.")
|
|
373
356
|
|
|
374
357
|
df = df[keep_cols].dropna(subset=["NIR_DI"])
|
|
375
358
|
|
|
@@ -392,8 +375,9 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
392
375
|
|
|
393
376
|
def bei_rates(
|
|
394
377
|
reference_date: str | pd.Timestamp,
|
|
395
|
-
|
|
396
|
-
|
|
378
|
+
settlement: str | pd.Timestamp,
|
|
379
|
+
rates: list[float] | pd.Series,
|
|
380
|
+
maturities: list[str | pd.Timestamp] | pd.Series,
|
|
397
381
|
) -> pd.DataFrame:
|
|
398
382
|
"""
|
|
399
383
|
Calculate the Breakeven Inflation (BEI) for NTN-B bonds based on nominal and real
|
|
@@ -402,26 +386,27 @@ def bei_rates(
|
|
|
402
386
|
Args:
|
|
403
387
|
reference_date (str or pd.Timestamp): The reference date for fetching data and
|
|
404
388
|
performing calculations.
|
|
405
|
-
|
|
406
|
-
|
|
389
|
+
settlement (str or pd.Timestamp): The settlement date for the bonds.
|
|
390
|
+
rates (pd.Series): A series of Yield to Maturity (YTM) rates corresponding
|
|
407
391
|
to the maturity dates of the bonds indexed by the maturity dates.
|
|
408
392
|
|
|
409
393
|
Returns:
|
|
410
394
|
pd.DataFrame: DataFrame containing the breakeven inflation rates.
|
|
411
395
|
|
|
412
396
|
Returned columns:
|
|
413
|
-
- MaturityDate: Maturity date of the
|
|
397
|
+
- MaturityDate: Maturity date of the bonds
|
|
414
398
|
- BDays: Number of business days from the settlement date to the maturity.
|
|
415
|
-
- YTM: Yield to Maturity rate for the
|
|
416
|
-
- RIR: Real Interest Rate for the
|
|
417
|
-
- NIR_DI: Nominal Interest Rate
|
|
418
|
-
- NIR_PRE: Nominal Interest Rate for the
|
|
419
|
-
- BEI_DI: Breakeven Inflation Rate
|
|
420
|
-
- BEI_PRE: Breakeven Inflation Rate
|
|
399
|
+
- YTM: Yield to Maturity rate for the bonds
|
|
400
|
+
- RIR: Real Interest Rate for the bonds based on the spot rates.
|
|
401
|
+
- NIR_DI: Nominal Interest Rate based on DI Futures.
|
|
402
|
+
- NIR_PRE: Nominal Interest Rate for the prefixed bonds.
|
|
403
|
+
- BEI_DI: Breakeven Inflation Rate calculated with DI Futures.
|
|
404
|
+
- BEI_PRE: Breakeven Inflation Rate calculated with prefixed bonds.
|
|
421
405
|
"""
|
|
422
406
|
# Normalize input dates
|
|
423
407
|
reference_date = dc.convert_date(reference_date)
|
|
424
|
-
|
|
408
|
+
settlement = dc.convert_date(settlement)
|
|
409
|
+
maturities = pd.to_datetime(maturities, errors="coerce", dayfirst=True)
|
|
425
410
|
|
|
426
411
|
# Fetch Nominal Interest Rate (NIR) data
|
|
427
412
|
df_nir = _get_nir_df(reference_date)
|
|
@@ -432,7 +417,7 @@ def bei_rates(
|
|
|
432
417
|
known_rates=df_nir["NIR_DI"],
|
|
433
418
|
)
|
|
434
419
|
# Calculate Real Interest Rate (RIR)
|
|
435
|
-
df = spot_rates(
|
|
420
|
+
df = spot_rates(settlement, rates, maturities)
|
|
436
421
|
df = df.rename(columns={"SpotRate": "RIR"})
|
|
437
422
|
df["BDays"] = bday.count(reference_date, df["MaturityDate"])
|
|
438
423
|
df["NIR_DI"] = df["BDays"].apply(ytm_interplator)
|
|
@@ -23,7 +23,7 @@ FINAL_PMT = 1048.80885
|
|
|
23
23
|
di_data = di.DIData()
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def check_maturity_date(
|
|
26
|
+
def check_maturity_date(maturity: pd.Timestamp) -> None:
|
|
27
27
|
"""
|
|
28
28
|
Check if the maturity date is a valid NTN-F maturity date.
|
|
29
29
|
|
|
@@ -33,13 +33,13 @@ def check_maturity_date(maturity_date: pd.Timestamp) -> None:
|
|
|
33
33
|
Raises:
|
|
34
34
|
ValueError: If the maturity date is not the 1st of January.
|
|
35
35
|
"""
|
|
36
|
-
if
|
|
36
|
+
if maturity.day != 1 or maturity.month not in COUPON_MONTHS:
|
|
37
37
|
raise ValueError("NTN-F maturity date must be the 1st of January.")
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def coupon_dates(
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
settlement: str | pd.Timestamp,
|
|
42
|
+
maturity: str | pd.Timestamp,
|
|
43
43
|
) -> pd.Series:
|
|
44
44
|
"""
|
|
45
45
|
Generate all remaining coupon dates between a settlement date and a maturity date.
|
|
@@ -48,29 +48,29 @@ def coupon_dates(
|
|
|
48
48
|
The NTN-F bond is determined by its maturity date.
|
|
49
49
|
|
|
50
50
|
Args:
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
settlement (str | pd.Timestamp): The settlement date.
|
|
52
|
+
maturity (str | pd.Timestamp): The maturity date.
|
|
53
53
|
|
|
54
54
|
Returns:
|
|
55
55
|
pd.Series: Series of coupon dates within the specified range.
|
|
56
56
|
"""
|
|
57
57
|
# Validate and normalize dates
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
settlement = dc.convert_date(settlement)
|
|
59
|
+
maturity = dc.convert_date(maturity)
|
|
60
60
|
|
|
61
61
|
# Check if the maturity date is valid
|
|
62
|
-
check_maturity_date(
|
|
62
|
+
check_maturity_date(maturity)
|
|
63
63
|
|
|
64
64
|
# Check if maturity date is after the start date
|
|
65
|
-
if
|
|
65
|
+
if maturity <= settlement:
|
|
66
66
|
raise ValueError("Maturity date must be after the start date.")
|
|
67
67
|
|
|
68
68
|
# Initialize loop variables
|
|
69
|
-
coupon_date =
|
|
69
|
+
coupon_date = maturity
|
|
70
70
|
coupon_dates = []
|
|
71
71
|
|
|
72
72
|
# Iterate backwards from the maturity date to the settlement date
|
|
73
|
-
while coupon_date >
|
|
73
|
+
while coupon_date > settlement:
|
|
74
74
|
coupon_dates.append(coupon_date)
|
|
75
75
|
# Move the coupon date back 6 months
|
|
76
76
|
coupon_date -= pd.DateOffset(months=6)
|
|
@@ -80,8 +80,8 @@ def coupon_dates(
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def cash_flows(
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
settlement: str | pd.Timestamp,
|
|
84
|
+
maturity: str | pd.Timestamp,
|
|
85
85
|
) -> pd.Series:
|
|
86
86
|
"""
|
|
87
87
|
Generate the cash flows for the NTN-F bond between the settlement (exclusive) and
|
|
@@ -89,24 +89,24 @@ def cash_flows(
|
|
|
89
89
|
payment at maturity.
|
|
90
90
|
|
|
91
91
|
Args:
|
|
92
|
-
|
|
92
|
+
settlement (str | pd.Timestamp): The settlement date in 'DD-MM-YYYY' format
|
|
93
93
|
or a pandas Timestamp.
|
|
94
|
-
|
|
94
|
+
maturity (str | pd.Timestamp): The maturity date in 'DD-MM-YYYY' format or
|
|
95
95
|
a pandas Timestamp.
|
|
96
96
|
|
|
97
97
|
Returns:
|
|
98
98
|
pd.Series: Series of cash flows within the specified range.
|
|
99
99
|
"""
|
|
100
100
|
# Validate input dates
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
check_maturity_date(
|
|
101
|
+
settlement = dc.convert_date(settlement)
|
|
102
|
+
maturity = dc.convert_date(maturity)
|
|
103
|
+
check_maturity_date(maturity)
|
|
104
104
|
|
|
105
105
|
# Get the coupon payment dates between the settlement and maturity dates
|
|
106
|
-
payment_dates = coupon_dates(
|
|
106
|
+
payment_dates = coupon_dates(settlement, maturity)
|
|
107
107
|
|
|
108
108
|
# Set the cash flow at maturity to FINAL_PMT and the others to COUPON_PMT
|
|
109
|
-
cfs = np.where(payment_dates ==
|
|
109
|
+
cfs = np.where(payment_dates == maturity, FINAL_PMT, COUPON_PMT)
|
|
110
110
|
|
|
111
111
|
df = pd.DataFrame(data=cfs, index=payment_dates, columns=["CashFlow"])
|
|
112
112
|
df.index.name = "PaymentDate"
|
|
@@ -115,21 +115,21 @@ def cash_flows(
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def price(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
settlement: str | pd.Timestamp,
|
|
119
|
+
maturity: str | pd.Timestamp,
|
|
120
|
+
rate: float,
|
|
121
121
|
) -> float:
|
|
122
122
|
"""
|
|
123
123
|
Calculate the NTN-F price using Anbima rules, which corresponds to the present
|
|
124
124
|
value of the cash flows discounted at the given yield to maturity rate (YTM).
|
|
125
125
|
|
|
126
126
|
Args:
|
|
127
|
-
|
|
127
|
+
settlement (str | pd.Timestamp): The settlement date in 'DD-MM-YYYY' format
|
|
128
128
|
or a pandas Timestamp.
|
|
129
|
-
|
|
129
|
+
maturity (str | pd.Timestamp): The maturity date in 'DD-MM-YYYY' format or
|
|
130
130
|
a pandas Timestamp.
|
|
131
|
-
|
|
132
|
-
the cash flows.
|
|
131
|
+
rate (float): The discount rate (yield to maturity) used to calculate the
|
|
132
|
+
present value of the cash flows.
|
|
133
133
|
|
|
134
134
|
Returns:
|
|
135
135
|
float: The NTN-F price using Anbima rules.
|
|
@@ -144,17 +144,32 @@ def price(
|
|
|
144
144
|
>>> price("05-07-2024", "01-01-2035", 0.11921)
|
|
145
145
|
895.359254
|
|
146
146
|
"""
|
|
147
|
-
df_cf = cash_flows(
|
|
147
|
+
df_cf = cash_flows(settlement, maturity).reset_index()
|
|
148
148
|
cfs = df_cf["CashFlow"]
|
|
149
|
-
bdays = bday.count(
|
|
149
|
+
bdays = bday.count(settlement, df_cf["PaymentDate"])
|
|
150
150
|
byears = ut.truncate(bdays / 252, 14)
|
|
151
|
-
discount_factors = (1 +
|
|
151
|
+
discount_factors = (1 + rate) ** byears
|
|
152
152
|
# Calculate the present value of each cash flow (DCF) rounded as per Anbima rules
|
|
153
153
|
dcf = (cfs / discount_factors).round(9)
|
|
154
154
|
# Return the sum of the discounted cash flows truncated as per Anbima rules
|
|
155
155
|
return ut.truncate(dcf.sum(), 6)
|
|
156
156
|
|
|
157
157
|
|
|
158
|
+
def maturities(reference_date: str | pd.Timestamp) -> list[pd.Timestamp]:
|
|
159
|
+
"""
|
|
160
|
+
Fetch the NTN-F bond maturities available for the given reference date.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
reference_date (str | pd.Timestamp): The reference date for fetching the data.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
list[pd.Timestamp]: A list of NTN-F bond maturities available for the reference
|
|
167
|
+
date.
|
|
168
|
+
"""
|
|
169
|
+
rates = an.get_anbima_rates(reference_date, "NTN-F")
|
|
170
|
+
return rates.index.to_list()
|
|
171
|
+
|
|
172
|
+
|
|
158
173
|
def anbima_data(reference_date: str | pd.Timestamp) -> pd.DataFrame:
|
|
159
174
|
"""
|
|
160
175
|
Fetch NTN-F Anbima data for the given reference date.
|
|
@@ -170,23 +185,24 @@ def anbima_data(reference_date: str | pd.Timestamp) -> pd.DataFrame:
|
|
|
170
185
|
|
|
171
186
|
def indicative_rates(
|
|
172
187
|
reference_date: str | pd.Timestamp,
|
|
173
|
-
|
|
188
|
+
maturity: str | pd.Timestamp | None = None,
|
|
174
189
|
) -> pd.Series | float:
|
|
175
190
|
"""
|
|
176
|
-
Fetch NTN-F Anbima indicative rates for the given reference date.
|
|
191
|
+
Fetch NTN-F Anbima indicative rates for the given reference date. If a maturity date
|
|
192
|
+
is provided, the function returns the rate for the given maturity date.
|
|
177
193
|
|
|
178
194
|
Args:
|
|
179
195
|
reference_date (str | pd.Timestamp): The reference date for fetching the data.
|
|
196
|
+
maturity_date (str | pd.Timestamp, optional): The maturity date of the bond.
|
|
180
197
|
|
|
181
198
|
Returns:
|
|
182
199
|
pd.Series: A Series containing the rates indexed by maturity date.
|
|
183
200
|
"""
|
|
184
201
|
rates = an.get_anbima_rates(reference_date, "NTN-F")
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return float(rates[maturity_date])
|
|
202
|
+
if maturity:
|
|
203
|
+
maturity = dc.convert_date(maturity)
|
|
204
|
+
if maturity in rates.index:
|
|
205
|
+
return float(rates[maturity])
|
|
190
206
|
else:
|
|
191
207
|
return float("nan")
|
|
192
208
|
return rates
|
|
@@ -207,27 +223,29 @@ def anbima_historical_rates(maturity_date: str | pd.Timestamp) -> pd.Series:
|
|
|
207
223
|
|
|
208
224
|
def _calculate_coupons_pv(
|
|
209
225
|
bootstrap_df: pd.DataFrame,
|
|
210
|
-
|
|
211
|
-
|
|
226
|
+
settlement: pd.Timestamp,
|
|
227
|
+
maturity: pd.Timestamp,
|
|
212
228
|
) -> float:
|
|
213
229
|
# Create a subset DataFrame with only the coupon payments (without last payment)
|
|
214
|
-
cp_dates_wo_last = coupon_dates(
|
|
230
|
+
cp_dates_wo_last = coupon_dates(settlement, maturity)[:-1] # noqa
|
|
215
231
|
df_coupons = bootstrap_df.query("MaturityDate in @cp_dates_wo_last").copy()
|
|
216
232
|
df_coupons["Coupon"] = COUPON_PMT
|
|
217
233
|
|
|
218
234
|
# Calculate the present value of the coupon payments
|
|
219
235
|
pv = ut.calculate_present_value(
|
|
220
236
|
cash_flows=df_coupons["Coupon"],
|
|
221
|
-
|
|
222
|
-
|
|
237
|
+
rates=df_coupons["SpotRate"],
|
|
238
|
+
periods=df_coupons["BDays"] / 252,
|
|
223
239
|
)
|
|
224
240
|
return pv
|
|
225
241
|
|
|
226
242
|
|
|
227
243
|
def spot_rates(
|
|
228
|
-
|
|
229
|
-
ltn_rates: pd.Series,
|
|
230
|
-
|
|
244
|
+
settlement: str | pd.Timestamp,
|
|
245
|
+
ltn_rates: list[float] | pd.Series,
|
|
246
|
+
ltn_maturities: list[pd.Timestamp] | pd.Series,
|
|
247
|
+
ntnf_rates: list[float] | pd.Series,
|
|
248
|
+
ntnf_maturities: list[pd.Timestamp] | pd.Series,
|
|
231
249
|
) -> pd.DataFrame:
|
|
232
250
|
"""
|
|
233
251
|
Calculate the spot rates for NTN-F bonds using the bootstrap method.
|
|
@@ -249,32 +267,32 @@ def spot_rates(
|
|
|
249
267
|
the corresponding spot rates.
|
|
250
268
|
"""
|
|
251
269
|
# Process and validate the input data
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
270
|
+
settlement = dc.convert_date(settlement)
|
|
271
|
+
ltn_maturities = pd.to_datetime(ltn_maturities, errors="coerce", dayfirst=True)
|
|
272
|
+
ntnf_maturities = pd.to_datetime(ntnf_maturities, errors="coerce", dayfirst=True)
|
|
255
273
|
|
|
256
274
|
# Create flat forward interpolators for LTN and NTN-F rates
|
|
257
275
|
ltn_rate_interpolator = Interpolator(
|
|
258
276
|
method="flat_forward",
|
|
259
|
-
known_bdays=bday.count(
|
|
277
|
+
known_bdays=bday.count(settlement, ltn_maturities),
|
|
260
278
|
known_rates=ltn_rates,
|
|
261
279
|
)
|
|
262
280
|
ntnf_rate_interpolator = Interpolator(
|
|
263
281
|
method="flat_forward",
|
|
264
|
-
known_bdays=bday.count(
|
|
282
|
+
known_bdays=bday.count(settlement, ntnf_maturities),
|
|
265
283
|
known_rates=ntnf_rates,
|
|
266
284
|
)
|
|
267
285
|
|
|
268
286
|
# Determine the last maturity dates for LTN and NTN-F rates
|
|
269
|
-
last_ltn =
|
|
270
|
-
last_ntnf =
|
|
287
|
+
last_ltn = ltn_maturities.max()
|
|
288
|
+
last_ntnf = ntnf_maturities.max()
|
|
271
289
|
|
|
272
290
|
# Generate all coupon dates up to the last NTN-F maturity date
|
|
273
|
-
all_coupon_dates = coupon_dates(
|
|
291
|
+
all_coupon_dates = coupon_dates(settlement, last_ntnf)
|
|
274
292
|
|
|
275
293
|
# Create a DataFrame with all coupon dates and the corresponding YTM
|
|
276
294
|
df_spot = pd.DataFrame(data=all_coupon_dates, columns=["MaturityDate"])
|
|
277
|
-
df_spot["BDays"] = bday.count(start=
|
|
295
|
+
df_spot["BDays"] = bday.count(start=settlement, end=df_spot["MaturityDate"])
|
|
278
296
|
df_spot["YTM"] = df_spot["BDays"].apply(ntnf_rate_interpolator)
|
|
279
297
|
|
|
280
298
|
# The Bootstrap loop to calculate spot rates
|
|
@@ -288,16 +306,16 @@ def spot_rates(
|
|
|
288
306
|
continue
|
|
289
307
|
|
|
290
308
|
# Calculate the spot rate for the bond
|
|
291
|
-
coupons_pv = _calculate_coupons_pv(df_spot,
|
|
309
|
+
coupons_pv = _calculate_coupons_pv(df_spot, settlement, maturity)
|
|
292
310
|
ytm = df_spot.at[index, "YTM"]
|
|
293
|
-
bond_price = price(
|
|
311
|
+
bond_price = price(settlement, maturity, ytm)
|
|
294
312
|
spot_rate = (FINAL_PMT / (bond_price - coupons_pv)) ** (252 / bdays) - 1
|
|
295
313
|
df_spot.at[index, "SpotRate"] = spot_rate
|
|
296
314
|
|
|
297
315
|
return df_spot
|
|
298
316
|
|
|
299
317
|
|
|
300
|
-
def
|
|
318
|
+
def di_gross_spreads(reference_date: str | pd.Timestamp) -> pd.Series:
|
|
301
319
|
"""
|
|
302
320
|
Calculates the DI spread for the NTN-F based on ANBIMA's indicative rates.
|
|
303
321
|
|
|
@@ -321,9 +339,9 @@ def gross_di_spreads(reference_date: str | pd.Timestamp) -> pd.Series:
|
|
|
321
339
|
return df["DISpread"]
|
|
322
340
|
|
|
323
341
|
|
|
324
|
-
def
|
|
325
|
-
|
|
326
|
-
|
|
342
|
+
def di_net_spread( # noqa
|
|
343
|
+
settlement: str | pd.Timestamp,
|
|
344
|
+
maturity: str | pd.Timestamp,
|
|
327
345
|
ytm: float,
|
|
328
346
|
di_rates: list[float] | pd.Series,
|
|
329
347
|
di_expirations: list[pd.Timestamp] | pd.Series,
|
|
@@ -338,47 +356,45 @@ def net_di_spread( # noqa
|
|
|
338
356
|
(in bps) that zeroes the difference between the bond's market price and its
|
|
339
357
|
discounted cash flows.
|
|
340
358
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
The
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
A list of DI expiration dates.
|
|
359
|
+
Args:
|
|
360
|
+
settlement (str | pd.Timestamp): The settlement date in 'DD-MM-YYYY' format
|
|
361
|
+
or a pandas Timestamp.
|
|
362
|
+
maturity (str | pd.Timestamp): The maturity date in 'DD-MM-YYYY'
|
|
363
|
+
format or a pandas Timestamp.
|
|
364
|
+
ytm (float): The yield to maturity (YTM) of the bond.
|
|
365
|
+
di_rates (list[float] | pd.Series): A list or Series of DI rates.
|
|
366
|
+
di_expirations (list[pd.Timestamp] | pd.Series): A list or Series of DI
|
|
367
|
+
expiration dates.
|
|
368
|
+
initial_guess (float, optional): An initial guess for the spread.
|
|
369
|
+
Defaults to None.
|
|
353
370
|
|
|
354
371
|
Returns:
|
|
355
|
-
|
|
356
|
-
float
|
|
357
|
-
The net DI spread in basis points. Returns NaN if a solution cannot be found.
|
|
372
|
+
float: The net DI spread in basis points.
|
|
358
373
|
"""
|
|
359
|
-
|
|
360
374
|
# Create an interpolator for the DI rates using the flat-forward method
|
|
361
|
-
|
|
362
|
-
|
|
375
|
+
settlement = dc.convert_date(settlement)
|
|
376
|
+
maturity = dc.convert_date(maturity)
|
|
363
377
|
|
|
364
378
|
ff_interpolator = Interpolator(
|
|
365
379
|
"flat_forward",
|
|
366
|
-
bday.count(
|
|
380
|
+
bday.count(settlement, di_expirations),
|
|
367
381
|
di_rates,
|
|
368
382
|
)
|
|
369
383
|
|
|
370
|
-
# Ensure the DI
|
|
384
|
+
# Ensure the DI data is valid
|
|
371
385
|
if len(di_rates) != len(di_expirations):
|
|
372
386
|
raise ValueError("di_rates and di_expirations must have the same length.")
|
|
387
|
+
if len(di_rates) == 0:
|
|
388
|
+
return float("nan")
|
|
373
389
|
|
|
374
390
|
# Calculate cash flows and business days between settlement and payment dates
|
|
375
|
-
df = cash_flows(
|
|
376
|
-
df["BDays"] = bday.count(
|
|
391
|
+
df = cash_flows(settlement, maturity).reset_index()
|
|
392
|
+
df["BDays"] = bday.count(settlement, df["PaymentDate"])
|
|
377
393
|
|
|
378
394
|
# Calculate business years (252 business days per year)
|
|
379
|
-
byears = bday.count(
|
|
395
|
+
byears = bday.count(settlement, df["PaymentDate"]) / 252
|
|
380
396
|
di_interp = df["BDays"].apply(ff_interpolator)
|
|
381
|
-
bond_price = price(
|
|
397
|
+
bond_price = price(settlement, maturity, ytm)
|
|
382
398
|
bond_cash_flows = df["CashFlow"]
|
|
383
399
|
|
|
384
400
|
def price_difference(p):
|
|
@@ -36,16 +36,10 @@ def truncate(values: float | pd.Series, decimal_places: int) -> float | pd.Serie
|
|
|
36
36
|
|
|
37
37
|
def calculate_present_value(
|
|
38
38
|
cash_flows: pd.Series,
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
rates: pd.Series,
|
|
40
|
+
periods: pd.Series,
|
|
41
41
|
) -> float:
|
|
42
|
-
return (cash_flows / (1 +
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def standardize_rates(rates: pd.Series) -> pd.Series:
|
|
46
|
-
if not isinstance(rates.index, pd.DatetimeIndex):
|
|
47
|
-
raise ValueError("The rates index must be a DatetimeIndex with maturity dates.")
|
|
48
|
-
return rates.dropna().sort_index()
|
|
42
|
+
return (cash_flows / (1 + rates) ** periods).sum()
|
|
49
43
|
|
|
50
44
|
|
|
51
45
|
def di_spreads(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
1
3
|
import pandas as pd
|
|
2
4
|
|
|
3
5
|
from . import bday
|
|
@@ -9,6 +11,11 @@ DI_URL = "https://raw.githubusercontent.com/crdcj/pyield-data/main/di_data.parqu
|
|
|
9
11
|
class DIData:
|
|
10
12
|
_df = pd.DataFrame()
|
|
11
13
|
|
|
14
|
+
def __init__(self):
|
|
15
|
+
"""Initialize the DIData class and load the data."""
|
|
16
|
+
if self._df.empty or not self._is_data_up_to_date():
|
|
17
|
+
self._load_data()
|
|
18
|
+
|
|
12
19
|
@classmethod
|
|
13
20
|
def _load_data(cls):
|
|
14
21
|
print("Loading DI dataset...")
|
|
@@ -36,19 +43,31 @@ class DIData:
|
|
|
36
43
|
cls._load_data()
|
|
37
44
|
return cls._df.copy()
|
|
38
45
|
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _get_rate_column(rate_type: str) -> str:
|
|
48
|
+
rate_map = {
|
|
49
|
+
"settlement": "SettlementRate",
|
|
50
|
+
"min": "MinRate",
|
|
51
|
+
"max": "MaxRate",
|
|
52
|
+
"close": "CloseRate",
|
|
53
|
+
}
|
|
54
|
+
if rate_type not in rate_map:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"Invalid rate type. Use 'settlement', 'min', 'max', or 'close'."
|
|
57
|
+
)
|
|
58
|
+
return rate_map[rate_type]
|
|
59
|
+
|
|
39
60
|
@classmethod
|
|
40
|
-
def
|
|
61
|
+
def rates(
|
|
41
62
|
cls,
|
|
42
63
|
trade_date: str | pd.Timestamp | None = None,
|
|
43
64
|
expiration_date: str | pd.Timestamp | None = None,
|
|
65
|
+
rate_type: Literal["settlement", "min", "max", "close"] = "settlement",
|
|
44
66
|
adjust_exp_date: bool = False,
|
|
45
|
-
) -> pd.DataFrame:
|
|
67
|
+
) -> pd.DataFrame | float:
|
|
68
|
+
rate_col = cls._get_rate_column(rate_type)
|
|
46
69
|
cls._check_for_updates()
|
|
47
|
-
df = cls._df[["TradeDate", "ExpirationDate", "
|
|
48
|
-
|
|
49
|
-
if trade_date:
|
|
50
|
-
trade_date = dc.convert_date(trade_date)
|
|
51
|
-
df.query("TradeDate == @trade_date", inplace=True)
|
|
70
|
+
df = cls._df[["TradeDate", "ExpirationDate", f"{rate_col}"]].copy()
|
|
52
71
|
|
|
53
72
|
if expiration_date:
|
|
54
73
|
expiration_date = dc.convert_date(expiration_date)
|
|
@@ -56,28 +75,16 @@ class DIData:
|
|
|
56
75
|
expiration_date = bday.offset(expiration_date, 0)
|
|
57
76
|
df.query("ExpirationDate == @expiration_date", inplace=True)
|
|
58
77
|
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
return df.sort_values(["TradeDate", "ExpirationDate"]).reset_index(drop=True)
|
|
65
|
-
|
|
66
|
-
@classmethod
|
|
67
|
-
def settlement_rate(
|
|
68
|
-
cls,
|
|
69
|
-
trade_date: str | pd.Timestamp,
|
|
70
|
-
expiration_date: str | pd.Timestamp,
|
|
71
|
-
) -> float:
|
|
72
|
-
cls._check_for_updates()
|
|
73
|
-
df = cls._df.copy()
|
|
74
|
-
|
|
75
|
-
trade_date = dc.convert_date(trade_date)
|
|
76
|
-
df.query("TradeDate == @trade_date", inplace=True)
|
|
77
|
-
|
|
78
|
-
expiration_date = dc.convert_date(expiration_date)
|
|
79
|
-
# Force the expiration date to be a business day
|
|
80
|
-
expiration_date = bday.offset(expiration_date, 0)
|
|
81
|
-
df.query("ExpirationDate == @expiration_date", inplace=True)
|
|
78
|
+
if trade_date:
|
|
79
|
+
trade_date = dc.convert_date(trade_date)
|
|
80
|
+
df.query("TradeDate == @trade_date", inplace=True)
|
|
82
81
|
|
|
83
|
-
|
|
82
|
+
if adjust_exp_date:
|
|
83
|
+
df["ExpirationDate"] = df["ExpirationDate"].dt.to_period("M")
|
|
84
|
+
df["ExpirationDate"] = df["ExpirationDate"].dt.to_timestamp()
|
|
85
|
+
|
|
86
|
+
df.sort_values(["TradeDate", "ExpirationDate"]).reset_index(drop=True)
|
|
87
|
+
if len(df.index) == 1:
|
|
88
|
+
return float(df[f"{rate_col}"].values[0])
|
|
89
|
+
else:
|
|
90
|
+
return df
|
|
@@ -13,7 +13,7 @@ ANBIMA_URL = "https://www.anbima.com.br/informacoes/merc-sec/arqs"
|
|
|
13
13
|
ANBIMA_MEMBER_URL = "http://www.anbima.associados.rtm/merc_sec/arqs"
|
|
14
14
|
# URL example: https://www.anbima.com.br/informacoes/merc-sec/arqs/ms240614.txt
|
|
15
15
|
RATES_URL = (
|
|
16
|
-
"https://raw.githubusercontent.com/crdcj/pyield-data/main/
|
|
16
|
+
"https://raw.githubusercontent.com/crdcj/pyield-data/main/anbima_data.parquet"
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
# Before 13/05/2014 the file was zipped and the endpoint ended with ".exe"
|
|
@@ -157,9 +157,8 @@ class RatesData:
|
|
|
157
157
|
@classmethod
|
|
158
158
|
def _load_data(cls):
|
|
159
159
|
# if cls._df.empty or not cls._is_data_up_to_date():
|
|
160
|
-
print("Loading ANBIMA
|
|
160
|
+
print("Loading ANBIMA data...")
|
|
161
161
|
cls._df = pd.read_parquet(RATES_URL)
|
|
162
|
-
cls._df["IndicativeRate"] = (cls._df["IndicativeRate"] / 100).round(6)
|
|
163
162
|
cls._last_update = pd.Timestamp.today().normalize()
|
|
164
163
|
|
|
165
164
|
@classmethod
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.12.2"
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|