PYield 0.16.2__tar.gz → 0.16.3__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.16.2 → pyield-0.16.3}/PKG-INFO +1 -1
- pyield-0.16.3/pyield/__about__.py +1 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/bond_tools.py +13 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/ntnb.py +43 -62
- {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/ntnf.py +31 -48
- {pyield-0.16.2 → pyield-0.16.3}/pyproject.toml +1 -1
- pyield-0.16.2/pyield/__about__.py +0 -1
- {pyield-0.16.2 → pyield-0.16.3}/LICENSE +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/README.md +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/__init__.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/bday.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/__init__.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/lft.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/ltn.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/__init__.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/anbima.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/data_cache.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/di.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/__init__.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/common.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/historical.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/intraday.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/xml.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/indicators.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/data/projections.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/date_converter.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/__init__.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/holidays_manager.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/interpolator.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/pyield/py.typed +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/tests/__init__.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/tests/data/SPRD231222.zip +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/tests/data/SPRD240426.zip +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/tests/test_bday.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/tests/test_futures.py +0 -0
- {pyield-0.16.2 → pyield-0.16.3}/tests/test_futures_xml.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.16.3"
|
|
@@ -40,6 +40,19 @@ def calculate_present_value(
|
|
|
40
40
|
rates: pd.Series,
|
|
41
41
|
periods: pd.Series,
|
|
42
42
|
) -> float:
|
|
43
|
+
# Return 0 if any input is empty
|
|
44
|
+
if cash_flows.empty or rates.empty or periods.empty:
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
# Reset the index to avoid issues with the series alignment
|
|
48
|
+
cash_flows = cash_flows.reset_index(drop=True)
|
|
49
|
+
rates = rates.reset_index(drop=True)
|
|
50
|
+
periods = periods.reset_index(drop=True)
|
|
51
|
+
|
|
52
|
+
# Check if data have the same length
|
|
53
|
+
if len(cash_flows) != len(rates) or len(cash_flows) != len(periods):
|
|
54
|
+
raise ValueError("All series must have the same length.")
|
|
55
|
+
|
|
43
56
|
return (cash_flows / (1 + rates) ** periods).sum()
|
|
44
57
|
|
|
45
58
|
|
|
@@ -65,7 +65,7 @@ def rates(reference_date: str | pd.Timestamp) -> pd.DataFrame:
|
|
|
65
65
|
pd.DataFrame: DataFrame containing the maturity dates and indicative rates
|
|
66
66
|
for the NTN-B bonds.
|
|
67
67
|
"""
|
|
68
|
-
ntnb_rates = anbima.rates(reference_date, "
|
|
68
|
+
ntnb_rates = anbima.rates(reference_date, "NTN-B")
|
|
69
69
|
if ntnb_rates.empty:
|
|
70
70
|
return pd.DataFrame()
|
|
71
71
|
return ntnb_rates[["MaturityDate", "IndicativeRate"]]
|
|
@@ -121,7 +121,7 @@ def _coupon_dates_map(
|
|
|
121
121
|
return pd.Series(coupon_dates).reset_index(drop=True)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def
|
|
124
|
+
def payment_dates(
|
|
125
125
|
settlement: str | pd.Timestamp,
|
|
126
126
|
maturity: str | pd.Timestamp,
|
|
127
127
|
) -> pd.Series:
|
|
@@ -186,13 +186,13 @@ def cash_flows(
|
|
|
186
186
|
maturity = dc.convert_date(maturity)
|
|
187
187
|
|
|
188
188
|
# Get the coupon dates between the settlement and maturity dates
|
|
189
|
-
|
|
189
|
+
p_dates = payment_dates(settlement, maturity)
|
|
190
190
|
|
|
191
191
|
# Set the cash flow at maturity to FINAL_PMT and the others to COUPON_PMT
|
|
192
|
-
cfs = np.where(
|
|
192
|
+
cfs = np.where(p_dates == maturity, FINAL_PMT, COUPON_PMT).tolist()
|
|
193
193
|
|
|
194
194
|
# Return a dataframe with the payment dates and cash flows
|
|
195
|
-
return pd.DataFrame(data={"PaymentDate":
|
|
195
|
+
return pd.DataFrame(data={"PaymentDate": p_dates, "CashFlow": cfs})
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
def quotation(
|
|
@@ -231,12 +231,12 @@ def quotation(
|
|
|
231
231
|
maturity = dc.convert_date(maturity)
|
|
232
232
|
_check_maturities(maturity)
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
cf_values =
|
|
234
|
+
cf_df = cash_flows(settlement, maturity)
|
|
235
|
+
cf_dates = cf_df["PaymentDate"]
|
|
236
|
+
cf_values = cf_df["CashFlow"]
|
|
237
237
|
|
|
238
238
|
# Calculate the number of business days between settlement and cash flow dates
|
|
239
|
-
bdays = bday.count(settlement,
|
|
239
|
+
bdays = bday.count(settlement, cf_dates)
|
|
240
240
|
|
|
241
241
|
# Calculate the number of periods truncated as per Anbima rules
|
|
242
242
|
num_of_years = bt.truncate(bdays / 252, 14)
|
|
@@ -244,10 +244,10 @@ def quotation(
|
|
|
244
244
|
discount_factor = (1 + rate) ** num_of_years
|
|
245
245
|
|
|
246
246
|
# Calculate the present value of each cash flow (DCF) rounded as per Anbima rules
|
|
247
|
-
|
|
247
|
+
cf_present_value = (cf_values / discount_factor).round(10)
|
|
248
248
|
|
|
249
249
|
# Return the quotation (the dcf sum) truncated as per Anbima rules
|
|
250
|
-
return bt.truncate(
|
|
250
|
+
return bt.truncate(cf_present_value.sum(), 4)
|
|
251
251
|
|
|
252
252
|
|
|
253
253
|
def price(
|
|
@@ -274,32 +274,10 @@ def price(
|
|
|
274
274
|
return bt.truncate(vna * quotation / 100, 6)
|
|
275
275
|
|
|
276
276
|
|
|
277
|
-
def _calculate_coupons_pv(
|
|
278
|
-
bootstrap_df: pd.DataFrame,
|
|
279
|
-
settlement: pd.Timestamp,
|
|
280
|
-
maturity: pd.Timestamp,
|
|
281
|
-
) -> float:
|
|
282
|
-
# Create a subset DataFrame with only the coupon payments (without last payment)
|
|
283
|
-
cp_dates_wo_last = coupon_dates(settlement, maturity)[:-1] # noqa
|
|
284
|
-
if len(cp_dates_wo_last) == 0:
|
|
285
|
-
return 0
|
|
286
|
-
|
|
287
|
-
df_coupons = bootstrap_df.query("MaturityDate in @cp_dates_wo_last").copy()
|
|
288
|
-
df_coupons["Coupon"] = COUPON_PMT
|
|
289
|
-
|
|
290
|
-
# Calculate the present value of the coupon payments
|
|
291
|
-
pv = bt.calculate_present_value(
|
|
292
|
-
cash_flows=df_coupons["Coupon"],
|
|
293
|
-
rates=df_coupons["SpotRate"],
|
|
294
|
-
periods=df_coupons["BDays"] / 252,
|
|
295
|
-
)
|
|
296
|
-
return pv
|
|
297
|
-
|
|
298
|
-
|
|
299
277
|
def spot_rates(
|
|
300
278
|
settlement: str | pd.Timestamp,
|
|
301
|
-
rates: pd.Series,
|
|
302
279
|
maturities: pd.Series,
|
|
280
|
+
rates: pd.Series,
|
|
303
281
|
) -> pd.DataFrame:
|
|
304
282
|
"""
|
|
305
283
|
Calculate the spot rates for NTN-B bonds using the bootstrap method.
|
|
@@ -312,9 +290,8 @@ def spot_rates(
|
|
|
312
290
|
|
|
313
291
|
Args:
|
|
314
292
|
settlement (str | pd.Timestamp): The reference date for settlement.
|
|
315
|
-
rates (pd.Series): Series of yield to maturity rates indexed by the
|
|
316
|
-
maturity dates of the bonds.
|
|
317
293
|
maturities (pd.Series): Series of maturity dates for the bonds.
|
|
294
|
+
rates (pd.Series): Series of yield to maturity rates.
|
|
318
295
|
|
|
319
296
|
Returns:
|
|
320
297
|
pd.DataFrame: DataFrame containing the maturity dates and corresponding real
|
|
@@ -329,51 +306,55 @@ def spot_rates(
|
|
|
329
306
|
"""
|
|
330
307
|
# Process and validate the input data
|
|
331
308
|
settlement = dc.convert_date(settlement)
|
|
332
|
-
maturities = pd.to_datetime(maturities, errors="
|
|
309
|
+
maturities = pd.to_datetime(maturities, errors="raise", dayfirst=True)
|
|
333
310
|
_check_maturities(maturities)
|
|
334
311
|
|
|
335
312
|
# Create the interpolator object
|
|
336
|
-
|
|
313
|
+
ff_interpolator = it.Interpolator(
|
|
337
314
|
method="flat_forward",
|
|
338
315
|
known_bdays=bday.count(settlement, maturities),
|
|
339
316
|
known_rates=rates,
|
|
340
317
|
)
|
|
341
318
|
|
|
342
|
-
last_ntnb = maturities.max()
|
|
343
|
-
|
|
344
319
|
# Generate coupon dates up to the longest maturity date
|
|
345
|
-
all_coupon_dates = _coupon_dates_map(start=settlement, end=
|
|
320
|
+
all_coupon_dates = _coupon_dates_map(start=settlement, end=maturities.max())
|
|
346
321
|
|
|
347
322
|
# Create a DataFrame with all coupon dates and the corresponding YTM
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
323
|
+
df = pd.DataFrame(data=all_coupon_dates, columns=["MaturityDate"])
|
|
324
|
+
df["BDays"] = bday.count(settlement, df["MaturityDate"])
|
|
325
|
+
df["BYears"] = df["BDays"] / 252
|
|
326
|
+
df["YTM"] = df["BDays"].apply(ff_interpolator)
|
|
327
|
+
df["Coupon"] = COUPON_PMT
|
|
328
|
+
df["SpotRate"] = pd.NA
|
|
351
329
|
|
|
352
330
|
# The Bootstrap loop to calculate spot rates
|
|
353
|
-
for index in
|
|
354
|
-
# Get the
|
|
355
|
-
|
|
356
|
-
bdays = df_spot.at[index, "BDays"]
|
|
357
|
-
ytm = df_spot.at[index, "YTM"]
|
|
358
|
-
|
|
359
|
-
# Get the coupon dates for the bond without the last one (principal + coupon)
|
|
360
|
-
cp_dates = coupon_dates(settlement, maturity)
|
|
331
|
+
for index, row in df.iterrows():
|
|
332
|
+
# Get the cash flow dates for the bond
|
|
333
|
+
cf_dates = payment_dates(settlement, row["MaturityDate"])
|
|
361
334
|
|
|
362
335
|
# If there is only one coupon date and this date is the first maturity date
|
|
363
336
|
# of an existing bond, the ytm rate is also a spot rate.
|
|
364
|
-
if len(
|
|
365
|
-
|
|
337
|
+
if len(cf_dates) == 1 and cf_dates[0] == maturities[0]:
|
|
338
|
+
df.at[index, "SpotRate"] = row["YTM"]
|
|
366
339
|
continue
|
|
367
340
|
|
|
368
|
-
# Calculate the
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
341
|
+
# Calculate the present value of the cash flows without last payment
|
|
342
|
+
cf_dates = cf_dates[:-1]
|
|
343
|
+
cf_df = df.query("MaturityDate in @cf_dates").reset_index(drop=True)
|
|
344
|
+
cf_present_value = bt.calculate_present_value(
|
|
345
|
+
cash_flows=cf_df["Coupon"],
|
|
346
|
+
rates=cf_df["SpotRate"],
|
|
347
|
+
periods=cf_df["BYears"],
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Calculate the Spot Rate for the bond
|
|
351
|
+
bond_price = quotation(settlement, row["MaturityDate"], row["YTM"])
|
|
352
|
+
price_factor = FINAL_PMT / (bond_price - cf_present_value)
|
|
353
|
+
df.at[index, "SpotRate"] = price_factor ** (1 / row["BYears"]) - 1
|
|
354
|
+
|
|
355
|
+
df.drop(columns=["BDays", "BYears", "Coupon"], inplace=True)
|
|
375
356
|
# Return the result without the intermediate coupon dates (virtual bonds)
|
|
376
|
-
return
|
|
357
|
+
return df.query("MaturityDate in @maturities").reset_index(drop=True)
|
|
377
358
|
|
|
378
359
|
|
|
379
360
|
def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
@@ -180,9 +180,9 @@ def price(
|
|
|
180
180
|
>>> price("05-07-2024", "01-01-2035", 0.11921)
|
|
181
181
|
895.359254
|
|
182
182
|
"""
|
|
183
|
-
|
|
184
|
-
cf_values =
|
|
185
|
-
bdays = bday.count(settlement,
|
|
183
|
+
cf_df = cash_flows(settlement, maturity)
|
|
184
|
+
cf_values = cf_df["CashFlow"]
|
|
185
|
+
bdays = bday.count(settlement, cf_df["PaymentDate"])
|
|
186
186
|
byears = bt.truncate(bdays / 252, 14)
|
|
187
187
|
discount_factors = (1 + rate) ** byears
|
|
188
188
|
# Calculate the present value of each cash flow (DCF) rounded as per Anbima rules
|
|
@@ -191,31 +191,12 @@ def price(
|
|
|
191
191
|
return bt.truncate(dcf.sum(), 6)
|
|
192
192
|
|
|
193
193
|
|
|
194
|
-
def _calculate_coupons_pv(
|
|
195
|
-
bootstrap_df: pd.DataFrame,
|
|
196
|
-
settlement: pd.Timestamp,
|
|
197
|
-
maturity: pd.Timestamp,
|
|
198
|
-
) -> float:
|
|
199
|
-
# Create a subset DataFrame with only the coupon payments (without last payment)
|
|
200
|
-
cp_dates_wo_last = coupon_dates(settlement, maturity)[:-1] # noqa
|
|
201
|
-
df_coupons = bootstrap_df.query("MaturityDate in @cp_dates_wo_last").copy()
|
|
202
|
-
df_coupons["Coupon"] = COUPON_PMT
|
|
203
|
-
|
|
204
|
-
# Calculate the present value of the coupon payments
|
|
205
|
-
pv = bt.calculate_present_value(
|
|
206
|
-
cash_flows=df_coupons["Coupon"],
|
|
207
|
-
rates=df_coupons["SpotRate"],
|
|
208
|
-
periods=df_coupons["BDays"] / 252,
|
|
209
|
-
)
|
|
210
|
-
return pv
|
|
211
|
-
|
|
212
|
-
|
|
213
194
|
def spot_rates(
|
|
214
195
|
settlement: str | pd.Timestamp,
|
|
215
|
-
ltn_rates: pd.Series,
|
|
216
196
|
ltn_maturities: pd.Series,
|
|
217
|
-
|
|
197
|
+
ltn_rates: pd.Series,
|
|
218
198
|
ntnf_maturities: pd.Series,
|
|
199
|
+
ntnf_rates: pd.Series,
|
|
219
200
|
) -> pd.DataFrame:
|
|
220
201
|
"""
|
|
221
202
|
Calculate the spot rates for NTN-F bonds using the bootstrap method.
|
|
@@ -252,36 +233,38 @@ def spot_rates(
|
|
|
252
233
|
known_rates=ntnf_rates,
|
|
253
234
|
)
|
|
254
235
|
|
|
255
|
-
# Determine the last maturity dates for LTN and NTN-F rates
|
|
256
|
-
last_ltn = ltn_maturities.max()
|
|
257
|
-
last_ntnf = ntnf_maturities.max()
|
|
258
|
-
|
|
259
236
|
# Generate all coupon dates up to the last NTN-F maturity date
|
|
260
|
-
all_coupon_dates = coupon_dates(settlement,
|
|
237
|
+
all_coupon_dates = coupon_dates(settlement, ntnf_maturities.max())
|
|
261
238
|
|
|
262
239
|
# Create a DataFrame with all coupon dates and the corresponding YTM
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
240
|
+
df = pd.DataFrame(data=all_coupon_dates, columns=["MaturityDate"])
|
|
241
|
+
df["BDays"] = bday.count(start=settlement, end=df["MaturityDate"])
|
|
242
|
+
df["BYears"] = df["BDays"] / 252
|
|
243
|
+
df["Coupon"] = COUPON_PMT
|
|
244
|
+
df["YTM"] = df["BDays"].apply(ntnf_rate_interpolator)
|
|
266
245
|
|
|
267
246
|
# The Bootstrap loop to calculate spot rates
|
|
268
|
-
for index in
|
|
269
|
-
|
|
270
|
-
bdays = df_spot.at[index, "BDays"]
|
|
271
|
-
|
|
272
|
-
if maturity <= last_ltn:
|
|
247
|
+
for index, row in df.iterrows():
|
|
248
|
+
if row["MaturityDate"] <= ltn_maturities.max():
|
|
273
249
|
# Use LTN rates for maturities before the last LTN maturity date
|
|
274
|
-
|
|
250
|
+
df.at[index, "SpotRate"] = ltn_rate_interpolator(row["BDays"])
|
|
275
251
|
continue
|
|
276
252
|
|
|
277
|
-
# Calculate the
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
253
|
+
# Calculate the present value of the coupon payments
|
|
254
|
+
cf_dates = coupon_dates(settlement, row["MaturityDate"])[:-1] # noqa
|
|
255
|
+
cf_df = df.query("MaturityDate in @cf_dates").reset_index(drop=True)
|
|
256
|
+
cf_present_value = bt.calculate_present_value(
|
|
257
|
+
cash_flows=cf_df["Coupon"],
|
|
258
|
+
rates=cf_df["SpotRate"],
|
|
259
|
+
periods=cf_df["BDays"] / 252,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
bond_price = price(settlement, row["MaturityDate"], row["YTM"])
|
|
263
|
+
price_factor = FINAL_PMT / (bond_price - cf_present_value)
|
|
264
|
+
df.at[index, "SpotRate"] = price_factor ** (1 / row["BYears"]) - 1
|
|
283
265
|
|
|
284
|
-
return
|
|
266
|
+
# Remove temporary columns and return the spot rates DataFrame
|
|
267
|
+
return df[["MaturityDate", "BDays", "YTM", "SpotRate"]].copy()
|
|
285
268
|
|
|
286
269
|
|
|
287
270
|
def di_spreads(reference_date: str | pd.Timestamp) -> pd.DataFrame:
|
|
@@ -366,8 +349,8 @@ def di_net_spread( # noqa
|
|
|
366
349
|
settlement: str | pd.Timestamp,
|
|
367
350
|
maturity: str | pd.Timestamp,
|
|
368
351
|
ytm: float,
|
|
369
|
-
di_rates: pd.Series,
|
|
370
352
|
di_expirations: pd.Series,
|
|
353
|
+
di_rates: pd.Series,
|
|
371
354
|
initial_guess: float | None = None,
|
|
372
355
|
) -> float:
|
|
373
356
|
"""
|
|
@@ -430,10 +413,10 @@ def di_net_spread( # noqa
|
|
|
430
413
|
|
|
431
414
|
def premium(
|
|
432
415
|
settlement: str | pd.Timestamp,
|
|
433
|
-
ntnf_rate: float,
|
|
434
416
|
ntnf_maturity: str | pd.Timestamp,
|
|
435
|
-
|
|
417
|
+
ntnf_rate: float,
|
|
436
418
|
di_expirations: pd.Series,
|
|
419
|
+
di_rates: pd.Series,
|
|
437
420
|
) -> float:
|
|
438
421
|
ntnf_maturity = dc.convert_date(ntnf_maturity)
|
|
439
422
|
settlement = dc.convert_date(settlement)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.16.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|