PYield 0.35.7__tar.gz → 0.35.8__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 (52) hide show
  1. {pyield-0.35.7 → pyield-0.35.8}/PKG-INFO +1 -1
  2. pyield-0.35.8/pyield/__about__.py +1 -0
  3. {pyield-0.35.7 → pyield-0.35.8}/pyield/bday.py +104 -46
  4. {pyield-0.35.7 → pyield-0.35.8}/pyield/date_converter.py +12 -3
  5. {pyield-0.35.7 → pyield-0.35.8}/pyield/holidays/brholidays.py +2 -1
  6. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/auction.py +2 -2
  7. pyield-0.35.7/pyield/__about__.py +0 -1
  8. {pyield-0.35.7 → pyield-0.35.8}/.gitignore +0 -0
  9. {pyield-0.35.7 → pyield-0.35.8}/LICENSE +0 -0
  10. {pyield-0.35.7 → pyield-0.35.8}/README.md +0 -0
  11. {pyield-0.35.7 → pyield-0.35.8}/pyield/__init__.py +0 -0
  12. {pyield-0.35.7 → pyield-0.35.8}/pyield/anbima/__init__.py +0 -0
  13. {pyield-0.35.7 → pyield-0.35.8}/pyield/anbima/ettj.py +0 -0
  14. {pyield-0.35.7 → pyield-0.35.8}/pyield/anbima/ima.py +0 -0
  15. {pyield-0.35.7 → pyield-0.35.8}/pyield/anbima/imaq.py +0 -0
  16. {pyield-0.35.7 → pyield-0.35.8}/pyield/anbima/ipca.py +0 -0
  17. {pyield-0.35.7 → pyield-0.35.8}/pyield/anbima/tpf.py +0 -0
  18. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/__init__.py +0 -0
  19. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/di1.py +0 -0
  20. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/di_over.py +0 -0
  21. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/futures/__init__.py +0 -0
  22. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/futures/common.py +0 -0
  23. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/futures/historical.py +0 -0
  24. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/futures/intraday.py +0 -0
  25. {pyield-0.35.7 → pyield-0.35.8}/pyield/b3/futures/xml.py +0 -0
  26. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/__init__.py +0 -0
  27. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/auction.py +0 -0
  28. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/bcdata.py +0 -0
  29. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/ptax.py +0 -0
  30. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/repo.py +0 -0
  31. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/trades_intraday.py +0 -0
  32. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/trades_monthly.py +0 -0
  33. {pyield-0.35.7 → pyield-0.35.8}/pyield/bc/vna.py +0 -0
  34. {pyield-0.35.7 → pyield-0.35.8}/pyield/conftest.py +0 -0
  35. {pyield-0.35.7 → pyield-0.35.8}/pyield/data_cache.py +0 -0
  36. {pyield-0.35.7 → pyield-0.35.8}/pyield/fwd.py +0 -0
  37. {pyield-0.35.7 → pyield-0.35.8}/pyield/holidays/__init__.py +0 -0
  38. {pyield-0.35.7 → pyield-0.35.8}/pyield/holidays/br_holidays_new.txt +0 -0
  39. {pyield-0.35.7 → pyield-0.35.8}/pyield/holidays/br_holidays_old.txt +0 -0
  40. {pyield-0.35.7 → pyield-0.35.8}/pyield/ibge/__init__.py +0 -0
  41. {pyield-0.35.7 → pyield-0.35.8}/pyield/ibge/ipca.py +0 -0
  42. {pyield-0.35.7 → pyield-0.35.8}/pyield/interpolator.py +0 -0
  43. {pyield-0.35.7 → pyield-0.35.8}/pyield/py.typed +0 -0
  44. {pyield-0.35.7 → pyield-0.35.8}/pyield/retry.py +0 -0
  45. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/__init__.py +0 -0
  46. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/lft.py +0 -0
  47. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/ltn.py +0 -0
  48. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/ntnb.py +0 -0
  49. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/ntnc.py +0 -0
  50. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/ntnf.py +0 -0
  51. {pyield-0.35.7 → pyield-0.35.8}/pyield/tn/tools.py +0 -0
  52. {pyield-0.35.7 → pyield-0.35.8}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PYield
