diff --git a/.env.example b/.env.example index ae9568b..4956528 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,8 @@ OPENAI_ORGANIZATION= MARKET_DATA_PROVIDER=yahoo ALPHAVANTAGE_API_KEY= FINNHUB_API_KEY= +ALPACA_API_KEY= +ALPACA_API_SECRET= # Cadence to refresh market data (in minutes) MARKET_DATA_REFRESH=30 diff --git a/app/Interfaces/MarketData/AlpacaMarketData.php b/app/Interfaces/MarketData/AlpacaMarketData.php new file mode 100644 index 0000000..1a8af13 --- /dev/null +++ b/app/Interfaces/MarketData/AlpacaMarketData.php @@ -0,0 +1,143 @@ +client = Http::withOptions([ + 'headers' => [ + 'content-type' => 'application/json', + 'accept' => 'application/json', + 'Apca-Api-Key-Id' => config('alpaca.key'), + 'Apca-Api-Secret-Key' => config('alpaca.secret'), + ], + ]); + } + + public function exists(string $symbol): bool + { + return (bool) $this->quote($symbol); + } + + public function quote(string $symbol): Quote + { + $response = $this->client->baseUrl($this->dataBaseUrl)->get("v2/stocks/{$symbol}/trades/latest"); + + $quote = $response->json('trade'); + + $fundamental = cache()->remember( + 'ap-symbol-'.$symbol, + 1440, + function () use ($symbol) { + + $basic = $this->client->baseUrl($this->apiBaseUrl)->get("v2/assets/{$symbol}")->json(); + $fifty_two_week = $this->client->baseUrl($this->dataBaseUrl)->withQueryParameters([ + 'timeframe' => '12M', + 'start' => now()->subWeeks(53)->format('Y-m-d'), + 'end' => now()->subWeeks(1)->format('Y-m-d'), // todo: can't query recent SIP data + ])->get("v2/stocks/{$symbol}/bars")->json(); + + return array_merge($fifty_two_week, $basic); + } + ); + + return new Quote([ + 'name' => Arr::get($fundamental, 'name'), + 'symbol' => $symbol, + 'market_value' => Arr::get($quote, 'p'), + 'fifty_two_week_high' => Arr::get($fundamental, 'bars.0.h'), + 'fifty_two_week_low' => Arr::get($fundamental, 'bars.0.l'), + ]); + } + + public function dividends(string $symbol, $startDate, $endDate): Collection + { + $response = $this->client->baseUrl($this->dataBaseUrl)->withQueryParameters([ + 'symbols' => $symbol, + 'limit' => 1000, + 'sort' => 'asc', + 'types' => 'cash_dividend', + 'start' => $startDate->format('Y-m-d'), + 'end' => $endDate->format('Y-m-d'), + ])->get('v1/corporate-actions'); + + $dividends = $response->json('corporate_actions.cash_dividends'); + + return collect($dividends) + ->map(function ($dividend) use ($symbol) { + + return new Dividend([ + 'symbol' => $symbol, + 'date' => Carbon::parse(Arr::get($dividend, 'ex_date')), + 'dividend_amount' => Arr::get($dividend, 'rate'), + ]); + }); + } + + public function splits(string $symbol, $startDate, $endDate): Collection + { + $response = $this->client->baseUrl($this->dataBaseUrl)->withQueryParameters([ + 'symbols' => $symbol, + 'limit' => 1000, + 'sort' => 'asc', + 'types' => 'forward_split', + 'start' => $startDate->format('Y-m-d'), + 'end' => $endDate->format('Y-m-d'), + ])->get('v1/corporate-actions'); + + $splits = $response->json('corporate_actions.forward_splits'); + + return collect($splits) + ->map(function ($split) use ($symbol) { + + return new Split([ + 'symbol' => $symbol, + 'date' => Carbon::parse(Arr::get($split, 'ex_date')), + 'split_amount' => Arr::get($split, 'new_rate') / Arr::get($split, 'old_rate'), + ]); + }); + } + + public function history(string $symbol, $startDate, $endDate): Collection + { + $response = $this->client->baseUrl($this->dataBaseUrl)->withQueryParameters([ + 'timeframe' => '1D', + 'start' => Carbon::parse($startDate)->format('Y-m-d'), + 'end' => Carbon::parse($endDate)->subHours(36)->format('Y-m-d'), // todo: can't query recent SIP data + ])->get("v2/stocks/{$symbol}/bars"); + + $history = $response->json('bars'); + + return collect($history) + ->map(function ($history) use ($symbol) { + + $date = Carbon::parse($history['t'])->format('Y-m-d'); + + return [$date => new Ohlc([ + 'symbol' => $symbol, + 'date' => $date, + 'close' => Arr::get($history, 'c'), + ])]; + }); + } +} diff --git a/config/alpaca.php b/config/alpaca.php new file mode 100644 index 0000000..ee28add --- /dev/null +++ b/config/alpaca.php @@ -0,0 +1,8 @@ + env('ALPACA_API_KEY'), + 'secret' => env('ALPACA_API_SECRET'), +]; diff --git a/config/investbrain.php b/config/investbrain.php index 86fcff0..e896cdf 100644 --- a/config/investbrain.php +++ b/config/investbrain.php @@ -11,6 +11,7 @@ return [ 'interfaces' => [ 'yahoo' => App\Interfaces\MarketData\YahooMarketData::class, 'alphavantage' => App\Interfaces\MarketData\AlphaVantageMarketData::class, + 'alpaca' => App\Interfaces\MarketData\AlpacaMarketData::class, 'finnhub' => App\Interfaces\MarketData\FinnhubMarketData::class, 'fake' => App\Interfaces\MarketData\FakeMarketData::class, ],