yahoo_finance_client 0.6.0 → 0.8.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: e33ae43499746470889d5ce981772a200757674872046a1e8c29eb69541ede39
4
- data.tar.gz: 25b281f7b93cba1e738f3c2f97f2b6470931b9ed94dda8739332d958d5408b23
3
+ metadata.gz: 1a66e077f86f9aaaf9fe6017760905c53641a89c58535ee8e0308492f826e74a
4
+ data.tar.gz: 5ccca2925b92065e274b1747014f6b7b79e464f079ffb57b941839739b7f196c
5
5
  SHA512:
6
- metadata.gz: 270aa2c3f57c379c36f81190768bad8059480d5dba852d51d25a2e3d5b22c7a21cbf5bf57de6f4509737c0288665b5a20c30d0689bc17d190983d08f10f03fd1
7
- data.tar.gz: 5e60fba40d2520447b488fe658f28d2e9630d4f96690e79e63470a47d5baa83da7f49cf51c31ff2610713da32258c404d974a9162a54c8624be41da1ef2371e5
6
+ metadata.gz: c979f457c9a49ed6487d2046d0aad76a7d36989dab90899319c297530e9778a8c37a6628ac21f4ef3785d4a08722bff88222873495487327e7f23897376012a7
7
+ data.tar.gz: dd999d278080461b7837a16a93c9344a1c079eebbf6541d78bf2e07cfc950fed9d60505dda9c4ef22a72cb0fdf52f51e241f0dc7662aacb7ea069f3bb774af1d
data/.rubocop.yml CHANGED
@@ -11,7 +11,7 @@ Metrics/AbcSize:
11
11
  Max: 25
12
12
 
13
13
  Metrics/ClassLength:
14
- Max: 280
14
+ Max: 350
15
15
 
16
16
  Metrics/MethodLength:
17
17
  Max: 11
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## [0.8.0] - 2026-05-17
4
+
5
+ - Add `Stock.get_quote_summary(symbol)` returning `{ sector:, industry: }` via Yahoo's `v10/finance/quoteSummary?modules=assetProfile` endpoint. Returns `nil` for funds, ETFs, or any symbol where the asset profile is empty. Same cookie + crumb auth as the existing quote calls.
6
+
7
+ ## [0.7.0] - 2026-05-16
8
+
9
+ - Add `Stock.get_fx_rate(from, to)` returning the current FX rate between two ISO 4217 currency codes via Yahoo's `<FROM><TO>=X` quote symbol. Returns `1.0` for identity pairs without hitting the API; returns `nil` on error.
10
+
3
11
  ## [0.6.0] - 2026-05-16
4
12
 
5
13
  - Expose Yahoo's `currency` field in `get_quote` / `get_quotes` results so callers can support multi-currency portfolios
data/README.md CHANGED
@@ -74,6 +74,26 @@ YahooFinanceClient::Stock.search("apple", count: 5)
74
74
 
75
75
  Search returns `[]` if the query is empty or the upstream request fails.
76
76
 
77
+ ### FX Rates
78
+
79
+ Fetch a current FX rate between two ISO 4217 currency codes via Yahoo's `<FROM><TO>=X` quote symbol:
80
+ ```ruby
81
+ YahooFinanceClient::Stock.get_fx_rate("EUR", "USD")
82
+ # => 1.0823
83
+ ```
84
+
85
+ Identity pairs short-circuit to `1.0` without hitting the API. Returns `nil` on any upstream error.
86
+
87
+ ### Sector / Industry
88
+
89
+ Fetch the sector and industry for a ticker via Yahoo's `quoteSummary` endpoint:
90
+ ```ruby
91
+ YahooFinanceClient::Stock.get_quote_summary("AAPL")
92
+ # => { sector: "Technology", industry: "Consumer Electronics" }
93
+ ```
94
+
95
+ Returns `nil` for funds, ETFs, or any symbol whose `assetProfile` is empty. Sector data changes rarely — cache aggressively on the caller side.
96
+
77
97
  ### Dividend History
78
98
 
79
99
  Fetch historical dividend payments via the chart API:
@@ -10,6 +10,7 @@ module YahooFinanceClient
10
10
  QUOTE_PATH = "/v7/finance/quote"
11
11
  CHART_PATH = "/v8/finance/chart"
12
12
  SEARCH_PATH = "/v1/finance/search"
13
+ QUOTE_SUMMARY_PATH = "/v10/finance/quoteSummary"
13
14
  SEARCH_BASE_URL = "https://query1.finance.yahoo.com"
14
15
  CACHE_TTL = 300
15
16
  MAX_RETRIES = 2
@@ -37,6 +38,30 @@ module YahooFinanceClient
37
38
  fetch_from_cache(cache_key) || fetch_and_cache_dividend_history(cache_key, symbol, range)