3
- Version: 0.35.7
3
+ Version: 0.35.8
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.35.8"
@@ -135,6 +135,21 @@ def offset(
135
135
  1 2024-09-23
136
136
  dtype: datetime64[ns]
137
137
 
138
+ Null values are propagated
139
+
140
+ >>> bday.offset(pd.NaT, 1) # NaT input
141
+ NaT
142
+
143
+ >>> bday.offset(pd.NaT, [1, 2]) # NaT input with a list of offsets
144
+ 0 NaT
145
+ 1 NaT
146
+ dtype: datetime64[ns]
147
+
148
+ >>> bday.offset(["19-09-2024", pd.NaT], 1) # NaT in a list of dates
149
+ 0 2024-09-20
150
+ 1 NaT
151
+ dtype: datetime64[ns]
152
+
138
153
  Note:
139
154
  This function uses `numpy.busday_offset` under the hood, which means it follows
140
155
  the same conventions and limitations for business day calculations. For detailed
@@ -144,6 +159,14 @@ def offset(
144
159
  """
145
160
  dates_pd = dc.convert_input_dates(dates)
146
161
 
162
+ # Antecipate NaT handling
163
+ if dates_pd is pd.NaT:
164
+ if isinstance(offset, (list, tuple, np.ndarray, pd.Series)):
165
+ # If offset is a list, return a Series of NaT with the same length
166
+ return pd.Series([pd.NaT] * len(offset), dtype="datetime64[ns]")
167
+ else:
168
+ return pd.NaT
169
+
147
170
  if isinstance(dates_pd, pd.Series):
148
171
  # Divide the input in order to apply the correct holiday list
149
172
  old_dates = dates_pd[dates_pd < br_holidays.TRANSITION_DATE]
@@ -216,7 +239,7 @@ def count(start: DateScalar, end: DateArray) -> pd.Series: ...
216
239
  def count(start: DateArray, end: DateArray) -> pd.Series: ...
217
240
 
218
241
 
219
- def count(
242
+ def count( # noqa
220
243
  start: DateScalar | DateArray,
221
244
  end: DateScalar | DateArray,
222
245
  ) -> int | pd.Series:
@@ -278,65 +301,100 @@ def count(
278
301
  0 22
279
302
  1 19
280
303
  dtype: Int64
304
+
305
+ Null values are propagated
306
+
307
+ >>> bday.count(pd.NaT, "01-01-2024") # NaT start
308
+ <NA>
309
+
310
+ >>> bday.count("01-01-2024", pd.NaT) # NaT end
311
+ <NA>
312
+
313
+ >>> bday.count("01-01-2024", ["01-02-2024", pd.NaT]) # NaT in end array
314
+ 0 22
315
+ 1 <NA>
316
+ dtype: Int64
317
+
318
+ Original pandas index is preserved
319
+
320
+ >>> start_dates = pd.Series(
321
+ ... ["01-01-2024", "01-02-2024", "01-03-2024"],
322
+ ... index=["a", "b", "c"],
323
+ ... )
324
+ >>> bday.count(start_dates, "01-01-2025")
325
+ a 253
326
+ b 231
327
+ c 212
328
+ dtype: Int64
281
329
  """
330
+ # Padroniza as entradas para objetos do Pandas
282
331
  start_pd = dc.convert_input_dates(start)
283
332
  end_pd = dc.convert_input_dates(end)
284
333
 
285
- # If inputs are Series, check if they have different lengths
334
+ # 1. VALIDAÇÃO ESTRUTURAL
286
335
  if isinstance(start_pd, pd.Series) and isinstance(end_pd, pd.Series):
287
- if start_pd.size != end_pd.size:
336
+ if len(start_pd) != len(end_pd):
288
337
  raise ValueError("Input Series must have the same length.")
338
+ if not start_pd.index.equals(end_pd.index):
339
+ raise ValueError("Input dates have different indices!")
289
340
 
290
- # Only start is used to determine the holiday list
291
- if isinstance(start_pd, pd.Series):
292
- # Divide the input in order to apply the correct holiday list
293
- start1 = start_pd[start_pd < br_holidays.TRANSITION_DATE]
294
- start2 = start_pd[start_pd >= br_holidays.TRANSITION_DATE]
295
- start3 = start_pd[start_pd.isna()]
296
-
297
- # If end is a Series, it must be divided as well
298
- if isinstance(end_pd, pd.Series):
299
- end1 = end_pd[start1.index]
300
- end2 = end_pd[start2.index]
301
- else:
302
- end1 = end_pd
303
- end2 = end_pd
304
-
305
- result1 = np.busday_count(
306
- begindates=dc.to_numpy_date_type(start1),
307
- enddates=dc.to_numpy_date_type(end1),
308
- holidays=OLD_HOLIDAYS_ARRAY,
309
- )
310
- result2 = np.busday_count(
311
- begindates=dc.to_numpy_date_type(start2),
312
- enddates=dc.to_numpy_date_type(end2),
313
- holidays=NEW_HOLIDAYS_ARRAY,
314
- )
315
-
316
- # Prepare results to be rejoined
317
- result1 = pd.Series(result1, dtype="Int64")
318
- result2 = pd.Series(result2, dtype="Int64")
319
- # Convert the third result from NaT to NA
320
- result3 = pd.Series(pd.NA, index=start3.index, dtype="Int64")
321
-
322
- # Old index is used to rejoin the results
323
- result1.index = start1.index
324
- result2.index = start2.index
341
+ # 2. LÓGICA PRINCIPAL
342
+ is_scalar_input = not (
343
+ isinstance(start_pd, pd.Series) or isinstance(end_pd, pd.Series)
344
+ )
325
345
 
326
- # Reorder the result to match the original input order
327
- result = pd.concat([result1, result2, result3]).sort_index()
346
+ # --- CASO 1: AMBAS AS ENTRADAS SÃO ESCALARES ---
347
+ if is_scalar_input:
348
+ if pd.isna(start_pd) or pd.isna(end_pd):
349
+ return pd.NA
328
350
 
329
- else: # Start is a single date
351
+ holidays = br_holidays.get_holiday_array(start_pd)
330
352
  result = np.busday_count(
331
353
  begindates=dc.to_numpy_date_type(start_pd),
332
354
  enddates=dc.to_numpy_date_type(end_pd),
333
- holidays=br_holidays.get_holiday_array(start_pd),
355
+ holidays=holidays,
334
356
  )
335
- result = pd.Series(result, dtype="Int64")
357
+ return int(result)
336
358
 
337
- if result.size == 1:
338
- return int(result[0])
339
- return result
359
+ # --- CASO 2: PELO MENOS UMA ENTRADA É UMA SÉRIE ---
360
+ else:
361
+ # Garante que ambas as variáveis sejam Series para alinhamento automático.
362
+ # O Pandas faz o "broadcast" do escalar para o tamanho da Series.
363
+ if not isinstance(start_pd, pd.Series):
364
+ # start escalar e end Series: cria uma Series de start com o índice de end.
365
+ start_pd = pd.Series(start_pd, index=end_pd.index)
366
+ if not isinstance(end_pd, pd.Series):
367
+ # end escalar e start Series: cria uma Series de end com o índice de start.
368
+ end_pd = pd.Series(end_pd, index=start_pd.index)
369
+
370
+ # Inicializa a série de resultados com o índice correto e tipo que suporta NA.
371
+ # Nulos nas entradas (start_pd ou end_pd) resultarão em NA aqui.
372
+ result = pd.Series(index=start_pd.index, dtype="Int64")
373
+
374
+ # Cria máscaras para linhas não-nulas, divididas pelo calendário de feriados.
375
+ # A lógica de feriados depende APENAS da data de início.
376
+ not_null_mask = start_pd.notna() & end_pd.notna()
377
+ mask_old_holidays = not_null_mask & (start_pd < br_holidays.TRANSITION_DATE)
378
+ mask_new_holidays = not_null_mask & (start_pd >= br_holidays.TRANSITION_DATE)
379
+
380
+ # Calcula e atribui os resultados para o subconjunto "OLD"
381
+ if mask_old_holidays.any():
382
+ result.loc[mask_old_holidays] = np.busday_count(
383
+ begindates=dc.to_numpy_date_type(start_pd[mask_old_holidays]),
384
+ enddates=dc.to_numpy_date_type(end_pd[mask_old_holidays]),
385
+ holidays=OLD_HOLIDAYS_ARRAY,
386
+ )
387
+
388
+ # Calcula e atribui os resultados para o subconjunto "NEW"
389
+ if mask_new_holidays.any():
390
+ result.loc[mask_new_holidays] = np.busday_count(
391
+ begindates=dc.to_numpy_date_type(start_pd[mask_new_holidays]),
392
+ enddates=dc.to_numpy_date_type(end_pd[mask_new_holidays]),
393
+ holidays=NEW_HOLIDAYS_ARRAY,
394
+ )
395
+
396
+ # As linhas onde not_null_mask era False já contêm o valor <NA> default.
397
+ return result
340
398
 
341
399
 
342
400
  def generate(
@@ -32,6 +32,12 @@ def convert_input_dates(dates: DateArray) -> pd.Series: ...
32
32
  def convert_input_dates(
33
33
  dates: DateScalar | DateArray,
34
34
  ) -> pd.Timestamp | pd.Series:
35
+ # Capturar apenas escalares nulos: a verificação `is True` é crucial,
36
+ # pois `pd.isna()` retorna um array booleano para entradas de array,
37
+ # e um array nunca é idêntico ao objeto singleton `True`.
38
+ if pd.isna(dates) is True:
39
+ return pd.NaT
40
+
35
41
  match dates:
36
42
  case str():
37
43
  validate_date_format(dates)
@@ -41,6 +47,7 @@ def convert_input_dates(
41
47
  return pd.Timestamp(dates).normalize()
42
48
 
43
49
  case pd.Series() | np.ndarray() | list() | tuple():
50
+ # Preserve input values making a copy
44
51
  result = pd.Series(dates)
45
52
 
46
53
  if result.empty:
@@ -48,14 +55,14 @@ def convert_input_dates(
48
55
 
49
56
  if pd.api.types.is_string_dtype(result):
50
57
  # Check first element to validate date format
51
- validate_date_format(result[0])
58
+ validate_date_format(result.iloc[0])
52
59
 
53
60
  return pd.to_datetime(result, dayfirst=True).astype("datetime64[ns]")
54
61
 
55
62
  case pd.DatetimeIndex():
56
63
  return pd.Series(dates).astype("datetime64[ns]")
57
64
 
58
- case _: # unreachable code
65
+ case _:
59
66
  raise ValueError("Invalid input type for 'dates'.")
60
67
 
61
68
 
@@ -71,7 +78,9 @@ def to_numpy_date_type(
71
78
  Returns:
72
79
  np.datetime64 | np.ndarray: The input dates in a numpy datetime64[D] format.
73
80
  """
74
- if isinstance(dates, pd.Timestamp):
81
+ if pd.isna(dates) is True:
82
+ return np.datetime64("NaT")
83
+ elif isinstance(dates, pd.Timestamp) or dates is pd.NaT:
75
84
  return np.datetime64(dates, "D")
76
85
  elif isinstance(dates, pd.Series):
77
86
  return dates.to_numpy().astype("datetime64[D]")
@@ -58,7 +58,8 @@ class BrHolidays:
58
58
  if isinstance(dates, pd.Timestamp):
59
59
  earliest_date = dates
60
60
  else:
61
- earliest_date = dates.min()
61
+ # NaN values will be ignored for min date calculation
62
+ earliest_date = dates.min(skipna=True)
62
63
 
63
64
  if earliest_date < BrHolidays.TRANSITION_DATE:
64
65
  holidays = self.old_holidays
@@ -61,12 +61,12 @@ def benchmarks(include_history: bool = False) -> pd.DataFrame:
61
61
  >>> # Get historical benchmarks
62
62
  >>> df_history = tn.benchmarks(include_history=True)
63
63
  >>> df_history.head()
64
- Benchmark MaturityDate BondType StartDate EndDate
64
+ Benchmark MaturityDate BondType StartDate EndDate
65
65
  0 LFT 6 anos 2020-03-01 LFT 2014-01-01 2014-06-30
66
66
  1 LFT 6 anos 2020-09-01 LFT 2014-07-01 2014-12-31
67
67
  2 LFT 6 anos 2021-03-01 LFT 2015-01-01 2015-04-30
68
68
  3 LFT 6 anos 2021-09-01 LFT 2015-05-01 2015-12-31
69
- 4 LFT 1 ano 2022-03-01 LFT 2020-10-15 2021-03-31
69
+ 4 LFT 6 anos 2022-03-01 LFT 2016-01-01 2016-06-30
70
70
  """
71
71
  session = requests.Session()
72
72
  include_history_param_value = "S" if include_history else "N"
@@ -1 +0,0 @@
1
- __version__ = "0.35.7"
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
File without changes
File without changes
File without changes