Skip to content

Commit

Permalink
Merge pull request #58 from investbrainapp/main
Browse files Browse the repository at this point in the history
sync multi-curr with main
  • Loading branch information
hackeresq authored Jan 29, 2025
2 parents 7694d8a + 878c668 commit 58e2ee5
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 32 deletions.
7 changes: 6 additions & 1 deletion app/Console/Commands/RefreshMarketData.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Models\Holding;
use App\Models\MarketData;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class RefreshMarketData extends Command
{
Expand Down Expand Up @@ -57,7 +58,11 @@ public function handle()
foreach ($holdings->get() as $holding) {
$this->line('Refreshing '.$holding->symbol);

MarketData::getMarketData($holding->symbol, $force);
try {
MarketData::getMarketData($holding->symbol, $force);
} catch (\Throwable $e) {
Log::error('Could not refresh '.$holding->symbol.' ('.$e->getMessage().')');
}
}
}
}
15 changes: 12 additions & 3 deletions app/Http/ApiControllers/MarketDataController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,17 @@ class MarketDataController extends ApiController
public function show(Request $request, string $symbol)
{

return MarketDataResource::make(
MarketData::getMarketData($symbol)
);
try {

return MarketDataResource::make(
MarketData::getMarketData($symbol)
);
} catch (\Throwable $e) {

return response([
'message' => 'Symbol '.$symbol.' not found.',
], 404);
}

}
}
6 changes: 1 addition & 5 deletions app/Interfaces/MarketData/AlphaVantageMarketData.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@ class AlphaVantageMarketData implements MarketDataInterface
public function exists(string $symbol): bool
{

return $this->quote($symbol)->isNotEmpty();
return (bool) $this->quote($symbol);
}

public function quote(string $symbol): Quote
{
$quote = Alphavantage::core()->quoteEndpoint($symbol);
$quote = Arr::get($quote, 'Global Quote', []);

if (empty($quote)) {
return new Quote;
}

$fundamental = cache()->remember(
'av-symbol-'.$symbol,
1440,
Expand Down
8 changes: 8 additions & 0 deletions app/Interfaces/MarketData/FallbackInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function __call($method, $arguments)
$provider = trim($provider);

try {
Log::warning("Calling method {$method} ({$provider})");

if (! in_array($provider, array_keys(config('investbrain.interfaces', [])))) {

Expand All @@ -38,6 +39,13 @@ public function __call($method, $arguments)
}
}

// don't need to throw error if calling exists
if ($method == 'exists') {

// symbol prob just doesn't exist
return false;
}

throw new \Exception("Could not get market data: {$this->latest_error}");
}
}
6 changes: 1 addition & 5 deletions app/Interfaces/MarketData/FinnhubMarketData.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,13 @@ public function __construct()
public function exists(string $symbol): bool
{

return $this->quote($symbol)->isNotEmpty();
return (bool) $this->quote($symbol);
}

public function quote(string $symbol): Quote
{
$quote = $this->client->quote($symbol);

if (empty($quote)) {
return new Quote;
}

$fundamental = cache()->remember(
'fh-symbol-'.$symbol,
1440,
Expand Down
4 changes: 2 additions & 2 deletions app/Interfaces/MarketData/Types/Quote.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class Quote extends MarketDataType
{
public function setName($name): self
public function setName(string $name): self
{
$this->items['name'] = (string) $name;

Expand All @@ -21,7 +21,7 @@ public function getName(): string
return $this->items['name'] ?? '';
}

public function setSymbol($symbol): self
public function setSymbol(string $symbol): self
{
$this->items['symbol'] = (string) $symbol;

Expand Down
26 changes: 11 additions & 15 deletions app/Interfaces/MarketData/YahooMarketData.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,26 @@ public function __construct()
public function exists(string $symbol): bool
{

return $this->quote($symbol)->isNotEmpty();
return (bool) $this->quote($symbol);
}

public function quote(string $symbol): Quote
{

$quote = $this->client->getQuote($symbol);

if (empty($quote)) {
return collect();
}

return new Quote([
'name' => $quote->getLongName() ?? $quote->getShortName(),
'name' => $quote?->getLongName() ?? $quote?->getShortName(),
'symbol' => $symbol,
'market_value' => $quote->getRegularMarketPrice(),
'fifty_two_week_high' => $quote->getFiftyTwoWeekHigh(),
'fifty_two_week_low' => $quote->getFiftyTwoWeekLow(),
'forward_pe' => $quote->getForwardPE(),
'trailing_pe' => $quote->getTrailingPE(),
'market_cap' => $quote->getMarketCap(),
'book_value' => $quote->getBookValue(),
'last_dividend_date' => $quote->getDividendDate(),
'dividend_yield' => $quote->getTrailingAnnualDividendYield() * 100,
'market_value' => $quote?->getRegularMarketPrice(),
'fifty_two_week_high' => $quote?->getFiftyTwoWeekHigh(),
'fifty_two_week_low' => $quote?->getFiftyTwoWeekLow(),
'forward_pe' => $quote?->getForwardPE(),
'trailing_pe' => $quote?->getTrailingPE(),
'market_cap' => $quote?->getMarketCap(),
'book_value' => $quote?->getBookValue(),
'last_dividend_date' => $quote?->getDividendDate(),
'dividend_yield' => $quote?->getTrailingAnnualDividendYield() * 100,
]);
}

Expand Down
3 changes: 2 additions & 1 deletion app/Rules/SymbolValidationRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ public function validate(string $attribute, mixed $value, \Closure $fail): void
{
$this->symbol = $value;

// Check if the symbol exists in the Market Data table first (avoid API call)
if (MarketData::find($this->symbol)) {

return;
}

// Check if the symbol exists in the Market Data table first (avoid API call)
// Then check against market data provider
if (! app(MarketDataInterface::class)->exists($value)) {
$fail('The symbol provided ('.$this->symbol.') is not valid');
}
Expand Down
27 changes: 27 additions & 0 deletions tests/FallbackInterfaceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,31 @@ public function test_all_providers_fail()
Log::shouldHaveReceived('warning')->with('Failed calling method quote (yahoo): Yahoo failed');
Log::shouldHaveReceived('warning')->with('Failed calling method quote (alpha): Alpha failed');
}

public function test_exists_method_fails_without_exception()
{
config()->set('investbrain.provider', 'yahoo,alpha');
config()->set('investbrain.interfaces', [
'yahoo' => YahooMarketData::class,
'alphavantage' => AlphaVantageMarketData::class,
]);

$yahooMock = Mockery::mock(YahooMarketData::class);
$yahooMock->shouldReceive('exists')
->andThrow(new \Exception('Yahoo failed'));

$alphaMock = Mockery::mock(AlphaVantageMarketData::class);
$alphaMock->shouldReceive('exists')
->andThrow(new \Exception('Alpha failed'));

$this->app->instance(YahooMarketData::class, $yahooMock);
$this->app->instance(AlphaVantageMarketData::class, $alphaMock);

$fallbackInterface = new FallbackInterface;

$result = $fallbackInterface->exists('ZZZ');

$this->assertIsBool($result);
$this->assertFalse($result);
}
}

0 comments on commit 58e2ee5

Please sign in to comment.