Skip to content

feat: add Yahoo Finance investment price provider - #464

Open
WMP wants to merge 4 commits into
kantorge:developfrom
WMP:feat/yahoo-finance-price-provider
Open

feat: add Yahoo Finance investment price provider#464
WMP wants to merge 4 commits into
kantorge:developfrom
WMP:feat/yahoo-finance-price-provider

Conversation

@WMP

@WMP WMP commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new price provider that fetches daily closing prices from the Yahoo Finance chart API. No API key is required, making it a zero-configuration option for stocks and ETFs listed on major world exchanges.

  • Supports incremental (last 5 days) and full refill (2 years) fetch modes
  • Extracts chart.error.description from 4xx error responses for clear, human-readable error messages
  • Rate limit policy: 10 req/min, 1 000 req/day
  • Symbols follow Yahoo Finance conventions, e.g. AGGG.L (London Stock Exchange), ISAC.MI (Borsa Italiana)

Changes

  • app/Services/InvestmentPriceProviders/YahooFinanceProvider.php — new provider class
  • app/Providers/InvestmentPriceProviderServiceProvider.php — register yahoo_finance in the provider registry

Test plan

  • Go to an investment's edit page and select Yahoo Finance as the price provider
  • Set symbol to a known ticker, e.g. AGGG.L
  • Click Test fetch — verify a non-zero price is returned
  • Run php artisan investments:fetch-prices — verify prices are saved to the database
  • Test with an invalid symbol (e.g. INVALID.XX) — verify a clear error message is shown

Summary by CodeRabbit

  • New Features

    • Yahoo Finance added as a new investment price provider for retrieving historical daily close prices. Supports historical sync, with rate limits of 10 requests/min and 1,000/day. No API credentials required.
  • Chores

    • Public provider listing/API updated so Yahoo Finance appears among available providers.

Review Change Stack

Adds a new price provider that fetches daily closing prices from the
unofficial Yahoo Finance chart API (v8/finance/chart). No API key is
required, making it a zero-configuration option for stocks and ETFs
listed on major exchanges.

- Supports standard refill (2y) and incremental (5d) fetch modes
- Extracts chart.error.description from error responses for clear messages
- Rate limit policy: 10 req/min, 1000 req/day
- Symbols follow Yahoo Finance conventions, e.g. AGGG.L (LSE), ISAC.MI (Borsa Italiana)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds YahooFinanceProvider with HTTP fetch/parsing/error mapping, registers it in the InvestmentPriceProviderRegistry, and updates the API test to include the new provider.

Changes

Yahoo Finance Provider

Layer / File(s) Summary
Service Provider Registration
app/Providers/InvestmentPriceProviderServiceProvider.php
Registers yahoo_financeYahooFinanceProvider in the InvestmentPriceProviderRegistry during singleton initialization, instantiating it with a GuzzleHttp\Client.
Yahoo Finance Provider Implementation
app/Services/InvestmentPriceProviders/YahooFinanceProvider.php
New provider implementing InvestmentPriceProvider. Implements fetchPrices to GET Yahoo chart JSON (range 2y for refill, 5d otherwise), compute cutoff, parse timestamps and close arrays, filter/validate entries, return {date, price} pairs, and map errors to InvalidPriceDataException or PriceProviderException. Adds credential stub, metadata, empty schemas, rate-limit policy (10/min, 1000/day), and supportsHistoricalSync().
API Test Update
tests/Feature/API/V1/InvestmentPriceProviderApiV1Test.php
Test expectation updated to include yahoo_finance in the /available endpoint response and assert it is marked available.

Sequence Diagram(s)

sequenceDiagram
  participant App as App / Service
  participant Registry as InvestmentPriceProviderRegistry
  participant Provider as YahooFinanceProvider
  participant Http as GuzzleHttp_Client
  participant Yahoo as Yahoo_Finance_API

  App->>Registry: Resolve provider by key (yahoo_finance)
  Registry->>Provider: Return instance
  App->>Provider: fetchPrices(investment, from?, refill?)
  Provider->>Http: GET /v8/finance/chart/{symbol}?range=...
  Http->>Yahoo: HTTP request
  Yahoo-->>Http: JSON response
  Http-->>Provider: Response body
  Provider->>Provider: Parse JSON, validate timestamps & close arrays
  alt valid data
    Provider-->>App: [{date, price}, ...]
  else invalid / error
    Provider-->>App: throws InvalidPriceDataException or PriceProviderException
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit scurries through code with glee,
Fetching Yahoo prices across land and sea,
With careful checks and a gentle hop,
Dates and closes lined up on the crop,
Hooray — new provider, tally and tea!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a new Yahoo Finance investment price provider. It directly matches the primary objective of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

