brapi-ruby-sdk 0.2.0 → 0.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17dcaf025c1a282bf971841780365f24611ba3b4ebeb3fa7700f780a7c4b54e8
4
- data.tar.gz: e754c29b797a0db5fe898252196944cfb65f5be5fcd8161690b523cdc24a1849
3
+ metadata.gz: 2f184be4deccafcbafd23f9275a7801290357e72c13d78eee0d5604aaa60edd2
4
+ data.tar.gz: f3e70b267473bc7dc2222463a7f175578b8ad3099657dccddf71571237e884e7
5
5
  SHA512:
6
- metadata.gz: e7bf7e24c594c784c2c9866d8a0cdb3cb1c71d2329ae640849d37f8ca456901e0baca13969677a8abbe3725f8c4ed0de2cecf191055530d5f1ddc0e919a4dc22
7
- data.tar.gz: 3e5c26a51421088b18f7bb1b2fc989c9e068d14c04a8c4356f6ca9810b75c01a55b6f5dfa5758913316f542af47f0118ef420881e1dec05d6da9e885437cc9fc
6
+ metadata.gz: 116a1de195f79a3b4943ae68ad6f0b9133f66145282e0f124564f2083a5a7b87d5325e65502fcaf059f49b2538bda73a3ef304c0ef9b8ffb8b3746bc41f4c8ad
7
+ data.tar.gz: f6516906c904e17d2732d108f4b989c30bba0a9477144aeeb59db534f640391b1d0937df282e600c6837ff39899bb52eb0f51eecebbcea7ea8b19b4993684b1b
data/CHANGELOG.md CHANGED
@@ -7,6 +7,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-05-28
11
+
12
+ ### Added
13
+
14
+ - **Auto-pagination** on every paginated resource. Three new public methods
15
+ show up on `client.quote`, `client.v2.fii` and `client.v2.treasury`:
16
+ - `#each_page { |page| ... }` — yields one full page response per iteration
17
+ until the upstream signals `has_next_page = false` (or after `max_pages:`
18
+ iterations, whichever comes first).
19
+ - `#each { |item| ... }` — auto-flattens across pages, yielding each row
20
+ (FII, bond or QuoteListItem) individually.
21
+ - Without a block, both return an `Enumerator` so the full Ruby
22
+ Enumerable surface is available: `client.v2.fii.first(10)`,
23
+ `client.v2.fii.select { |f| f.dividend_yield12m > 0.1 }`,
24
+ `client.v2.treasury.lazy.find { |b| b.indexer == "ipca" }`, etc.
25
+ - `Brapi::Resources::Paginated` mixin (declarative `paginates items: :fiis`
26
+ in the resource class) ready to be reused by any future paginated
27
+ endpoint.
28
+ - `max_pages:` keyword on `#each` / `#each_page` (default `10_000`)
29
+ protects against runaway loops if a buggy upstream forgets to flip
30
+ `has_next_page = false`.
31
+ - Resources are now `Enumerable`, so the standard `map` / `select` /
32
+ `take` / `find` / `lazy` / `count` methods Just Work on paginated
33
+ endpoints.
34
+
35
+ ## [0.3.0] - 2026-05-28
36
+
37
+ ### Added
38
+
39
+ - **FIIs (Fundos Imobiliários)** — new `client.v2.fii.*` resource backed by
40
+ `/api/v2/fii/...`:
41
+ - `client.v2.fii.list(**params)` — paginated listing
42
+ - `client.v2.fii.indicators(symbols, **params)` — current NAV / yield / etc.
43
+ - `client.v2.fii.historical(symbols, **params)` — OHLCV history
44
+ - `client.v2.fii.dividends(symbols, **params)` — payment history
45
+ - **Macro (séries temporais)** — new `client.v2.macro.*` resource backed by
46
+ `/api/v2/macro`:
47
+ - `client.v2.macro.retrieve(symbols, **params)` — series + observations
48
+ for SELIC, IPCA, CDI and other indicators
49
+ - `client.v2.macro.list_available` — all available series + categories
50
+ - **Tesouro Direto** — new `client.v2.treasury.*` resource backed by
51
+ `/api/v2/treasury/...`:
52
+ - `client.v2.treasury.list(**params)` — paginated bond listing
53
+ - `client.v2.treasury.indicators(symbols, **params)` — current rates +
54
+ prices for specific bonds
55
+ - Typed models for every new shape: `Fii`, `FiiDividend`, `FiiHistory`,
56
+ `MacroSeries`, `MacroObservation`, `MacroResult`, `TreasuryBond`,
57
+ `TreasuryRateInfo`, `Pagination`, plus per-endpoint Response classes.
58
+ - `Brapi::Models::V2::Pagination` reused across the paginated FII and
59
+ Treasury list endpoints.
60
+
61
+ ### Notes
62
+
63
+ - All v0.3 endpoints require a paid brapi token (Startup or Pro plan).
64
+ - Schemas were captured directly from the live API (MXRF11, SELIC,
65
+ tesouro-selic-01032031); an ad-hoc smoke test against brapi.dev
66
+ confirmed round-trip parsing for every new endpoint.
67
+
10
68
  ## [0.2.0] - 2026-05-28
