yahoo_finance_client 0.4.0 → 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 +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +30 -0
- data/README.md +55 -1
- data/lib/yahoo_finance_client/stock.rb +96 -0
- data/lib/yahoo_finance_client/version.rb +1 -1
- data/mise.toml +2 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e0827edebfc4f170f5a750a27e04cac4fb5dd158fcc85ab47d684a777d6dac8
|
|
4
|
+
data.tar.gz: 3922682a35baff70fb5492f6f21a8feca3ceb24cf549c7678caca26f922814bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b699a914a9e2b929b14180e0251ff4ab77ab41e53ec5cf7becff1abf0210fbd477d0f7a4b92f4a77015cf1b659f622d26eb3828e74fc8957edcf6d1f03449f8
|
|
7
|
+
data.tar.gz: 177c5c3577a4a7b31e480b9c85c097def09cdb1aa546da8dc82626239afd6b97b138bc5707f2a7df07c342015be3ad5cd3160f28df27e25ef55633afc1f11957
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
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
|
+
|
|
7
|
+
## [0.4.1] - 2026-02-13
|
|
8
|
+
|
|
9
|
+
- Add `fifty_two_week_high` and `fifty_two_week_low` fields to quote data
|
|
10
|
+
|
|
11
|
+
## [0.4.0] - 2026-02-12
|
|
12
|
+
|
|
13
|
+
- Add dividend history fetching via chart API (`get_dividend_history`)
|
|
14
|
+
- Update README with bulk quotes, dividend history, and caching docs
|
|
15
|
+
|
|
16
|
+
## [0.3.1] - 2026-02-12
|
|
17
|
+
|
|
18
|
+
- Add `ex_dividend_date` and `dividend_date` fields to quote data
|
|
19
|
+
|
|
20
|
+
## [0.3.0] - 2026-02-10
|
|
21
|
+
|
|
22
|
+
- Add bulk quotes support with `get_quotes` method
|
|
23
|
+
|
|
24
|
+
## [0.2.1] - 2026-01-29
|
|
25
|
+
|
|
26
|
+
- Add extended stock information fields (EPS, PE ratio, dividend data, moving averages)
|
|
27
|
+
|
|
28
|
+
## [0.2.0] - 2026-01-27
|
|
29
|
+
|
|
30
|
+
- Add multi-strategy authentication with retry logic
|
|
31
|
+
- Add CLAUDE.md with project instructions
|
|
32
|
+
|
|
3
33
|
## [0.1.6] - 2025-02-18
|
|
4
34
|
|
|
5
35
|
- Adding a simple cache
|
data/README.md
CHANGED
|
@@ -32,11 +32,65 @@ gem install yahoo_finance_client
|
|
|
32
32
|
|
|
33
33
|
## Usage
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
### Single Quote
|
|
36
|
+
|
|
37
|
+
Fetch stock data by passing a ticker symbol:
|
|
36
38
|
```ruby
|
|
37
39
|
YahooFinanceClient::Stock.get_quote("AAPL")
|
|
40
|
+
# => {
|
|
41
|
+
# symbol: "AAPL", name: "Apple Inc.", price: 182.52,
|
|
42
|
+
# change: 1.25, percent_change: 0.69, volume: 48123456,
|
|
43
|
+
# pe_ratio: 28.5, eps: 6.40,
|
|
44
|
+
# dividend: 0.96, dividend_yield: 0.53, payout_ratio: 15.0,
|
|
45
|
+
# ma50: 178.30, ma200: 172.15,
|
|
46
|
+
# ex_dividend_date: #<Date: 2025-02-07>, dividend_date: #<Date: 2025-02-15>
|
|
47
|
+
# }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Bulk Quotes
|
|
51
|
+
|
|
52
|
+
Fetch multiple quotes at once (batched in groups of 50):
|
|
53
|
+
```ruby
|
|
54
|
+
YahooFinanceClient::Stock.get_quotes(["AAPL", "MSFT", "GOOG"])
|
|
55
|
+
# => { "AAPL" => { symbol: "AAPL", ... }, "MSFT" => { ... }, "GOOG" => { ... } }
|
|
56
|
+
```
|
|
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
|
+
|
|
77
|
+
### Dividend History
|
|
78
|
+
|
|
79
|
+
Fetch historical dividend payments via the chart API:
|
|
80
|
+
```ruby
|
|
81
|
+
YahooFinanceClient::Stock.get_dividend_history("AAPL")
|
|
82
|
+
# => [{ date: #<Date: 2024-02-09>, amount: 0.24 }, ...]
|
|
38
83
|
```
|
|
39
84
|
|
|
85
|
+
The default range is `"2y"`. You can pass a different range:
|
|
86
|
+
```ruby
|
|
87
|
+
YahooFinanceClient::Stock.get_dividend_history("AAPL", range: "5y")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Caching
|
|
91
|
+
|
|
92
|
+
All responses are cached for 5 minutes (300 seconds). The cache is shared across `get_quote`, `get_quotes`, and `get_dividend_history`.
|
|
93
|
+
|
|
40
94
|
## Development
|
|
41
95
|
|
|
42
96
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -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)
|
|
@@ -152,6 +168,7 @@ module YahooFinanceClient
|
|
|
152
168
|
dividend: dividend, dividend_yield: calculate_yield(dividend, price),
|
|
153
169
|
payout_ratio: calculate_payout(dividend, eps),
|
|
154
170
|
ma50: quote["fiftyDayAverage"], ma200: quote["twoHundredDayAverage"],
|
|
171
|
+
fifty_two_week_high: quote["fiftyTwoWeekHigh"], fifty_two_week_low: quote["fiftyTwoWeekLow"],
|
|
155
172
|
ex_dividend_date: parse_unix_date(quote["exDividendDate"]),
|
|
156
173
|
dividend_date: parse_unix_date(quote["dividendDate"])
|
|
157
174
|
}
|
|
@@ -230,6 +247,85 @@ module YahooFinanceClient
|
|
|
230
247
|
{ date: date, amount: amount.round(4) }
|
|
231
248
|
end
|
|
232
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
|
+
""esCount=#{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
|
+
|
|
233
329
|
def fetch_from_cache(key)
|
|
234
330
|
cached_entry = @cache[key]
|
|
235
331
|
return unless cached_entry && Time.now - cached_entry[:timestamp] < CACHE_TTL
|
data/mise.toml
ADDED
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
|
+
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:
|
|
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.
|
|
85
|
+
rubygems_version: 3.6.2
|
|
85
86
|
specification_version: 4
|
|
86
87
|
summary: Basic Yahoo! Finance API client
|
|
87
88
|
test_files: []
|