PYield 0.24.0__tar.gz → 0.24.2__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 (34) hide show
  1. {pyield-0.24.0 → pyield-0.24.2}/PKG-INFO +1 -1
  2. pyield-0.24.2/pyield/__about__.py +1 -0
  3. {pyield-0.24.0 → pyield-0.24.2}/pyield/bonds/bond_tools.py +1 -1
  4. {pyield-0.24.0 → pyield-0.24.2}/pyield/bonds/ntnf.py +16 -6
  5. {pyield-0.24.0 → pyield-0.24.2}/pyield/di.py +63 -46
  6. {pyield-0.24.0 → pyield-0.24.2}/pyield/interpolator.py +28 -40
  7. {pyield-0.24.0 → pyield-0.24.2}/pyield/tools.py +0 -3
  8. pyield-0.24.0/pyield/__about__.py +0 -1
  9. {pyield-0.24.0 → pyield-0.24.2}/.gitignore +0 -0
  10. {pyield-0.24.0 → pyield-0.24.2}/LICENSE +0 -0
  11. {pyield-0.24.0 → pyield-0.24.2}/README.md +0 -0
  12. {pyield-0.24.0 → pyield-0.24.2}/pyield/__init__.py +0 -0
  13. {pyield-0.24.0 → pyield-0.24.2}/pyield/anbima.py +0 -0
  14. {pyield-0.24.0 → pyield-0.24.2}/pyield/b3_futures/__init__.py +0 -0
  15. {pyield-0.24.0 → pyield-0.24.2}/pyield/b3_futures/common.py +0 -0
  16. {pyield-0.24.0 → pyield-0.24.2}/pyield/b3_futures/historical.py +0 -0
  17. {pyield-0.24.0 → pyield-0.24.2}/pyield/b3_futures/intraday.py +0 -0
  18. {pyield-0.24.0 → pyield-0.24.2}/pyield/b3_futures/xml.py +0 -0
  19. {pyield-0.24.0 → pyield-0.24.2}/pyield/bday.py +0 -0
  20. {pyield-0.24.0 → pyield-0.24.2}/pyield/bonds/__init__.py +0 -0
  21. {pyield-0.24.0 → pyield-0.24.2}/pyield/bonds/lft.py +0 -0
  22. {pyield-0.24.0 → pyield-0.24.2}/pyield/bonds/ltn.py +0 -0
  23. {pyield-0.24.0 → pyield-0.24.2}/pyield/bonds/ntnb.py +0 -0
  24. {pyield-0.24.0 → pyield-0.24.2}/pyield/conftest.py +0 -0
  25. {pyield-0.24.0 → pyield-0.24.2}/pyield/data_cache.py +0 -0
  26. {pyield-0.24.0 → pyield-0.24.2}/pyield/date_converter.py +0 -0
  27. {pyield-0.24.0 → pyield-0.24.2}/pyield/holidays/__init__.py +0 -0
  28. {pyield-0.24.0 → pyield-0.24.2}/pyield/holidays/br_holidays_new.txt +0 -0
  29. {pyield-0.24.0 → pyield-0.24.2}/pyield/holidays/br_holidays_old.txt +0 -0
  30. {pyield-0.24.0 → pyield-0.24.2}/pyield/holidays/brholidays.py +0 -0
  31. {pyield-0.24.0 → pyield-0.24.2}/pyield/indicators.py +0 -0
  32. {pyield-0.24.0 → pyield-0.24.2}/pyield/projections.py +0 -0
  33. {pyield-0.24.0 → pyield-0.24.2}/pyield/py.typed +0 -0
  34. {pyield-0.24.0 → pyield-0.24.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: PYield
3
- Version: 0.24.0
3
+ Version: 0.24.2
4
4
  Summary: A Python library for analysis of fixed income instruments in Brazil
5
5
  Project-URL: Source, https://github.com/crdcj/PYield
6
6
  Author-email: Carlos Carvalho <cr.cj@outlook.com>
@@ -0,0 +1 @@
1
+ __version__ = "0.24.2"
@@ -77,7 +77,7 @@ def pre_spreads(reference_date: DateScalar) -> pd.DataFrame:
77
77
  """
78
78
  # Fetch DI rates for the reference date
79
79
  converted_date = dc.convert_input_dates(reference_date)
80
- di = DIFutures(trade_dates=converted_date, adj_expirations=True)
80
+ di = DIFutures(trade_dates=converted_date, month_start=True)
81
81
  df_di = di.data
82
82
  if "SettlementRate" not in df_di.columns:
83
83
  raise ValueError("DI rates data is missing the 'SettlementRate' column.")
@@ -3,7 +3,7 @@ from collections.abc import Callable
3
3
  import numpy as np
4
4
  import pandas as pd
5
5
 
6
- from pyield import anbima, bday, di
6
+ from pyield import anbima, bday, data_cache, di
7
7
  from pyield import date_converter as dc
8
8
  from pyield import interpolator as ip
9
9
  from pyield.bonds import bond_tools as bt
@@ -22,6 +22,17 @@ COUPON_PMT = 48.80885
22
22
  FINAL_PMT = 1048.80885
23
23
 
24
24
 
25
+ def reference_dates() -> pd.Series:
26
+ """
27
+ Fetch the NTN-F bond reference dates available in the Anbima database.
28
+
29
+ Returns:
30
+ pd.Series: A Series of NTN-F bond reference dates.
31
+ """
32
+ df = data_cache.get_anbima_dataset().query("BondType == 'NTN-F'")
33
+ return df["ReferenceDate"].drop_duplicates().sort_values().reset_index(drop=True)
34
+
35
+
25
36
  def rates(reference_date: DateScalar) -> pd.DataFrame:
26
37
  """
27
38
  Fetch the bond indicative rates for the given reference date.
@@ -592,14 +603,13 @@ def historical_premium(
592
603
  df["BDays"] = bday.count(reference_date, df["PaymentDate"])
593
604
  df["BYears"] = df["BDays"] / 252
594
605
  df["ReferenceDate"] = reference_date
595
- # Instantiate the DI Futures class
596
- dif = di.DIFutures()
606
+
607
+ dif = di.DIFutures() # Instantiate the DI Futures class
597
608
  df["DIRate"] = dif.interpolate_rates(
598
609
  reference_dates=df["ReferenceDate"],
599
- maturities=df["PaymentDate"],
600
- allow_extrapolation=False,
610
+ maturity_dates=df["PaymentDate"],
611
+ extrapolate=False,
601
612
  )
602
- # df["DIRate"] = df["PaymentDate"].apply(dif.rate)
603
613
 
604
614
  # Calculate the present value of the cash flows using the DI rate
605
615
  bond_price = bt.calculate_present_value(
@@ -18,14 +18,15 @@ class DIFutures:
18
18
 
19
19
  Args:
20
20
  trade_date (DateScalar): The date to retrieve the contract data.
21
- adj_expirations (bool): If True, adjusts the expiration dates to the start
21
+ month_start (bool): If True, adjusts the expiration dates to the start
22
22
  of the month.
23
- prefixed_filter (bool): If True, filters the DI contracts to match only
24
- expirations with existing LTN and NTN-F bond maturities.
23
+ prefixed (bool): If True, filters the DI contracts to match only
24
+ expirations with existing prefixed TN bond maturities (LTN and NTN-F).
25
+ all_columns (bool): If True, returns all available columns in the DI dataset.
25
26
 
26
27
  Examples:
27
28
  To create a `DIFutures` instance and retrieve data:
28
- >>> di = yd.DIFutures(trade_dates="16-10-2024", adj_expirations=True)
29
+ >>> di = yd.DIFutures(trade_dates="16-10-2024", month_start=True)
29
30
  >>> df = di.data # Retrieve DI contract dataframe for the specified date
30
31
  >>> df.iloc[:5, :5] # Show the first five rows and columns
31
32
  TradeDate ExpirationDate TickerSymbol BDaysToExp OpenContracts
@@ -43,7 +44,6 @@ class DIFutures:
43
44
  2 2024-10-16 2025-01-01 0.11164 0.1154
44
45
  3 2024-10-16 2025-02-01 0.11362 0.118314
45
46
  4 2024-10-16 2025-03-01 0.1157 0.12343
46
-
47
47
  """
48
48
 
49
49
  historical_trade_dates = (
@@ -61,16 +61,16 @@ class DIFutures:
61
61
  def __init__(
62
62
  self,
63
63
  trade_dates: DateScalar | DateArray | None = None,
64
- adj_expirations: bool = False,
65
- prefixed_filter: bool = False,
64
+ month_start: bool = False,
65
+ prefixed: bool = False,
66
66
  all_columns: bool = True,
67
67
  ):
68
68
  """
69
69
  Initialize the DIFutures instance with a specific trade date.
70
70
  """
71
71
  self.trade_dates = trade_dates
72
- self.adj_expirations = adj_expirations
73
- self.prefixed_filter = prefixed_filter
72
+ self.month_start = month_start
73
+ self.prefixed = prefixed
74
74
  self.all_columns = all_columns
75
75
 
76
76
  @property
@@ -93,7 +93,7 @@ class DIFutures:
93
93
  if "DaysToExpiration" in df.columns:
94
94
  df.drop(columns=["DaysToExpiration"], inplace=True)
95
95
 
96
- if self._prefixed_filter:
96
+ if self._prefixed:
97
97
  df_pre = (
98
98
  get_anbima_dataset()
99
99
  .query("BondType in ['LTN', 'NTN-F']")
@@ -113,7 +113,7 @@ class DIFutures:
113
113
 
114
114
  df = df.merge(df_pre, how="inner")
115
115
 
116
- if self._adj_expirations:
116
+ if self._month_start:
117
117
  df["ExpirationDate"] = (
118
118
  df["ExpirationDate"].dt.to_period("M").dt.to_timestamp()
119
119
  )
@@ -167,8 +167,8 @@ class DIFutures:
167
167
  @staticmethod
168
168
  def interpolate_rates(
169
169
  reference_dates: DateScalar | DateArray,
170
- maturities: DateScalar | DateArray,
171
- allow_extrapolation: bool = True,
170
+ maturity_dates: DateScalar | DateArray,
171
+ extrapolate: bool = True,
172
172
  ) -> pd.Series:
173
173
  """
174
174
  Interpolates DI rates for specified reference dates and maturities.
@@ -177,11 +177,15 @@ class DIFutures:
177
177
  dates and maturities using a flat-forward interpolation method. If no DI
178
178
  rates are available for a reference date, the interpolated rate is set to NaN.
179
179
 
180
+ If reference dates is provided as a scalar and maturities as an array, the
181
+ method assumes the scalar value is the same for all maturities. The same logic
182
+ applies when the maturities are scalar and the reference dates are an array.
183
+
180
184
  Args:
181
185
  reference_dates (DateScalar | DateArray): The reference dates for the rates.
182
- maturities (DateScalar | DateArray): The maturity dates corresponding to the
186
+ maturity_dates (DateScalar | DateArray): The maturities corresponding to the
183
187
  reference dates.
184
- allow_extrapolation (bool): Whether to allow extrap. beyond known DI rates.
188
+ extrapolate (bool): Whether to allow extrapolation beyond known DI rates.
185
189
 
186
190
  Returns:
187
191
  pd.Series: A Series containing the interpolated DI rates.
@@ -190,27 +194,38 @@ class DIFutures:
190
194
  ValueError: If `reference_dates` and `maturities` have different lengths.
191
195
  """
192
196
  # Convert input dates to a consistent format
193
- reference_dates = list(dc.convert_input_dates(reference_dates))
194
- maturities = list(dc.convert_input_dates(maturities))
197
+ reference_dates = dc.convert_input_dates(reference_dates)
198
+ maturity_dates = dc.convert_input_dates(maturity_dates)
195
199
 
196
200
  # Ensure the lengths of input arrays are consistent
197
- if len(reference_dates) != len(maturities):
198
- raise ValueError("Dates and maturities must have the same length.")
201
+ match (reference_dates, maturity_dates):
202
+ case pd.Timestamp(), pd.Series():
203
+ dfi = pd.DataFrame({"mat": maturity_dates})
204
+ dfi["ref"] = reference_dates
205
+
206
+ case pd.Series(), pd.Timestamp():
207
+ dfi = pd.DataFrame({"ref": reference_dates})
208
+ dfi["mat"] = maturity_dates
209
+
210
+ case pd.Series(), pd.Series():
211
+ if len(reference_dates) != len(maturity_dates):
212
+ raise ValueError("Args. should have the same length.")
213
+ dfi = pd.DataFrame({"ref": reference_dates, "mat": maturity_dates})
199
214
 
200
- # Create a DataFrame with reference dates and maturities
201
- dfi = pd.DataFrame({"reference_date": reference_dates, "maturity": maturities})
215
+ case pd.Timestamp(), pd.Timestamp():
216
+ dfi = pd.DataFrame({"ref": [reference_dates], "mat": [maturity_dates]})
202
217
 
203
218
  # Compute business days between reference dates and maturities
204
- dfi["bdays"] = bday.count(dfi["reference_date"], dfi["maturity"])
219
+ dfi["bdays"] = bday.count(dfi["ref"], dfi["mat"])
205
220
 
206
221
  # Initialize the interpolated rate column with NaN
207
- dfi["interpolated_rate"] = pd.NA
208
- dfi["interpolated_rate"] = dfi["interpolated_rate"].astype("Float64")
222
+ dfi["irate"] = pd.NA
223
+ dfi["irate"] = dfi["irate"].astype("Float64")
209
224
 
210
225
  # Load DI rates dataset filtered by the provided reference dates
211
226
  dfr = (
212
227
  get_di_dataset()
213
- .query("TradeDate in @reference_dates")
228
+ .query("TradeDate in @dfi['ref'].unique()")
214
229
  .reset_index(drop=True)
215
230
  )
216
231
 
@@ -219,7 +234,7 @@ class DIFutures:
219
234
  return pd.DataFrame()
220
235
 
221
236
  # Iterate over each unique reference date
222
- for date in set(reference_dates):
237
+ for date in dfi["ref"].unique():
223
238
  # Filter DI rates for the current reference date
224
239
  dfr_subset = dfr.query("TradeDate == @date").copy()
225
240
 
@@ -232,15 +247,17 @@ class DIFutures:
232
247
  method="flat_forward",
233
248
  known_bdays=dfr_subset["BDaysToExp"],
234
249
  known_rates=dfr_subset["SettlementRate"],
235
- extrapolate=allow_extrapolation,
250
+ extrapolate=extrapolate,
236
251
  )
237
252
 
238
253
  # Apply interpolation to rows matching the current reference date
239
- mask = dfi["reference_date"] == date
240
- dfi.loc[mask, "interpolated_rate"] = dfi.loc[mask, "bdays"].apply(interp)
254
+ mask = dfi["ref"] == date
255
+ dfi.loc[mask, "irate"] = dfi.loc[mask, "bdays"].apply(interp)
241
256
 
242
- # Return the DataFrame with interpolated rates
243
- return dfi["interpolated_rate"]
257
+ # Return the Series with interpolated rates
258
+ s_irates = dfi["irate"]
259
+ s_irates.name = "InterpolatedRates"
260
+ return s_irates
244
261
 
245
262
  def rate(
246
263
  self,
@@ -251,7 +268,7 @@ class DIFutures:
251
268
  """Retrieve the DI rate for a specified expiration date."""
252
269
  expiration = dc.convert_input_dates(expiration)
253
270
 
254
- if self._adj_expirations:
271
+ if self._month_start:
255
272
  # Force the expiration date to be the start of the month
256
273
  expiration = expiration.to_period("M").to_timestamp()
257
274
  else:
@@ -313,7 +330,7 @@ class DIFutures:
313
330
  self._trade_dates = trade_dates.drop_duplicates().sort_values().to_list()
314
331
 
315
332
  @property
316
- def adj_expirations(self) -> bool:
333
+ def month_start(self) -> bool:
317
334
  """
318
335
  Adjusts the expiration dates to the start of the month.
319
336
 
@@ -324,29 +341,29 @@ class DIFutures:
324
341
  Returns:
325
342
  bool: Whether expiration dates are adjusted to the start of the month.
326
343
  """
327
- return self._adj_expirations
344
+ return self._month_start
328
345
 
329
- @adj_expirations.setter
330
- def adj_expirations(self, value: bool):
331
- self._adj_expirations = value
346
+ @month_start.setter
347
+ def month_start(self, value: bool):
348
+ self._month_start = value
332
349
 
333
350
  @property
334
- def prefixed_filter(self) -> bool:
351
+ def prefixed(self) -> bool:
335
352
  """
336
353
  Filters DI Futures to match prefixed TN bond maturities.
337
354
 
338
- This property can be both read and set. When set to `True`, DI Futures will be
339
- filtered to match the maturities of LTN and NTN-F bonds from the Anbima dataset.
355
+ This property can be both read and set. When set to `True`, only contracts that
356
+ match the maturities of LTN and NTN-F bonds will be shown.
340
357
 
341
358
  Returns:
342
- bool: Whether DI Futures are filtered to match prefixed Anbima bond
343
- maturities.
359
+ bool: Whether the contracts are filtered to match prefixed TN bond
360
+ maturities from the ANBIMA dataset.
344
361
  """
345
- return self._prefixed_filter
362
+ return self._prefixed
346
363
 
347
- @prefixed_filter.setter
348
- def prefixed_filter(self, value: bool):
349
- self._prefixed_filter = value
364
+ @prefixed.setter
365
+ def prefixed(self, value: bool):
366
+ self._prefixed = value
350
367
 
351
368
  @property
352
369
  def all_columns(self) -> bool:
@@ -22,7 +22,9 @@ class Interpolator:
22
22
  ValueError: If the interpolation method is not recognized
23
23
 
24
24
  Note:
25
- This class uses a 252 business days per year convention.
25
+ - This class uses a 252 business days per year convention.
26
+ - Instances of this class are **immutable**. To modify the interpolation
27
+ settings, create a new instance.
26
28
 
27
29
  Examples:
28
30
  >>> from pyield import Interpolator
@@ -43,34 +45,21 @@ class Interpolator:
43
45
  def __init__(
44
46
  self,
45
47
  method: Literal["flat_forward", "linear"],
46
- known_bdays: pd.Series | list[int],
47
- known_rates: pd.Series | list[float],
48
+ known_bdays: pd.Series | np.ndarray | tuple[int] | list[int],
49
+ known_rates: pd.Series | np.ndarray | tuple[float] | list[float],
48
50
  extrapolate: bool = False,
49
51
  ):
50
- self.method = method
51
- self.known_bdays = known_bdays
52
- self.known_rates = known_rates
53
- self.extrapolate = extrapolate
54
- self._validate_and_process_inputs()
55
-
56
- def _validate_and_process_inputs(self) -> None:
57
- """Validate and process the inputs of the Interpolator."""
58
- known_bdays = self.known_bdays
59
- known_rates = self.known_rates
60
- # Series may have different index, so we convert to list
61
- if isinstance(known_bdays, pd.Series):
62
- known_bdays = known_bdays.to_list()
63
- if isinstance(known_rates, pd.Series):
64
- known_rates = known_rates.to_list()
65
-
66
- if len(known_bdays) != len(known_rates):
67
- raise ValueError("known_bdays and known_rates must have the same length.")
68
-
69
- df = pd.DataFrame({"bday": known_bdays, "rate": known_rates})
70
- df = df.dropna().drop_duplicates(subset="bday").sort_values("bday")
71
-
72
- self._known_bdays = df["bday"].to_list()
73
- self._known_rates = df["rate"].to_list()
52
+ df = (
53
+ pd.DataFrame({"bday": known_bdays, "rate": known_rates})
54
+ .dropna()
55
+ .drop_duplicates(subset="bday")
56
+ .sort_values("bday", ignore_index=True)
57
+ )
58
+ self._df = df
59
+ self._method = method
60
+ self._known_bdays = tuple(df["bday"])
61
+ self._known_rates = tuple(df["rate"])
62
+ self._extrapolate = bool(extrapolate)
74
63
 
75
64
  def _flat_forward(self, bday: int) -> float:
76
65
  """Performs the interest rate interpolation using the flat forward method."""
@@ -90,11 +79,6 @@ class Interpolator:
90
79
  c = (bday - prev_bday) / (next_bday - prev_bday)
91
80
  return (a * (b / a) ** c) ** (252 / bday) - 1
92
81
 
93
- def _linear(self, bday: int) -> float:
94
- """Performs linear interpolation."""
95
- np_float = np.interp(bday, self._known_bdays, self._known_rates)
96
- return float(np_float)
97
-
98
82
  def interpolate(self, bday: int) -> float:
99
83
  """
100
84
  Finds the appropriate interpolation point and returns the interest rate
@@ -114,17 +98,14 @@ class Interpolator:
114
98
  elif bday in self._known_bdays:
115
99
  return self._known_rates[self._known_bdays.index(bday)]
116
100
  elif bday > self._known_bdays[-1]:
117
- if self.extrapolate:
118
- return self._known_rates[-1]
119
- else:
120
- return float("NaN") # Return NaN if extrapolation is not allowed
101
+ return self._known_rates[-1] if self._extrapolate else float("NaN")
121
102
 
122
- if self.method == "flat_forward":
103
+ if self._method == "flat_forward":
123
104
  return self._flat_forward(bday)
124
- elif self.method == "linear":
125
- return self._linear(bday)
105
+ elif self._method == "linear":
106
+ return float(np.interp(bday, self._known_bdays, self._known_rates))
126
107
  else:
127
- raise ValueError(f"Unknown interpolation method: {self.method}.")
108
+ raise ValueError(f"Unknown interpolation method: {self._method}.")
128
109
 
129
110
  def __call__(self, bday: int) -> float:
130
111
  """
@@ -139,3 +120,10 @@ class Interpolator:
139
120
  number of business days.
140
121
  """
141
122
  return self.interpolate(bday)
123
+
124
+ def __repr__(self) -> str:
125
+ """
126
+ Textual representation, used in terminal or scripts.
127
+ """
128
+
129
+ return repr(self._df)
@@ -1,8 +1,5 @@
1
1
  import pandas as pd
2
2
 
3
- # Expose pre_spreads function from pyield.bonds.bond_tools
4
- # from pyield.bonds.bond_tools import pre_spreads # noqa
5
-
6
3
 
7
4
  def forward_rates(
8
5
  business_days: pd.Series,
@@ -1 +0,0 @@
1
- __version__ = "0.24.0"
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