11
69
 
12
70
  ### Added
data/README.md CHANGED
@@ -93,6 +93,14 @@ client.quote.retrieve("PETR4")
93
93
  | `client.v2.inflation.list_available` | `GET /api/v2/inflation/available` | Countries with inflation data |
94
94
  | `client.v2.prime_rate.retrieve(...)` | `GET /api/v2/prime-rate` | SELIC / prime-rate series |
95
95
  | `client.v2.prime_rate.list_available` | `GET /api/v2/prime-rate/available` | Countries with prime-rate data |
96
+ | `client.v2.fii.list(...)` | `GET /api/v2/fii/list` | Real Estate Funds (FIIs) listing |
97
+ | `client.v2.fii.indicators(syms, ...)` | `GET /api/v2/fii/indicators` | FII indicators (NAV, dividend yield) |
98
+ | `client.v2.fii.historical(syms, ...)` | `GET /api/v2/fii/historical` | FII OHLCV historical prices |
99
+ | `client.v2.fii.dividends(syms, ...)` | `GET /api/v2/fii/dividends` | FII dividend payment history |
100
+ | `client.v2.macro.retrieve(syms, ...)` | `GET /api/v2/macro` | Macro time series (SELIC, IPCA, CDI…) |
101
+ | `client.v2.macro.list_available` | `GET /api/v2/macro/available` | Available macro series |
102
+ | `client.v2.treasury.list(...)` | `GET /api/v2/treasury/list` | Tesouro Direto bonds listing |
103
+ | `client.v2.treasury.indicators(syms…)` | `GET /api/v2/treasury/indicators` | Current rates / prices for bonds |
96
104
 
97
105
  All params are passed as Ruby kwargs (snake_case) and the SDK converts them to the camelCase expected by the API (e.g. `sort_by: "volume"` → `?sortBy=volume`).
98
106
 
@@ -161,6 +169,49 @@ resp = Brapi.v2.prime_rate.retrieve(country: "brazil")
161
169
  resp.prime_rate.each { |p| puts "#{p.date}: #{p.value}% p.a." }
162
170
  ```
163
171
 
172
+ ### Real Estate Funds (FIIs)
173
+
174
+ ```ruby
175
+ # Browse FIIs with pagination
176
+ page = Brapi.v2.fii.list(limit: 20)
177
+ page.fiis.each { |f| puts "#{f.symbol} (#{f.segmento_atuacao}): R$ #{f.price}" }
178
+ puts "Page #{page.pagination.page}/#{page.pagination.total_pages}"
179
+
180
+ # Current indicators for specific FIIs
181
+ ind = Brapi.v2.fii.indicators(%w[MXRF11 KNRI11])
182
+ ind.fiis.each { |f| puts "#{f.symbol} NAV=#{f.nav_per_share} DY12m=#{f.dividend_yield12m}" }
183
+
184
+ # Dividend history
185
+ divs = Brapi.v2.fii.dividends("MXRF11")
186
+ divs.dividends.each { |d| puts "#{d.payment_date}: R$ #{d.rate}" }
187
+ ```
188
+
189
+ ### Macro time series (SELIC, IPCA, CDI...)
190
+
191
+ ```ruby
192
+ resp = Brapi.v2.macro.retrieve("SELIC")
193
+ result = resp.results.first
194
+ puts "#{result.series.name} (#{result.series.unit})"
195
+ result.observations.each { |o| puts "#{o.date}: #{o.value}" }
196
+
197
+ # List available series
198
+ Brapi.v2.macro.list_available.results.each { |s| puts "#{s.slug}: #{s.name}" }
199
+ ```
200
+
201
+ ### Tesouro Direto (Treasury)
202
+
203
+ ```ruby
204
+ # Browse available bonds
205
+ page = Brapi.v2.treasury.list
206
+ page.results.each do |bond|
207
+ puts "#{bond.symbol}: buy=#{bond.buy_rate}% sell_price=R$ #{bond.sell_price}"
208
+ puts " Rate interpretation: #{bond.rate_info.description}"
209
+ end
210
+
211
+ # Current rates for specific bonds
212
+ Brapi.v2.treasury.indicators("tesouro-selic-01032031")
213
+ ```
214
+
164
215
  ### List/filter stocks
165
216
 
166
217
  ```ruby
@@ -169,6 +220,47 @@ resp.stocks.each { |s| puts "#{s.stock} (#{s.name}): R$ #{s.close}" }
169
220
  puts "Page #{resp.current_page}/#{resp.total_pages}"
170
221
  ```
171
222
 
223
+ ### Auto-pagination
224
+
225
+ Every paginated resource (`client.quote.list`, `client.v2.fii.list`,
226
+ `client.v2.treasury.list`) gains `#each_page`, `#each` and the full
227
+ Ruby `Enumerable` surface:
228
+
229
+ ```ruby
230
+ # Yield each row across all pages — lazy by default, stops when
231
+ # the API reports has_next_page = false.
232
+ Brapi.v2.fii.each { |f| puts "#{f.symbol}: DY12m=#{f.dividend_yield12m}" }
233
+
234
+ # Or yield one page at a time:
235
+ Brapi.v2.treasury.each_page(max_pages: 5) do |page|
236
+ puts "Page #{page.pagination.page}/#{page.pagination.total_pages}"
237
+ end
238
+
239
+ # Enumerable methods Just Work — `each` without a block returns
240
+ # an Enumerator, so you can use first/select/lazy/etc.:
241
+ top_yield = Brapi.v2.fii
242
+ .first(100)
243
+ .sort_by { |f| -(f.dividend_yield12m || 0) }
244
+ .first(5)
245
+
246
+ ipca_bonds = Brapi.v2.treasury
247
+ .lazy
248
+ .select { |b| b.indexer == "ipca" }
249
+ .first(10)
250
+
251
+ # Custom params pass through to the underlying `list` call:
252
+ Brapi.quote.each(sort_by: "volume", sort_order: "desc") { |s| ... }
253
+ ```
254
+
255
+ `max_pages:` (default `10_000`) caps the walk in case the upstream
256
+ forgets to signal the end. `page:` lets you start from a specific page.
257
+
258
+ `count` and `size` take a fast path: they fetch only the first page and
259
+ read `pagination.total_items` (or `item_count` on Quote), so
260
+ `Brapi.v2.fii.count` is one HTTP call, not 81. Pass a block to `count`
261
+ when you want Enumerable filtering semantics — that still walks every
262
+ page, as expected.
263
+
172
264
  ## Error handling
173
265
 
174
266
  All errors inherit from `Brapi::Error`:
@@ -207,13 +299,12 @@ The SDK automatically retries transient 5xx errors (502/503/504) up to twice wit
207
299
 
208
300
  ## Roadmap
209
301
 
210
- v0.1 ships the 11 endpoints supported by the official SDKs. Future minor versions will add:
302
+ v0.1 shipped the 11 endpoints supported by the official SDKs. v0.2 added typed
303
+ models for every Quote fundamental module. v0.3 added FIIs, Macro and Tesouro
304
+ Direto. Future minor versions:
211
305
 
212
- - `client.fii.*` — Real Estate Investment Funds (FIIs)
213
- - `client.macro.*` — Macroeconomic time series
214
- - `client.options.*` — Options chain & history
215
- - `client.treasury.*` — Tesouro Direto
216
- - `client.futures.*` — Futures contracts
306
+ - `client.v2.options.*` — Options chain & history
307
+ - `client.v2.futures.*` — Futures contracts
217
308
 
218
309
  ## Development
219
310
 
data/lib/brapi/model.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
3
4
  require "time"
4
5
 
5
6
  module Brapi
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ # Pagination metadata returned by paginated brapi endpoints
6
+ # (e.g. /api/v2/fii/list, /api/v2/treasury/list, /api/quote/list).
7
+ class Pagination < Brapi::Model
8
+ attribute :page, type: :integer
9
+ attribute :limit, type: :integer
10
+ attribute :total_items, type: :integer
11
+ attribute :total_pages, type: :integer
12
+ attribute :has_next_page, type: :boolean
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ # Single class backing both /api/v2/fii/list (basic info) and
7
+ # /api/v2/fii/indicators (basic info + current indicators).
8
+ # Indicator-only fields come back nil from the list endpoint.
9
+ class Fii < Brapi::Model
10
+ attribute :symbol, type: :string
11
+ attribute :name, type: :string
12
+ attribute :cnpj, type: :string
13
+ attribute :mandate, type: :string
14
+ # NOTE: brapi exposes these two fields in Portuguese (`segmentoAtuacao`,
15
+ # `tipoGestao`); everything else in the response uses English. If the
16
+ # upstream renames them in a future API revision, callers reading
17
+ # `#segmento_atuacao` / `#tipo_gestao` will silently get nil and need
18
+ # the attribute names updated here.
19
+ attribute :segmento_atuacao, type: :string
20
+ attribute :tipo_gestao, type: :string
21
+ attribute :segment_type, type: :string
22
+
23
+ attribute :administrator_name, type: :string
24
+ attribute :administrator_cnpj, type: :string
25
+ attribute :administrator_address, type: :string
26
+ attribute :administrator_address_number, type: :string
27
+ attribute :administrator_address_complement, type: :string
28
+ attribute :administrator_district, type: :string
29
+ attribute :administrator_city, type: :string
30
+ attribute :administrator_state, type: :string
31
+ attribute :administrator_zip_code, type: :string
32
+ attribute :administrator_phone1, type: :string
33
+ attribute :administrator_phone2, type: :string
34
+ attribute :administrator_phone3, type: :string
35
+ attribute :administrator_website, type: :string
36
+ attribute :administrator_email, type: :string
37
+
38
+ attribute :price, type: :float
39
+ attribute :nav_per_share, type: :float
40
+ attribute :price_to_nav, type: :float
41
+ attribute :dividend_yield12m, type: :float, json_key: "dividendYield12m"
42
+ attribute :total_investors, type: :integer
43
+
44
+ # Indicators-only fields (nil when returned by /fii/list)
45
+ attribute :as_of_date, type: :date
46
+ attribute :dividend_yield1m, type: :float, json_key: "dividendYield1m"
47
+ attribute :monthly_return, type: :float
48
+ attribute :shares_outstanding, type: :integer
49
+ attribute :equity
50
+ attribute :total_assets
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class FiiDividend < Brapi::Model
7
+ attribute :symbol, type: :string
8
+ attribute :label, type: :string
9
+ attribute :rate, type: :float
10
+ attribute :related_to, type: :string
11
+ attribute :approved_on, type: :time
12
+ attribute :last_date_prior, type: :time
13
+ attribute :payment_date, type: :time
14
+ attribute :isin_code, type: :string
15
+ attribute :remarks, type: :string
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class FiiDividendsResponse < Brapi::Model
7
+ attribute :dividends, type: [Brapi::Models::V2::FiiDividend]
8
+ attribute :requested_at, type: :time
9
+ attribute :took, type: :integer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class FiiHistoricalResponse < Brapi::Model
7
+ attribute :fiis, type: [Brapi::Models::V2::FiiHistory]
8
+ attribute :requested_at, type: :time
9
+ attribute :took, type: :integer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ # Wraps a single FII's historical price series. The historical_data_price
7
+ # entries reuse Brapi::Models::HistoricalDataPrice (same OHLCV shape as
8
+ # /api/quote/{ticker}?range=...).
9
+ class FiiHistory < Brapi::Model
10
+ attribute :symbol, type: :string
11
+ attribute :historical_data_price, type: [Brapi::Models::HistoricalDataPrice]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class FiiIndicatorsResponse < Brapi::Model
7
+ attribute :fiis, type: [Brapi::Models::V2::Fii]
8
+ attribute :requested_at, type: :time
9
+ attribute :took, type: :integer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class FiiListResponse < Brapi::Model
7
+ attribute :fiis, type: [Brapi::Models::V2::Fii]
8
+ attribute :pagination, type: Brapi::Models::Pagination
9
+ attribute :requested_at, type: :time
10
+ attribute :took, type: :integer
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class MacroListAvailableResponse < Brapi::Model
7
+ attribute :results, type: [Brapi::Models::V2::MacroSeries]
8
+ attribute :categories
9
+ attribute :count, type: :integer
10
+ attribute :requested_at, type: :time
11
+ attribute :took, type: :integer
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class MacroObservation < Brapi::Model
7
+ attribute :date, type: :date
8
+ attribute :value, type: :float
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ # One result entry from /api/v2/macro: a MacroSeries description plus
7
+ # the observation rows (date + value) for that series.
8
+ class MacroResult < Brapi::Model
9
+ attribute :series, type: Brapi::Models::V2::MacroSeries
10
+ attribute :observations, type: [Brapi::Models::V2::MacroObservation]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class MacroRetrieveResponse < Brapi::Model
7
+ attribute :results, type: [Brapi::Models::V2::MacroResult]
8
+ attribute :requested_at, type: :time
9
+ attribute :took, type: :integer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class MacroSeries < Brapi::Model
7
+ attribute :slug, type: :string
8
+ attribute :name, type: :string
9
+ attribute :description, type: :string
10
+ attribute :unit, type: :string
11
+ attribute :frequency, type: :string
12
+ attribute :category, type: :string
13
+ attribute :start_date, type: :date
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ # Single Tesouro Direto bond as returned by /api/v2/treasury/list and
7
+ # /api/v2/treasury/indicators (both endpoints share the same shape).
8
+ #
9
+ # buyRate / sellRate semantics depend on the bond's indexer — see
10
+ # TreasuryRateInfo#rate_type / #description.
11
+ class TreasuryBond < Brapi::Model
12
+ attribute :symbol, type: :string
13
+ attribute :bond_type, type: :string
14
+ attribute :indexer, type: :string
15
+ attribute :coupon_type, type: :string
16
+ attribute :maturity_date, type: :date
17
+ attribute :duration_days, type: :integer
18
+ attribute :base_date, type: :date
19
+ attribute :buy_rate, type: :float
20
+ attribute :sell_rate, type: :float
21
+ attribute :buy_price, type: :float
22
+ attribute :sell_price, type: :float
23
+ attribute :base_price, type: :float
24
+ attribute :rate_info, type: Brapi::Models::V2::TreasuryRateInfo
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class TreasuryIndicatorsResponse < Brapi::Model
7
+ attribute :results, type: [Brapi::Models::V2::TreasuryBond]
8
+ attribute :requested_at, type: :time
9
+ attribute :took, type: :integer
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class TreasuryListResponse < Brapi::Model
7
+ attribute :results, type: [Brapi::Models::V2::TreasuryBond]
8
+ attribute :pagination, type: Brapi::Models::Pagination
9
+ attribute :requested_at, type: :time
10
+ attribute :took, type: :integer
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Models
5
+ module V2
6
+ class TreasuryRateInfo < Brapi::Model
7
+ attribute :rate_type, type: :string
8
+ attribute :rate_unit, type: :string
9
+ attribute :description, type: :string
10
+ end
11
+ end
12
+ end
13
+ end
@@ -14,6 +14,13 @@ module Brapi
14
14
  client.request(:get, path, params: camelize_keys(params))
15
15
  end
16
16
 
17
+ # Brapi endpoints that accept a `symbols` query param take a comma-separated
18
+ # list. Accepts a single String, a single Symbol, or any Enumerable of the
19
+ # two; always returns a String.
20
+ def format_symbols(symbols)
21
+ Array(symbols).join(",")
22
+ end
23
+
17
24
  def camelize_keys(params)
18
25
  return params if params.nil? || params.empty?
19
26
 
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Resources
5
+ # Adds auto-pagination helpers (`#each_page`, `#each`, plus a fast
6
+ # `#count` / `#size`) to a Resource around an existing list-style method.
7
+ #
8
+ # Usage:
9
+ #
10
+ # class Brapi::Resources::V2::Fii < Brapi::Resource
11
+ # include Brapi::Resources::Paginated
12
+ # paginates items: :fiis
13
+ #
14
+ # def list(**params)
15
+ # # ...
16
+ # end
17
+ # end
18
+ #
19
+ # ## Requirements on the host
20
+ #
21
+ # The list-style method named by `via:` (default `:list`) must accept a
22
+ # `page:` keyword argument. The mixin sets this on every iteration to walk
23
+ # through pages; if the host's `list` rejects `page:` the first iteration
24
+ # will raise `ArgumentError`.
25
+ #
26
+ # ## Pagination shapes
27
+ #
28
+ # Two shapes are supported:
29
+ #
30
+ # 1. **Nested** (FII / Treasury): the response has a `pagination` sub-object
31
+ # with `page`, `has_next_page`, `total_items`. This is the default.
32
+ # 2. **Flat** (Quote#list): the response exposes `current_page` /
33
+ # `has_next_page` / `item_count` directly. Pass `has_next:`, `next_page:`
34
+ # and `count_from:` lambdas to override the readers.
35
+ #
36
+ # ## Safety
37
+ #
38
+ # `max_pages:` (default `DEFAULT_MAX_PAGES`) caps any walk — protects
39
+ # against runaway loops if the upstream forgets to set
40
+ # `has_next_page = false`. The cap is checked **before** each fetch so
41
+ # `max_pages: 0` yields nothing.
42
+ #
43
+ # ## #count behaviour
44
+ #
45
+ # When called with no args and no block, `#count` fetches **only the first
46
+ # page** and reads `pagination.total_items` (or whatever `count_from:`
47
+ # returns), avoiding a full walk. When called with an item or a block, it
48
+ # delegates to the standard `Enumerable#count` (which walks every page).
49
+ module Paginated
50
+ DEFAULT_MAX_PAGES = 10_000
51
+
52
+ def self.included(base)
53
+ base.include(Enumerable)
54
+ base.extend(ClassMethods)
55
+ end
56
+
57
+ module ClassMethods
58
+ def paginates(items:, via: :list, has_next: nil, next_page: nil, count_from: nil)
59
+ has_next ||= ->(resp) { resp.pagination&.has_next_page }
60
+ next_page ||= ->(resp) { (resp.pagination&.page || 0) + 1 }
61
+ count_from ||= ->(resp) { resp.pagination&.total_items }
62
+
63
+ define_each_page(via: via, has_next: has_next, next_page: next_page)
64
+ define_each(items: items)
65
+ define_count(via: via, count_from: count_from)
66
+ end
67
+
68
+ private
69
+
70
+ def define_each_page(via:, has_next:, next_page:)
71
+ define_method(:each_page) do |max_pages: Brapi::Resources::Paginated::DEFAULT_MAX_PAGES, **params, &block|
72
+ return enum_for(:each_page, max_pages: max_pages, **params) unless block
73
+
74
+ current = params.delete(:page) || 1
75
+ pages_seen = 0
76
+ loop do
77
+ break if pages_seen >= max_pages
78
+
79
+ resp = public_send(via, **params, page: current)
80
+ block.call(resp)
81
+ pages_seen += 1
82
+ break unless has_next.call(resp)
83
+
84
+ current = next_page.call(resp)
85
+ end
86
+ end
87
+ end
88
+
89
+ def define_each(items:)
90
+ define_method(:each) do |max_pages: Brapi::Resources::Paginated::DEFAULT_MAX_PAGES, **params, &block|
91
+ return enum_for(:each, max_pages: max_pages, **params) unless block
92
+
93
+ each_page(max_pages: max_pages, **params) do |resp|
94
+ resp.public_send(items).each { |item| block.call(item) }
95
+ end
96
+ end
97
+ end
98
+
99
+ def define_count(via:, count_from:)
100
+ define_method(:count) do |*args, &block|
101
+ # count(item) and count { block } use Enumerable's filtering
102
+ # semantics — fall back to the standard walk.
103
+ return super(*args, &block) unless args.empty? && block.nil?
104
+
105
+ total = count_from.call(public_send(via, page: 1))
106
+ total.nil? ? super(*args, &block) : total
107
+ end
108
+
109
+ define_method(:size) { count }
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -5,6 +5,15 @@ require "cgi"
5
5
  module Brapi
6
6
  module Resources
7
7
  class Quote < Brapi::Resource
8
+ include Brapi::Resources::Paginated
9
+
10
+ # rubocop:disable Style/SymbolProc -- arrow-form keeps the three lambdas visually aligned
11
+ paginates items: :stocks,
12
+ has_next: ->(r) { r.has_next_page },
13
+ next_page: ->(r) { (r.current_page || 0) + 1 },
14
+ count_from: ->(r) { r.item_count }
15
+ # rubocop:enable Style/SymbolProc
16
+
8
17
  # GET /api/quote/{tickers}
9
18
  def retrieve(tickers, **params)
10
19
  tickers_str = Array(tickers).join(",")
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Resources
5
+ class V2
6
+ class Fii < Brapi::Resource
7
+ include Brapi::Resources::Paginated
8
+
9
+ paginates items: :fiis
10
+
11
+ # GET /api/v2/fii/list
12
+ def list(**params)
13
+ raw = get("/api/v2/fii/list", params: params)
14
+ Brapi::Models::V2::FiiListResponse.from_h(raw)
15
+ end
16
+
17
+ # GET /api/v2/fii/indicators?symbols=...
18
+ def indicators(symbols, **params)
19
+ raw = get("/api/v2/fii/indicators", params: params.merge(symbols: format_symbols(symbols)))
20
+ Brapi::Models::V2::FiiIndicatorsResponse.from_h(raw)
21
+ end
22
+
23
+ # GET /api/v2/fii/historical?symbols=...
24
+ def historical(symbols, **params)
25
+ raw = get("/api/v2/fii/historical", params: params.merge(symbols: format_symbols(symbols)))
26
+ Brapi::Models::V2::FiiHistoricalResponse.from_h(raw)
27
+ end
28
+
29
+ # GET /api/v2/fii/dividends?symbols=...
30
+ def dividends(symbols, **params)
31
+ raw = get("/api/v2/fii/dividends", params: params.merge(symbols: format_symbols(symbols)))
32
+ Brapi::Models::V2::FiiDividendsResponse.from_h(raw)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Resources
5
+ class V2
6
+ class Macro < Brapi::Resource
7
+ # GET /api/v2/macro?symbols=...
8
+ def retrieve(symbols, **params)
9
+ raw = get("/api/v2/macro", params: params.merge(symbols: format_symbols(symbols)))
10
+ Brapi::Models::V2::MacroRetrieveResponse.from_h(raw)
11
+ end
12
+
13
+ # GET /api/v2/macro/available
14
+ def list_available
15
+ raw = get("/api/v2/macro/available")
16
+ Brapi::Models::V2::MacroListAvailableResponse.from_h(raw)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brapi
4
+ module Resources
5
+ class V2
6
+ class Treasury < Brapi::Resource
7
+ include Brapi::Resources::Paginated
8
+
9
+ paginates items: :results
10
+
11
+ # GET /api/v2/treasury/list
12
+ def list(**params)
13
+ raw = get("/api/v2/treasury/list", params: params)
14
+ Brapi::Models::V2::TreasuryListResponse.from_h(raw)
15
+ end
16
+
17
+ # GET /api/v2/treasury/indicators?symbols=...
18
+ def indicators(symbols, **params)
19
+ raw = get("/api/v2/treasury/indicators", params: params.merge(symbols: format_symbols(symbols)))
20
+ Brapi::Models::V2::TreasuryIndicatorsResponse.from_h(raw)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -24,6 +24,18 @@ module Brapi
24
24
  def prime_rate
