Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eodhistoricaldata Laravel Package

radicalloop/eodhistoricaldata

Laravel wrapper for the EOD Historical Data API, offering a simple client to fetch end-of-day stock quotes, historical prices, dividends, splits, and other market data for global symbols, with configurable API access and straightforward integration into Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure in three steps:

composer require radicalloop/eodhistoricaldata
php artisan vendor:publish --provider="Radicalloop\EodHistoricalData\EodHistoricalDataServiceProvider"

Add your token (from eodhd.com — the free tier includes a handful of tickers, enough for smoke-testing) to .env:

EOD_HISTORICAL_DATA_API_KEY=your_token_here

Where to look first:

  1. config/eodhistoricaldata.php — confirm the env var name matches what the package actually reads (it has shifted between EOD_HISTORICAL_DATA_API_KEY and EODHD_API_KEY across forks; check before you debug why you're getting 401s).
  2. src/EodHistoricalData.php (or similar entry class) — this wrapper is thin, so the public methods on the main class are the documentation. Skim them before reading the README; the README often lags the code.
  3. tests/ — the stub responses show you the exact shape of each endpoint's payload, which is more reliable than the vendor's docs for edge cases.

First use case — pull historical candles for a ticker:

use Radicalloop\EodHistoricalData\Facades\EodHistoricalData;

$prices = EodHistoricalData::historical('AAPL.US', '2024-01-01', '2024-12-31', 'd');
// [['date'=>'2024-01-02','open'=>187.15,'high'=>188.44,'low'=>183.89,'close'=>185.64,'adjusted_close'=>...,'volume'=>...], ...]

That single call gets you the 80% case: charting, backtesting, or feeding a prices table.

Implementation Patterns

Wrap it — don't call the facade from controllers. Create a MarketDataService that injects the client (or resolves the facade) and returns typed DTOs or Collections. This is the single biggest lever: the package returns raw arrays, so your service layer is where you enforce a shape, normalize symbol suffixes, and cache. When you later switch providers or bump the wrapper's major version, you edit one file.

Symbol conventions are the real integration cost. EODHD uses suffixes: AAPL.US, VOD.LSE, SAP.XETRA, BTC-USD.CC. Build a small helper that maps your internal ticker + exchange to a qualified symbol, and store the qualified symbol on your model. Don't let dotted strings leak into your domain.

class MarketDataService {
    public function daily(string $symbol, Carbon $from, Carbon $to): Collection
    {
        return Cache::remember("eod:{$symbol}:{$from->toDateString()}:{$to->toDateString()}", now()->addDay(), fn () =>
            collect(EodHistoricalData::historical($symbol, $from->toDateString(), $to->toDateString(), 'd'))
        );
    }
}

Cache aggressively and treat the API as a nightly batch, not a request-time dependency. Historical data is effectively immutable — cache it for a day or more. Fundamentals change quarterly. Only real-time quotes need short TTLs, and on lower plan tiers real-time is delayed or unavailable. A scheduled SyncPrices job that pulls yesterday's close for your watchlist beats any synchronous call from a controller.

Split reads across endpoints deliberately:

  • historical() — candles, backtests, charts (the workhorse).
  • fundamentals() — company profile, financials, ratios (large payload; cache for weeks, not days).
  • dividends() / splits() — adjust historical series or build yield features.
  • exchanges() — enumerate tradable venues; run once and persist, don't call per request.
  • realtime() / bulk endpoints — availability depends on your plan; guard behind a config flag.

Testing: point the HTTP client at a fake and return fixture JSON. If the package uses Laravel's HTTP client, Http::fake(['*eodhd.com*' => Http::response($fixture, 200)]) works. Keep fixtures from a real response in tests/Fixtures/ — they double as schema contracts when you upgrade the package.

Bulk vs per-symbol: for watchlists over ~20 symbols, prefer a bulk endpoint or batch job with concurrency control rather than looping single calls. The per-call overhead (and rate budget) adds up fast.

Gotchas and Tips

Rate limits are plan-scoped and enforced server-side. Free tiers often allow ~20 requests/day; paid tiers scale to thousands of calls/minute. Budget your calls in the scheduler, not in the controller, and add retry-with-backoff (->retry(3, 500)) around transient 429s. A rateLimiter on your job helps.

Errors arrive inconsistently. Depending on endpoint, failures surface as HTTP 401/403/429, a 200 with an {"error": "..."} body, or an empty array. Don't assume count($prices) > 0 means success and don't assume exceptions mean failure. A defensive try/catch plus a shape check is warranted.

Timezone and date parsing. EODHD returns dates as YYYY-MM-DD strings and intraday timestamps in exchange-local or UTC depending on endpoint. Cast to CarbonImmutable and normalize to UTC at the service boundary. Adjusted vs. unadjusted close is also easy to mix up — use adjusted_close for returns/backtests, close for display.

Empty payloads are common on weekends, holidays, and delisted tickers. Your charts and aggregations need to handle a zero-length collection without blowing up.

Config caching bites during development. After tweaking the config or .env, php artisan config:clear — Laravel caches config and your key changes won't appear. The publish step also won't overwrite an existing config file without --force.

Small package, thin maintenance. With ~23 stars the community is small; issues may not move quickly. Read the source before filing a bug — most surprises are in how the wrapper maps arguments to the underlying HTTP URL (e.g., parameter order for historical($symbol, $from, $to, $period) — getting it wrong returns an empty array, not an error). Pin the version in composer.json and read the changelog before upgrading; the 2025-11 release likely touched EODHD's API v1→v2 migration.

Extension points are limited. If you need an endpoint the wrapper hasn't exposed, either use the underlying Guzzle/HTTP client directly or extend the main client class — don't fork the whole package. Wrap any raw calls in your own service so they're discoverable.

Watch for the API version shift. EODHD has been migrating endpoints; if you see 404s on a call that used to work, it's usually the wrapper lagging the vendor's URL change. Check the vendor docs and the package's issue tracker together before assuming your code is wrong.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings