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.
Files changed (38) hide show
  1. {pyield-0.16.2 → pyield-0.16.3}/PKG-INFO +1 -1
  2. pyield-0.16.3/pyield/__about__.py +1 -0
  3. {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/bond_tools.py +13 -0
  4. {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/ntnb.py +43 -62
  5. {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/ntnf.py +31 -48
  6. {pyield-0.16.2 → pyield-0.16.3}/pyproject.toml +1 -1
  7. pyield-0.16.2/pyield/__about__.py +0 -1
  8. {pyield-0.16.2 → pyield-0.16.3}/LICENSE +0 -0
  9. {pyield-0.16.2 → pyield-0.16.3}/README.md +0 -0
  10. {pyield-0.16.2 → pyield-0.16.3}/pyield/__init__.py +0 -0
  11. {pyield-0.16.2 → pyield-0.16.3}/pyield/bday.py +0 -0
  12. {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/__init__.py +0 -0
  13. {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/lft.py +0 -0
  14. {pyield-0.16.2 → pyield-0.16.3}/pyield/bonds/ltn.py +0 -0
  15. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/__init__.py +0 -0
  16. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/anbima.py +0 -0
  17. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/data_cache.py +0 -0
  18. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/di.py +0 -0
  19. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/__init__.py +0 -0
  20. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/common.py +0 -0
  21. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/historical.py +0 -0
  22. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/intraday.py +0 -0
  23. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/futures_data/xml.py +0 -0
  24. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/indicators.py +0 -0
  25. {pyield-0.16.2 → pyield-0.16.3}/pyield/data/projections.py +0 -0
  26. {pyield-0.16.2 → pyield-0.16.3}/pyield/date_converter.py +0 -0
  27. {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/__init__.py +0 -0
  28. {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/br_holidays_new.txt +0 -0
  29. {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/br_holidays_old.txt +0 -0
  30. {pyield-0.16.2 → pyield-0.16.3}/pyield/holidays/holidays_manager.py +0 -0
  31. {pyield-0.16.2 → pyield-0.16.3}/pyield/interpolator.py +0 -0
  32. {pyield-0.16.2 → pyield-0.16.3}/pyield/py.typed +0 -0
  33. {pyield-0.16.2 → pyield-0.16.3}/tests/__init__.py +0 -0
  34. {pyield-0.16.2 → pyield-0.16.3}/tests/data/SPRD231222.zip +0 -0
  35. {pyield-0.16.2 → pyield-0.16.3}/tests/data/SPRD240426.zip +0 -0
  36. {pyield-0.16.2 → pyield-0.16.3}/tests/test_bday.py +0 -0
  37. {pyield-0.16.2 → pyield-0.16.3}/tests/test_futures.py +0 -0
  38. {pyield-0.16.2 → pyield-0.16.3}/tests/test_futures_xml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PYield
3
- Version: 0.16.2
3
+ Version: 0.16.3
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>
@@ -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, "LTN")
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 coupon_dates(
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
- payment_dates = coupon_dates(settlement, maturity)
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(payment_dates == maturity, FINAL_PMT, COUPON_PMT).tolist()
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": payment_dates, "CashFlow": cfs})
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
- df_cf = cash_flows(settlement, maturity)
235
- payment_dates = df_cf["PaymentDate"]
236
- cf_values = df_cf["CashFlow"]
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, payment_dates)
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
- discounted_cash_flows = (cf_values / discount_factor).round(10)
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(discounted_cash_flows.sum(), 4)
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="coerce", dayfirst=True)
309
+ maturities = pd.to_datetime(maturities, errors="raise", dayfirst=True)
333
310
  _check_maturities(maturities)
334
311
 
335
312
  # Create the interpolator object
336
- ytm_rate_interpolator = it.Interpolator(
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=last_ntnb)
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
- df_spot = pd.DataFrame(data=all_coupon_dates, columns=["MaturityDate"])
349
- df_spot["BDays"] = bday.count(settlement, df_spot["MaturityDate"])
350
- df_spot["YTM"] = df_spot["BDays"].apply(ytm_rate_interpolator)
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 df_spot.index:
354
- # Get the row values using the index
355
- maturity = df_spot.at[index, "MaturityDate"]
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(cp_dates) == 1 and cp_dates[0] == rates[0]:
365
- df_spot.at[index, "SpotRate"] = ytm
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 real spot rate for the bond
369
- coupons_pv = _calculate_coupons_pv(df_spot, settlement, maturity)
370
- bond_price = quotation(settlement, maturity, ytm)
371
- spot_rate = (FINAL_PMT / (bond_price - coupons_pv)) ** (252 / bdays) - 1
372
- df_spot.at[index, "SpotRate"] = spot_rate
373
-
374
- df_spot.drop(columns=["BDays"], inplace=True)
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 df_spot.query("MaturityDate in @maturities").reset_index(drop=True)
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
- df_cf = cash_flows(settlement, maturity)
184
- cf_values = df_cf["CashFlow"]
185
- bdays = bday.count(settlement, df_cf["PaymentDate"])
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
- ntnf_rates: pd.Series,
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, last_ntnf)
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
- df_spot = pd.DataFrame(data=all_coupon_dates, columns=["MaturityDate"])
264
- df_spot["BDays"] = bday.count(start=settlement, end=df_spot["MaturityDate"])
265
- df_spot["YTM"] = df_spot["BDays"].apply(ntnf_rate_interpolator)
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 df_spot.index:
269
- maturity = df_spot.at[index, "MaturityDate"]
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
- df_spot.at[index, "SpotRate"] = ltn_rate_interpolator(bdays)
250
+ df.at[index, "SpotRate"] = ltn_rate_interpolator(row["BDays"])
275
251
  continue
276
252
 
277
- # Calculate the spot rate for the bond
278
- coupons_pv = _calculate_coupons_pv(df_spot, settlement, maturity)
279
- ytm = df_spot.at[index, "YTM"]
280
- bond_price = price(settlement, maturity, ytm)
281
- spot_rate = (FINAL_PMT / (bond_price - coupons_pv)) ** (252 / bdays) - 1
282
- df_spot.at[index, "SpotRate"] = spot_rate
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 df_spot
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
- di_rates: pd.Series,
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)
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "types-requests",
33
33
  ]
34
34
  dynamic = []
35
- version = "0.16.2"
35
+ version = "0.16.3"
36
36
 
37
37
  [project.license]
38
38
  file = "LICENSE"
@@ -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