gemini-code-assist[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/Services/InvestmentPriceProviders/YahooFinanceProvider.php (1)

128-131: Move the no-op explanation out of the method body.

The empty implementation is fine, but the inline comment does not need to live inside executable code. A short PHPDoc is cleaner if you want to keep the rationale.

💡 Suggested refactor
-    public function validateCredentials(array $credentials): void
-    {
-        // No credentials needed
-    }
+    /**
+     * Yahoo Finance does not require user credentials.
+     *
+     * `@param` array<string, mixed> $credentials
+     */
+    public function validateCredentials(array $credentials): void
+    {
+    }

As per coding guidelines, "Use PHPDoc blocks instead of inline comments, and avoid comments within code unless the logic is exceptionally complex."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/InvestmentPriceProviders/YahooFinanceProvider.php` around lines
128 - 131, The method validateCredentials(array $credentials): void in
YahooFinanceProvider currently contains an inline comment explaining it's a
no-op; move that rationale into a PHPDoc block above the method (e.g., /** No
credentials required for YahooFinanceProvider. */) and remove the inline comment
from the method body so the implementation stays empty but documented; update
the PHPDoc to reference the method name validateCredentials to clarify why it
intentionally does nothing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Services/InvestmentPriceProviders/YahooFinanceProvider.php`:
- Around line 23-24: The provider sets $range = $refill ? '2y' : '5d' but later
(in the loop around the logic that falls back to now()->subDays(5)) still clamps
the start to 5 days and compares quote timestamps at midnight, causing two-year
refills to be trimmed and mismatches when callers pass Carbon with time
components; update the logic in YahooFinanceProvider (the $range assignment and
the code that computes $start/$from and the loop that filters quotes) to compute
the request window as the max span needed (e.g., if $from is provided and older
than 5 days use a longer range like '2y' or compute a dynamic range covering
$from), compare dates using startOfDay() or normalize both sides to date-only
before filtering, and ensure the filtering loop uses inclusive date comparisons
so quotes from the requested $from are retained.
- Around line 99-110: The catch block for ClientException currently always
throws InvalidPriceDataException; change it to inspect
$e->getResponse()->getStatusCode() (from the ClientException $e) and for 429 and
403 (and any other transient 4xx you deem retriable) throw a
PriceProviderException instead, while keeping 400 and 404 mapped to
InvalidPriceDataException; preserve the existing attempt to parse $description
from the response body and pass that (or the default message) into the thrown
exception, referencing the same InvalidPriceDataException and
PriceProviderException classes and the $investment->symbol identifier.

---

Nitpick comments:
In `@app/Services/InvestmentPriceProviders/YahooFinanceProvider.php`:
- Around line 128-131: The method validateCredentials(array $credentials): void
in YahooFinanceProvider currently contains an inline comment explaining it's a
no-op; move that rationale into a PHPDoc block above the method (e.g., /** No
credentials required for YahooFinanceProvider. */) and remove the inline comment
from the method body so the implementation stays empty but documented; update
the PHPDoc to reference the method name validateCredentials to clarify why it
intentionally does nothing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a08dad5-a34c-4232-bb74-6665e9fbd4a3

📥 Commits

Reviewing files that changed from the base of the PR and between 020a17d and 5bb6f53.

📒 Files selected for processing (2)
  • app/Providers/InvestmentPriceProviderServiceProvider.php
  • app/Services/InvestmentPriceProviders/YahooFinanceProvider.php

Comment thread app/Services/InvestmentPriceProviders/YahooFinanceProvider.php
Comment thread app/Services/InvestmentPriceProviders/YahooFinanceProvider.php
WMP and others added 2 commits April 12, 2026 19:42
- rawurlencode() symbol in URL path (handles ^INDEX-style tickers)
- Compute $cutoff once before the loop using startOfDay() to avoid
  repeated Carbon parsing and correctly handle $from values with time
  components; $cutoff is null for refill so all 2y data is accepted
- Distinguish transient (403/429) vs permanent (400/404) HTTP errors:
  transient errors throw PriceProviderException so the caller can retry,
  permanent errors throw InvalidPriceDataException
- Move inline comment on validateCredentials() to PHPDoc block
- Wrap getDisplayName/getDescription/getInstructions strings in __()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Yahoo Finance requires no credentials so it appears as available by
default, alongside web_scraping. Updated the assertion count from 2 to 3
and added explicit checks for the yahoo_finance provider.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/Services/InvestmentPriceProviders/YahooFinanceProvider.php (1)

25-26: Consider consolidating inline comments into method PHPDoc.

The inline comments here and at lines 115-116 explain important business logic. Per coding guidelines, PHPDoc blocks are preferred over inline comments. These could be moved to a method-level @note or expanded @param documentation describing the cutoff behavior and error classification strategy.

Example PHPDoc consolidation
+    /**
+     * Fetch historical prices from Yahoo Finance.
+     *
+     * For incremental fetches ($refill=false), uses a 5-day range and filters
+     * prices to those on or after $from (defaulting to 5 days ago).
+     * For refill fetches ($refill=true), uses a 2-year range with no cutoff.
+     *
+     * `@throws` InvalidPriceDataException For invalid symbols (400/404)
+     * `@throws` PriceProviderException For transient errors (403/429) or network failures
+     */
     public function fetchPrices(Investment $investment, ?Carbon $from = null, bool $refill = false): array
     {
         $range = $refill ? '2y' : '5d';

-        // Default cutoff: beginning of today minus 5 days for incremental fetches.
-        // For refill, keep $from as null so all returned data is accepted.
         $cutoff = $from

As per coding guidelines: "Prefer PHPDoc blocks over inline comments."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/InvestmentPriceProviders/YahooFinanceProvider.php` around lines
25 - 26, Move the important inline comments about the default cutoff and refill
behavior and the error-classification strategy into the method-level PHPDoc for
the relevant method in YahooFinanceProvider (e.g., the method that computes/uses
$from for incremental vs refill fetches and handles API errors), replacing the
inline comments at lines ~25-26 and ~115-116; add an `@note` (or expand
`@param/`@return) that documents the "beginning of today minus 5 days" default
cutoff, that $from is kept null for refill so all returned data is accepted, and
the error classification rules, then remove the duplicate inline comments from
the method body. Ensure the PHPDoc references the $from parameter and any error
enums or helper methods used for clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/Services/InvestmentPriceProviders/YahooFinanceProvider.php`:
- Around line 25-26: Move the important inline comments about the default cutoff
and refill behavior and the error-classification strategy into the method-level
PHPDoc for the relevant method in YahooFinanceProvider (e.g., the method that
computes/uses $from for incremental vs refill fetches and handles API errors),
replacing the inline comments at lines ~25-26 and ~115-116; add an `@note` (or
expand `@param/`@return) that documents the "beginning of today minus 5 days"
default cutoff, that $from is kept null for refill so all returned data is
accepted, and the error classification rules, then remove the duplicate inline
comments from the method body. Ensure the PHPDoc references the $from parameter
and any error enums or helper methods used for clarity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a548f154-4b83-4f4f-9bca-41d8093c44f7

📥 Commits

Reviewing files that changed from the base of the PR and between 5bb6f53 and cf4ca28.

📒 Files selected for processing (2)
  • app/Services/InvestmentPriceProviders/YahooFinanceProvider.php
  • tests/Feature/API/V1/InvestmentPriceProviderApiV1Test.php

Pint's global_namespace_import requires global-namespace classes to be
imported via `use`. The catch block used \Throwable inline; replaced
with imported Throwable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/Services/InvestmentPriceProviders/YahooFinanceProvider.php (1)

149-154: 💤 Low value

Consider suppressing the PHPMD warning for the unused parameter.

The $credentials parameter is required by the interface but intentionally unused here. Adding a suppression annotation documents this intent and silences the static analysis warning.

💡 Optional: Add suppression annotation
     /**
      * No credentials are required for Yahoo Finance.
+     *
+     * `@SuppressWarnings`(PHPMD.UnusedFormalParameter)
      */
     public function validateCredentials(array $credentials): void
     {
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Services/InvestmentPriceProviders/YahooFinanceProvider.php` around lines
149 - 154, The validateCredentials(array $credentials): void method in
YahooFinanceProvider intentionally does not use the $credentials parameter; add
a PHPMD suppression annotation to its docblock (e.g.
`@SuppressWarnings`(PHPMD.UnusedFormalParameter)) to document intent and silence
the static analysis warning while keeping the method signature required by the
interface.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/Services/InvestmentPriceProviders/YahooFinanceProvider.php`:
- Around line 149-154: The validateCredentials(array $credentials): void method
in YahooFinanceProvider intentionally does not use the $credentials parameter;
add a PHPMD suppression annotation to its docblock (e.g.
`@SuppressWarnings`(PHPMD.UnusedFormalParameter)) to document intent and silence
the static analysis warning while keeping the method signature required by the
interface.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68387f25-2e13-4ba8-8b2d-49c0f19e795c

📥 Commits

Reviewing files that changed from the base of the PR and between cf4ca28 and ae5f51a.

📒 Files selected for processing (1)
  • app/Services/InvestmentPriceProviders/YahooFinanceProvider.php

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants