diff --git a/app/Interfaces/MarketData/YahooMarketData.php b/app/Interfaces/MarketData/YahooMarketData.php
index 737ffea..3fe88db 100644
--- a/app/Interfaces/MarketData/YahooMarketData.php
+++ b/app/Interfaces/MarketData/YahooMarketData.php
@@ -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());
diff --git a/app/Models/Holding.php b/app/Models/Holding.php
index bc7b3a0..605642b 100644
--- a/app/Models/Holding.php
+++ b/app/Models/Holding.php
@@ -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
*
diff --git a/app/Models/MarketData.php b/app/Models/MarketData.php
index cada5f0..dcd7dc5 100644
--- a/app/Models/MarketData.php
+++ b/app/Models/MarketData.php
@@ -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
diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php
index 70a05dd..0295981 100644
--- a/app/Models/Transaction.php
+++ b/app/Models/Transaction.php
@@ -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,
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 452e6b6..e9971df 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -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
+ );
}
/**
diff --git a/app/Rules/SymbolValidationRule.php b/app/Rules/SymbolValidationRule.php
new file mode 100644
index 0000000..428aefd
--- /dev/null
+++ b/app/Rules/SymbolValidationRule.php
@@ -0,0 +1,45 @@
+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');
+ }
+ }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index c490d9d..4f18154 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,7 @@
"maatwebsite/excel": "^3.1",
"predis/predis": "^2.2",
"robsontenorio/mary": "^1.35",
+ "scheb/yahoo-finance-api": "^4.10",
"staudenmeir/eloquent-has-many-deep": "^1.20"
},
"require-dev": {
diff --git a/composer.lock b/composer.lock
index 6a2bb40..daf2143 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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": "87ff0113a9121d64fb4518b040767e96",
+ "content-hash": "3d6ea585015c0a9442f1faf0d5c9b141",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -131,16 +131,16 @@
},
{
"name": "blade-ui-kit/blade-icons",
- "version": "1.7.0",
+ "version": "1.7.1",
"source": {
"type": "git",
"url": "https://github.com/blade-ui-kit/blade-icons.git",
- "reference": "74275f44c71e815b85bf7cea66e3bf98c57fb7e4"
+ "reference": "8f787baf09d88cdfd6ec4dbaba11ebfa885f0595"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/74275f44c71e815b85bf7cea66e3bf98c57fb7e4",
- "reference": "74275f44c71e815b85bf7cea66e3bf98c57fb7e4",
+ "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/8f787baf09d88cdfd6ec4dbaba11ebfa885f0595",
+ "reference": "8f787baf09d88cdfd6ec4dbaba11ebfa885f0595",
"shasum": ""
},
"require": {
@@ -208,7 +208,7 @@
"type": "paypal"
}
],
- "time": "2024-07-29T21:49:30+00:00"
+ "time": "2024-08-14T14:25:11+00:00"
},
{
"name": "brick/math",
@@ -422,23 +422,23 @@
},
{
"name": "dasprid/enum",
- "version": "1.0.5",
+ "version": "1.0.6",
"source": {
"type": "git",
"url": "https://github.com/DASPRiD/Enum.git",
- "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016"
+ "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016",
- "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016",
+ "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
+ "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
"shasum": ""
},
"require": {
"php": ">=7.1 <9.0"
},
"require-dev": {
- "phpunit/phpunit": "^7 | ^8 | ^9",
+ "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "*"
},
"type": "library",
@@ -466,9 +466,9 @@
],
"support": {
"issues": "https://github.com/DASPRiD/Enum/issues",
- "source": "https://github.com/DASPRiD/Enum/tree/1.0.5"
+ "source": "https://github.com/DASPRiD/Enum/tree/1.0.6"
},
- "time": "2023-08-25T16:18:39+00:00"
+ "time": "2024-08-09T14:30:48+00:00"
},
{
"name": "dflydev/dot-access-data",
@@ -1685,16 +1685,16 @@
},
{
"name": "laravel/fortify",
- "version": "v1.22.0",
+ "version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/fortify.git",
- "reference": "33f8af0d4d11c4d30c47b450d097815d0eebd665"
+ "reference": "fbe67f018c1fe26d00913de56a6d60589b4be9b2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/fortify/zipball/33f8af0d4d11c4d30c47b450d097815d0eebd665",
- "reference": "33f8af0d4d11c4d30c47b450d097815d0eebd665",
+ "url": "https://api.github.com/repos/laravel/fortify/zipball/fbe67f018c1fe26d00913de56a6d60589b4be9b2",
+ "reference": "fbe67f018c1fe26d00913de56a6d60589b4be9b2",
"shasum": ""
},
"require": {
@@ -1746,20 +1746,20 @@
"issues": "https://github.com/laravel/fortify/issues",
"source": "https://github.com/laravel/fortify"
},
- "time": "2024-07-22T14:37:15+00:00"
+ "time": "2024-08-20T14:43:56+00:00"
},
{
"name": "laravel/framework",
- "version": "v11.18.1",
+ "version": "v11.21.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8"
+ "reference": "9d9d36708d56665b12185493f684abce38ad2d30"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/b19ba518c56852567e99fbae9321bc436c2cc5a8",
- "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/9d9d36708d56665b12185493f684abce38ad2d30",
+ "reference": "9d9d36708d56665b12185493f684abce38ad2d30",
"shasum": ""
},
"require": {
@@ -1952,20 +1952,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2024-07-26T10:39:29+00:00"
+ "time": "2024-08-20T15:00:52+00:00"
},
{
"name": "laravel/jetstream",
- "version": "v5.1.4",
+ "version": "v5.1.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/jetstream.git",
- "reference": "0eecfe8554e934d15c73cba5fd6c7f30ed640f3d"
+ "reference": "653a422fe65278c1c4f319e99d5cb700c4117ea0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/jetstream/zipball/0eecfe8554e934d15c73cba5fd6c7f30ed640f3d",
- "reference": "0eecfe8554e934d15c73cba5fd6c7f30ed640f3d",
+ "url": "https://api.github.com/repos/laravel/jetstream/zipball/653a422fe65278c1c4f319e99d5cb700c4117ea0",
+ "reference": "653a422fe65278c1c4f319e99d5cb700c4117ea0",
"shasum": ""
},
"require": {
@@ -2019,20 +2019,20 @@
"issues": "https://github.com/laravel/jetstream/issues",
"source": "https://github.com/laravel/jetstream"
},
- "time": "2024-07-10T19:01:59+00:00"
+ "time": "2024-08-08T13:28:23+00:00"
},
{
"name": "laravel/prompts",
- "version": "v0.1.24",
+ "version": "v0.1.25",
"source": {
"type": "git",
"url": "https://github.com/laravel/prompts.git",
- "reference": "409b0b4305273472f3754826e68f4edbd0150149"
+ "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149",
- "reference": "409b0b4305273472f3754826e68f4edbd0150149",
+ "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95",
+ "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95",
"shasum": ""
},
"require": {
@@ -2075,9 +2075,9 @@
"description": "Add beautiful and user-friendly forms to your command-line applications.",
"support": {
"issues": "https://github.com/laravel/prompts/issues",
- "source": "https://github.com/laravel/prompts/tree/v0.1.24"
+ "source": "https://github.com/laravel/prompts/tree/v0.1.25"
},
- "time": "2024-06-17T13:58:22+00:00"
+ "time": "2024-08-12T22:06:33+00:00"
},
{
"name": "laravel/sanctum",
@@ -2145,26 +2145,27 @@
},
{
"name": "laravel/serializable-closure",
- "version": "v1.3.3",
+ "version": "v1.3.4",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
- "reference": "3dbf8a8e914634c48d389c1234552666b3d43754"
+ "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754",
- "reference": "3dbf8a8e914634c48d389c1234552666b3d43754",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
+ "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
"shasum": ""
},
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
- "nesbot/carbon": "^2.61",
+ "illuminate/support": "^8.0|^9.0|^10.0|^11.0",
+ "nesbot/carbon": "^2.61|^3.0",
"pestphp/pest": "^1.21.3",
"phpstan/phpstan": "^1.8.2",
- "symfony/var-dumper": "^5.4.11"
+ "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0"
},
"type": "library",
"extra": {
@@ -2201,7 +2202,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
- "time": "2023-11-08T14:08:06+00:00"
+ "time": "2024-08-02T07:48:17+00:00"
},
{
"name": "laravel/tinker",
@@ -2271,16 +2272,16 @@
},
{
"name": "league/commonmark",
- "version": "2.5.1",
+ "version": "2.5.3",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c"
+ "reference": "b650144166dfa7703e62a22e493b853b58d874b0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ac815920de0eff6de947eac0a6a94e5ed0fb147c",
- "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0",
+ "reference": "b650144166dfa7703e62a22e493b853b58d874b0",
"shasum": ""
},
"require": {
@@ -2293,8 +2294,8 @@
},
"require-dev": {
"cebe/markdown": "^1.0",
- "commonmark/cmark": "0.31.0",
- "commonmark/commonmark.js": "0.31.0",
+ "commonmark/cmark": "0.31.1",
+ "commonmark/commonmark.js": "0.31.1",
"composer/package-versions-deprecated": "^1.8",
"embed/embed": "^4.4",
"erusev/parsedown": "^1.0",
@@ -2373,7 +2374,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-24T12:52:09+00:00"
+ "time": "2024-08-16T11:46:16+00:00"
},
{
"name": "league/config",
@@ -2647,16 +2648,16 @@
},
{
"name": "livewire/livewire",
- "version": "v3.5.4",
+ "version": "v3.5.6",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
- "reference": "b158c6386a892efc6c5e4682e682829baac1f933"
+ "reference": "597a2808d8d3001cc3ed5ce89a6ebab00f83b80f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/livewire/livewire/zipball/b158c6386a892efc6c5e4682e682829baac1f933",
- "reference": "b158c6386a892efc6c5e4682e682829baac1f933",
+ "url": "https://api.github.com/repos/livewire/livewire/zipball/597a2808d8d3001cc3ed5ce89a6ebab00f83b80f",
+ "reference": "597a2808d8d3001cc3ed5ce89a6ebab00f83b80f",
"shasum": ""
},
"require": {
@@ -2664,6 +2665,7 @@
"illuminate/routing": "^10.0|^11.0",
"illuminate/support": "^10.0|^11.0",
"illuminate/validation": "^10.0|^11.0",
+ "laravel/prompts": "^0.1.24",
"league/mime-type-detection": "^1.9",
"php": "^8.1",
"symfony/console": "^6.0|^7.0",
@@ -2672,7 +2674,6 @@
"require-dev": {
"calebporzio/sushi": "^2.1",
"laravel/framework": "^10.15.0|^11.0",
- "laravel/prompts": "^0.1.6",
"mockery/mockery": "^1.3.1",
"orchestra/testbench": "^8.21.0|^9.0",
"orchestra/testbench-dusk": "^8.24|^9.1",
@@ -2711,7 +2712,7 @@
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
- "source": "https://github.com/livewire/livewire/tree/v3.5.4"
+ "source": "https://github.com/livewire/livewire/tree/v3.5.6"
},
"funding": [
{
@@ -2719,7 +2720,7 @@
"type": "github"
}
],
- "time": "2024-07-15T18:27:32+00:00"
+ "time": "2024-08-19T11:52:18+00:00"
},
{
"name": "livewire/volt",
@@ -2795,16 +2796,16 @@
},
{
"name": "maatwebsite/excel",
- "version": "3.1.55",
+ "version": "3.1.56",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
- "reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260"
+ "reference": "0381d0225b42c3f328d90f0dd05ca071fca3953f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260",
- "reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260",
+ "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/0381d0225b42c3f328d90f0dd05ca071fca3953f",
+ "reference": "0381d0225b42c3f328d90f0dd05ca071fca3953f",
"shasum": ""
},
"require": {
@@ -2860,7 +2861,7 @@
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
- "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.55"
+ "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.56"
},
"funding": [
{
@@ -2872,7 +2873,7 @@
"type": "github"
}
],
- "time": "2024-02-20T08:27:10+00:00"
+ "time": "2024-08-19T09:40:43+00:00"
},
{
"name": "maennchen/zipstream-php",
@@ -3229,16 +3230,16 @@
},
{
"name": "nesbot/carbon",
- "version": "3.7.0",
+ "version": "3.8.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
- "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139"
+ "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cb4374784c87d0a0294e8513a52eb63c0aff3139",
- "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f",
+ "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f",
"shasum": ""
},
"require": {
@@ -3331,7 +3332,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-16T22:29:20+00:00"
+ "time": "2024-08-19T06:22:39+00:00"
},
{
"name": "nette/schema",
@@ -3397,20 +3398,20 @@
},
{
"name": "nette/utils",
- "version": "v4.0.4",
+ "version": "v4.0.5",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
- "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218"
+ "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218",
- "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218",
+ "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
+ "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"shasum": ""
},
"require": {
- "php": ">=8.0 <8.4"
+ "php": "8.0 - 8.4"
},
"conflict": {
"nette/finder": "<3",
@@ -3477,9 +3478,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
- "source": "https://github.com/nette/utils/tree/v4.0.4"
+ "source": "https://github.com/nette/utils/tree/v4.0.5"
},
- "time": "2024-01-17T16:50:36+00:00"
+ "time": "2024-08-07T15:39:19+00:00"
},
{
"name": "nikic/php-parser",
@@ -4300,16 +4301,16 @@
},
{
"name": "psr/log",
- "version": "3.0.0",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ "reference": "79dff0b268932c640297f5208d6298f71855c03e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
- "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e",
+ "reference": "79dff0b268932c640297f5208d6298f71855c03e",
"shasum": ""
},
"require": {
@@ -4344,9 +4345,9 @@
"psr-3"
],
"support": {
- "source": "https://github.com/php-fig/log/tree/3.0.0"
+ "source": "https://github.com/php-fig/log/tree/3.0.1"
},
- "time": "2021-07-14T16:46:02+00:00"
+ "time": "2024-08-21T13:31:24+00:00"
},
{
"name": "psr/simple-cache",
@@ -4705,16 +4706,16 @@
},
{
"name": "robsontenorio/mary",
- "version": "1.35.3",
+ "version": "1.35.6",
"source": {
"type": "git",
"url": "https://github.com/robsontenorio/mary.git",
- "reference": "5b641d64b8cd2f8602758eb0e2c6ad0ae68cb6a9"
+ "reference": "46294f8e692836109011b0264e006adb49bf2506"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/robsontenorio/mary/zipball/5b641d64b8cd2f8602758eb0e2c6ad0ae68cb6a9",
- "reference": "5b641d64b8cd2f8602758eb0e2c6ad0ae68cb6a9",
+ "url": "https://api.github.com/repos/robsontenorio/mary/zipball/46294f8e692836109011b0264e006adb49bf2506",
+ "reference": "46294f8e692836109011b0264e006adb49bf2506",
"shasum": ""
},
"require": {
@@ -4780,7 +4781,7 @@
],
"support": {
"issues": "https://github.com/robsontenorio/mary/issues",
- "source": "https://github.com/robsontenorio/mary/tree/1.35.3"
+ "source": "https://github.com/robsontenorio/mary/tree/1.35.6"
},
"funding": [
{
@@ -4788,7 +4789,62 @@
"type": "github"
}
],
- "time": "2024-07-26T15:41:31+00:00"
+ "time": "2024-08-17T21:24:32+00:00"
+ },
+ {
+ "name": "scheb/yahoo-finance-api",
+ "version": "v4.10.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/scheb/yahoo-finance-api.git",
+ "reference": "9a43debe17ce90a9aeea9c998aa1ecce2bd9f8ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/scheb/yahoo-finance-api/zipball/9a43debe17ce90a9aeea9c998aa1ecce2bd9f8ed",
+ "reference": "9a43debe17ce90a9aeea9c998aa1ecce2bd9f8ed",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "php": ">=7.1.3"
+ },
+ "require-dev": {
+ "escapestudios/symfony2-coding-standard": "^3.9",
+ "phpunit/phpunit": "^7.5 || ^8 || ^9",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^3.11|^4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Scheb\\YahooFinanceApi\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Scheb",
+ "email": "me@christianscheb.de"
+ }
+ ],
+ "description": "PHP library for accessing Yahoo Finance data",
+ "homepage": "https://github.com/scheb/yahoo-finance-api",
+ "keywords": [
+ "api",
+ "finance",
+ "stock",
+ "yahoo"
+ ],
+ "support": {
+ "issues": "https://github.com/scheb/yahoo-finance-api/issues",
+ "source": "https://github.com/scheb/yahoo-finance-api/tree/v4.10.1"
+ },
+ "time": "2024-06-09T11:50:56+00:00"
},
{
"name": "staudenmeir/eloquent-has-many-deep",
@@ -7656,16 +7712,16 @@
},
{
"name": "laravel/pint",
- "version": "v1.17.0",
+ "version": "v1.17.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5"
+ "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
- "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/e8a88130a25e3f9d4d5785e6a1afca98268ab110",
+ "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110",
"shasum": ""
},
"require": {
@@ -7676,13 +7732,13 @@
"php": "^8.1.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.59.3",
- "illuminate/view": "^10.48.12",
- "larastan/larastan": "^2.9.7",
+ "friendsofphp/php-cs-fixer": "^3.61.1",
+ "illuminate/view": "^10.48.18",
+ "larastan/larastan": "^2.9.8",
"laravel-zero/framework": "^10.4.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^1.15.1",
- "pestphp/pest": "^2.34.8"
+ "pestphp/pest": "^2.35.0"
},
"bin": [
"builds/pint"
@@ -7718,20 +7774,20 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2024-07-23T16:40:20+00:00"
+ "time": "2024-08-06T15:11:54+00:00"
},
{
"name": "laravel/sail",
- "version": "v1.31.0",
+ "version": "v1.31.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/sail.git",
- "reference": "48d89608a3bb5be763c9bb87121d31e7da27c1cb"
+ "reference": "3d06dd18cee8059baa7b388af00ba47f6d96bd85"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/sail/zipball/48d89608a3bb5be763c9bb87121d31e7da27c1cb",
- "reference": "48d89608a3bb5be763c9bb87121d31e7da27c1cb",
+ "url": "https://api.github.com/repos/laravel/sail/zipball/3d06dd18cee8059baa7b388af00ba47f6d96bd85",
+ "reference": "3d06dd18cee8059baa7b388af00ba47f6d96bd85",
"shasum": ""
},
"require": {
@@ -7781,7 +7837,7 @@
"issues": "https://github.com/laravel/sail/issues",
"source": "https://github.com/laravel/sail"
},
- "time": "2024-07-22T14:36:50+00:00"
+ "time": "2024-08-02T07:45:47+00:00"
},
{
"name": "mockery/mockery",
@@ -7928,23 +7984,23 @@
},
{
"name": "nunomaduro/collision",
- "version": "v8.3.0",
+ "version": "v8.4.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
- "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229"
+ "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b49f5b2891ce52726adfd162841c69d4e4c84229",
- "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229",
+ "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a",
+ "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a",
"shasum": ""
},
"require": {
"filp/whoops": "^2.15.4",
"nunomaduro/termwind": "^2.0.1",
"php": "^8.2.0",
- "symfony/console": "^7.1.2"
+ "symfony/console": "^7.1.3"
},
"conflict": {
"laravel/framework": "<11.0.0 || >=12.0.0",
@@ -7952,13 +8008,13 @@
},
"require-dev": {
"larastan/larastan": "^2.9.8",
- "laravel/framework": "^11.16.0",
- "laravel/pint": "^1.16.2",
- "laravel/sail": "^1.30.2",
+ "laravel/framework": "^11.19.0",
+ "laravel/pint": "^1.17.1",
+ "laravel/sail": "^1.31.0",
"laravel/sanctum": "^4.0.2",
"laravel/tinker": "^2.9.0",
- "orchestra/testbench-core": "^9.2.1",
- "pestphp/pest": "^2.34.9 || ^3.0.0",
+ "orchestra/testbench-core": "^9.2.3",
+ "pestphp/pest": "^2.35.0 || ^3.0.0",
"sebastian/environment": "^6.1.0 || ^7.0.0"
},
"type": "library",
@@ -8021,7 +8077,7 @@
"type": "patreon"
}
],
- "time": "2024-07-16T22:41:01+00:00"
+ "time": "2024-08-03T15:32:23+00:00"
},
{
"name": "phar-io/manifest",
@@ -8143,32 +8199,32 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "11.0.5",
+ "version": "11.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861"
+ "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861",
- "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
+ "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^5.0",
+ "nikic/php-parser": "^5.1.0",
"php": ">=8.2",
- "phpunit/php-file-iterator": "^5.0",
- "phpunit/php-text-template": "^4.0",
- "sebastian/code-unit-reverse-lookup": "^4.0",
- "sebastian/complexity": "^4.0",
- "sebastian/environment": "^7.0",
- "sebastian/lines-of-code": "^3.0",
- "sebastian/version": "^5.0",
- "theseer/tokenizer": "^1.2.0"
+ "phpunit/php-file-iterator": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "sebastian/code-unit-reverse-lookup": "^4.0.1",
+ "sebastian/complexity": "^4.0.1",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/lines-of-code": "^3.0.1",
+ "sebastian/version": "^5.0.1",
+ "theseer/tokenizer": "^1.2.3"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
@@ -8180,7 +8236,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "11.0-dev"
+ "dev-main": "11.0.x-dev"
}
},
"autoload": {
@@ -8209,7 +8265,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6"
},
"funding": [
{
@@ -8217,7 +8273,7 @@
"type": "github"
}
],
- "time": "2024-07-03T05:05:37+00:00"
+ "time": "2024-08-22T04:37:56+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -8466,16 +8522,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "11.2.8",
+ "version": "11.3.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39"
+ "reference": "fe179875ef0c14e90b75617002767eae0a742641"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7a29e8d3113806f18f99d670f580a30e8ffff39",
- "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fe179875ef0c14e90b75617002767eae0a742641",
+ "reference": "fe179875ef0c14e90b75617002767eae0a742641",
"shasum": ""
},
"require": {
@@ -8496,7 +8552,7 @@
"phpunit/php-timer": "^7.0.1",
"sebastian/cli-parser": "^3.0.2",
"sebastian/code-unit": "^3.0.1",
- "sebastian/comparator": "^6.0.1",
+ "sebastian/comparator": "^6.0.2",
"sebastian/diff": "^6.0.2",
"sebastian/environment": "^7.2.0",
"sebastian/exporter": "^6.1.3",
@@ -8514,7 +8570,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "11.2-dev"
+ "dev-main": "11.3-dev"
}
},
"autoload": {
@@ -8546,7 +8602,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.8"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.3.1"
},
"funding": [
{
@@ -8562,7 +8618,7 @@
"type": "tidelift"
}
],
- "time": "2024-07-18T14:56:37+00:00"
+ "time": "2024-08-13T06:14:23+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -8736,16 +8792,16 @@
},
{
"name": "sebastian/comparator",
- "version": "6.0.1",
+ "version": "6.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "131942b86d3587291067a94f295498ab6ac79c20"
+ "reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/131942b86d3587291067a94f295498ab6ac79c20",
- "reference": "131942b86d3587291067a94f295498ab6ac79c20",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/450d8f237bd611c45b5acf0733ce43e6bb280f81",
+ "reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81",
"shasum": ""
},
"require": {
@@ -8801,7 +8857,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.1"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.2"
},
"funding": [
{
@@ -8809,7 +8865,7 @@
"type": "github"
}
],
- "time": "2024-07-03T04:48:07+00:00"
+ "time": "2024-08-12T06:07:25+00:00"
},
{
"name": "sebastian/complexity",
diff --git a/config/market_data.php b/config/market_data.php
new file mode 100644
index 0000000..528ccb5
--- /dev/null
+++ b/config/market_data.php
@@ -0,0 +1,11 @@
+ 30, // minutes
+
+ 'default' => env('MARKET_DATA_PROVIDER', 'yahoo'),
+
+ 'yahoo' => App\Interfaces\MarketData\YahooMarketData::class,
+ // 'fake' => App\Interfaces\MarketData\FakeMarketData::class,
+];
\ No newline at end of file
diff --git a/database/migrations/2021_02_25_041221_create_market_data_table.php b/database/migrations/2021_02_25_041221_create_market_data_table.php
index 7e493c7..12ca625 100644
--- a/database/migrations/2021_02_25_041221_create_market_data_table.php
+++ b/database/migrations/2021_02_25_041221_create_market_data_table.php
@@ -21,6 +21,11 @@ class CreateMarketDataTable extends Migration
$table->float('fifty_two_week_high', 12, 4)->nullable();
$table->timestamp('last_dividend_date')->nullable();
$table->float('last_dividend_amount', 12, 4)->nullable();
+ $table->unsignedBigInteger('market_cap')->nullable();
+ $table->float('trailing_pe', 12, 4)->nullable();
+ $table->float('forward_pe', 12, 4)->nullable();
+ $table->float('pe_growth', 12, 4)->nullable();
+ $table->json('meta_data')->nullable();
$table->timestamps();
});
}
diff --git a/database/seeders/MarketDataSeeder.php b/database/seeders/MarketDataSeeder.php
new file mode 100644
index 0000000..2b5a443
--- /dev/null
+++ b/database/seeders/MarketDataSeeder.php
@@ -0,0 +1,81 @@
+ $data['symbol'],
+ 'name' => $data['name'],
+ 'meta_data' => json_encode([
+ 'country' => $data['country'],
+ 'first_trade_year' => $data['first_trade_year'],
+ 'sector' => $data['sector'],
+ 'industry' => $data['industry'],
+ ]),
+ ];
+
+ $rowCount++;
+
+ if ($rowCount % $chunkSize == 0) {
+ DB::table('market_data')->insertOrIgnore($rows);
+ $rows = [];
+ }
+ } catch (\Throwable $e) {
+
+ throw new \Exception('Error: '. $e->getMessage());
+ }
+ }
+ }
+
+ // final clean up
+ if (!empty($rows)) {
+ DB::table('market_data')->insertOrIgnore($rows);
+ }
+
+ // Close the CSV file
+ fclose($handle);
+
+ echo "Imported $rowCount items successfully!\n";
+
+ } else {
+
+ echo "Failed to open the CSV.\n";
+ }
+ }
+}
diff --git a/resources/views/livewire/manage-transaction-form.blade.php b/resources/views/livewire/manage-transaction-form.blade.php
index cd82566..129f2ff 100644
--- a/resources/views/livewire/manage-transaction-form.blade.php
+++ b/resources/views/livewire/manage-transaction-form.blade.php
@@ -2,11 +2,11 @@
use App\Models\Transaction;
use App\Models\Portfolio;
+use App\Rules\SymbolValidationRule;
use Illuminate\Support\Collection;
-use Livewire\Attributes\Rule;
+use Livewire\Attributes\{Computed};
use Livewire\Volt\Component;
use Mary\Traits\Toast;
-use Livewire\Attributes\Computed;
new class extends Component {
use Toast;
@@ -15,27 +15,29 @@ new class extends Component {
public ?Portfolio $portfolio;
public ?Transaction $transaction;
- #[Rule('required|string|max:15')]
public String $symbol;
-
- #[Rule('required|string|in:BUY,SELL')]
public String $transaction_type;
-
- #[Rule('required|date_format:Y-m-d')]
public String $date;
-
- #[Rule('required|min:0|numeric')]
public Float $quantity;
-
- #[Rule('exclude_if:transaction_type,SELL|min:0|numeric')]
public ?Float $cost_basis;
-
- #[Rule('exclude_if:transaction_type,BUY|min:0|numeric')]
public ?Float $sale_price;
public Bool $confirmingTransactionDeletion = false;
// methods
+ public function rules()
+ {
+
+ return [
+ 'symbol' => ['required', 'string', new SymbolValidationRule],
+ 'transaction_type' => 'required|string|in:BUY,SELL',
+ 'date' => 'required|date_format:Y-m-d',
+ 'quantity' => 'required|min:0|numeric',
+ 'cost_basis' => 'exclude_if:transaction_type,SELL|min:0|numeric',
+ 'sale_price' => 'exclude_if:transaction_type,BUY|min:0|numeric',
+ ];
+ }
+
public function mount()
{
if (isset($this->transaction)) {
@@ -128,7 +130,13 @@ new class extends Component {
/>
@endif
-