adds finnhub market data provider

This commit is contained in:
hackerESQ
2024-09-12 21:05:01 -05:00
parent 166fdee521
commit 2075d8273c
11 changed files with 213 additions and 26 deletions
+1
View File
@@ -61,6 +61,7 @@ MAIL_FROM_NAME="${APP_NAME}"
MARKET_DATA_PROVIDER=yahoo
MARKET_DATA_REFRESH=30
ALPHAVANTAGE_API_KEY=
FINNHUB_API_KEY=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
+1 -1
View File
@@ -8,7 +8,7 @@ Investbrain helps you manage and track the performance of your investments.
## Under the hood
Investbrain is a Laravel PHP web application that leverages Livewire, Mary UI, and Tailwind for its frontend. Most databases should work, including MySQL and SQLite. Out of the box, we feature two market data providers: Yahoo Finance and Alpha Vantage. But we also offer an extensible market data provider interface for intrepid developers to create their own! Finally, of course we have robust support for i18n, a11y, and dark mode.
Investbrain is a Laravel PHP web application that leverages Livewire, Mary UI, and Tailwind for its frontend. Most databases should work, including MySQL and SQLite. Out of the box, we feature two market data providers: [Yahoo Finance](https://finance.yahoo.com/), [Finnhub](https://finnhub.io/pricing-stock-api-market-data), and [Alpha Vantage](https://www.alphavantage.co/support/). But we also offer an extensible market data provider interface for intrepid developers to create their own! Finally, of course we have robust support for i18n, a11y, and dark mode.
## Installation
@@ -15,7 +15,7 @@ class AlphaVantageMarketData implements MarketDataInterface
return $this->quote($symbol)->isNotEmpty();
}
public function quote($symbol): Collection
public function quote(String $symbol): Collection
{
$quote = Alphavantage::core()->quoteEndpoint($symbol);
$quote = Arr::get($quote, 'Global Quote', []);
@@ -49,7 +49,7 @@ class AlphaVantageMarketData implements MarketDataInterface
]);
}
public function dividends($symbol, $startDate, $endDate): Collection
public function dividends(String $symbol, $startDate, $endDate): Collection
{
$dividends = Alphavantage::fundamentals()->dividends($symbol);
@@ -67,7 +67,7 @@ class AlphaVantageMarketData implements MarketDataInterface
});
}
public function splits($symbol, $startDate, $endDate): Collection
public function splits(String $symbol, $startDate, $endDate): Collection
{
$splits = Alphavantage::fundamentals()->splits($symbol);
@@ -86,7 +86,7 @@ class AlphaVantageMarketData implements MarketDataInterface
});
}
public function history($symbol, $startDate, $endDate): Collection
public function history(String $symbol, $startDate, $endDate): Collection
{
$history = Alphavantage::timeSeries()->daily($symbol, 'full');
+4 -4
View File
@@ -13,7 +13,7 @@ class FakeMarketData implements MarketDataInterface
return true;
}
public function quote($symbol): Collection
public function quote(String $symbol): Collection
{
return collect([
@@ -31,7 +31,7 @@ class FakeMarketData implements MarketDataInterface
]);
}
public function dividends($symbol, $startDate, $endDate): Collection
public function dividends(String $symbol, $startDate, $endDate): Collection
{
return collect([
@@ -53,7 +53,7 @@ class FakeMarketData implements MarketDataInterface
]);
}
public function splits($symbol, $startDate, $endDate): Collection
public function splits(String $symbol, $startDate, $endDate): Collection
{
return collect([
@@ -65,7 +65,7 @@ class FakeMarketData implements MarketDataInterface
]);
}
public function history($symbol, $startDate, $endDate): Collection
public function history(String $symbol, $startDate, $endDate): Collection
{
$numDays = Carbon::parse($startDate)->diffInDays($endDate, true);
@@ -0,0 +1,106 @@
<?php
namespace App\Interfaces\MarketData;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
class FinnhubMarketData implements MarketDataInterface
{
public \Finnhub\Api\DefaultApi $client;
public function __construct()
{
$this->client = new \Finnhub\Api\DefaultApi(
new \GuzzleHttp\Client(),
\Finnhub\Configuration::getDefaultConfiguration()->setApiKey('token', config('finnhub.key'))
);
}
public function exists(String $symbol): Bool
{
return $this->quote($symbol)->isNotEmpty();
}
public function quote($symbol): Collection
{
$quote = $this->client->quote($symbol);
$fundamental = cache()->tags(['quote', 'finnhub', $symbol])->remember(
'symbol-'.$symbol,
1440,
function () use ($symbol) {
return $this->client->companyBasicFinancials($symbol, "all");
}
);
if (empty($fundamental)) return collect();
return collect([
'name' => Arr::get($fundamental, 'metric.name'),
'symbol' => $symbol,
'market_value' => Arr::get($quote, 'c'),
'fifty_two_week_high' => Arr::get($fundamental, 'metric.52WeekHigh'),
'fifty_two_week_low' => Arr::get($fundamental, 'metric.52WeekLow'),
'forward_pe' => Arr::get($fundamental, 'metric.forwardPE'), // confirm
'trailing_pe' => Arr::get($fundamental, 'metric.trailingPE'), // confirm
'market_cap' => Arr::get($fundamental, 'metric.marketCapitalization'), // confirm
'book_value' => Arr::get($fundamental, 'metric.bookValuePerShare'), // confirm
'last_dividend_date' => Arr::get($fundamental, 'metric.lastDivDate'), // confirm
'dividend_yield' => Arr::get($fundamental, 'metric.dividendYield'), // confirm
]);
}
public function dividends($symbol, $startDate, $endDate): Collection
{
$dividends = $this->client->stockDividends($symbol, $startDate->format('Y-m-d'), $endDate->format('Y-m-d'));
return collect($dividends)->map(function($dividend) use ($symbol) {
return [
'symbol' => $symbol,
'date' => Carbon::parse(Arr::get($dividend, 'date'))
->format('Y-m-d H:i:s'),
'dividend_amount' => Arr::get($dividend, 'amount'),
];
});
}
public function splits($symbol, $startDate, $endDate): Collection
{
$splits = $this->client->stockSplits($symbol, $startDate->format('Y-m-d'), $endDate->format('Y-m-d'));
return collect($splits)->map(function($split) use ($symbol) {
return [
'symbol' => $symbol,
'date' => Carbon::parse(Arr::get($split, 'date'))
->format('Y-m-d H:i:s'),
'split_amount' => Arr::get($split, 'toFactor') / Arr::get($split, 'fromFactor'),
];
});
}
public function history($symbol, $startDate, $endDate): Collection
{
$history = $this->client->stockCandles($symbol, "D", $startDate->timestamp, $endDate->timestamp);
$timestamps = Arr::get($history, 't', []);
$closes = Arr::get($history, 'c', []);
return collect($timestamps)->mapWithKeys(function ($timestamp, $index) use ($symbol, $closes) {
$date = Carbon::createFromTimestamp($timestamp)->format('Y-m-d');
return [ $date => [
'symbol' => $symbol,
'date' => $date,
'close' => (float) $closes[$index],
]];
});
}
}
@@ -22,7 +22,7 @@ class YahooMarketData implements MarketDataInterface
return $this->quote($symbol)->isNotEmpty();
}
public function quote($symbol): Collection
public function quote(String $symbol): Collection
{
$quote = $this->client->getQuote($symbol);
@@ -44,7 +44,7 @@ class YahooMarketData implements MarketDataInterface
]);
}
public function dividends($symbol, $startDate, $endDate): Collection
public function dividends(String $symbol, $startDate, $endDate): Collection
{
return collect($this->client->getHistoricalDividendData($symbol, $startDate, $endDate))
@@ -58,7 +58,7 @@ class YahooMarketData implements MarketDataInterface
});
}
public function splits($symbol, $startDate, $endDate): Collection
public function splits(String $symbol, $startDate, $endDate): Collection
{
return collect($this->client->getHistoricalSplitData($symbol, $startDate, $endDate))
@@ -73,7 +73,7 @@ class YahooMarketData implements MarketDataInterface
});
}
public function history($symbol, $startDate, $endDate): Collection
public function history(String $symbol, $startDate, $endDate): Collection
{
return collect($this->client->getHistoricalQuoteData($symbol, ApiClient::INTERVAL_1_DAY, $startDate, $endDate))
+7 -6
View File
@@ -11,15 +11,16 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
if (!in_array(
$interface = config('investbrain.default', 'yahoo'),
array_keys(config('investbrain.interfaces', []))
)) {
throw new \Exception("Error: '$interface' is not a valid market data interface.");
}
$market_data = config(
"investbrain." .
config('investbrain.default', 'yahoo')
);
$this->app->bind(
\App\Interfaces\MarketData\MarketDataInterface::class,
$market_data
config("investbrain.interfaces.$interface")
);
}
+8 -1
View File
@@ -6,6 +6,7 @@
"license": "CC-BY-NC 4.0",
"require": {
"php": "^8.2",
"finnhub/client": "dev-master",
"laravel/framework": "^11.9",
"laravel/jetstream": "^5.1",
"laravel/sanctum": "^4.0",
@@ -27,6 +28,12 @@
"nunomaduro/collision": "^8.0",
"phpunit/phpunit": "^11.0.1"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/hackerESQ/finnhub-php"
}
],
"autoload": {
"files": [
"app/Support/Helpers.php"
@@ -73,6 +80,6 @@
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"minimum-stability": "dev",
"prefer-stable": true
}
Generated
+67 -3
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "40816463478b2d00c6a7c18e71aa7e7c",
"content-hash": "94cd2d270d276ef1634b6b44dc12145e",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -902,6 +902,68 @@
},
"time": "2023-11-17T15:01:25+00:00"
},
{
"name": "finnhub/client",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/hackerESQ/finnhub-php.git",
"reference": "1f1b35a0c0a6a68f9a791e3ac5cdb6f44ff69d80"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hackerESQ/finnhub-php/zipball/1f1b35a0c0a6a68f9a791e3ac5cdb6f44ff69d80",
"reference": "1f1b35a0c0a6a68f9a791e3ac5cdb6f44ff69d80",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/guzzle": "^7.2",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.12",
"phpunit/phpunit": "^8.0 || ^9.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Finnhub\\": "lib/"
}
},
"autoload-dev": {
"psr-4": {
"Finnhub\\Test\\": "test/"
}
},
"license": [
"unlicense"
],
"authors": [
{
"name": "OpenAPI-Generator contributors",
"homepage": "https://openapi-generator.tech"
}
],
"description": "Official Finnhub stock API PHP library. https://finnhub.io/",
"homepage": "https://openapi-generator.tech",
"keywords": [
"api",
"openapi",
"openapi-generator",
"openapitools",
"php",
"rest",
"sdk"
],
"support": {
"source": "https://github.com/hackerESQ/finnhub-php/tree/master"
},
"time": "2024-09-13T01:29:18+00:00"
},
{
"name": "fruitcake/php-cors",
"version": "v1.3.0",
@@ -9805,8 +9867,10 @@
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"minimum-stability": "dev",
"stability-flags": {
"finnhub/client": 20
},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
+5
View File
@@ -0,0 +1,5 @@
<?php
return [
'key' => env('FINNHUB_API_KEY'),
];
+6 -3
View File
@@ -6,9 +6,12 @@ return [
'default' => env('MARKET_DATA_PROVIDER', 'yahoo'),
'yahoo' => App\Interfaces\MarketData\YahooMarketData::class,
'alphavantage' => App\Interfaces\MarketData\AlphaVantageMarketData::class,
'fake' => App\Interfaces\MarketData\FakeMarketData::class,
'interfaces' => [
'yahoo' => App\Interfaces\MarketData\YahooMarketData::class,
'alphavantage' => App\Interfaces\MarketData\AlphaVantageMarketData::class,
'finnhub' => App\Interfaces\MarketData\FinnhubMarketData::class,
'fake' => App\Interfaces\MarketData\FakeMarketData::class,
],
'self_hosted' => env('SELF_HOSTED', true)
];