yahoo_finance_client 0.4.1 → 0.5.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: ec1a70f9b1a9927d6b36ea559dbaf4204f8c82ad37d70b2c2feab47864cd01ac
4
- data.tar.gz: 3a91d7848dffe1d5ca1532e31c513f4465ad9147f93c8a6984dfabf320c65337
3
+ metadata.gz: 7e0827edebfc4f170f5a750a27e04cac4fb5dd158fcc85ab47d684a777d6dac8
4
+ data.tar.gz: 3922682a35baff70fb5492f6f21a8feca3ceb24cf549c7678caca26f922814bb
5
5
  SHA512:
6
- metadata.gz: b489b0a5689876b9f13e8876263ce54c2878a7aea9e4ad421114d9ee4434c50764c89d9cb6a19a0605bc0e4bc777f14ff641edbef314e57bdf4ddc365c9428ac
7
- data.tar.gz: daffd51bf44af895b9741b94278ef5d81729f7bdc0b3250b91b0897009cfe30b069beedcd14c3d02a7f22bc40e01907eb8fab80655a5ea02c14e90e002d1a87c
6
+ metadata.gz: 6b699a914a9e2b929b14180e0251ff4ab77ab41e53ec5cf7becff1abf0210fbd477d0f7a4b92f4a77015cf1b659f622d26eb3828e74fc8957edcf6d1f03449f8
7
+ data.tar.gz: 177c5c3577a4a7b31e480b9c85c097def09cdb1aa546da8dc82626239afd6b97b138bc5707f2a7df07c342015be3ad5cd3160f28df27e25ef55633afc1f11957
data/.rubocop.yml CHANGED
@@ -11,7 +11,7 @@ Metrics/AbcSize:
11
11
  Max: 25
12
12
 
13
13
  Metrics/ClassLength:
14
- Max: 200
14
+ Max: 280
15
15
 
16
16
  Metrics/MethodLength:
17
17
  Max: 11
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Change Log
2
2
 
3
+ ## [0.5.0] - 2026-05-15
4
+
5
+ - Add `Stock.search(query, count:)` returning matching tickers from Yahoo's `v1/finance/search` autocomplete endpoint
6
+
3
7
  ## [0.4.1] - 2026-02-13
4
8
 
5
9
  - Add `fifty_two_week_high` and `fifty_two_week_low` fields to quote data
data/README.md CHANGED
@@ -55,6 +55,25 @@ YahooFinanceClient::Stock.get_quotes(["AAPL", "MSFT", "GOOG"])
55
55
  # => { "AAPL" => { symbol: "AAPL", ... }, "MSFT" => { ... }, "GOOG" => { ... } }
56
56
  ```
57
57
 
58
+ ### Search
59
+
60
+ Search the Yahoo Finance autocomplete index by ticker or company name:
61
+ ```ruby
62
+ YahooFinanceClient::Stock.search("apple")
63
+ # => [
64
+ # { symbol: "AAPL", name: "Apple Inc.", exchange: "NasdaqGS", type: "EQUITY", type_display: "Equity" },
65
+ # { symbol: "AAPL.MX", name: "Apple Inc.", exchange: "Mexico", type: "EQUITY", type_display: "Equity" },
66
+ # ...
67
+ # ]
68
+ ```
69
+
70
+ The default result count is 10. Pass `count:` to override:
71
+ ```ruby
72
+ YahooFinanceClient::Stock.search("apple", count: 5)
73
+ ```
74
+
75
+ Search returns `[]` if the query is empty or the upstream request fails.
76
+
58
77
  ### Dividend History
59
78
 
60
79
  Fetch historical dividend payments via the chart API:
@@ -2,12 +2,15 @@
2
2
 
3
3
  require "httparty"
4
4
  require "json"
5
+ require "uri"
5
6
 
6
7
  module YahooFinanceClient
7
8
  # This class provides methods to interact with Yahoo Finance API for stock data.
8
9
  class Stock
9
10
  QUOTE_PATH = "/v7/finance/quote"
10
11
  CHART_PATH = "/v8/finance/chart"
12
+ SEARCH_PATH = "/v1/finance/search"
13
+ SEARCH_BASE_URL = "https://query1.finance.yahoo.com"
11
14
  CACHE_TTL = 300
12
15
  MAX_RETRIES = 2
13
16
  BATCH_SIZE = 50
@@ -34,6 +37,19 @@ module YahooFinanceClient
34
37
  fetch_from_cache(cache_key) || fetch_and_cache_dividend_history(cache_key, symbol, range)
35
38
  end
36
39
 
40
+ # Search the Yahoo Finance autocomplete index for matching symbols.
41
+ #
42
+ # @param query [String] free-text query (ticker or company name)
43
+ # @param count [Integer] max number of results to return
44
+ # @return [Array<Hash>] each entry has symbol, name, exchange, type, type_display
45
+ def search(query, count: 10)
46
+ normalized = query.to_s.strip
47
+ return [] if normalized.empty?
48
+
49
+ cache_key = "search_#{normalized.downcase}_#{count}"
50
+ fetch_from_cache(cache_key) || fetch_and_cache_search(cache_key, normalized, count)
51
+ end
52
+
37
53
  private
38
54
 
39
55
  def partition_cached(symbols)
@@ -231,6 +247,85 @@ module YahooFinanceClient
231
247
  { date: date, amount: amount.round(4) }
232
248
  end
233
249
 
250
+ def fetch_and_cache_search(cache_key, query, count)
251
+ data = fetch_search_data(query, count)
252
+ store_in_cache(cache_key, data) unless data.empty?
253
+ data
254
+ end
255
+
256
+ # Try an unauthenticated request first — the v1/finance/search endpoint is historically
257
+ # public and does not validate the crumb. Only fall through to the cookie+crumb flow if
258
+ # Yahoo actually returns an auth error.
259
+ def fetch_search_data(query, count)
260
+ url = build_search_url(query, count)
261
+ response = HTTParty.get(url, headers: { "User-Agent" => Session::USER_AGENT })
262
+
263
+ return fetch_authenticated_search(query, count) if auth_error?(response)
264
+ return [] unless response.success?
265
+
266
+ parse_search_response(response.body)
267
+ rescue StandardError
268
+ []
269
+ end
270
+
271
+ def fetch_authenticated_search(query, count)
272
+ retries = 0
273
+ begin
274
+ handle_authenticated_search_response(make_authenticated_search_request(query, count))
275
+ rescue AuthenticationError
276
+ retries += 1
277
+ retry if retries <= MAX_RETRIES
278
+ []
279
+ end
280
+ end
281
+
282
+ def handle_authenticated_search_response(response)
283
+ if auth_error?(response)
284
+ Session.instance.invalidate!
285
+ raise AuthenticationError, "Authentication failed"
286
+ end
287
+ response.success? ? parse_search_response(response.body) : []
288
+ end
289
+
290
+ def make_authenticated_search_request(query, count)
291
+ session = Session.instance
292
+ session.ensure_authenticated
293
+ url = "#{build_search_url(query, count)}&crumb=#{session.crumb}"
294
+ HTTParty.get(url, headers: { "User-Agent" => Session::USER_AGENT, "Cookie" => session.cookie })
295
+ end
296
+
297
+ def build_search_url(query, count)
298
+ "#{SEARCH_BASE_URL}#{SEARCH_PATH}?q=#{URI.encode_www_form_component(query)}" \
299
+ "&quotesCount=#{count}&newsCount=0"
300
+ end
301
+
302
+ def parse_search_response(body)
303
+ quotes = JSON.parse(body)["quotes"] || []
304
+ quotes.filter_map { |q| format_search_quote(q) }
305
+ rescue JSON::ParserError
306
+ []
307
+ end
308
+
309
+ def format_search_quote(quote)
310
+ symbol = quote["symbol"].to_s
311
+ return nil if symbol.empty?
312
+
313
+ {
314
+ symbol: symbol,
315
+ name: pick_search_name(symbol, quote["shortname"], quote["longname"]),
316
+ exchange: quote["exchDisp"] || quote["exchange"],
317
+ type: quote["quoteType"],
318
+ type_display: quote["typeDisp"]
319
+ }
320
+ end
321
+
322
+ # Mutual funds and some foreign listings report the symbol code as their `shortname`
323
+ # (e.g. Baelo Dividendo Creciente comes back with shortname="0P0001QYEF.F"); prefer the
324
+ # longname when shortname is missing or just echoes the symbol.
325
+ def pick_search_name(symbol, shortname, longname)
326
+ [shortname, longname, symbol].find { |n| !n.to_s.empty? && n != symbol } || symbol
327
+ end
328
+
234
329
  def fetch_from_cache(key)
235
330
  cached_entry = @cache[key]
236
331
  return unless cached_entry && Time.now - cached_entry[:timestamp] < CACHE_TTL
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YahooFinanceClient
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.0"
5
5
  end
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "3.4.1"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yahoo_finance_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francesc Leveque
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-05-15 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: csv
@@ -57,6 +57,7 @@ files:
57
57
  - lib/yahoo_finance_client/session.rb
58
58
  - lib/yahoo_finance_client/stock.rb
59
59
  - lib/yahoo_finance_client/version.rb
60
+ - mise.toml
60
61
  - sig/yahoo_finance_client.rbs
61
62
  homepage: https://github.com/fleveque/dividend-portfolio
62
63
  licenses:
@@ -81,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  - !ruby/object:Gem::Version
82
83
  version: '0'
83
84
  requirements: []
84
- rubygems_version: 3.6.7
85
+ rubygems_version: 3.6.2
85
86
  specification_version: 4
86
87
  summary: Basic Yahoo! Finance API client
87
88
  test_files: []