25
25
  @prime_rate ||= Brapi::Resources::V2::PrimeRate.new(client)
26
26
  end
27
+
28
+ def fii
29
+ @fii ||= Brapi::Resources::V2::Fii.new(client)
30
+ end
31
+
32
+ def macro
33
+ @macro ||= Brapi::Resources::V2::Macro.new(client)
34
+ end
35
+
36
+ def treasury
37
+ @treasury ||= Brapi::Resources::V2::Treasury.new(client)
38
+ end
27
39
  end
28
40
  end
29
41
  end
data/lib/brapi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brapi
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/brapi.rb CHANGED
@@ -42,7 +42,30 @@ require "brapi/models/v2/prime_rate_entry"
42
42
  require "brapi/models/v2/prime_rate_retrieve_response"
43
43
  require "brapi/models/v2/prime_rate_list_available_response"
44
44
 
45
- # Resources — v2 (parent class) must load before its nested classes
45
+ require "brapi/models/pagination"
46
+
47
+ require "brapi/models/v2/fii"
48
+ require "brapi/models/v2/fii_dividend"
49
+ require "brapi/models/v2/fii_history"
50
+ require "brapi/models/v2/fii_list_response"
51
+ require "brapi/models/v2/fii_indicators_response"
52
+ require "brapi/models/v2/fii_historical_response"
53
+ require "brapi/models/v2/fii_dividends_response"
54
+
55
+ require "brapi/models/v2/macro_series"
56
+ require "brapi/models/v2/macro_observation"
57
+ require "brapi/models/v2/macro_result"
58
+ require "brapi/models/v2/macro_retrieve_response"
59
+ require "brapi/models/v2/macro_list_available_response"
60
+
61
+ require "brapi/models/v2/treasury_rate_info"
62
+ require "brapi/models/v2/treasury_bond"
63
+ require "brapi/models/v2/treasury_list_response"
64
+ require "brapi/models/v2/treasury_indicators_response"
65
+
66
+ # Resources — v2 (parent class) must load before its nested classes,
67
+ # and Paginated must load before any resource that includes it.
68
+ require "brapi/resources/paginated"
46
69
  require "brapi/resources/quote"
