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.
- {pyield-0.16.3 → pyield-0.16.4}/PKG-INFO +1 -1
- pyield-0.16.4/pyield/__about__.py +1 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/lft.py +18 -39
- {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/ntnf.py +4 -7
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/indicators.py +39 -22
- {pyield-0.16.3 → pyield-0.16.4}/pyproject.toml +1 -1
- pyield-0.16.3/pyield/__about__.py +0 -1
- {pyield-0.16.3 → pyield-0.16.4}/LICENSE +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/README.md +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/__init__.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/bday.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/__init__.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/bond_tools.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/ltn.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/bonds/ntnb.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/__init__.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/anbima.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/data_cache.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/di.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/__init__.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/common.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/historical.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/intraday.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/futures_data/xml.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/data/projections.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/date_converter.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/__init__.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/holidays/holidays_manager.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/interpolator.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/pyield/py.typed +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/tests/__init__.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/tests/data/SPRD231222.zip +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/tests/data/SPRD240426.zip +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/tests/test_bday.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/tests/test_futures.py +0 -0
- {pyield-0.16.3 → pyield-0.16.4}/tests/test_futures_xml.py +0 -0
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 -
|
|
329
|
+
a = initial_guess - 0.005 # 50 bps
|
|
333
330
|
# range_width_bps above the initial guess
|
|
334
|
-
b = initial_guess +
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
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"
|
|
@@ -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
|
|
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
|