PYield 0.16.3__tar.gz → 0.16.4__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.3 → pyield-0.16.4}/PKG-INFO +1 -1
  2. pyield-0.16.4/pyield/__about__.py +1 -0
  3. {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/lft.py +18 -39
  4. {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/ntnf.py +4 -7
  5. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/indicators.py +39 -22
  6. {pyield-0.16.3 → pyield-0.16.4}/pyproject.toml +1 -1
  7. pyield-0.16.3/pyield/__about__.py +0 -1
  8. {pyield-0.16.3 → pyield-0.16.4}/LICENSE +0 -0
  9. {pyield-0.16.3 → pyield-0.16.4}/README.md +0 -0
  10. {pyield-0.16.3 → pyield-0.16.4}/pyield/__init__.py +0 -0
  11. {pyield-0.16.3 → pyield-0.16.4}/pyield/bday.py +0 -0
  12. {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/__init__.py +0 -0
  13. {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/bond_tools.py +0 -0
  14. {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/ltn.py +0 -0
  15. {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/ntnb.py +0 -0
  16. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/__init__.py +0 -0
  17. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/anbima.py +0 -0
  18. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/data_cache.py +0 -0
  19. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/di.py +0 -0
  20. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/__init__.py +0 -0
  21. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/common.py +0 -0
  22. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/historical.py +0 -0
  23. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/intraday.py +0 -0
  24. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/xml.py +0 -0
  25. {pyield-0.16.3 → pyield-0.16.4}/pyield/data/projections.py +0 -0
  26. {pyield-0.16.3 → pyield-0.16.4}/pyield/date_converter.py +0 -0
  27. {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/__init__.py +0 -0
  28. {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/br_holidays_new.txt +0 -0
  29. {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/br_holidays_old.txt +0 -0
  30. {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/holidays_manager.py +0 -0
  31. {pyield-0.16.3 → pyield-0.16.4}/pyield/interpolator.py +0 -0
  32. {pyield-0.16.3 → pyield-0.16.4}/pyield/py.typed +0 -0
  33. {pyield-0.16.3 → pyield-0.16.4}/tests/__init__.py +0 -0
  34. {pyield-0.16.3 → pyield-0.16.4}/tests/data/SPRD231222.zip +0 -0
  35. {pyield-0.16.3 → pyield-0.16.4}/tests/data/SPRD240426.zip +0 -0
  36. {pyield-0.16.3 → pyield-0.16.4}/tests/test_bday.py +0 -0
  37. {pyield-0.16.3 → pyield-0.16.4}/tests/test_futures.py +0 -0
  38. {pyield-0.16.3 → pyield-0.16.4}/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.3
3
+ Version: 0.16.4
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.4"
@@ -2,7 +2,7 @@ import pandas as pd
2
2
 
3
3
  from .. import bday
4
4
  from .. import date_converter as dc
5
- from ..data import anbima, di
5
+ from ..data import anbima
6
6
  from . import bond_tools as bt
7
7
 
8
8
 
@@ -77,47 +77,26 @@ def quotation(
77
77
  return bt.truncate(100 * discount_factor, 4)
78
78
 
79
79
 
80
- def premium(lft_rate: float, di_rate: float) -> float:
81
- di_factor = (1 + di_rate) ** (1 / 252)
82
- lft_factor = (1 + lft_rate) ** (1 / 252) * di_factor
83
-
84
- return (lft_factor - 1) / (di_factor - 1)
85
-
86
-
87
- def historical_premium(
88
- reference_date: str | pd.Timestamp,
89
- maturity: str | pd.Timestamp,
90
- ) -> float:
80
+ def premium(lft_rate: float, selic_over: float) -> float:
91
81
  """
92
- Calculate the premium of the LFT bond over the DI Future rate for a given date.
82
+ Calculate the premium of the LFT bond over the Selic rate (overnight).
83
+ Obs: The Selic rate (overnight) is not the same as the Selic target rate
93
84
 
94
85
  Args:
95
- reference_date (str | pd.Timestamp): The reference date to fetch the rates.
96
- maturity (str | pd.Timestamp): The maturity date of the LFT bond.
86
+ lft_rate (float): The LFT rate for the bond.
87
+ selic_over (float): The Selic overnight rate.
97
88
 
98
89
  Returns:
99
- float: The premium of the LFT bond over the DI Future rate for the given date.
100
- If the data is not available, returns NaN.
101
- """
102
- # Convert input dates to a consistent format
103
- reference_date = dc.convert_date(reference_date)
104
- maturity = dc.convert_date(maturity)
90
+ float: The premium of the LFT bond over the Selic rate.
105
91
 
106
- # Retrieve LFT rates for the reference date
107
- df_anbima = rates(reference_date)
108
- if df_anbima.empty:
109
- return float("NaN")
110
-
111
- # Extract the LFT rate for the specified maturity date
112
- lft_rates = df_anbima.query("MaturityDate == @maturity")["IndicativeRate"]
113
- if lft_rates.empty:
114
- return float("NaN")
115
- lft_rate = float(lft_rates.iloc[0])
116
-
117
- # Retrieve DI rate for the reference date and maturity
118
- di_rate = di.rate(trade_date=reference_date, expiration=maturity)
119
- if pd.isnull(di_rate): # Check if the DI rate is NaN
120
- return float("NaN")
121
-
122
- # Calculate and return the premium using the extracted rates
123
- return premium(lft_rate, di_rate)
92
+ Examples:
93
+ Calculate the premium of a LFT bond with a 0.02 yield rate over the Selic rate:
94
+ >>> lft_rate = 0.1695 / 100 # 0.1695%
95
+ >>> selic_over = 10.40 / 100 # 10.40%
96
+ >>> lft.premium(lft_rate, selic_over)
97
+ 101.71205192837589
98
+ """
99
+ adjusted_lft_rate = (lft_rate + 1) * (selic_over + 1) - 1
100
+ f1 = (adjusted_lft_rate + 1) ** (1 / 252) - 1
101
+ f2 = (selic_over + 1) ** (1 / 252) - 1
102
+ return f1 / f2
@@ -311,7 +311,6 @@ def _bisection_method(func, a, b, tol=1e-8, maxiter=100):
311
311
  def _solve_spread(
312
312
  price_difference_func: Callable,
313
313
  initial_guess: float | None = None,
314
- range_width_bps: float = 50,
315
314
  ) -> float:
316
315
  """
317
316
  Solve for the spread that zeroes the price difference using a bisection method.
@@ -320,8 +319,6 @@ def _solve_spread(
320
319
  price_difference_func (callable): The function that computes the difference
321
320
  between the bond's market price and its discounted cash flows.
322
321
  initial_guess (float, optional): An initial guess for the spread.
323
- range_width_bps (float, optional): The width of the range around the initial
324
- guess in bps. Defaults to 50 bps.
325
322
 
326
323
  Returns:
327
324
  float: The solution for the spread in bps or NaN if no solution is found.
@@ -329,9 +326,9 @@ def _solve_spread(
329
326
  try:
330
327
  if initial_guess is not None:
331
328
  # range_width_bps below the initial guess
332
- a = initial_guess - range_width_bps / 10_000
329
+ a = initial_guess - 0.005 # 50 bps
333
330
  # range_width_bps above the initial guess
334
- b = initial_guess + range_width_bps / 10_000
331
+ b = initial_guess + 0.005 # 50 bps
335
332
  else:
336
333
  a = -0.01 # Initial guess of -100 bps
337
334
  b = 0.01 # Initial guess of 100 bps
@@ -371,7 +368,7 @@ def di_net_spread( # noqa
371
368
  di_rates (pd.Series): A Series of DI rates.
372
369
  di_expirations (pd.Series): A list or Series of DI expiration dates.
373
370
  initial_guess (float, optional): An initial guess for the spread. Defaults to
374
- None.
371
+ None. A good initial guess is the DI gross spread for the bond.
375
372
 
376
373
  Returns:
377
374
  float: The net DI spread in basis points.
@@ -405,7 +402,7 @@ def di_net_spread( # noqa
405
402
  # Difference between the bond's price and its discounted cash flows
406
403
  return (bond_cash_flows / (1 + di_interp + p) ** byears).sum() - bond_price
407
404
 
408
- # Solve for the spread that zeroes the price difference using the Brent method
405
+ # Solve for the spread that zeroes the price difference using the bisection method
409
406
  p_solution = _solve_spread(price_difference, initial_guess)
410
407
  # Convert the solution to basis points (bps) and round to two decimal places
411
408
  return round((p_solution * 10_000), 2)
@@ -7,15 +7,17 @@ from .. import date_converter as dc
7
7
 
8
8
 
9
9
  def indicator(
10
- indicator_type: Literal["IPCA_MR", "SELIC_TARGET", "DI", "VNA_LFT"],
10
+ indicator_type: Literal["IPCA_MR", "SELIC_TARGET", "SELIC_OVER", "DI", "VNA_LFT"],
11
11
  reference_date: str | pd.Timestamp,
12
- ) -> float | None:
12
+ ) -> float:
13
13
  reference_date = dc.convert_date(reference_date)
14
14
  ind_type = str(indicator_type).upper()
15
15
  if ind_type == "IPCA_MR":
16
16
  return ipca_monthly_rate(reference_date)
17
17
  elif ind_type == "SELIC_TARGET":
18
18
  return selic_target(reference_date)
19
+ elif ind_type == "SELIC_OVER":
20
+ return selic_over(reference_date)
19
21
  elif ind_type == "DI":
20
22
  return di(reference_date)
21
23
  elif ind_type == "VNA_LFT":
@@ -24,7 +26,7 @@ def indicator(
24
26
  raise ValueError(f"Invalid indicator type: {ind_type}")
25
27
 
26
28
 
27
- def ipca_monthly_rate(reference_date: pd.Timestamp) -> float | None:
29
+ def ipca_monthly_rate(reference_date: pd.Timestamp) -> float:
28
30
  """
29
31
  Fetches the IPCA (Índice Nacional de Preços ao Consumidor Amplo) monthly rate
30
32
  from the IBGE (Instituto Brasileiro de Geografia e Estatística) for a given
@@ -35,7 +37,7 @@ def ipca_monthly_rate(reference_date: pd.Timestamp) -> float | None:
35
37
  is fetched. If a string is passed, it should be in 'DD-MM-YYYY' format.
36
38
 
37
39
  Returns:
38
- float | None: The IPCA monthly rate for the specified date as a float.
40
+ float: The IPCA monthly rate for the specified date as a float.
39
41
  Returns None if data is not found or in case of an error.
40
42
 
41
43
  Notes:
@@ -65,15 +67,14 @@ def ipca_monthly_rate(reference_date: pd.Timestamp) -> float | None:
65
67
  # Parse the JSON response
66
68
  data = response.json()
67
69
 
70
+ if not data:
71
+ return float("nan")
68
72
  # Extract and return the IPCA monthly growth rate if data is available
69
- if data:
70
- ipca_str = data[0]["resultados"][0]["series"][0]["serie"][ipca_date]
71
- return round(float(ipca_str) / 100, 4)
72
- else:
73
- return None
73
+ ipca_str = data[0]["resultados"][0]["series"][0]["serie"][ipca_date]
74
+ return round(float(ipca_str) / 100, 4)
74
75
 
75
76
 
76
- def selic_target(reference_date: pd.Timestamp) -> float | None:
77
+ def selic_target(reference_date: pd.Timestamp) -> float:
77
78
  """
78
79
  Examples:
79
80
  >>> selic_taget("31-05-2024")
@@ -85,14 +86,31 @@ def selic_target(reference_date: pd.Timestamp) -> float | None:
85
86
  response = requests.get(api_url, timeout=10)
86
87
  response.raise_for_status()
87
88
 
88
- if selic_date in response.text:
89
- data = response.json()
90
- return round(float(data[0]["valor"]) / 100, 4)
91
- else:
92
- return None
89
+ if selic_date not in response.text:
90
+ return float("nan")
91
+ data = response.json()
92
+ return round(float(data[0]["valor"]) / 100, 4)
93
+
94
+
95
+ def selic_over(reference_date: pd.Timestamp) -> float:
96
+ """
97
+ Examples:
98
+ >>> selic_over("26-08-2024")
99
+ 0.1040 # Indicates a SELIC rate of 10.40% p.a.
100
+ """
101
+ # https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial=12/04/2024&dataFinal=12/04/2024
102
+ formatted_date = reference_date.strftime("%d/%m/%Y")
103
+ api_url = f"https://api.bcb.gov.br/dados/serie/bcdata.sgs.1178/dados?formato=json&dataInicial={formatted_date}&dataFinal={formatted_date}"
104
+ response = requests.get(api_url, timeout=10)
105
+ response.raise_for_status()
93
106
 
107
+ if formatted_date not in response.text:
108
+ return float("nan")
109
+ data = response.json()
110
+ return round(float(data[0]["valor"]) / 100, 4)
94
111
 
95
- def di(reference_date: pd.Timestamp) -> float | None:
112
+
113
+ def di(reference_date: pd.Timestamp) -> float:
96
114
  """
97
115
  Examples:
98
116
  >>> di("31-05-2024")
@@ -104,14 +122,13 @@ def di(reference_date: pd.Timestamp) -> float | None:
104
122
  response = requests.get(api_url, timeout=10)
105
123
  response.raise_for_status()
106
124
 
107
- if di_date in response.text:
108
- data = response.json()
109
- return round(float(data[0]["valor"]) / 100, 8)
110
- else:
111
- return None
125
+ if di_date not in response.text:
126
+ return float("nan")
127
+ data = response.json()
128
+ return round(float(data[0]["valor"]) / 100, 8)
112
129
 
113
130
 
114
- def vna_lft(reference_date: pd.Timestamp) -> float | None:
131
+ def vna_lft(reference_date: pd.Timestamp) -> float:
115
132
  # url example: https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/20240418APC238
116
133
  url_base = "https://www3.bcb.gov.br/novoselic/rest/arquivosDiarios/pub/download/3/"
117
134
  url_file = f"{reference_date.strftime('%Y%m%d')}APC238"
@@ -32,7 +32,7 @@ dependencies = [
32
32
  "types-requests",
33
33
  ]
34
34
  dynamic = []
35
- version = "0.16.3"
35
+ version = "0.16.4"
36
36
 
37
37
  [project.license]
38
38
  file = "LICENSE"
@@ -1 +0,0 @@
1
- __version__ = "0.16.3"
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