wip
This commit is contained in:
@@ -2,27 +2,28 @@
|
||||
|
||||
namespace App\Interfaces\MarketData;
|
||||
|
||||
use App\Models\Holding;
|
||||
use Illuminate\Support\Collection;
|
||||
use Scheb\YahooFinanceApi\ApiClientFactory;
|
||||
|
||||
class YahooMarketData implements MarketDataInterface
|
||||
{
|
||||
// api client
|
||||
public $client;
|
||||
|
||||
public function __construct() {
|
||||
|
||||
// create yahoo finance client factory
|
||||
$this->client = ApiClientFactory::createApiClient();
|
||||
}
|
||||
|
||||
public function exists(String $symbol): Bool
|
||||
{
|
||||
|
||||
return $this->quote($symbol)->isNotEmpty();
|
||||
}
|
||||
|
||||
public function quote($symbol): Collection
|
||||
{
|
||||
|
||||
$quote = $this->client->getQuote($symbol);
|
||||
|
||||
if (empty($quote)) return collect();
|
||||
@@ -33,13 +34,18 @@ class YahooMarketData implements MarketDataInterface
|
||||
'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()
|
||||
]);
|
||||
}
|
||||
|
||||
public function dividends($symbol, $startDate, $endDate): Collection
|
||||
{
|
||||
|
||||
return collect($this->client->getHistoricalDividendData($symbol, $startDate, $endDate))
|
||||
->map(function($dividend) use ($symbol) {
|
||||
|
||||
return [
|
||||
'symbol' => $symbol,
|
||||
'date' => $dividend->getDate()->format('Y-m-d H:i:s'),
|
||||
@@ -50,6 +56,7 @@ class YahooMarketData implements MarketDataInterface
|
||||
|
||||
public function splits($symbol, $startDate, $endDate): Collection
|
||||
{
|
||||
|
||||
return collect($this->client->getHistoricalSplitData($symbol, $startDate, $endDate))
|
||||
->map(function($split) use ($symbol) {
|
||||
$split_amount = explode(':', $split->getStockSplits());
|
||||
|
||||
+18
-10
@@ -14,11 +14,6 @@ class Holding extends Model
|
||||
|
||||
protected $with = ['market_data'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'portfolio_id',
|
||||
'symbol',
|
||||
@@ -31,16 +26,29 @@ class Holding extends Model
|
||||
'dividends_synced_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'splits_synced_at' => 'datetime',
|
||||
'dividends_synced_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'market_gain_percent'
|
||||
];
|
||||
|
||||
/**
|
||||
* Append the market gain / loss percent attribute
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
*/
|
||||
public function getMarketGainPercentAttribute()
|
||||
{
|
||||
|
||||
return (int) !empty($this->market_data?->market_value) && !empty($this->average_cost_basis)
|
||||
? (($this->market_data->market_value - $this->average_cost_basis) / $this->average_cost_basis) * 100
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Market data for holding
|
||||
*
|
||||
|
||||
@@ -25,6 +25,9 @@ class MarketData extends Model
|
||||
'market_value',
|
||||
'fifty_two_week_high',
|
||||
'fifty_two_week_low',
|
||||
'forward_pe',
|
||||
'trailing_pe',
|
||||
'market_cap'
|
||||
];
|
||||
|
||||
public static function setSplitsHoldingSynced($symbol)
|
||||
@@ -49,15 +52,17 @@ class MarketData extends Model
|
||||
]);
|
||||
|
||||
// check if new or stale
|
||||
if (!$market_data->exists || now()->diffInMinutes($market_data->updated_at) >= config('market_data.refresh')) {
|
||||
if (
|
||||
!$market_data->exists
|
||||
|| is_null($market_data->updated_at)
|
||||
|| $market_data->updated_at->diffInMinutes(now()) >= config('market_data.refresh')
|
||||
) {
|
||||
|
||||
// get quote
|
||||
// $quote = app(MarketDataInterface::class)->quote($symbol);
|
||||
$quote = app(MarketDataInterface::class)->quote($symbol);
|
||||
|
||||
// fill data
|
||||
// $market_data->fill($quote->toArray());
|
||||
|
||||
|
||||
$market_data->fill($quote->toArray());
|
||||
}
|
||||
|
||||
// save with timestamps updated
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\MarketData;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
@@ -143,6 +144,17 @@ class Transaction extends Model
|
||||
* @return void
|
||||
*/
|
||||
public function syncHolding() {
|
||||
|
||||
// sync previous symbol too
|
||||
if (Arr::has($this->changes, 'symbol')) {
|
||||
|
||||
$temp = new Transaction;
|
||||
$temp->symbol = $this->original['symbol'];
|
||||
$temp->portfolio_id = $this->portfolio_id;
|
||||
|
||||
$temp->syncHolding();
|
||||
}
|
||||
|
||||
// get the holding for a symbol and portfolio (or create one)
|
||||
$holding = Holding::firstOrNew([
|
||||
'portfolio_id' => $this->portfolio_id,
|
||||
|
||||
@@ -11,7 +11,16 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
|
||||
$market_data = config(
|
||||
"market_data." .
|
||||
config('market_data.default', 'yahoo')
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
\App\Interfaces\MarketData\MarketDataInterface::class,
|
||||
$market_data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\Interfaces\MarketData\MarketDataInterface;
|
||||
use App\Models\MarketData;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class SymbolValidationRule implements ValidationRule
|
||||
{
|
||||
public $symbol;
|
||||
|
||||
/**
|
||||
* Create a new rule instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the attribute.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @param \Closure $fail
|
||||
* @return void
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
$this->symbol = $value;
|
||||
|
||||
if (MarketData::find($this->symbol)) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the symbol exists in the Market Data table first (avoid API call)
|
||||
if (!app(MarketDataInterface::class)->exists($value)) {
|
||||
$fail('The symbol provided (' . $this->symbol . ') is not valid');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user