38
39
  end
39
40
 
41
+ # Fetch the current FX rate between two ISO 4217 currency codes via Yahoo's
42
+ # `<FROM><TO>=X` quote symbol. Returns 1.0 for identity pairs without hitting the API.
43
+ #
44
+ # @param from [String] source currency code (e.g. "EUR")
45
+ # @param to [String] target currency code (e.g. "USD")
46
+ # @return [Float, nil] one unit of `from` expressed in `to`, or nil on error
47
+ def get_fx_rate(from, to)
48
+ return 1.0 if from == to
49
+
50
+ cache_key = "fx_#{from}_#{to}"
51
+ fetch_from_cache(cache_key) || fetch_and_cache_fx_rate(cache_key, from, to)
52
+ end
53
+
54
+ # Fetch the sector and industry for a single ticker via Yahoo's
55
+ # quoteSummary endpoint (`assetProfile` module). Returns nil for funds,
56
+ # ETFs, or any symbol where Yahoo doesn't expose an asset profile.
57
+ #
58
+ # @param symbol [String] stock ticker
59
+ # @return [Hash{Symbol => String}, nil] { sector:, industry: } or nil
60
+ def get_quote_summary(symbol)
61
+ cache_key = "quote_summary_#{symbol}"
62
+ fetch_from_cache(cache_key) || fetch_and_cache_quote_summary(cache_key, symbol)
63
+ end
64
+
40
65
  # Search the Yahoo Finance autocomplete index for matching symbols.
41
66
  #
42
67
  # @param query [String] free-text query (ticker or company name)
@@ -192,6 +217,75 @@ module YahooFinanceClient
192
217
  Time.at(value).utc.to_date
193
218
  end
194
219
 
220
+ def fetch_and_cache_fx_rate(cache_key, from, to)
221
+ rate = fetch_fx_rate_data("#{from}#{to}=X")
222
+ store_in_cache(cache_key, rate) if rate
223
+ rate
224
+ end
225
+
226
+ def fetch_fx_rate_data(symbol)
227
+ retries = 0
228
+ begin
229
+ parse_fx_response(make_authenticated_request(symbol))
230
+ rescue AuthenticationError
231
+ retries += 1
232
+ retry if retries <= MAX_RETRIES
233
+ nil
234
+ end
235
+ end
236
+
237
+ def parse_fx_response(response)
238
+ if auth_error?(response)
239
+ Session.instance.invalidate!
240
+ raise AuthenticationError, "Authentication failed"
241
+ end
242
+ return nil unless response.success?
243
+
244
+ rate = JSON.parse(response.body).dig("quoteResponse", "result", 0, "regularMarketPrice")
245
+ rate&.to_f
246
+ rescue JSON::ParserError
247
+ nil
248
+ end
249
+
250
+ def fetch_and_cache_quote_summary(cache_key, symbol)
251
+ profile = fetch_quote_summary_data(symbol)
252
+ store_in_cache(cache_key, profile) if profile
253
+ profile
254
+ end
255
+
256
+ def fetch_quote_summary_data(symbol)
257
+ retries = 0
258
+ begin
259
+ parse_quote_summary_response(make_quote_summary_request(symbol))
260
+ rescue AuthenticationError
261
+ retries += 1
262
+ retry if retries <= MAX_RETRIES
263
+ nil
264
+ end
265
+ end
266
+
267
+ def make_quote_summary_request(symbol)
268
+ session = Session.instance
269
+ session.ensure_authenticated
270
+ url = "#{session.base_url}#{QUOTE_SUMMARY_PATH}/#{symbol}?modules=assetProfile&crumb=#{session.crumb}"
271
+ HTTParty.get(url, headers: { "User-Agent" => Session::USER_AGENT, "Cookie" => session.cookie })
272
+ end
273
+
274
+ def parse_quote_summary_response(response)
275
+ if auth_error?(response)
276
+ Session.instance.invalidate!
277
+ raise AuthenticationError, "Authentication failed"
278
+ end
279
+ return nil unless response.success?
280
+
281
+ profile = JSON.parse(response.body).dig("quoteSummary", "result", 0, "assetProfile")
282
+ return nil if profile.nil? || profile["sector"].to_s.empty?
283
+
284
+ { sector: profile["sector"], industry: profile["industry"] }
285
+ rescue JSON::ParserError
286
+ nil
287
+ end
288
+
195
289
  def fetch_and_cache_dividend_history(cache_key, symbol, range)
196
290
  data = fetch_dividend_history_data(symbol, range)
197
291
  store_in_cache(cache_key, data) unless data.empty?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YahooFinanceClient
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
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.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francesc Leveque
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-05-16 00:00:00.000000000 Z
10
+ date: 2026-05-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: csv