47
70
  require "brapi/resources/available"
48
71
  require "brapi/resources/v2"
@@ -50,6 +73,9 @@ require "brapi/resources/v2/crypto"
50
73
  require "brapi/resources/v2/currency"
51
74
  require "brapi/resources/v2/inflation"
52
75
  require "brapi/resources/v2/prime_rate"
76
+ require "brapi/resources/v2/fii"
77
+ require "brapi/resources/v2/macro"
78
+ require "brapi/resources/v2/treasury"
53
79
 
54
80
  require "brapi/client"
55
81
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brapi-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rômulo Storel
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-28 00:00:00.000000000 Z
10
+ date: 2026-05-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: faraday
@@ -63,6 +63,7 @@ files:
63
63
  - lib/brapi/models/historical_data_price.rb
64
64
  - lib/brapi/models/income_statement_entry.rb
65
65
  - lib/brapi/models/key_statistics_entry.rb
66
+ - lib/brapi/models/pagination.rb
66
67
  - lib/brapi/models/quote.rb
67
68
  - lib/brapi/models/quote_list_item.rb
68
69
  - lib/brapi/models/quote_list_response.rb
@@ -76,21 +77,41 @@ files:
76
77
  - lib/brapi/models/v2/currency.rb
77
78
  - lib/brapi/models/v2/currency_list_available_response.rb
78
79
  - lib/brapi/models/v2/currency_retrieve_response.rb
80
+ - lib/brapi/models/v2/fii.rb
81
+ - lib/brapi/models/v2/fii_dividend.rb
82
+ - lib/brapi/models/v2/fii_dividends_response.rb
83
+ - lib/brapi/models/v2/fii_historical_response.rb
84
+ - lib/brapi/models/v2/fii_history.rb
85
+ - lib/brapi/models/v2/fii_indicators_response.rb
86
+ - lib/brapi/models/v2/fii_list_response.rb
79
87
  - lib/brapi/models/v2/inflation_entry.rb
80
88
  - lib/brapi/models/v2/inflation_list_available_response.rb
81
89
  - lib/brapi/models/v2/inflation_retrieve_response.rb
90
+ - lib/brapi/models/v2/macro_list_available_response.rb
91
+ - lib/brapi/models/v2/macro_observation.rb
92
+ - lib/brapi/models/v2/macro_result.rb
93
+ - lib/brapi/models/v2/macro_retrieve_response.rb
94
+ - lib/brapi/models/v2/macro_series.rb
82
95
  - lib/brapi/models/v2/prime_rate_entry.rb
83
96
  - lib/brapi/models/v2/prime_rate_list_available_response.rb
84
97
  - lib/brapi/models/v2/prime_rate_retrieve_response.rb
98
+ - lib/brapi/models/v2/treasury_bond.rb
99
+ - lib/brapi/models/v2/treasury_indicators_response.rb
100
+ - lib/brapi/models/v2/treasury_list_response.rb
101
+ - lib/brapi/models/v2/treasury_rate_info.rb
85
102
  - lib/brapi/models/value_added_entry.rb
86
103
  - lib/brapi/resource.rb
87
104
  - lib/brapi/resources/available.rb
105
+ - lib/brapi/resources/paginated.rb
88
106
  - lib/brapi/resources/quote.rb
89
107
  - lib/brapi/resources/v2.rb
90
108
  - lib/brapi/resources/v2/crypto.rb
91
109
  - lib/brapi/resources/v2/currency.rb
110
+ - lib/brapi/resources/v2/fii.rb
92
111
  - lib/brapi/resources/v2/inflation.rb
112
+ - lib/brapi/resources/v2/macro.rb
93
113
  - lib/brapi/resources/v2/prime_rate.rb
114
+ - lib/brapi/resources/v2/treasury.rb
94
115
  - lib/brapi/version.rb
95
116
  homepage: https://github.com/romulostorel/brapi-ruby-sdk
96
117
  licenses: