PYield 0.8.1__tar.gz → 0.8.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.
- {pyield-0.8.1 → pyield-0.8.2}/PKG-INFO +1 -1
- pyield-0.8.2/pyield/__about__.py +1 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/historical.py +1 -1
- {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/intraday.py +40 -31
- {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/xml.py +9 -3
- {pyield-0.8.1 → pyield-0.8.2}/pyield/ntnb.py +44 -27
- {pyield-0.8.1 → pyield-0.8.2}/pyield/projections.py +1 -1
- {pyield-0.8.1 → pyield-0.8.2}/pyproject.toml +1 -1
- pyield-0.8.1/pyield/__about__.py +0 -1
- {pyield-0.8.1 → pyield-0.8.2}/LICENSE +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/README.md +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/__init__.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/bday.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/data_access.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/date_validator.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/__init__.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/futures/common.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/__init__.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/br_holidays_new.txt +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/br_holidays_old.txt +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/holidays/core.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/indicators.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/interpolator.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/py.typed +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/spreads.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/pyield/tn_bonds.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/tests/__init__.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/tests/data/SPRD231222.zip +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/tests/data/SPRD240426.zip +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/tests/test_bday.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/tests/test_futures.py +0 -0
- {pyield-0.8.1 → pyield-0.8.2}/tests/test_futures_xml.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.2"
|
|
@@ -119,7 +119,7 @@ def fetch_raw_df(asset_code: str, trade_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
119
119
|
url_date = trade_date.strftime("%d/%m/%Y")
|
|
120
120
|
# url example: https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data=05/10/2023&Mercadoria=DI1
|
|
121
121
|
url = f"https://www2.bmf.com.br/pages/portal/bmfbovespa/boletim1/SistemaPregao_excel1.asp?Data={url_date}&Mercadoria={asset_code}&XLS=false"
|
|
122
|
-
r = requests.get(url)
|
|
122
|
+
r = requests.get(url, timeout=10)
|
|
123
123
|
|
|
124
124
|
text = r.text
|
|
125
125
|
if "VENCTO" not in text:
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
|
|
1
3
|
import pandas as pd
|
|
2
4
|
import requests
|
|
3
5
|
|
|
4
6
|
from .. import bday
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
# Função para salvar DataFrame em CSV e ler com read_csv
|
|
10
|
+
def convert_with_read_csv(df):
|
|
11
|
+
buffer = StringIO()
|
|
12
|
+
df.to_csv(buffer, index=False)
|
|
13
|
+
buffer.seek(0) # Reposiciona o cursor no início do buffer
|
|
14
|
+
return pd.read_csv(buffer, dtype_backend="numpy_nullable")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _fetch_b3_df(future_code: str) -> pd.DataFrame:
|
|
8
18
|
"""
|
|
9
19
|
Fetch the latest data for a given future code from B3 derivatives quotation API.
|
|
10
20
|
|
|
@@ -34,34 +44,8 @@ def _fetch_raw_df(future_code: str) -> pd.DataFrame:
|
|
|
34
44
|
# Normalize JSON response into a flat table
|
|
35
45
|
df = pd.json_normalize(r.json()["Scty"])
|
|
36
46
|
|
|
37
|
-
# Clean and reformat the DataFrame columns
|
|
38
|
-
df.columns = (df.columns
|
|
39
|
-
.str.replace("SctyQtn.", "")
|
|
40
|
-
.str.replace("asset.AsstSummry.", "")
|
|
41
|
-
) # fmt: skip
|
|
42
|
-
df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
|
|
43
|
-
|
|
44
|
-
# Convert maturity codes to datetime and drop rows with missing values
|
|
45
|
-
df["mtrtyCode"] = pd.to_datetime(df["mtrtyCode"], errors="coerce")
|
|
46
|
-
df.dropna(subset=["mtrtyCode"], inplace=True)
|
|
47
|
-
|
|
48
|
-
# Sort the DataFrame by maturity code and reset the index
|
|
49
|
-
df.sort_values("mtrtyCode", inplace=True, ignore_index=True)
|
|
50
|
-
|
|
51
|
-
# Get currante date
|
|
52
|
-
today = pd.Timestamp.now().normalize()
|
|
53
|
-
df["TradeDate"] = today
|
|
54
|
-
|
|
55
|
-
# Get current date and time
|
|
56
|
-
now = pd.Timestamp.now().round("s")
|
|
57
|
-
# Subtract 15 minutes from the current time to account for API delay
|
|
58
|
-
trade_ts = now - pd.Timedelta(minutes=15)
|
|
59
|
-
df["TradeTime"] = trade_ts
|
|
60
|
-
|
|
61
47
|
# Convert DataFrame to use nullable data types for better type consistency
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return df
|
|
48
|
+
return convert_with_read_csv(df)
|
|
65
49
|
|
|
66
50
|
|
|
67
51
|
def _rename_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
@@ -101,6 +85,32 @@ def _rename_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
101
85
|
def _process_df(raw_df: pd.DataFrame) -> pd.DataFrame:
|
|
102
86
|
df = raw_df.copy()
|
|
103
87
|
|
|
88
|
+
# Clean and reformat the DataFrame columns
|
|
89
|
+
df.columns = (df.columns
|
|
90
|
+
.str.replace("SctyQtn.", "")
|
|
91
|
+
.str.replace("asset.AsstSummry.", "")
|
|
92
|
+
) # fmt: skip
|
|
93
|
+
df.drop(columns=["desc", "asset.code", "mkt.cd"], inplace=True)
|
|
94
|
+
|
|
95
|
+
df = _rename_columns(df)
|
|
96
|
+
|
|
97
|
+
# Convert maturity codes to datetime and drop rows with missing values
|
|
98
|
+
df["ExpirationDate"] = pd.to_datetime(df["ExpirationDate"], errors="coerce")
|
|
99
|
+
df.dropna(subset=["ExpirationDate"], inplace=True)
|
|
100
|
+
|
|
101
|
+
# Sort the DataFrame by maturity code and reset the index
|
|
102
|
+
df.sort_values("ExpirationDate", inplace=True, ignore_index=True)
|
|
103
|
+
|
|
104
|
+
# Get currante date
|
|
105
|
+
today = pd.Timestamp.now().normalize()
|
|
106
|
+
df["TradeDate"] = today
|
|
107
|
+
|
|
108
|
+
# Get current date and time
|
|
109
|
+
now = pd.Timestamp.now().round("s")
|
|
110
|
+
# Subtract 15 minutes from the current time to account for API delay
|
|
111
|
+
trade_ts = now - pd.Timedelta(minutes=15)
|
|
112
|
+
df["TradeTime"] = trade_ts
|
|
113
|
+
|
|
104
114
|
df["BDaysToExp"] = bday.count_bdays(df["TradeDate"], df["ExpirationDate"])
|
|
105
115
|
|
|
106
116
|
df["DaysToExp"] = (df["ExpirationDate"] - df["TradeDate"]).dt.days
|
|
@@ -156,10 +166,9 @@ def fetch_intraday_df(future_code: str) -> pd.DataFrame:
|
|
|
156
166
|
Returns:
|
|
157
167
|
pd.DataFrame: A Pandas pd.DataFrame containing the latest DI futures data.
|
|
158
168
|
"""
|
|
159
|
-
raw_df =
|
|
169
|
+
raw_df = _fetch_b3_df(future_code)
|
|
160
170
|
if raw_df.empty:
|
|
161
171
|
return raw_df
|
|
162
|
-
df =
|
|
163
|
-
df = _process_df(df)
|
|
172
|
+
df = _process_df(raw_df)
|
|
164
173
|
df = _select_and_reorder_columns(df)
|
|
165
174
|
return df
|
|
@@ -43,7 +43,7 @@ def _get_file_from_url(trade_date: pd.Timestamp, source_type: str) -> io.BytesIO
|
|
|
43
43
|
else:
|
|
44
44
|
raise ValueError("Invalid source type. Must be either 'PR' or 'SPR'.")
|
|
45
45
|
|
|
46
|
-
response = requests.get(url)
|
|
46
|
+
response = requests.get(url, timeout=10)
|
|
47
47
|
|
|
48
48
|
# File will be considered invalid if it is too small
|
|
49
49
|
if response.status_code != 200 or len(response.content) < 1024:
|
|
@@ -74,9 +74,15 @@ def _extract_xml_from_zip(zip_file: io.BytesIO) -> io.BytesIO:
|
|
|
74
74
|
|
|
75
75
|
def _extract_data_from_xml(xml_file: io.BytesIO, asset_code: str) -> list[dict]:
|
|
76
76
|
parser = etree.XMLParser(
|
|
77
|
-
ns_clean=True,
|
|
77
|
+
ns_clean=True,
|
|
78
|
+
remove_blank_text=True,
|
|
79
|
+
remove_comments=True,
|
|
80
|
+
recover=True,
|
|
81
|
+
resolve_entities=False,
|
|
82
|
+
no_network=True,
|
|
83
|
+
load_dtd=False, # Disable DTD loading
|
|
78
84
|
)
|
|
79
|
-
tree = etree.parse(xml_file, parser)
|
|
85
|
+
tree = etree.parse(xml_file, parser=parser)
|
|
80
86
|
|
|
81
87
|
# XPath para encontrar elementos cujo texto começa com código do ativo: DI1, FRC...
|
|
82
88
|
namespaces = {"ns": "urn:bvmf.217.01.xsd"}
|
|
@@ -302,7 +302,7 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
302
302
|
"""
|
|
303
303
|
df = da.fetch_asset(asset_code="DI1", reference_date=reference_date)
|
|
304
304
|
if "CurrentRate" in df.columns:
|
|
305
|
-
df = df.rename(columns={"CurrentRate": "
|
|
305
|
+
df = df.rename(columns={"CurrentRate": "NSR_DI"})
|
|
306
306
|
keep_cols = [
|
|
307
307
|
"TradeDate",
|
|
308
308
|
"TradeTime",
|
|
@@ -311,15 +311,21 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
311
311
|
"BDaysToExp",
|
|
312
312
|
"CurrentAskRate",
|
|
313
313
|
"CurrentBidRate",
|
|
314
|
-
"
|
|
314
|
+
"NSR_DI",
|
|
315
315
|
]
|
|
316
316
|
elif "SettlementRate" in df.columns:
|
|
317
|
-
df = df.rename(columns={"SettlementRate": "
|
|
318
|
-
keep_cols = [
|
|
317
|
+
df = df.rename(columns={"SettlementRate": "NSR_DI"})
|
|
318
|
+
keep_cols = [
|
|
319
|
+
"TradeDate",
|
|
320
|
+
"TickerSymbol",
|
|
321
|
+
"ExpirationDate",
|
|
322
|
+
"BDaysToExp",
|
|
323
|
+
"NSR_DI",
|
|
324
|
+
]
|
|
319
325
|
else:
|
|
320
326
|
raise ValueError("NIR data not found in the DataFrame.")
|
|
321
327
|
|
|
322
|
-
df = df[keep_cols].dropna(subset=["
|
|
328
|
+
df = df[keep_cols].dropna(subset=["NSR_DI"])
|
|
323
329
|
|
|
324
330
|
# Add DI spreads for prefixed bonds (LTN) and adjust NIR
|
|
325
331
|
today = pd.Timestamp.today().normalize()
|
|
@@ -334,7 +340,7 @@ def _get_nir_df(reference_date: pd.Timestamp) -> pd.DataFrame:
|
|
|
334
340
|
df_pre.drop(columns=["BondType"], inplace=True)
|
|
335
341
|
|
|
336
342
|
df = pd.merge_asof(df, df_pre, left_on="ExpirationDate", right_on="MaturityDate")
|
|
337
|
-
df["
|
|
343
|
+
df["NSR_PRE"] = df["NSR_DI"] + df["DISpread"]
|
|
338
344
|
|
|
339
345
|
return df
|
|
340
346
|
|
|
@@ -346,46 +352,57 @@ def breakeven_inflation(
|
|
|
346
352
|
ytm_rates: pd.Series,
|
|
347
353
|
) -> pd.DataFrame:
|
|
348
354
|
"""
|
|
349
|
-
Calculate the Breakeven Inflation (
|
|
355
|
+
Calculate the Breakeven Inflation (INF) for NTN-B bonds based on nominal and real
|
|
350
356
|
interest rates.
|
|
351
357
|
|
|
352
358
|
Args:
|
|
353
|
-
reference_date (str
|
|
354
|
-
calculations.
|
|
355
|
-
settlement_date (str
|
|
356
|
-
maturity_dates (pd.Series):
|
|
357
|
-
ytm_rates (pd.Series):
|
|
358
|
-
the maturity dates.
|
|
359
|
+
reference_date (str or pd.Timestamp): The reference date for fetching data and
|
|
360
|
+
performing calculations.
|
|
361
|
+
settlement_date (str or pd.Timestamp): The settlement date for the bonds.
|
|
362
|
+
maturity_dates (pd.Series): A series of maturity dates for the bonds.
|
|
363
|
+
ytm_rates (pd.Series): A series of Yield to Maturity (YTM) rates corresponding
|
|
364
|
+
to the maturity dates.
|
|
359
365
|
|
|
360
366
|
Returns:
|
|
361
|
-
pd.DataFrame: DataFrame containing the maturity dates,
|
|
362
|
-
(
|
|
367
|
+
pd.DataFrame: DataFrame containing the maturity dates, business days to maturity
|
|
368
|
+
(BDays), Yield to Maturity (YTM), real spot rates (RSR), nominal spot rates
|
|
369
|
+
(NSR_DI and NSR_PRE), and breakeven inflation rates (INF_DI and INF_PRE).
|
|
363
370
|
"""
|
|
364
371
|
# Normalize input dates
|
|
365
372
|
reference_date = dv.normalize_date(reference_date)
|
|
366
373
|
settlement_date = dv.normalize_date(settlement_date)
|
|
367
374
|
|
|
368
|
-
# Fetch Nominal
|
|
375
|
+
# Fetch Nominal Spot Rate (NSR) data
|
|
369
376
|
df_nir = _get_nir_df(reference_date)
|
|
370
377
|
known_bdays = df_nir["BDaysToExp"].to_list()
|
|
371
|
-
known_rates = df_nir["
|
|
378
|
+
known_rates = df_nir["NSR_DI"].to_list()
|
|
372
379
|
|
|
373
|
-
# Calculate Real
|
|
380
|
+
# Calculate Real Spot Rate (RSR)
|
|
374
381
|
df_rir = spot_rates(settlement_date, maturity_dates, ytm_rates)
|
|
375
|
-
df_rir = df_rir.rename(columns={"SpotRate": "
|
|
382
|
+
df_rir = df_rir.rename(columns={"SpotRate": "RSR"})
|
|
376
383
|
df_rir["BDays"] = bday.count_bdays(reference_date, df_rir["MaturityDate"])
|
|
377
|
-
df_rir["
|
|
384
|
+
df_rir["NSR_DI"] = df_rir["BDays"].apply(
|
|
378
385
|
lambda x: ip.find_and_interpolate_flat_forward(x, known_bdays, known_rates)
|
|
379
386
|
)
|
|
380
387
|
|
|
381
|
-
# Calculate Breakeven Inflation (
|
|
382
|
-
df_rir["
|
|
388
|
+
# Calculate Breakeven Inflation (INF)
|
|
389
|
+
df_rir["INF_DI"] = ((df_rir["NSR_DI"] + 1) / (df_rir["RSR"] + 1)) - 1
|
|
383
390
|
|
|
384
391
|
# Adjust BEI for DI spread in prefixed bonds
|
|
385
|
-
known_rates = df_nir["
|
|
386
|
-
df_rir["
|
|
392
|
+
known_rates = df_nir["NSR_PRE"].to_list()
|
|
393
|
+
df_rir["NSR_PRE"] = df_rir["BDays"].apply(
|
|
387
394
|
lambda x: ip.find_and_interpolate_flat_forward(x, known_bdays, known_rates)
|
|
388
395
|
)
|
|
389
|
-
df_rir["
|
|
390
|
-
|
|
391
|
-
|
|
396
|
+
df_rir["INF_PRE"] = ((df_rir["NSR_PRE"] + 1) / (df_rir["RSR"] + 1)) - 1
|
|
397
|
+
|
|
398
|
+
order_cols = [
|
|
399
|
+
"MaturityDate",
|
|
400
|
+
"BDays",
|
|
401
|
+
"YTM",
|
|
402
|
+
"RSR",
|
|
403
|
+
"NSR_DI",
|
|
404
|
+
"NSR_PRE",
|
|
405
|
+
"INF_DI",
|
|
406
|
+
"INF_PRE",
|
|
407
|
+
]
|
|
408
|
+
return df_rir[order_cols].copy()
|
|
@@ -54,7 +54,7 @@ def fetch_current_month_ipca_projection() -> IndicatorProjection:
|
|
|
54
54
|
"""
|
|
55
55
|
# Define the URL and get the data
|
|
56
56
|
url = "https://www.anbima.com.br/informacoes/indicadores/arqs/indicadores.xls"
|
|
57
|
-
response = requests.get(url)
|
|
57
|
+
response = requests.get(url, timeout=10)
|
|
58
58
|
excel_data = io.BytesIO(response.content)
|
|
59
59
|
|
|
60
60
|
# Load the workbook and select the first sheet
|
pyield-0.8.1/pyield/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.1"
|
|
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
|