PYield 0.40.0__tar.gz → 0.40.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 (60) hide show
  1. {pyield-0.40.0 → pyield-0.40.2}/PKG-INFO +13 -13
  2. {pyield-0.40.0 → pyield-0.40.2}/README.md +12 -12
  3. pyield-0.40.2/pyield/__about__.py +1 -0
  4. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/di1.py +7 -7
  5. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/di_over.py +3 -3
  6. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/futures/__init__.py +1 -1
  7. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/ptax_api.py +4 -4
  8. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/rates.py +10 -10
  9. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/vna.py +3 -3
  10. {pyield-0.40.0 → pyield-0.40.2}/pyield/bday/core.py +104 -75
  11. pyield-0.40.2/pyield/data_cache.py +88 -0
  12. {pyield-0.40.0 → pyield-0.40.2}/pyield/fwd.py +28 -19
  13. {pyield-0.40.0 → pyield-0.40.2}/pyield/interpolator.py +12 -13
  14. {pyield-0.40.0 → pyield-0.40.2}/pyield/retry.py +20 -27
  15. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/lft.py +6 -6
  16. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/ltn.py +9 -9
  17. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/ntnb.py +11 -11
  18. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/ntnc.py +9 -9
  19. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/ntnf.py +191 -146
  20. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/tools.py +32 -3
  21. {pyield-0.40.0 → pyield-0.40.2}/pyield/types.py +12 -12
  22. pyield-0.40.0/pyield/__about__.py +0 -1
  23. pyield-0.40.0/pyield/data_cache.py +0 -83
  24. {pyield-0.40.0 → pyield-0.40.2}/.gitignore +0 -0
  25. {pyield-0.40.0 → pyield-0.40.2}/LICENSE +0 -0
  26. {pyield-0.40.0 → pyield-0.40.2}/pyield/__init__.py +0 -0
  27. {pyield-0.40.0 → pyield-0.40.2}/pyield/anbima/__init__.py +0 -0
  28. {pyield-0.40.0 → pyield-0.40.2}/pyield/anbima/difusao.py +0 -0
  29. {pyield-0.40.0 → pyield-0.40.2}/pyield/anbima/ettj_intraday.py +0 -0
  30. {pyield-0.40.0 → pyield-0.40.2}/pyield/anbima/ettj_last.py +0 -0
  31. {pyield-0.40.0 → pyield-0.40.2}/pyield/anbima/ima.py +0 -0
  32. {pyield-0.40.0 → pyield-0.40.2}/pyield/anbima/imaq.py +0 -0
  33. {pyield-0.40.0 → pyield-0.40.2}/pyield/anbima/tpf.py +0 -0
  34. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/__init__.py +0 -0
  35. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/futures/common.py +0 -0
  36. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/futures/historical.py +0 -0
  37. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/futures/intraday.py +0 -0
  38. {pyield-0.40.0 → pyield-0.40.2}/pyield/b3/futures/xml.py +0 -0
  39. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/__init__.py +0 -0
  40. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/auction.py +0 -0
  41. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/repo.py +0 -0
  42. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/trades_intraday.py +0 -0
  43. {pyield-0.40.0 → pyield-0.40.2}/pyield/bc/trades_monthly.py +0 -0
  44. {pyield-0.40.0 → pyield-0.40.2}/pyield/bday/__init__.py +0 -0
  45. {pyield-0.40.0 → pyield-0.40.2}/pyield/bday/holidays/__init__.py +0 -0
  46. {pyield-0.40.0 → pyield-0.40.2}/pyield/bday/holidays/br_holidays_new.txt +0 -0
  47. {pyield-0.40.0 → pyield-0.40.2}/pyield/bday/holidays/br_holidays_old.txt +0 -0
  48. {pyield-0.40.0 → pyield-0.40.2}/pyield/bday/holidays/brholidays.py +0 -0
  49. {pyield-0.40.0 → pyield-0.40.2}/pyield/config.py +0 -0
  50. {pyield-0.40.0 → pyield-0.40.2}/pyield/conftest.py +0 -0
  51. {pyield-0.40.0 → pyield-0.40.2}/pyield/converters.py +0 -0
  52. {pyield-0.40.0 → pyield-0.40.2}/pyield/ipca/__init__.py +0 -0
  53. {pyield-0.40.0 → pyield-0.40.2}/pyield/ipca/historical.py +0 -0
  54. {pyield-0.40.0 → pyield-0.40.2}/pyield/ipca/projected.py +0 -0
  55. {pyield-0.40.0 → pyield-0.40.2}/pyield/py.typed +0 -0
  56. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/__init__.py +0 -0
  57. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/auctions.py +0 -0
  58. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/benchmark.py +0 -0
  59. {pyield-0.40.0 → pyield-0.40.2}/pyield/tn/pre.py +0 -0
  60. {pyield-0.40.0 → pyield-0.40.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PYield
3
- Version: 0.40.0
3
+ Version: 0.40.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>
@@ -49,9 +49,9 @@ Description-Content-Type: text/markdown
49
49
  PYield is a Python library designed for the analysis of Brazilian fixed income instruments. Leveraging the power of popular Python libraries like Polars, Pandas, Numpy and Requests, PYield simplifies the process of obtaining and processing data from key sources such as ANBIMA, BCB, IBGE and B3.
50
50
 
51
51
  ---
52
- ### ✅ Polars Migration Completed
52
+ ### ✅ Polars migration from version 0.40.0 onwards:
53
53
 
54
- All public functions now return **Polars DataFrames or Series** as the canonical format. This provides stronger typing, faster execution and more reliable date/rate handling.
54
+ All public functions now return **Polars DataFrames or Series** as the canonical format. This provides stronger typing, faster execution and more reliable date/rate handling. The last version to return Pandas objects by default was **0.39.xx**.
55
55
 
56
56
  Need Pandas? Convert explicitly:
57
57
  ```python
@@ -171,16 +171,16 @@ Series: 'bdays' [i64]
171
171
  # Get ANBIMA LTN data for a given date
172
172
  >>> ltn.data("23-08-2024")
173
173
  shape: (13, 14)
174
- ┌──────────────┬─────────┬──────────┬──────────────┬───┬─────────┬─────────┬──────────────┬────────┐
174
+ ┌──────────────┬─────────┬──────────┬──────────────┬───┬─────────┬─────────┬───────────────┬────────┐
175
175
  │ ReferenceDate│ BondType│ SelicCode│ IssueBaseDate│ … │ BidRate │ AskRate │ IndicativeRate│ DIRate │
176
176
  │ --- │ --- │ --- │ --- │ │ --- │ --- │ --- │ --- │
177
177
  │ date │ str │ i64 │ date │ │ f64 │ f64 │ f64 │ f64 │
178
- ├──────────────┼─────────┼──────────┼──────────────┼───┼─────────┼─────────┼──────────────┼────────┤
178
+ ├──────────────┼─────────┼──────────┼──────────────┼───┼─────────┼─────────┼───────────────┼────────┤
179
179
  │ 2024-08-23 │ LTN │ 100000 │ 2022-07-08 │ … │ 0.10459 │ 0.104252│ 0.104416 │ 0.10472│
180
180
  │ 2024-08-23 │ LTN │ 100000 │ 2018-02-01 │ … │ 0.107366│ 0.107016│ 0.107171 │ 0.10823│
181
181
  │ 2024-08-23 │ LTN │ 100000 │ 2023-01-06 │ … │ 0.110992│ 0.110746│ 0.110866 │ 0.11179│
182
182
  │ … │ … │ … │ … │ … │ … │ … │ … │ … │
183
- └──────────────┴─────────┴──────────┴──────────────┴───┴─────────┴─────────┴──────────────┴────────┘
183
+ └──────────────┴─────────┴──────────┴──────────────┴───┴─────────┴─────────┴───────────────┴────────┘
184
184
 
185
185
  # Calculate the quotation of an NTN-B bond (base 100, truncated to 4 decimals)
186
186
  >>> ntnb.quotation("31-05-2024", "15-05-2035", 0.061490)
@@ -191,17 +191,17 @@ shape: (13, 14)
191
191
  # DI Spreads: IndicativeRate - SettlementRate (bps=True multiplies by 10_000)
192
192
  >>> ntnf.di_spreads("30-05-2025", bps=True)
193
193
  shape: (5, 3)
194
- ┌─────────┬────────────┬──────────┐
194
+ ┌─────────┬─────────────┬──────────┐
195
195
  │ BondType│ MaturityDate│ DISpread │
196
196
  │ --- │ --- │ --- │
197
197
  │ str │ date │ f64 │
198
- ├─────────┼────────────┼──────────┤
198
+ ├─────────┼─────────────┼──────────┤
199
199
  │ NTN-F │ 2027-01-01 │ -3.31 │
200
200
  │ NTN-F │ 2029-01-01 │ 14.21 │
201
201
  │ NTN-F │ 2031-01-01 │ 21.61 │
202
202
  │ NTN-F │ 2033-01-01 │ 11.51 │
203
203
  │ NTN-F │ 2035-01-01 │ 22.0 │
204
- └─────────┴────────────┴──────────┘
204
+ └─────────┴─────────────┴──────────┘
205
205
  ```
206
206
 
207
207
  ### Business Days Tools (Brazilian holidays automatically considered)
@@ -244,16 +244,16 @@ Series: '' [date]
244
244
  # Fetch DI1 futures (historical)
245
245
  >>> futures("DI1", "31-05-2024")
246
246
  shape: (40, 20)
247
- ┌────────────┬──────────────┬──────────────┬────────────┬───┬──────────────┬─────────┬──────────────┬─────────────┐
247
+ ┌────────────┬──────────────┬───────────────┬────────────┬───┬──────────────┬──────────┬───────────────┬─────────────┐
248
248
  │ TradeDate ┆ TickerSymbol ┆ ExpirationDate┆ BDaysToExp ┆ … ┆ CloseBidRate ┆ CloseRate┆ SettlementRate┆ ForwardRate │
249
249
  │ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
250
250
  │ date ┆ str ┆ date ┆ i64 ┆ ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
251
- ├────────────┼──────────────┼──────────────┼────────────┼───┼──────────────┼─────────┼──────────────┼─────────────┤
251
+ ├────────────┼──────────────┼───────────────┼────────────┼───┼──────────────┼──────────┼───────────────┼─────────────┤
252
252
  │ 2024-05-31 ┆ DI1M24 ┆ 2024-06-03 ┆ 1 ┆ … ┆ 0.10404 ┆ 0.10404 ┆ 0.10399 ┆ 0.10399 │
253
253
  │ 2024-05-31 ┆ DI1N24 ┆ 2024-07-01 ┆ 21 ┆ … ┆ 0.1039 ┆ 0.10386 ┆ 0.1039 ┆ 0.103896 │
254
254
  │ 2024-05-31 ┆ DI1Q24 ┆ 2024-08-01 ┆ 44 ┆ … ┆ 0.10374 ┆ 0.10374 ┆ 0.1037 ┆ 0.103517 │
255
- │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
256
- └────────────┴──────────────┴──────────────┴────────────┴───┴──────────────┴─────────┴──────────────┴─────────────┘
255
+ │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
256
+ └────────────┴──────────────┴───────────────┴────────────┴───┴──────────────┴──────────┴───────────────┴─────────────┘
257
257
  ```
258
258
 
259
259
  ### Indicators Data
@@ -7,9 +7,9 @@
7
7
  PYield is a Python library designed for the analysis of Brazilian fixed income instruments. Leveraging the power of popular Python libraries like Polars, Pandas, Numpy and Requests, PYield simplifies the process of obtaining and processing data from key sources such as ANBIMA, BCB, IBGE and B3.
8
8
 
9
9
  ---
10
- ### ✅ Polars Migration Completed
10
+ ### ✅ Polars migration from version 0.40.0 onwards:
11
11
 
12
- All public functions now return **Polars DataFrames or Series** as the canonical format. This provides stronger typing, faster execution and more reliable date/rate handling.
12
+ All public functions now return **Polars DataFrames or Series** as the canonical format. This provides stronger typing, faster execution and more reliable date/rate handling. The last version to return Pandas objects by default was **0.39.xx**.
13
13
 
14
14
  Need Pandas? Convert explicitly:
15
15
  ```python
@@ -129,16 +129,16 @@ Series: 'bdays' [i64]
129
129
  # Get ANBIMA LTN data for a given date
130
130
  >>> ltn.data("23-08-2024")
131
131
  shape: (13, 14)
132
- ┌──────────────┬─────────┬──────────┬──────────────┬───┬─────────┬─────────┬──────────────┬────────┐
132
+ ┌──────────────┬─────────┬──────────┬──────────────┬───┬─────────┬─────────┬───────────────┬────────┐
133
133
  │ ReferenceDate│ BondType│ SelicCode│ IssueBaseDate│ … │ BidRate │ AskRate │ IndicativeRate│ DIRate │
134
134
  │ --- │ --- │ --- │ --- │ │ --- │ --- │ --- │ --- │
135
135
  │ date │ str │ i64 │ date │ │ f64 │ f64 │ f64 │ f64 │
136
- ├──────────────┼─────────┼──────────┼──────────────┼───┼─────────┼─────────┼──────────────┼────────┤
136
+ ├──────────────┼─────────┼──────────┼──────────────┼───┼─────────┼─────────┼───────────────┼────────┤
137
137
  │ 2024-08-23 │ LTN │ 100000 │ 2022-07-08 │ … │ 0.10459 │ 0.104252│ 0.104416 │ 0.10472│
138
138
  │ 2024-08-23 │ LTN │ 100000 │ 2018-02-01 │ … │ 0.107366│ 0.107016│ 0.107171 │ 0.10823│
139
139
  │ 2024-08-23 │ LTN │ 100000 │ 2023-01-06 │ … │ 0.110992│ 0.110746│ 0.110866 │ 0.11179│
140
140
  │ … │ … │ … │ … │ … │ … │ … │ … │ … │
141
- └──────────────┴─────────┴──────────┴──────────────┴───┴─────────┴─────────┴──────────────┴────────┘
141
+ └──────────────┴─────────┴──────────┴──────────────┴───┴─────────┴─────────┴───────────────┴────────┘
142
142
 
143
143
  # Calculate the quotation of an NTN-B bond (base 100, truncated to 4 decimals)
144
144
  >>> ntnb.quotation("31-05-2024", "15-05-2035", 0.061490)
@@ -149,17 +149,17 @@ shape: (13, 14)
149
149
  # DI Spreads: IndicativeRate - SettlementRate (bps=True multiplies by 10_000)
150
150
  >>> ntnf.di_spreads("30-05-2025", bps=True)
151
151
  shape: (5, 3)
152
- ┌─────────┬────────────┬──────────┐
152
+ ┌─────────┬─────────────┬──────────┐
153
153
  │ BondType│ MaturityDate│ DISpread │
154
154
  │ --- │ --- │ --- │
155
155
  │ str │ date │ f64 │
156
- ├─────────┼────────────┼──────────┤
156
+ ├─────────┼─────────────┼──────────┤
157
157
  │ NTN-F │ 2027-01-01 │ -3.31 │
158
158
  │ NTN-F │ 2029-01-01 │ 14.21 │
159
159
  │ NTN-F │ 2031-01-01 │ 21.61 │
160
160
  │ NTN-F │ 2033-01-01 │ 11.51 │
161
161
  │ NTN-F │ 2035-01-01 │ 22.0 │
162
- └─────────┴────────────┴──────────┘
162
+ └─────────┴─────────────┴──────────┘
163
163
  ```
164
164
 
165
165
  ### Business Days Tools (Brazilian holidays automatically considered)
@@ -202,16 +202,16 @@ Series: '' [date]
202
202
  # Fetch DI1 futures (historical)
203
203
  >>> futures("DI1", "31-05-2024")
204
204
  shape: (40, 20)
205
- ┌────────────┬──────────────┬──────────────┬────────────┬───┬──────────────┬─────────┬──────────────┬─────────────┐
205
+ ┌────────────┬──────────────┬───────────────┬────────────┬───┬──────────────┬──────────┬───────────────┬─────────────┐
206
206
  │ TradeDate ┆ TickerSymbol ┆ ExpirationDate┆ BDaysToExp ┆ … ┆ CloseBidRate ┆ CloseRate┆ SettlementRate┆ ForwardRate │
207
207
  │ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │
208
208
  │ date ┆ str ┆ date ┆ i64 ┆ ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
209
- ├────────────┼──────────────┼──────────────┼────────────┼───┼──────────────┼─────────┼──────────────┼─────────────┤
209
+ ├────────────┼──────────────┼───────────────┼────────────┼───┼──────────────┼──────────┼───────────────┼─────────────┤
210
210
  │ 2024-05-31 ┆ DI1M24 ┆ 2024-06-03 ┆ 1 ┆ … ┆ 0.10404 ┆ 0.10404 ┆ 0.10399 ┆ 0.10399 │
211
211
  │ 2024-05-31 ┆ DI1N24 ┆ 2024-07-01 ┆ 21 ┆ … ┆ 0.1039 ┆ 0.10386 ┆ 0.1039 ┆ 0.103896 │
212
212
  │ 2024-05-31 ┆ DI1Q24 ┆ 2024-08-01 ┆ 44 ┆ … ┆ 0.10374 ┆ 0.10374 ┆ 0.1037 ┆ 0.103517 │
213
- │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
214
- └────────────┴──────────────┴──────────────┴────────────┴───┴──────────────┴─────────┴──────────────┴─────────────┘
213
+ │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
214
+ └────────────┴──────────────┴───────────────┴────────────┴───┴──────────────┴──────────┴───────────────┴─────────────┘
215
215
  ```
216
216
 
217
217
  ### Indicators Data
@@ -0,0 +1 @@
1
+ __version__ = "0.40.2"
@@ -255,7 +255,7 @@ def interpolate_rates(
255
255
  0.13881
256
256
  ]
257
257
 
258
- >>> # With extrapolation set to False, the second rate will be null
258
+ >>> # With extrapolation set to False, the second rate will be NaN
259
259
  >>> # Note: 0.13576348733268917 is shown as 0.135763
260
260
  >>> di1.interpolate_rates(
261
261
  ... dates="25-04-2025",
@@ -266,7 +266,7 @@ def interpolate_rates(
266
266
  Series: 'FlatFwdRate' [f64]
267
267
  [
268
268
  0.135763
269
- null
269
+ NaN
270
270
  ]
271
271
 
272
272
  Raises:
@@ -331,7 +331,7 @@ def interpolate_rate(
331
331
  date: DateScalar,
332
332
  expiration: DateScalar,
333
333
  extrapolate: bool = False,
334
- ) -> float | None:
334
+ ) -> float:
335
335
  """
336
336
  Interpolates or retrieves the DI rate for a single expiration date.
337
337
 
@@ -349,8 +349,8 @@ def interpolate_rate(
349
349
  expirations for the given `date`. Defaults to False.
350
350
 
351
351
  Returns:
352
- float | None: The exact or interpolated DI settlement rate for the specified
353
- date and expiration. Returns `None` if:
352
+ float: The exact or interpolated DI settlement rate for the specified
353
+ date and expiration. Returns `float("nan")` if:
354
354
  - No DI data is found for the `date`.
355
355
  - The `expiration` is outside range and `extrapolate` is False.
356
356
  - An interpolation calculation fails.
@@ -371,7 +371,7 @@ def interpolate_rate(
371
371
  """
372
372
  if has_null_args(date, expiration):
373
373
  logger.warning("Both 'date' and 'expiration' must be provided. Returning NaN.")
374
- return None
374
+ return float("nan")
375
375
 
376
376
  converted_date = cv.convert_dates(date)
377
377
  converted_expiration = cv.convert_dates(expiration)
@@ -385,7 +385,7 @@ def interpolate_rate(
385
385
  df = _get_data(dates=converted_date)
386
386
 
387
387
  if df.is_empty():
388
- return None
388
+ return float("nan")
389
389
 
390
390
  ff_interp = interpolator.Interpolator(
391
391
  method="flat_forward",
@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
10
10
  DI_OVER_DECIMAL_PLACES = 4
11
11
 
12
12
 
13
- def di_over(date: DateScalar) -> float | None:
13
+ def di_over(date: DateScalar) -> float:
14
14
  """
15
15
  Gets the DI (Interbank Deposit) rate for a specific date from B3/CETIP FTP server.
16
16
 
@@ -18,7 +18,7 @@ def di_over(date: DateScalar) -> float | None:
18
18
  date (str): Date in DD/MM/YYYY format
19
19
 
20
20
  Returns:
21
- float | None: DI rate for the specified date or None if no data is found.
21
+ float: DI rate for the specified date or float("nan") if no data is found.
22
22
 
23
23
  Raises:
24
24
  ValueError: If date is not in the correct format
@@ -30,7 +30,7 @@ def di_over(date: DateScalar) -> float | None:
30
30
  0.1315
31
31
  """
32
32
  if has_null_args(date):
33
- return None
33
+ return float("nan")
34
34
  ftp = None
35
35
  try:
36
36
  # Convert date to file format
@@ -181,7 +181,7 @@ def futures(
181
181
  # Existe a chance de que os dados consolidados estejam disponíveis após as 20h
182
182
  if time >= HISTORICAL_START_TIME:
183
183
  df_hist = _get_historical_data(selected_contract, trade_date)
184
- if not df_hist.empty:
184
+ if not df_hist.is_empty():
185
185
  logger.info("Consolidated data is already available and will be used.")
186
186
  return df_hist
187
187
 
@@ -241,7 +241,7 @@ def ptax_series(
241
241
  return pl.DataFrame()
242
242
 
243
243
 
244
- def ptax(date: DateScalar) -> float | None:
244
+ def ptax(date: DateScalar) -> float:
245
245
  """Busca a cotação PTAX média de fechamento para uma data específica.
246
246
 
247
247
  Esta função é um wrapper para a função `ptax_series`, otimizada para
@@ -263,8 +263,8 @@ def ptax(date: DateScalar) -> float | None:
263
263
  5.4389
264
264
 
265
265
  >>> # Busca a PTAX para um fim de semana (sem dados)
266
- >>> bc.ptax("23-08-2025") is None
267
- True
266
+ >>> bc.ptax("23-08-2025")
267
+ nan
268
268
  """
269
269
  # Reutiliza a função ptax_series para buscar os dados para o dia específico.
270
270
  # Definir start e end com a mesma data busca a cotação para aquele dia.
@@ -274,7 +274,7 @@ def ptax(date: DateScalar) -> float | None:
274
274
  # Isso ocorre em fins de semana, feriados ou datas futuras.
275
275
  if df_ptax.is_empty():
276
276
  logger.warning(f"No PTAX data found for date: {date}")
277
- return None
277
+ return float("nan")
278
278
 
279
279
  # A API retorna uma única linha para a cotação de fechamento de um dia.
280
280
  # A coluna "MidRate" representa a PTAX de fechamento.
@@ -245,7 +245,7 @@ def selic_over_series(
245
245
  return df.with_columns(pl.col("Value").round(DECIMAL_PLACES_ANNUALIZED))
246
246
 
247
247
 
248
- def selic_over(date: DateScalar) -> float | None:
248
+ def selic_over(date: DateScalar) -> float:
249
249
  """
250
250
  Fetches the SELIC Over rate value for a specific date.
251
251
 
@@ -264,10 +264,10 @@ def selic_over(date: DateScalar) -> float | None:
264
264
  0.104
265
265
  """
266
266
  if has_null_args(date):
267
- return None
267
+ return float("nan")
268
268
  df = selic_over_series(date, date)
269
269
  if df.is_empty():
270
- return None
270
+ return float("nan")
271
271
  return df["Value"].item(0)
272
272
 
273
273
 
@@ -312,7 +312,7 @@ def selic_target_series(
312
312
  return df
313
313
 
314
314
 
315
- def selic_target(date: DateScalar) -> float | None:
315
+ def selic_target(date: DateScalar) -> float:
316
316
  """
317
317
  Fetches the SELIC Target rate value for a specific date.
318
318
 
@@ -331,10 +331,10 @@ def selic_target(date: DateScalar) -> float | None:
331
331
  0.105
332
332
  """
333
333
  if has_null_args(date):
334
- return None
334
+ return float("nan")
335
335
  df = selic_target_series(date, date)
336
336
  if df.is_empty():
337
- return None
337
+ return float("nan")
338
338
  return df["Value"].item(0)
339
339
 
340
340
 
@@ -397,7 +397,7 @@ def di_over_series(
397
397
  return df
398
398
 
399
399
 
400
- def di_over(date: DateScalar, annualized: bool = True) -> float | None:
400
+ def di_over(date: DateScalar, annualized: bool = True) -> float:
401
401
  """
402
402
  Fetches the DI Over rate value for a specific date.
403
403
 
@@ -410,7 +410,7 @@ def di_over(date: DateScalar, annualized: bool = True) -> float | None:
410
410
  days per year), otherwise returns the daily rate.
411
411
 
412
412
  Returns:
413
- The DI Over rate as a float or None if not available.
413
+ The DI Over rate as a float or float("nan") if not available.
414
414
 
415
415
  Examples:
416
416
  >>> from pyield import bc
@@ -421,8 +421,8 @@ def di_over(date: DateScalar, annualized: bool = True) -> float | None:
421
421
  0.00045513
422
422
  """
423
423
  if has_null_args(date):
424
- return None
424
+ return float("nan")
425
425
  df = di_over_series(date, date, annualized)
426
426
  if df.is_empty():
427
- return None
427
+ return float("nan")
428
428
  return df["Value"].item(0)
@@ -59,7 +59,7 @@ def _validate_vna_values(vnas: list[float]) -> float:
59
59
  return vna_value
60
60
 
61
61
 
62
- def vna_lft(date: DateScalar) -> float | None:
62
+ def vna_lft(date: DateScalar) -> float:
63
63
  """Retrieves the VNA (Valor Nominal Atualizado) from the BCB for a given date.
64
64
 
65
65
  This function fetches daily data from the BCB website, extracts the
@@ -73,7 +73,7 @@ def vna_lft(date: DateScalar) -> float | None:
73
73
  `convert_input_dates` function.
74
74
 
75
75
  Returns:
76
- float | None: The VNA (Valor Nominal Atualizado) value for the specified date,
76
+ float: The VNA (Valor Nominal Atualizado) value for the specified date,
77
77
  or None if the date is invalid or data is not available.
78
78
 
79
79
  Examples:
@@ -93,7 +93,7 @@ def vna_lft(date: DateScalar) -> float | None:
93
93
  """
94
94
  if has_null_args(date):
95
95
  logger.warning("No valid date provided. Returning NaN.")
96
- return None
96
+ return float("nan")
97
97
  text = _get_text(date)
98
98
  table_text = _extract_vna_table_text(text)
99
99
  table_lines = _parse_vna_table_lines(table_text)
@@ -34,43 +34,37 @@ def count(
34
34
  end: DateScalar | DateArray,
35
35
  ) -> int | pl.Series | None:
36
36
  """
37
- Counts the number of business days between a `start` date (inclusive) and an `end`
38
- date (exclusive). The function can handle single dates, arrays of dates and
39
- mixed inputs, returning either a single integer or a series of integers depending
40
- on the inputs. It accounts for specified holidays, effectively excluding them from
41
- the business day count.
42
-
43
- **Important Note:** Each date in the `start` input is evaluated individually to
44
- determine which list of holidays (old or new) applies to the calculation. The
45
- transition date is 2023-12-26, which means:
46
- - Dates before 2023-12-26 use the old holiday list.
47
- - Dates on or after 2023-12-26 use the new holiday list.
37
+ Count business days between `start` (inclusive) and `end` (exclusive) with
38
+ Brazilian holiday adjustment and per-row holiday regime selection.
39
+
40
+ ORDER PRESERVATION (critical): The output order ALWAYS matches the element-wise
41
+ order of the original inputs. No sorting, deduplication, alignment or reshaping is
42
+ performed. If you pass arrays, the i-th result corresponds to the i-th pair of
43
+ (`start`, `end`) after broadcasting. This guarantees safe assignment back to the
44
+ originating DataFrame.
45
+
46
+ Holiday regime: For each `start` value, the holiday list (old vs. new) is chosen
47
+ based on the transition date 2023-12-26 (`TRANSITION_DATE`). Starts before the
48
+ transition use the old list for that row's count; starts on/after use the new list.
49
+
50
+ Null propagation: If any scalar argument is null, returns `None`. Nulls inside
51
+ array inputs yield nulls in corresponding result positions.
52
+
53
+ Return type: If both inputs are scalars (non-null) an `int` is returned; otherwise
54
+ a `polars.Series` of int counts (name: 'bdays'). If a null scalar short-circuits,
55
+ `None` is returned.
48
56
 
49
57
  Args:
50
- start (DateScalar | DateArray): The start date(s) for counting (inclusive).
51
- **Transition Handling:** The holiday list used for the *entire* counting
52
- period between `start` and `end` is determined solely by the `start` date's
53
- relation to the holiday transition date (2023-12-26). If `start` is before
54
- this date, the old holiday list is used for the whole count, even if `end`
55
- is after it. If `start` is on or after this date, new holiday list is used.
56
- end (DateScalar | DateArray): The end date(s) for counting (exclusive).
58
+ start: Single date or collection (inclusive boundary).
59
+ end: Single date or collection (exclusive boundary).
57
60
 
58
61
  Returns:
59
62
  int | pl.Series | None: Returns an integer or None if `start` and `end` are
60
63
  single dates, or a Series if any of them is an array of dates.
61
64
 
62
65
  Notes:
63
- - This function is a wrapper around `numpy.busday_count`, adapted to work
64
- directly with various Pandas and Numpy date formats.
65
- - It supports flexible date inputs, including single dates, lists, Series, and
66
- more, for both `start` and `end` parameters.
67
- - The return type depends on the input types: single dates return an int, while
68
- arrays of dates return a pl.Series with the count for each date range.
69
- - The `start` date determines the holiday list, ensuring consistency with the
70
- applicable calendar at the time.
71
- - See `numpy.busday_count` documentation for more details on how holidays are
72
- handled and how business day counts are calculated:
73
- https://numpy.org/doc/stable/reference/generated/numpy.busday_count.html.
66
+ - This function is a wrapper around `polars.business_day_count`.
67
+ - The holiday list is determined per-row based on the `start` date.
74
68
 
75
69
  Examples:
76
70
  >>> from pyield import bday
@@ -157,7 +151,7 @@ def count(
157
151
 
158
152
  s_bdays = df.select(result_expr.alias("bdays"))["bdays"]
159
153
 
160
- # Se a entrada original era escalar, retorna o valor escalar
154
+ # Se a série resultante tem tamanho 1, retorna o valor escalar
161
155
  if len(s_bdays) == 1:
162
156
  return s_bdays.item()
163
157
 
@@ -184,37 +178,56 @@ def offset(
184
178
  roll: Literal["forward", "backward"] = "forward",
185
179
  ) -> dt.date | pl.Series | None:
186
180
  """
187
- First adjusts the date to fall on a valid day according to the roll rule, then
188
- applies offsets to the given dates to the next or previous business day, considering
189
- brazilian holidays. This function supports both single dates and collections of
190
- dates. It is a wrapper for `polars.Expr.dt.add_business_days`.
191
-
192
- **Important Note:** Each date in the `dates` input is evaluated individually to
193
- determine which list of holidays applies to the calculation. Transition date
194
- is 2023-12-26, which means:
195
- - Dates before 2023-12-26 use the old holiday list.
196
- - Dates on or after 2023-12-26 use the new holiday list.
181
+ Offset date(s) by a number of business days with per-row Brazilian holiday
182
+ regime selection. The operation is performed in two steps per element:
183
+ 1) ROLL: If the original date falls on a weekend or holiday, move it according
184
+ to ``roll`` ("forward" -> next business day; "backward" -> previous).
185
+ 2) ADD: Apply the signed business-day ``offset`` (positive forward, negative
186
+ backward, zero = stay on the rolled date).
187
+
188
+ ORDER PRESERVATION (critical): Output ordering strictly matches the element-wise
189
+ pairing after broadcasting between ``dates`` and ``offset``. No sorting,
190
+ deduplication or shape changes occur. The i-th result corresponds to the i-th
191
+ (date, offset) pair, enabling safe assignment back into the originating DataFrame.
192
+
193
+ Holiday regime: For EACH date the appropriate holiday list (old vs. new) is
194
+ chosen based on the transition date ``2023-12-26`` (``TRANSITION_DATE``). Dates
195
+ before the transition use the *old* list; dates on/after use the *new* list.
196
+
197
+ Roll semantics: ``roll`` only acts when the original date is not already a
198
+ business day under its regime. After rolling, the subsequent business-day
199
+ addition is applied from that rolled anchor. An ``offset`` of 0 therefore
200
+ returns either the original date (if already a business day) or the rolled
201
+ business day.
202
+
203
+ Null propagation: If any scalar argument is null, the function short-circuits
204
+ to ``None``. Nulls inside array inputs propagate to their corresponding output
205
+ positions.
206
+
207
+ Broadcasting: ``dates`` and ``offset`` may be scalars or array-like. Standard
208
+ Polars broadcasting rules apply when constructing the per-row pairs.
209
+
210
+ Return type: If both inputs are non-null scalars a ``datetime.date`` is returned.
211
+ Otherwise a ``polars.Series`` of dates named ``'result'`` is produced. Null scalar
212
+ inputs yield ``None``.
197
213
 
198
214
  Args:
199
- dates (DateScalar | DateArray): The date(s) to offset. Can be a scalar date type
200
- or a collection of dates. **Transition Handling:** Due to a change in
201
- Brazilian national holidays effective from 2023-12-26 (`TRANSITION_DATE`),
202
- this function automatically selects the appropriate holiday list
203
- (old or new) based on **each individual date** in the `dates` input.
204
- Dates before 2023-12-26 use the old list for their offset calculation,
205
- while dates on or after 2023-12-26 use the new list.
206
- offset (int | Series | np.ndarray | list[int] | tuple[int], optional):
207
- The number of business days to offset the dates. Positive for future dates,
208
- negative for past dates. Zero will return the same date if it's a business
209
- day, or the next/previous business day otherwise, according to `roll`.
210
- roll (Literal["forward", "backward"], optional): Direction to roll the date if
211
- it falls on a holiday or weekend. 'forward' to the next business day,
212
- 'backward' to the previous. Defaults to 'forward'.
215
+ dates: Single date or collection of dates to be rolled (if needed) and then
216
+ offset. Each date independently selects the holiday regime.
217
+ offset: Signed count of business days to apply after rolling. Positive moves
218
+ forward, negative backward, zero keeps the rolled anchor.
219
+ roll: Direction to roll a non-business starting date ("forward" or
220
+ "backward"). Defaults to "forward".
213
221
 
214
222
  Returns:
215
- dt.date | pl.Series | None: If a single date is provided, returns a single
216
- `date` of the offset date or None. If a series of dates is provided, returns
217
- a `polars.Series` of offset dates.
223
+ dt.date | pl.Series | None: A Python ``date`` for scalar inputs, a Polars
224
+ Series of dates for any array input, or ``None`` if a null scalar argument
225
+ was provided.
226
+
227
+ Notes:
228
+ - Wrapper around ``polars.Expr.dt.add_business_days`` applied conditionally.
229
+ - Holiday regime is decided per element by comparing to ``TRANSITION_DATE``.
230
+ - Weekends are always treated as non-business days.
218
231
 
219
232
  Examples:
220
233
  >>> from pyield import bday
@@ -337,7 +350,7 @@ def offset(
337
350
  # Executa a expressão e obtém a série de resultados
338
351
  result_series = df.select(result_expr.alias("result"))["result"]
339
352
 
340
- # Se a entrada original era escalar, retorna o valor escalar
353
+ # Se a série resultante tem tamanho 1, retorna o valor escalar
341
354
  if len(result_series) == 1:
342
355
  return result_series.item()
343
356
 
@@ -419,7 +432,7 @@ def generate(
419
432
  holidays=applicable_holidays,
420
433
  )
421
434
  s_pd = pd.Series(result_dti.values).astype("date32[pyarrow]")
422
- return pl.Series(s_pd)
435
+ return pl.from_pandas(s_pd)
423
436
 
424
437
 
425
438
  @overload
@@ -427,25 +440,40 @@ def is_business_day(dates: DateScalar) -> bool | None: ...
427
440
  @overload
428
441
  def is_business_day(dates: DateArray) -> pl.Series: ...
429
442
  def is_business_day(dates: DateScalar | DateArray) -> bool | pl.Series | None:
430
- """Check if date(s) are business day(s) in Brazil.
443
+ """Determine whether date(s) are Brazilian business days with per-element
444
+ holiday regime selection.
445
+
446
+ PER-ROW HOLIDAY REGIME: For EACH input date the appropriate holiday list
447
+ ("old" vs. "new") is selected by comparing to the transition date
448
+ ``2023-12-26`` (``TRANSITION_DATE``). Dates strictly before the transition
449
+ use the old list; dates on or after it use the new list. This mirrors the
450
+ behavior of ``count`` and ``offset`` which apply regime logic element-wise.
451
+
452
+ ORDER & SHAPE PRESERVATION: The output preserves the original element order.
453
+ No sorting, deduplication, reshaping or alignment is performed; the i-th
454
+ result corresponds to the i-th provided date after broadcasting (if any
455
+ broadcasting occurred from a scalar input elsewhere in the call chain).
456
+
457
+ NULL PROPAGATION: A null scalar argument short-circuits to ``None``. Null
458
+ values inside array-like inputs produce nulls at the corresponding output
459
+ positions.
431
460
 
432
- This function applies the correct holiday list (old vs. new) for EACH date
433
- relative to the transition date ``2023-12-26``. Dates before the transition
434
- use the *old* holiday list; dates on/after the transition use the *new*
435
- holiday list.
461
+ RETURN TYPE: If the (non-null) input resolves to a single element a Python
462
+ ``bool`` is returned. If that lone element is null, ``None`` is returned.
463
+ Otherwise a ``polars.Series`` of booleans named ``'is_bday'`` is produced.
436
464
 
437
- Behavior mirrors other functions in this module: if the resulting length is 1
438
- (even if the user passed a single-element collection) a Python ``bool`` (or
439
- ``None`` for null input) is returned; otherwise a ``polars.Series`` of booleans
440
- is returned with nulls propagated.
465
+ WEEKENDS: Saturdays and Sundays are never business days regardless of the
466
+ holiday regime.
441
467
 
442
468
  Args:
443
- dates: A single date or a collection of dates (scalar, list/tuple/Series,
444
- numpy array, Polars/Pandas Series). Null scalar inputs return ``None``.
469
+ dates: Single date or collection (list/tuple/ndarray/Polars/Pandas
470
+ Series). May include nulls which propagate. Null scalar input
471
+ returns ``None``.
445
472
 
446
473
  Returns:
447
- bool | pl.Series | None: ``True`` if business day, ``False`` if not, ``None``
448
- for null scalar input, or a Polars boolean Series for array inputs.
474
+ bool | pl.Series | None: ``True`` if business day, ``False`` otherwise
475
+ for scalar input; ``None`` for null scalar input; or a Polars boolean
476
+ Series (name: ``'is_bday'``) for array inputs.
449
477
 
450
478
  Examples:
451
479
  >>> from pyield import bday
@@ -462,9 +490,10 @@ def is_business_day(dates: DateScalar | DateArray) -> bool | pl.Series | None:
462
490
  ]
463
491
 
464
492
  Notes:
465
- - The transition date is defined in ``TRANSITION_DATE``.
466
- - Null elements in array inputs propagate as nulls.
467
- - Weekends are never business days.
493
+ - Transition date defined in ``TRANSITION_DATE``.
494
+ - Mirrors per-row logic used in ``count`` and ``offset``.
495
+ - Weekends always evaluate to ``False``.
496
+ - Null elements propagate.
468
497
  """
469
498
  # Validate and normalize inputs
470
499
  if has_null_args(dates):