Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4b7d399ea | |||
| ffe53e91c0 | |||
| aeb1b12afe | |||
| fe81ec7ee7 | |||
| f0ecc0fd3d | |||
| 03b75fb683 | |||
| dc93621547 | |||
| 7ab6f79e56 | |||
| 9e48f21c8d | |||
| 10e6de8df4 | |||
| 00fbdec6f1 | |||
| 730903c383 | |||
| 5fc9455908 | |||
| 28e0ad68fc | |||
| ca48d702a7 | |||
| 812b9ed075 | |||
| 93a0595652 | |||
| 8a357e8cab | |||
| 22e12977f8 | |||
| 732cf02317 | |||
| 6dea75651b | |||
| 6cff252813 | |||
| 0d06ca6a04 | |||
| a3f875270b |
+4
-21
@@ -40,13 +40,10 @@ LINKEDIN_CLIENT_SECRET=
|
|||||||
FACEBOOK_CLIENT_ID=
|
FACEBOOK_CLIENT_ID=
|
||||||
FACEBOOK_CLIENT_SECRET=
|
FACEBOOK_CLIENT_SECRET=
|
||||||
|
|
||||||
APP_NAME=Investbrain
|
FILESYSTEM_DISK=local
|
||||||
APP_TIMEZONE=UTC
|
SESSION_DRIVER=redis
|
||||||
APP_ENV=production
|
QUEUE_CONNECTION=redis
|
||||||
APP_DEBUG=true
|
CACHE_STORE=redis
|
||||||
APP_LOCALE=en
|
|
||||||
APP_FALLBACK_LOCALE=en
|
|
||||||
SELF_HOSTED=true
|
|
||||||
|
|
||||||
DB_CONNECTION=mysql
|
DB_CONNECTION=mysql
|
||||||
DB_HOST=investbrain-mysql
|
DB_HOST=investbrain-mysql
|
||||||
@@ -55,19 +52,7 @@ DB_DATABASE=investbrain
|
|||||||
DB_USERNAME=investbrain
|
DB_USERNAME=investbrain
|
||||||
DB_PASSWORD=investbrain
|
DB_PASSWORD=investbrain
|
||||||
|
|
||||||
SESSION_DRIVER=redis
|
|
||||||
SESSION_LIFETIME=120
|
|
||||||
SESSION_ENCRYPT=false
|
|
||||||
SESSION_PATH=/
|
|
||||||
SESSION_DOMAIN=null
|
|
||||||
|
|
||||||
FILESYSTEM_DISK=local
|
|
||||||
QUEUE_CONNECTION=redis
|
|
||||||
|
|
||||||
CACHE_STORE=redis
|
|
||||||
|
|
||||||
REDIS_HOST=investbrain-redis
|
REDIS_HOST=investbrain-redis
|
||||||
REDIS_PATH=/tmp/database_server.sock
|
|
||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
@@ -85,5 +70,3 @@ AWS_SECRET_ACCESS_KEY=
|
|||||||
AWS_DEFAULT_REGION=us-east-1
|
AWS_DEFAULT_REGION=us-east-1
|
||||||
AWS_BUCKET=
|
AWS_BUCKET=
|
||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
|
||||||
|
|||||||
+2
-1
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 1.0.x | :white_check_mark: |
|
| 1.1.x | :white_check_mark: |
|
||||||
|
| 1.0.x | :x: |
|
||||||
| < 1.0.0 | :x: |
|
| < 1.0.0 | :x: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ use Illuminate\Support\Carbon;
|
|||||||
|
|
||||||
class Quote extends MarketDataType
|
class Quote extends MarketDataType
|
||||||
{
|
{
|
||||||
public function setName(string $name): self
|
public function setName($name): self
|
||||||
{
|
{
|
||||||
$this->items['name'] = (string) $name;
|
if (! empty($name)) {
|
||||||
|
$this->items['name'] = (string) $name;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-14
@@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Dividend extends Model
|
class Dividend extends Model
|
||||||
@@ -109,22 +110,28 @@ class Dividend extends Model
|
|||||||
public static function syncHoldings(string $symbol): void
|
public static function syncHoldings(string $symbol): void
|
||||||
{
|
{
|
||||||
// group by holdings
|
// group by holdings
|
||||||
$dividends = self::select(['holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount'])
|
$subQuery = self::select([
|
||||||
->selectRaw('
|
'holdings.portfolio_id',
|
||||||
(COALESCE(CASE WHEN transactions.transaction_type = "BUY"
|
'dividends.date',
|
||||||
AND date(transactions.date) <= date(dividends.date)
|
'dividends.symbol',
|
||||||
THEN transactions.quantity ELSE 0 END, 0)
|
'dividends.dividend_amount',
|
||||||
- COALESCE(CASE WHEN transactions.transaction_type = "SELL"
|
])->selectRaw("
|
||||||
AND date(transactions.date) <= date(dividends.date)
|
(COALESCE(SUM(CASE WHEN transactions.transaction_type = 'BUY'
|
||||||
THEN transactions.quantity ELSE 0 END, 0))
|
AND date(transactions.date) <= date(dividends.date)
|
||||||
* dividends.dividend_amount
|
THEN transactions.quantity ELSE 0 END), 0)
|
||||||
AS total_received
|
- COALESCE(SUM(CASE WHEN transactions.transaction_type = 'SELL'
|
||||||
')
|
AND date(transactions.date) <= date(dividends.date)
|
||||||
->join('transactions', 'transactions.symbol', '=', 'dividends.symbol')
|
THEN transactions.quantity ELSE 0 END), 0))
|
||||||
|
* dividends.dividend_amount
|
||||||
|
AS total_received
|
||||||
|
")->join('transactions', 'transactions.symbol', '=', 'dividends.symbol')
|
||||||
->join('holdings', 'transactions.portfolio_id', '=', 'holdings.portfolio_id')
|
->join('holdings', 'transactions.portfolio_id', '=', 'holdings.portfolio_id')
|
||||||
->where('dividends.symbol', $symbol)
|
->where('dividends.symbol', $symbol)
|
||||||
->groupBy('holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount', 'total_received')
|
->groupBy('holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount');
|
||||||
->havingRaw('total_received > 0')
|
|
||||||
|
$dividends = DB::table(DB::raw("({$subQuery->toSql()}) as sub"))
|
||||||
|
->mergeBindings($subQuery->getQuery())
|
||||||
|
->where('total_received', '>', 0)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
// iterate through holdings and update
|
// iterate through holdings and update
|
||||||
|
|||||||
+102
-50
@@ -7,6 +7,7 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class Holding extends Model
|
class Holding extends Model
|
||||||
@@ -62,8 +63,8 @@ class Holding extends Model
|
|||||||
return $this->hasMany(Dividend::class, 'symbol', 'symbol')
|
return $this->hasMany(Dividend::class, 'symbol', 'symbol')
|
||||||
->select(['dividends.symbol', 'dividends.date', 'dividends.dividend_amount'])
|
->select(['dividends.symbol', 'dividends.date', 'dividends.dividend_amount'])
|
||||||
->selectRaw("SUM(
|
->selectRaw("SUM(
|
||||||
CASE WHEN transaction_type = 'BUY'
|
CASE WHEN transaction_type = 'BUY'
|
||||||
AND transactions.symbol = dividends.symbol
|
AND transactions.symbol = dividends.symbol
|
||||||
AND transactions.portfolio_id = '$this->portfolio_id'
|
AND transactions.portfolio_id = '$this->portfolio_id'
|
||||||
AND date(dividends.date) >= date(transactions.date)
|
AND date(dividends.date) >= date(transactions.date)
|
||||||
THEN transactions.quantity
|
THEN transactions.quantity
|
||||||
@@ -71,22 +72,22 @@ class Holding extends Model
|
|||||||
) AS purchased")
|
) AS purchased")
|
||||||
->selectRaw("SUM(
|
->selectRaw("SUM(
|
||||||
CASE WHEN transaction_type = 'SELL'
|
CASE WHEN transaction_type = 'SELL'
|
||||||
AND transactions.symbol = dividends.symbol
|
AND transactions.symbol = dividends.symbol
|
||||||
AND transactions.portfolio_id = '$this->portfolio_id'
|
AND transactions.portfolio_id = '$this->portfolio_id'
|
||||||
AND date(dividends.date) >= date(transactions.date)
|
AND date(dividends.date) >= date(transactions.date)
|
||||||
THEN transactions.quantity
|
THEN transactions.quantity
|
||||||
ELSE 0 END
|
ELSE 0 END
|
||||||
) AS sold")
|
) AS sold")
|
||||||
->selectRaw("SUM(
|
->selectRaw("SUM(
|
||||||
(CASE WHEN transaction_type = 'BUY'
|
(CASE WHEN transaction_type = 'BUY'
|
||||||
AND transactions.symbol = dividends.symbol
|
AND transactions.symbol = dividends.symbol
|
||||||
AND transactions.portfolio_id = '$this->portfolio_id'
|
AND transactions.portfolio_id = '$this->portfolio_id'
|
||||||
AND date(transactions.date) <= date(dividends.date)
|
AND date(transactions.date) <= date(dividends.date)
|
||||||
THEN transactions.quantity ELSE 0 END
|
THEN transactions.quantity ELSE 0 END
|
||||||
- CASE WHEN transaction_type = 'SELL'
|
- CASE WHEN transaction_type = 'SELL'
|
||||||
AND transactions.symbol = dividends.symbol
|
AND transactions.symbol = dividends.symbol
|
||||||
AND transactions.portfolio_id = '$this->portfolio_id'
|
AND transactions.portfolio_id = '$this->portfolio_id'
|
||||||
AND date(transactions.date) <= date(dividends.date)
|
AND date(transactions.date) <= date(dividends.date)
|
||||||
THEN transactions.quantity ELSE 0 END)
|
THEN transactions.quantity ELSE 0 END)
|
||||||
* dividends.dividend_amount
|
* dividends.dividend_amount
|
||||||
) AS total_received")
|
) AS total_received")
|
||||||
@@ -99,7 +100,25 @@ class Holding extends Model
|
|||||||
->whereRaw("transactions.portfolio_id = '$this->portfolio_id'")
|
->whereRaw("transactions.portfolio_id = '$this->portfolio_id'")
|
||||||
->whereRaw("transactions.symbol = '$this->symbol'");
|
->whereRaw("transactions.symbol = '$this->symbol'");
|
||||||
})
|
})
|
||||||
->having('total_received', '>', 0);
|
->havingRaw("SUM(
|
||||||
|
(CASE
|
||||||
|
WHEN transaction_type = 'BUY'
|
||||||
|
AND transactions.symbol = dividends.symbol
|
||||||
|
AND transactions.portfolio_id = '$this->portfolio_id'
|
||||||
|
AND transactions.date <= dividends.date
|
||||||
|
THEN transactions.quantity
|
||||||
|
ELSE 0
|
||||||
|
END)
|
||||||
|
-
|
||||||
|
(CASE
|
||||||
|
WHEN transaction_type = 'SELL'
|
||||||
|
AND transactions.symbol = dividends.symbol
|
||||||
|
AND transactions.portfolio_id = '$this->portfolio_id'
|
||||||
|
AND transactions.date <= dividends.date
|
||||||
|
THEN transactions.quantity
|
||||||
|
ELSE 0
|
||||||
|
END)
|
||||||
|
) * dividends.dividend_amount > 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,7 +166,7 @@ class Holding extends Model
|
|||||||
{
|
{
|
||||||
return $query->selectRaw('COALESCE(market_data.market_value * holdings.quantity, 0) AS total_market_value')
|
return $query->selectRaw('COALESCE(market_data.market_value * holdings.quantity, 0) AS total_market_value')
|
||||||
->selectRaw('COALESCE((market_data.market_value - holdings.average_cost_basis) * holdings.quantity, 0) AS market_gain_dollars')
|
->selectRaw('COALESCE((market_data.market_value - holdings.average_cost_basis) * holdings.quantity, 0) AS market_gain_dollars')
|
||||||
->selectRaw('COALESCE(((market_data.market_value - holdings.average_cost_basis) / holdings.average_cost_basis) * 100, 0) AS market_gain_percent');
|
->selectRaw('COALESCE(((market_data.market_value - holdings.average_cost_basis) / NULLIF(holdings.average_cost_basis, 0)) * 100, 0) AS market_gain_percent');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopePortfolio($query, $portfolio)
|
public function scopePortfolio($query, $portfolio)
|
||||||
@@ -191,10 +210,10 @@ class Holding extends Model
|
|||||||
$query = Transaction::where([
|
$query = Transaction::where([
|
||||||
'portfolio_id' => $this->portfolio_id,
|
'portfolio_id' => $this->portfolio_id,
|
||||||
'symbol' => $this->symbol,
|
'symbol' => $this->symbol,
|
||||||
])->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN quantity ELSE 0 END) AS `qty_purchases`')
|
])->selectRaw("SUM(CASE WHEN transaction_type = 'BUY' THEN quantity ELSE 0 END) AS qty_purchases")
|
||||||
->selectRaw('SUM(CASE WHEN transaction_type = "SELL" THEN quantity ELSE 0 END) AS `qty_sales`')
|
->selectRaw("SUM(CASE WHEN transaction_type = 'SELL' THEN quantity ELSE 0 END) AS qty_sales")
|
||||||
->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN (quantity * cost_basis) ELSE 0 END) AS `total_cost_basis`')
|
->selectRaw("SUM(CASE WHEN transaction_type = 'BUY' THEN (quantity * cost_basis) ELSE 0 END) AS total_cost_basis")
|
||||||
->selectRaw('SUM(CASE WHEN transaction_type = "SELL" THEN (quantity * sale_price) ELSE 0 END) AS `total_sale_price`')
|
->selectRaw("SUM(CASE WHEN transaction_type = 'SELL' THEN (quantity * sale_price) ELSE 0 END) AS total_sale_price")
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
$total_quantity = round($query->qty_purchases - $query->qty_sales, 3);
|
$total_quantity = round($query->qty_purchases - $query->qty_sales, 3);
|
||||||
@@ -202,9 +221,8 @@ class Holding extends Model
|
|||||||
$average_cost_basis = (
|
$average_cost_basis = (
|
||||||
$query->qty_purchases > 0
|
$query->qty_purchases > 0
|
||||||
&& $total_quantity > 0
|
&& $total_quantity > 0
|
||||||
)
|
) ? $query->total_cost_basis / $query->qty_purchases
|
||||||
? $query->total_cost_basis / $query->qty_purchases
|
: 0;
|
||||||
: 0;
|
|
||||||
|
|
||||||
// update holding
|
// update holding
|
||||||
$this->fill([
|
$this->fill([
|
||||||
@@ -246,49 +264,83 @@ class Holding extends Model
|
|||||||
$end_date = now();
|
$end_date = now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MySQL default interval
|
||||||
$date_interval = 'DATE_ADD(date, INTERVAL 1 DAY)';
|
$date_interval = 'DATE_ADD(date, INTERVAL 1 DAY)';
|
||||||
|
$castNumberType = 'decimal';
|
||||||
|
|
||||||
|
// Use SQLite interval grammar
|
||||||
if (config('database.default') === 'sqlite') {
|
if (config('database.default') === 'sqlite') {
|
||||||
|
|
||||||
$date_interval = "date(date, '+1 day')";
|
$date_interval = "date(date, '+1 day')";
|
||||||
} else {
|
|
||||||
|
|
||||||
DB::statement('SET cte_max_recursion_depth=1000000;');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DB::table(DB::raw("(
|
// Default CTE time series query (for MySQL and SQLite)
|
||||||
WITH RECURSIVE date_series AS (
|
$timeSeriesQuery = DB::table(DB::raw("(
|
||||||
SELECT '{$start_date->format('Y-m-d')}' AS date
|
WITH RECURSIVE date_series AS (
|
||||||
UNION ALL
|
SELECT '{$start_date->format('Y-m-d')}' AS date
|
||||||
SELECT $date_interval
|
UNION ALL
|
||||||
FROM date_series
|
SELECT $date_interval
|
||||||
WHERE date < '{$end_date->format('Y-m-d')}'
|
|
||||||
)
|
|
||||||
SELECT date_series.date
|
|
||||||
FROM date_series
|
FROM date_series
|
||||||
) as date_series")
|
WHERE date < '{$end_date->format('Y-m-d')}'
|
||||||
)
|
)
|
||||||
|
SELECT date_series.date
|
||||||
|
FROM date_series
|
||||||
|
) as date_series"));
|
||||||
|
|
||||||
|
// PGSql time series query
|
||||||
|
if (config('database.default') === 'pgsql') {
|
||||||
|
|
||||||
|
$timeSeriesQuery = DB::table(DB::raw("
|
||||||
|
generate_series(
|
||||||
|
date '{$start_date->format('Y-m-d')}',
|
||||||
|
date '{$end_date->format('Y-m-d')}',
|
||||||
|
interval '1 day'
|
||||||
|
) as date_series"));
|
||||||
|
|
||||||
|
$castNumberType = 'numeric';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set MySQL-like query CTE max iterations
|
||||||
|
if (config('database.default') === 'mysql') {
|
||||||
|
|
||||||
|
// MySQL default
|
||||||
|
$max_recursion_var_name = 'cte_max_recursion_depth';
|
||||||
|
|
||||||
|
// Determine if running MySQL or MariaDB
|
||||||
|
$versionString = Arr::get(
|
||||||
|
DB::select('SELECT VERSION() as version;'),
|
||||||
|
'0', new \stdClass
|
||||||
|
)->version;
|
||||||
|
if (stripos($versionString, 'MariaDB') !== false) {
|
||||||
|
$max_recursion_var_name = 'max_recursive_iterations'; // Must be MariaDB
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::statement("SET $max_recursion_var_name=1000000;");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracted query for counting QTY owned
|
||||||
|
$quantityQuery = "ROUND(CAST(COALESCE(
|
||||||
|
SUM(CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity ELSE 0 END)
|
||||||
|
- SUM(CASE WHEN transactions.transaction_type = 'SELL' THEN transactions.quantity ELSE 0 END),
|
||||||
|
0
|
||||||
|
) AS {$castNumberType}), 3)";
|
||||||
|
|
||||||
|
return $timeSeriesQuery
|
||||||
->select([
|
->select([
|
||||||
'date_series.date',
|
'date_series.date',
|
||||||
DB::raw("
|
DB::raw("
|
||||||
ROUND(
|
{$quantityQuery} AS owned
|
||||||
COALESCE(SUM(CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity ELSE 0 END), 0) -
|
"),
|
||||||
COALESCE(SUM(CASE WHEN transactions.transaction_type = 'SELL' THEN transactions.quantity ELSE 0 END), 0), 3) AS `owned`
|
|
||||||
"),
|
|
||||||
DB::raw("
|
DB::raw("
|
||||||
COALESCE(CASE
|
CASE
|
||||||
WHEN (
|
WHEN ({$quantityQuery}) = 0 THEN 0
|
||||||
ROUND(
|
ELSE SUM(CASE
|
||||||
COALESCE(SUM(CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity ELSE 0 END), 0) -
|
WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity * transactions.cost_basis
|
||||||
COALESCE(SUM(CASE WHEN transactions.transaction_type = 'SELL' THEN transactions.quantity ELSE 0 END), 0), 3)
|
ELSE 0
|
||||||
) = 0 THEN 0
|
END)
|
||||||
ELSE SUM(CASE
|
END AS cost_basis
|
||||||
WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity * transactions.cost_basis
|
"),
|
||||||
ELSE 0
|
DB::raw("COALESCE(SUM(CASE WHEN transaction_type = 'SELL' THEN ((sale_price - cost_basis) * quantity) ELSE 0 END), 0) AS realized_gains"),
|
||||||
END)
|
|
||||||
END, 0) AS cost_basis
|
|
||||||
"),
|
|
||||||
DB::raw("COALESCE(SUM(CASE WHEN transaction_type = 'SELL' THEN ((sale_price - cost_basis) * quantity) ELSE 0 END), 0) AS `realized_gains`"),
|
|
||||||
])
|
])
|
||||||
->leftJoin('transactions', function ($join) {
|
->leftJoin('transactions', function ($join) {
|
||||||
$join->on(DB::raw('DATE(transactions.date)'), '<=', 'date_series.date')
|
$join->on(DB::raw('DATE(transactions.date)'), '<=', 'date_series.date')
|
||||||
|
|||||||
@@ -211,6 +211,11 @@ class Portfolio extends Model
|
|||||||
if (! empty($total_performance)) {
|
if (! empty($total_performance)) {
|
||||||
DB::transaction(function () use ($total_performance) {
|
DB::transaction(function () use ($total_performance) {
|
||||||
|
|
||||||
|
// delete old history
|
||||||
|
$firstDate = array_keys($total_performance)[0];
|
||||||
|
$this->daily_change()->where('date', '<', $firstDate)->delete();
|
||||||
|
|
||||||
|
// upsert new history
|
||||||
$this->daily_change()->upsert(
|
$this->daily_change()->upsert(
|
||||||
$total_performance,
|
$total_performance,
|
||||||
['date', 'portfolio_id'],
|
['date', 'portfolio_id'],
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class Split extends Model
|
|||||||
->where([
|
->where([
|
||||||
'splits.symbol' => $symbol,
|
'splits.symbol' => $symbol,
|
||||||
])
|
])
|
||||||
->whereDate('splits.date', '>', DB::raw('IFNULL(holdings.splits_synced_at, "0000-00-00")'))
|
->whereDate('splits.date', '>', DB::raw("COALESCE(holdings.splits_synced_at, '1901-01-01')"))
|
||||||
->where('holdings.quantity', '>', 0)
|
->where('holdings.quantity', '>', 0)
|
||||||
->join('holdings', 'splits.symbol', 'holdings.symbol')
|
->join('holdings', 'splits.symbol', 'holdings.symbol')
|
||||||
->orderBy('splits.date', 'ASC')
|
->orderBy('splits.date', 'ASC')
|
||||||
@@ -115,8 +115,8 @@ class Split extends Model
|
|||||||
'portfolio_id' => $split->portfolio_id,
|
'portfolio_id' => $split->portfolio_id,
|
||||||
])
|
])
|
||||||
->whereDate('transactions.date', '<', $split->date->format('Y-m-d'))
|
->whereDate('transactions.date', '<', $split->date->format('Y-m-d'))
|
||||||
->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN quantity ELSE 0 END) -
|
->selectRaw("SUM(CASE WHEN transaction_type = 'BUY' THEN quantity ELSE 0 END) -
|
||||||
SUM(CASE WHEN transaction_type = "SELL" THEN quantity ELSE 0 END) AS qty_owned')
|
SUM(CASE WHEN transaction_type = 'SELL' THEN quantity ELSE 0 END) AS qty_owned")
|
||||||
->value('qty_owned');
|
->value('qty_owned');
|
||||||
|
|
||||||
if ($qty_owned > 0) {
|
if ($qty_owned > 0) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Spotlight
|
|||||||
}
|
}
|
||||||
|
|
||||||
$portfolios = $request->user()->portfolios()
|
$portfolios = $request->user()->portfolios()
|
||||||
->where('title', 'LIKE', '%'.$request->input('search').'%')
|
->whereFullText('title', $request->input('search'))
|
||||||
->limit(5)
|
->limit(5)
|
||||||
->get();
|
->get();
|
||||||
$portfolios->each(function ($portfolio) use ($results) {
|
$portfolios->each(function ($portfolio) use ($results) {
|
||||||
@@ -35,8 +35,8 @@ class Spotlight
|
|||||||
$holdings = $request->user()->holdings()
|
$holdings = $request->user()->holdings()
|
||||||
->where('holdings.quantity', '>', 0)
|
->where('holdings.quantity', '>', 0)
|
||||||
->where(function ($query) use ($request) {
|
->where(function ($query) use ($request) {
|
||||||
return $query->where('holdings.symbol', 'LIKE', '%'.$request->input('search').'%')
|
return $query->whereFullText('holdings.symbol', $request->input('search'))
|
||||||
->orWhere('market_data.name', 'LIKE', '%'.$request->input('search').'%');
|
->orWhereFullText('market_data.name', $request->input('search'));
|
||||||
})
|
})
|
||||||
->limit(5)
|
->limit(5)
|
||||||
->get();
|
->get();
|
||||||
|
|||||||
Generated
+261
-275
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -100,7 +100,8 @@ return [
|
|||||||
|
|
||||||
'cipher' => 'AES-256-CBC',
|
'cipher' => 'AES-256-CBC',
|
||||||
|
|
||||||
'key' => env('APP_KEY'),
|
'key' => env('APP_KEY')
|
||||||
|
?: when(file_exists(storage_path('app/.key')), fn () => trim(file_get_contents(storage_path('app/.key')))),
|
||||||
|
|
||||||
'previous_keys' => [
|
'previous_keys' => [
|
||||||
...array_filter(
|
...array_filter(
|
||||||
|
|||||||
@@ -77,6 +77,6 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'profile_photo_disk' => 'public',
|
'profile_photo_disk' => env('JETSTREAM_PROFILE_PHOTO_DISK', 'public'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
+1
-1
@@ -116,7 +116,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'inject_assets' => true,
|
'inject_assets' => false,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|---------------------------------------------------------------------------
|
|---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ return [
|
|||||||
'processors' => [PsrLogMessageProcessor::class],
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'sentry' => [
|
||||||
|
'driver' => 'sentry',
|
||||||
|
'level' => env('LOG_LEVEL', 'error'),
|
||||||
|
],
|
||||||
|
|
||||||
'stderr' => [
|
'stderr' => [
|
||||||
'driver' => 'monolog',
|
'driver' => 'monolog',
|
||||||
'level' => env('LOG_LEVEL', 'debug'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
Schema::create('portfolios', function (Blueprint $table) {
|
Schema::create('portfolios', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->uuid('id')->primary();
|
||||||
$table->string('title');
|
$table->string('title')->when(config('database.default') != 'sqlite', fn ($ctx) => $ctx->fulltext());
|
||||||
$table->text('notes')->nullable();
|
$table->text('notes')->nullable();
|
||||||
$table->boolean('wishlist')->default(false);
|
$table->boolean('wishlist')->default(false);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ class CreateMarketDataTable extends Migration
|
|||||||
public function up()
|
public function up()
|
||||||
{
|
{
|
||||||
Schema::create('market_data', function (Blueprint $table) {
|
Schema::create('market_data', function (Blueprint $table) {
|
||||||
$table->string('symbol', 15)->primary();
|
$table->string('symbol', 25)->primary();
|
||||||
$table->string('name')->nullable();
|
$table->string('name')->nullable()->when(config('database.default') != 'sqlite', fn ($ctx) => $ctx->fulltext());
|
||||||
$table->float('market_value', 12, 4)->nullable();
|
$table->float('market_value', 12, 4)->nullable();
|
||||||
$table->float('fifty_two_week_low', 12, 4)->nullable();
|
$table->float('fifty_two_week_low', 12, 4)->nullable();
|
||||||
$table->float('fifty_two_week_high', 12, 4)->nullable();
|
$table->float('fifty_two_week_high', 12, 4)->nullable();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Models\MarketData;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
@@ -19,7 +18,7 @@ class CreateDividendsTable extends Migration
|
|||||||
Schema::create('dividends', function (Blueprint $table) {
|
Schema::create('dividends', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->uuid('id')->primary();
|
||||||
$table->date('date');
|
$table->date('date');
|
||||||
$table->foreignIdFor(MarketData::class, 'symbol');
|
$table->string('symbol', 25);
|
||||||
$table->float('dividend_amount', 12, 4);
|
$table->float('dividend_amount', 12, 4);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Models\MarketData;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
@@ -19,7 +18,7 @@ class CreateSplitsTable extends Migration
|
|||||||
Schema::create('splits', function (Blueprint $table) {
|
Schema::create('splits', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->uuid('id')->primary();
|
||||||
$table->date('date');
|
$table->date('date');
|
||||||
$table->foreignIdFor(MarketData::class, 'symbol');
|
$table->string('symbol', 25);
|
||||||
$table->float('split_amount', 12, 4);
|
$table->float('split_amount', 12, 4);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Models\MarketData;
|
|
||||||
use App\Models\Portfolio;
|
use App\Models\Portfolio;
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
@@ -19,7 +18,7 @@ class CreateTransactionsTable extends Migration
|
|||||||
{
|
{
|
||||||
Schema::create('transactions', function (Blueprint $table) {
|
Schema::create('transactions', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->uuid('id')->primary();
|
||||||
$table->foreignIdFor(MarketData::class, 'symbol');
|
$table->string('symbol', 25);
|
||||||
$table->foreignIdFor(Portfolio::class, 'portfolio_id')->constrained()->onDelete('cascade');
|
$table->foreignIdFor(Portfolio::class, 'portfolio_id')->constrained()->onDelete('cascade');
|
||||||
$table->string('transaction_type', 15);
|
$table->string('transaction_type', 15);
|
||||||
$table->float('quantity', 12, 4);
|
$table->float('quantity', 12, 4);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Models\MarketData;
|
|
||||||
use App\Models\Portfolio;
|
use App\Models\Portfolio;
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
@@ -20,12 +19,13 @@ class CreateHoldingsTable extends Migration
|
|||||||
Schema::create('holdings', function (Blueprint $table) {
|
Schema::create('holdings', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->uuid('id')->primary();
|
||||||
$table->foreignIdFor(Portfolio::class, 'portfolio_id')->constrained()->onDelete('cascade');
|
$table->foreignIdFor(Portfolio::class, 'portfolio_id')->constrained()->onDelete('cascade');
|
||||||
$table->foreignIdFor(MarketData::class, 'symbol');
|
$table->string('symbol', 25)->when(config('database.default') != 'sqlite', fn ($ctx) => $ctx->fulltext());
|
||||||
$table->float('quantity', 12, 4);
|
$table->float('quantity', 12, 4);
|
||||||
$table->float('average_cost_basis', 12, 4)->default(0);
|
$table->float('average_cost_basis', 12, 4)->default(0);
|
||||||
$table->float('total_cost_basis', 12, 4)->default(0);
|
$table->float('total_cost_basis', 12, 4)->default(0);
|
||||||
$table->float('realized_gain_dollars', 12, 4)->default(0);
|
$table->float('realized_gain_dollars', 12, 4)->default(0);
|
||||||
$table->float('dividends_earned', 12, 4)->default(0);
|
$table->float('dividends_earned', 12, 4)->default(0);
|
||||||
|
$table->boolean('reinvest_dividends')->default(false);
|
||||||
$table->timestamp('splits_synced_at')->nullable();
|
$table->timestamp('splits_synced_at')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class MarketDataSeeder extends Seeder
|
|||||||
{
|
{
|
||||||
use WithoutModelEvents;
|
use WithoutModelEvents;
|
||||||
|
|
||||||
|
public array $rows = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the database seeds.
|
* Run the database seeds.
|
||||||
*/
|
*/
|
||||||
@@ -26,7 +28,6 @@ class MarketDataSeeder extends Seeder
|
|||||||
if (($handle = fopen($csvFilePath, 'r')) !== false) {
|
if (($handle = fopen($csvFilePath, 'r')) !== false) {
|
||||||
|
|
||||||
$header = null;
|
$header = null;
|
||||||
$rows = [];
|
|
||||||
$rowCount = 0;
|
$rowCount = 0;
|
||||||
|
|
||||||
while (($row = fgetcsv($handle, 0, ',')) !== false) {
|
while (($row = fgetcsv($handle, 0, ',')) !== false) {
|
||||||
@@ -38,46 +39,55 @@ class MarketDataSeeder extends Seeder
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
try {
|
$data = array_combine($header, $row);
|
||||||
$data = array_combine($header, $row);
|
|
||||||
|
|
||||||
$rows[] = [
|
$this->rows[] = [
|
||||||
'symbol' => $data['symbol'],
|
'symbol' => $data['symbol'],
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
'meta_data' => json_encode([
|
'meta_data' => json_encode([
|
||||||
'country' => $data['country'],
|
'country' => $data['country'],
|
||||||
'first_trade_year' => $data['first_trade_year'],
|
'first_trade_year' => $data['first_trade_year'],
|
||||||
'sector' => $data['sector'],
|
'sector' => $data['sector'],
|
||||||
'industry' => $data['industry'],
|
'industry' => $data['industry'],
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
$rowCount++;
|
$rowCount++;
|
||||||
|
|
||||||
if ($rowCount % $chunkSize == 0) {
|
if ($rowCount % $chunkSize == 0) {
|
||||||
DB::table('market_data')->insertOrIgnore($rows);
|
|
||||||
$rows = [];
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
|
|
||||||
throw new \Exception('Error: '.$e->getMessage());
|
$this->bulkInsert($this->rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// final clean up
|
// final clean up
|
||||||
if (! empty($rows)) {
|
if (! empty($this->rows)) {
|
||||||
DB::table('market_data')->insertOrIgnore($rows);
|
|
||||||
|
$this->bulkInsert($this->rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the CSV file
|
// Close the CSV file
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
|
||||||
echo "Imported $rowCount market data items successfully!\n";
|
echo "\n > Imported $rowCount market data items successfully!";
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
echo "Failed to open the CSV.\n";
|
echo "Failed to open the CSV.\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function bulkInsert(array $rows)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
|
||||||
|
DB::table('market_data')->insertOrIgnore($rows);
|
||||||
|
$this->rows = [];
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
|
||||||
|
throw new \Exception('Error: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,8 +241,6 @@ AKA,a.k.a. Brands Holding Corp. Common Stock,,2021,Consumer Discretionary,Catalo
|
|||||||
AKAM,Akamai Technologies Inc. Common Stock,United States,1999,Consumer Discretionary,Business Services
|
AKAM,Akamai Technologies Inc. Common Stock,United States,1999,Consumer Discretionary,Business Services
|
||||||
AKAN,Akanda Corp. Common Shares,United Kingdom,2022,Health Care, Medicinal Chemicals and Botanical Products
|
AKAN,Akanda Corp. Common Shares,United Kingdom,2022,Health Care, Medicinal Chemicals and Botanical Products
|
||||||
AKBA,Akebia Therapeutics Inc. Common Stock,United States,2014,Health Care,Biotechnology: Pharmaceutical Preparations
|
AKBA,Akebia Therapeutics Inc. Common Stock,United States,2014,Health Care,Biotechnology: Pharmaceutical Preparations
|
||||||
AKO-A,Embotelladora Andina S.A.,Chile,,,
|
|
||||||
AKO-B,Embotelladora Andina S.A.,Chile,,,
|
|
||||||
AKR,Acadia Realty Trust Common Stock,United States,,Real Estate,Real Estate Investment Trusts
|
AKR,Acadia Realty Trust Common Stock,United States,,Real Estate,Real Estate Investment Trusts
|
||||||
AKRO,Akero Therapeutics Inc. Common Stock,United States,2019,Health Care,Biotechnology: Pharmaceutical Preparations
|
AKRO,Akero Therapeutics Inc. Common Stock,United States,2019,Health Care,Biotechnology: Pharmaceutical Preparations
|
||||||
AKTS,Akoustis Technologies Inc. Common Stock,United States,,Utilities,Telecommunications Equipment
|
AKTS,Akoustis Technologies Inc. Common Stock,United States,,Utilities,Telecommunications Equipment
|
||||||
@@ -789,8 +787,6 @@ BERY,Berry Global Group Inc. Common Stock,United States,2012,Industrials,Plastic
|
|||||||
BEST,BEST Inc. American Depositary Shares each representing twenty (20) Class A Ordinary Shares,Cayman Islands,2023,Industrials,Trucking Freight/Courier Services
|
BEST,BEST Inc. American Depositary Shares each representing twenty (20) Class A Ordinary Shares,Cayman Islands,2023,Industrials,Trucking Freight/Courier Services
|
||||||
BETR,Better Home & Finance Holding Company Class A Common Stock,United Kingdom,2021,Finance,Finance: Consumer Services
|
BETR,Better Home & Finance Holding Company Class A Common Stock,United Kingdom,2021,Finance,Finance: Consumer Services
|
||||||
BETRW,Better Home & Finance Holding Company Warrant,United Kingdom,2021,Finance,Finance: Consumer Services
|
BETRW,Better Home & Finance Holding Company Warrant,United Kingdom,2021,Finance,Finance: Consumer Services
|
||||||
BF-A,Brown Forman Corporation,United States,,,
|
|
||||||
BF-B,Brown Forman Corporation,United States,,,
|
|
||||||
BFAC,Battery Future Acquisition Corp. Class A Ordinary Shares,,2022,Finance,Blank Checks
|
BFAC,Battery Future Acquisition Corp. Class A Ordinary Shares,,2022,Finance,Blank Checks
|
||||||
BFAM,Bright Horizons Family Solutions Inc. Common Stock,United States,2013,Consumer Discretionary,Other Consumer Services
|
BFAM,Bright Horizons Family Solutions Inc. Common Stock,United States,2013,Consumer Discretionary,Other Consumer Services
|
||||||
BFC,Bank First Corporation Common Stock,United States,,Finance,Major Banks
|
BFC,Bank First Corporation Common Stock,United States,,Finance,Major Banks
|
||||||
@@ -1004,8 +1000,6 @@ BREA,Brera Holdings PLC Class B Ordinary Shares,Ireland,2023,Consumer Discretion
|
|||||||
BRFH,Barfresh Food Group Inc. Common Stock,United States,,Consumer Staples,Packaged Foods
|
BRFH,Barfresh Food Group Inc. Common Stock,United States,,Consumer Staples,Packaged Foods
|
||||||
BRFS,BRF S.A.,Brazil,,Consumer Staples,Meat/Poultry/Fish
|
BRFS,BRF S.A.,Brazil,,Consumer Staples,Meat/Poultry/Fish
|
||||||
BRID,Bridgford Foods Corporation Common Stock,United States,,Consumer Staples,Specialty Foods
|
BRID,Bridgford Foods Corporation Common Stock,United States,,Consumer Staples,Specialty Foods
|
||||||
BRK-A,Berkshire Hathaway Inc.,United States,,,
|
|
||||||
BRK-B,Berkshire Hathaway Inc.,United States,,,
|
|
||||||
BRKH,BurTech Acquisition Corp. Class A Common Stock,United States,2022,Finance,Blank Checks
|
BRKH,BurTech Acquisition Corp. Class A Common Stock,United States,2022,Finance,Blank Checks
|
||||||
BRKHU,BurTech Acquisition Corp. Unit,United States,2021,Finance,Blank Checks
|
BRKHU,BurTech Acquisition Corp. Unit,United States,2021,Finance,Blank Checks
|
||||||
BRKHW,BurTech Acquisition Corp. Warrants,United States,2022,Finance,Blank Checks
|
BRKHW,BurTech Acquisition Corp. Warrants,United States,2022,Finance,Blank Checks
|
||||||
@@ -1542,8 +1536,6 @@ CRBP,Corbus Pharmaceuticals Holdings Inc. Common Stock,United States,,Health Car
|
|||||||
CRBU,Caribou Biosciences Inc. Common Stock,United States,2021,Health Care,Biotechnology: Biological Products (No Diagnostic Substances)
|
CRBU,Caribou Biosciences Inc. Common Stock,United States,2021,Health Care,Biotechnology: Biological Products (No Diagnostic Substances)
|
||||||
CRC,California Resources Corporation Common Stock,United States,2020,Energy,Oil & Gas Production
|
CRC,California Resources Corporation Common Stock,United States,2020,Energy,Oil & Gas Production
|
||||||
CRCT,Cricut Inc. Class A Common Stock,United States,2021,Technology,Industrial Machinery/Components
|
CRCT,Cricut Inc. Class A Common Stock,United States,2021,Technology,Industrial Machinery/Components
|
||||||
CRD-A,Crawford & Company,United States,,,
|
|
||||||
CRD-B,Crawford & Company,United States,,,
|
|
||||||
CRDF,Cardiff Oncology Inc. Common Stock,United States,,Health Care,Biotechnology: Biological Products (No Diagnostic Substances)
|
CRDF,Cardiff Oncology Inc. Common Stock,United States,,Health Care,Biotechnology: Biological Products (No Diagnostic Substances)
|
||||||
CRDL,Cardiol Therapeutics Inc. Class A Common Shares,Canada,,Health Care,Biotechnology: Biological Products (No Diagnostic Substances)
|
CRDL,Cardiol Therapeutics Inc. Class A Common Shares,Canada,,Health Care,Biotechnology: Biological Products (No Diagnostic Substances)
|
||||||
CRDO,Credo Technology Group Holding Ltd Ordinary Shares,United States,2022,Technology,Semiconductors
|
CRDO,Credo Technology Group Holding Ltd Ordinary Shares,United States,2022,Technology,Semiconductors
|
||||||
@@ -2850,7 +2842,6 @@ HE,Hawaiian Electric Industries Inc. Common Stock,United States,,Utilities,Elect
|
|||||||
HEAR,Turtle Beach Corporation Common Stock,United States,,Telecommunications,Telecommunications Equipment
|
HEAR,Turtle Beach Corporation Common Stock,United States,,Telecommunications,Telecommunications Equipment
|
||||||
HEES,H&E Equipment Services Inc. Common Stock,United States,2006,Industrials,Misc Corporate Leasing Services
|
HEES,H&E Equipment Services Inc. Common Stock,United States,2006,Industrials,Misc Corporate Leasing Services
|
||||||
HEI,Heico Corporation Common Stock,United States,2000,Industrials,Aerospace
|
HEI,Heico Corporation Common Stock,United States,2000,Industrials,Aerospace
|
||||||
HEI-A,Heico Corporation,United States,,,
|
|
||||||
HELE,Helen of Troy Limited Common Stock,Bermuda,,Consumer Discretionary,Home Furnishings
|
HELE,Helen of Troy Limited Common Stock,Bermuda,,Consumer Discretionary,Home Furnishings
|
||||||
HEPA,Hepion Pharmaceuticals Inc. Common Stock,United States,,Health Care,Biotechnology: Pharmaceutical Preparations
|
HEPA,Hepion Pharmaceuticals Inc. Common Stock,United States,,Health Care,Biotechnology: Pharmaceutical Preparations
|
||||||
HEPS,D-Market Electronic Services & Trading American Depositary Shares,Turkey,2021,Consumer Discretionary,Catalog/Specialty Distribution
|
HEPS,D-Market Electronic Services & Trading American Depositary Shares,Turkey,2021,Consumer Discretionary,Catalog/Specialty Distribution
|
||||||
@@ -3011,7 +3002,6 @@ HUnited States,Houston American Energy Corporation Common Stock,United States,,E
|
|||||||
HUT,Hut 8 Corp. Common Stock,United States,,Finance,Finance: Consumer Services
|
HUT,Hut 8 Corp. Common Stock,United States,,Finance,Finance: Consumer Services
|
||||||
HUYA,HUYA Inc. American depositary shares each representing one Class A ordinary share,United States,2018,Technology,Computer Software: Programming Data Processing
|
HUYA,HUYA Inc. American depositary shares each representing one Class A ordinary share,United States,2018,Technology,Computer Software: Programming Data Processing
|
||||||
HVT,Haverty Furniture Companies Inc. Common Stock,United States,,Consumer Discretionary,Other Specialty Stores
|
HVT,Haverty Furniture Companies Inc. Common Stock,United States,,Consumer Discretionary,Other Specialty Stores
|
||||||
HVT-A,Haverty Furniture Companies Inc.,United States,,,
|
|
||||||
HWBK,Hawthorn Bancshares Inc. Common Stock,United States,,Finance,Major Banks
|
HWBK,Hawthorn Bancshares Inc. Common Stock,United States,,Finance,Major Banks
|
||||||
HWC,Hancock Whitney Corporation Common Stock,United States,,Finance,Major Banks
|
HWC,Hancock Whitney Corporation Common Stock,United States,,Finance,Major Banks
|
||||||
HWCPZ,Hancock Whitney Corporation 6.25% Subordinated Notes due 2060,United States,,Finance,Major Banks
|
HWCPZ,Hancock Whitney Corporation 6.25% Subordinated Notes due 2060,United States,,Finance,Major Banks
|
||||||
@@ -11488,7 +11478,6 @@ NYNYR,Empire Resorts Inc.,United States,,,
|
|||||||
LLEX,"Lilis Energy, Inc.",United States,,Independent Oil & Gas,
|
LLEX,"Lilis Energy, Inc.",United States,,Independent Oil & Gas,
|
||||||
EZT,"Entergy Texas, Inc.",United States,,,
|
EZT,"Entergy Texas, Inc.",United States,,,
|
||||||
EXPN.L,Experian plc,United Kingdom,,Business Services,
|
EXPN.L,Experian plc,United Kingdom,,Business Services,
|
||||||
ETX,Eaton Vance Municipal Income 2028 Term Trust,United States,,,
|
|
||||||
EMI,Eaton Vance Michigan Municipal Income Trust,United States,,,
|
EMI,Eaton Vance Michigan Municipal Income Trust,United States,,,
|
||||||
EMG,"Emergent Capital, Inc.",United States,,,
|
EMG,"Emergent Capital, Inc.",United States,,,
|
||||||
EMCF,Emclaire Financial Corp.,United States,,Regional - Northeast Banks,
|
EMCF,Emclaire Financial Corp.,United States,,Regional - Northeast Banks,
|
||||||
@@ -11531,7 +11520,6 @@ ENZY,Enzymotec Ltd.,United States,,Biotechnology,
|
|||||||
ENBP,ENB Financial Corp,United States,,Regional - Northeast Banks,
|
ENBP,ENB Financial Corp,United States,,Regional - Northeast Banks,
|
||||||
EMP-A.TO,Empire Company Limited,Canada,,Gold,
|
EMP-A.TO,Empire Company Limited,Canada,,Gold,
|
||||||
ECCA,Eagle Point Credit Company Inc.,United States,,,
|
ECCA,Eagle Point Credit Company Inc.,United States,,,
|
||||||
ECC,Eagle Point Credit Company Inc.,United States,,,
|
|
||||||
NESV,"National Energy Services, Inc.",United States,,,
|
NESV,"National Energy Services, Inc.",United States,,,
|
||||||
YECO,Yulong Eco-Materials Limited,United States,,,
|
YECO,Yulong Eco-Materials Limited,United States,,,
|
||||||
TIK,Tel-Instrument Electronics Corp.,United States,,Aerospace/Defense - Major Diversified,
|
TIK,Tel-Instrument Electronics Corp.,United States,,Aerospace/Defense - Major Diversified,
|
||||||
@@ -13136,7 +13124,6 @@ HRG,"HRG Group, Inc.",United States,,Conglomerates,
|
|||||||
GSS,Golden Star Resources Ltd.,United States,,Gold,
|
GSS,Golden Star Resources Ltd.,United States,,Gold,
|
||||||
GIG,"GigPeak, Inc.",United States,,,
|
GIG,"GigPeak, Inc.",United States,,,
|
||||||
SGEN,"Seattle Genetics, Inc.",United States,,Biotechnology,
|
SGEN,"Seattle Genetics, Inc.",United States,,Biotechnology,
|
||||||
SAND,Sandstorm Gold Ltd.,United States,,Gold,
|
|
||||||
PGEM,"Ply Gem Holdings, Inc",United States,,General Building Materials,
|
PGEM,"Ply Gem Holdings, Inc",United States,,General Building Materials,
|
||||||
ININ,"Interactive Intelligence Group, Inc.",United States,,,
|
ININ,"Interactive Intelligence Group, Inc.",United States,,,
|
||||||
SNR,New Senior Investment Group Inc.,United States,,,
|
SNR,New Senior Investment Group Inc.,United States,,,
|
||||||
@@ -13682,7 +13669,6 @@ VIG.VI,Vienna Insurance Group AG,Austria,,,
|
|||||||
TACXF,TAC GOLD,United States,,,
|
TACXF,TAC GOLD,United States,,,
|
||||||
VELA.L,Vela Technologies PLC,United Kingdom,,,
|
VELA.L,Vela Technologies PLC,United Kingdom,,,
|
||||||
VEGPF,Vectura Group plc,United States,,,
|
VEGPF,Vectura Group plc,United States,,,
|
||||||
United States.TO,Americas Silver Corporation,Canada,,Industrial Metals & Minerals,
|
|
||||||
URHG,"United Resource Holdings Group, Inc.",United States,,,
|
URHG,"United Resource Holdings Group, Inc.",United States,,,
|
||||||
UQA.VI,UNIQA Insurance Group AG,Austria,,,
|
UQA.VI,UNIQA Insurance Group AG,Austria,,,
|
||||||
UIBGF,UIB Group Limited,United States,,,
|
UIBGF,UIB Group Limited,United States,,,
|
||||||
@@ -20863,7 +20849,6 @@ USMT,US Metro Bank,United States,,,
|
|||||||
USK.F,"Skullcandy, Inc.",France,,,
|
USK.F,"Skullcandy, Inc.",France,,,
|
||||||
USF.AX,"US Select Private Opportunities Fund, LP",Australia,,,
|
USF.AX,"US Select Private Opportunities Fund, LP",Australia,,,
|
||||||
USBL,"United States Basketball League, Inc.",United States,,Conglomerates,
|
USBL,"United States Basketball League, Inc.",United States,,Conglomerates,
|
||||||
United States.AX,Uraniumsa Limited,Australia,,Industrial Metals & Minerals,
|
|
||||||
US2.BE,"USG CORP. DL-,10",Germany,,,
|
US2.BE,"USG CORP. DL-,10",Germany,,,
|
||||||
US1.F,"Ultratech, Inc.",France,,,
|
US1.F,"Ultratech, Inc.",France,,,
|
||||||
URXZD,TENTH AVENUE PETROLEUM CORPORAT,United States,,,
|
URXZD,TENTH AVENUE PETROLEUM CORPORAT,United States,,,
|
||||||
@@ -22474,7 +22459,6 @@ O3X2.F,AAN Ventures Inc,France,,,
|
|||||||
D94417.CR,DP04947-0011,Venezuela,,,
|
D94417.CR,DP04947-0011,Venezuela,,,
|
||||||
ENV.CR,ENVASES VENEZOLANOS S.A.,Venezuela,,,
|
ENV.CR,ENVASES VENEZOLANOS S.A.,Venezuela,,,
|
||||||
BVF.DU,VALEANT PHARMACEUT. INTL,Germany,,,
|
BVF.DU,VALEANT PHARMACEUT. INTL,Germany,,,
|
||||||
CUnited StatesN.IS,Cuhadaroglu Metal Sanayi ve Pazarlama A.S.,Turkey,,,
|
|
||||||
PUM9.L,Puma VCT 9 plc,United Kingdom,,,
|
PUM9.L,Puma VCT 9 plc,United Kingdom,,,
|
||||||
RAP1V.HE,Rapala VMC Corporation,Finland,,,
|
RAP1V.HE,Rapala VMC Corporation,Finland,,,
|
||||||
VIS.BE,"VISCOFAN SA INH. EO 0,70",Germany,,,
|
VIS.BE,"VISCOFAN SA INH. EO 0,70",Germany,,,
|
||||||
@@ -22540,7 +22524,6 @@ WWSG,Worldwide Strategies Incorporated,United States,,Diversified Communication
|
|||||||
WWTH,"With, Inc.",United States,,,
|
WWTH,"With, Inc.",United States,,,
|
||||||
WUMSF,Wumart Stores Inc.,United States,,,
|
WUMSF,Wumart Stores Inc.,United States,,,
|
||||||
WSSH,West Shore Bank Corp.,United States,,,
|
WSSH,West Shore Bank Corp.,United States,,,
|
||||||
WSO-B,"Watsco, Inc.",United States,,,
|
|
||||||
WRCKF,WESC AB,United States,,,
|
WRCKF,WESC AB,United States,,,
|
||||||
WMSI,"Williams Industries, Incorporated",United States,,,
|
WMSI,"Williams Industries, Incorporated",United States,,,
|
||||||
WLKR,Walker Innovation Inc.,United States,,,
|
WLKR,Walker Innovation Inc.,United States,,,
|
||||||
@@ -29715,12 +29698,10 @@ AK1.SG,AMETEK INC. Registered Shares D,Germany,,,
|
|||||||
SRT.DE,Sartorius Aktiengesellschaft,Germany,,Scientific & Technical Instruments,
|
SRT.DE,Sartorius Aktiengesellschaft,Germany,,Scientific & Technical Instruments,
|
||||||
0G29.L,Semperit Aktiengesellschaft Holding,United Kingdom,,,
|
0G29.L,Semperit Aktiengesellschaft Holding,United Kingdom,,,
|
||||||
0IPT.L,Akastor ASA,United Kingdom,,,
|
0IPT.L,Akastor ASA,United Kingdom,,,
|
||||||
United StatesK.IS,United Statesk Seramik Sanayi A.S.,Turkey,,,
|
|
||||||
0NCV.L,Lenzing Aktiengesellschaft,United Kingdom,,,
|
0NCV.L,Lenzing Aktiengesellschaft,United Kingdom,,,
|
||||||
0OK7.L,Akka Technologies,United Kingdom,,,
|
0OK7.L,Akka Technologies,United Kingdom,,,
|
||||||
1AKA.BE,"AKER SOLUTIONS ASA NK1,08",Germany,,,
|
1AKA.BE,"AKER SOLUTIONS ASA NK1,08",Germany,,,
|
||||||
AK2.BE,"AK STEEL HLDG",Germany,,,
|
AK2.BE,"AK STEEL HLDG",Germany,,,
|
||||||
United StatesS.IS,United Statess Yatirimlar Holding A.S.,Turkey,,,
|
|
||||||
0QBM.L,Alior Bank S.A.,United Kingdom,,,
|
0QBM.L,Alior Bank S.A.,United Kingdom,,,
|
||||||
T1W.HM,Aktiengesellschaft Tokugawa,Germany,,,
|
T1W.HM,Aktiengesellschaft Tokugawa,Germany,,,
|
||||||
0OIU.L,Arctic Paper S.A.,United Kingdom,,,
|
0OIU.L,Arctic Paper S.A.,United Kingdom,,,
|
||||||
|
|||||||
|
Can't render this file because it is too large.
|
+5
-5
@@ -10,9 +10,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8000:80
|
- 8000:80
|
||||||
environment: # You can either use these properties OR an .env file. Do not use both!
|
environment: # You can either use these properties OR an .env file. Do not use both!
|
||||||
APP_KEY: "" # Generate a key using `echo base64:$(openssl rand -base64 32)`
|
|
||||||
APP_URL: "http://localhost:8000"
|
APP_URL: "http://localhost:8000"
|
||||||
ASSET_URL: "http://localhost:8000"
|
|
||||||
DB_CONNECTION: mysql
|
DB_CONNECTION: mysql
|
||||||
DB_HOST: investbrain-mysql
|
DB_HOST: investbrain-mysql
|
||||||
DB_PORT: 3306
|
DB_PORT: 3306
|
||||||
@@ -25,7 +23,7 @@ services:
|
|||||||
REDIS_HOST: investbrain-redis
|
REDIS_HOST: investbrain-redis
|
||||||
volumes:
|
volumes:
|
||||||
- investbrain-storage:/var/app/storage # You can use a volume...
|
- investbrain-storage:/var/app/storage # You can use a volume...
|
||||||
# - /path/to/storage:/var/app/storage # ...or you can use a path on host
|
# - /path/to/storage:/var/app/storage:delegated # ...or you can use a path on host
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- mysql
|
||||||
- redis
|
- redis
|
||||||
@@ -36,10 +34,12 @@ services:
|
|||||||
container_name: investbrain-redis
|
container_name: investbrain-redis
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
tty: true
|
tty: true
|
||||||
networks:
|
command:
|
||||||
- investbrain-network
|
- --loglevel warning
|
||||||
volumes:
|
volumes:
|
||||||
- investbrain-redis:/data
|
- investbrain-redis:/data
|
||||||
|
networks:
|
||||||
|
- investbrain-network
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
container_name: investbrain-mysql
|
container_name: investbrain-mysql
|
||||||
|
|||||||
+46
-18
@@ -1,17 +1,16 @@
|
|||||||
FROM php:8.3-fpm
|
# Stage 1: Build stage
|
||||||
|
FROM php:8.3-fpm AS builder
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV APP_NAME=Investbrain
|
ENV APP_NAME=Investbrain
|
||||||
ENV VITE_APP_NAME=Investbrain
|
ENV VITE_APP_NAME=Investbrain
|
||||||
|
|
||||||
# Set the working directory
|
# Set the working directory
|
||||||
COPY . /var/app
|
|
||||||
WORKDIR /var/app
|
WORKDIR /var/app
|
||||||
|
|
||||||
# Install required packages
|
# Install required packages
|
||||||
RUN apt-get update && apt-get upgrade -y \
|
RUN apt-get update && apt-get upgrade -y \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
nginx \
|
|
||||||
libfreetype-dev \
|
libfreetype-dev \
|
||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
libpng-dev \
|
libpng-dev \
|
||||||
@@ -20,22 +19,18 @@ RUN apt-get update && apt-get upgrade -y \
|
|||||||
libicu-dev \
|
libicu-dev \
|
||||||
libpq-dev \
|
libpq-dev \
|
||||||
binutils libc6-dev \
|
binutils libc6-dev \
|
||||||
supervisor \
|
|
||||||
unzip curl git \
|
unzip curl git \
|
||||||
nodejs npm \
|
nodejs npm \
|
||||||
# Clean up APT
|
|
||||||
&& apt-get -y autoremove \
|
&& apt-get -y autoremove \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
# Install PHP extensions
|
|
||||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
|
||||||
&& docker-php-ext-install -j$(nproc) \
|
|
||||||
gd pgsql zip pdo_mysql mysqli intl
|
|
||||||
|
|
||||||
# Remove default nginx config
|
# Install PHP extensions
|
||||||
RUN rm /etc/nginx/sites-enabled/default \
|
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||||
&& rm -rf /var/www/html \
|
&& docker-php-ext-install -j$(nproc) gd zip
|
||||||
&& ln -s /var/app /var/www/app
|
|
||||||
|
# Copy application files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# Install Composer and Node.js Install PHP dependencies and build front end assets
|
# Install Composer and Node.js Install PHP dependencies and build front end assets
|
||||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||||
@@ -43,13 +38,46 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
|
|||||||
&& npm install && npm run build \
|
&& npm install && npm run build \
|
||||||
&& rm -rf node_modules
|
&& rm -rf node_modules
|
||||||
|
|
||||||
|
# Stage 2: Production stage
|
||||||
|
FROM php:8.3-fpm-alpine
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /var/app
|
||||||
|
|
||||||
|
# Copy necessary files from the builder stage
|
||||||
|
COPY --from=builder /var/app /var/app
|
||||||
|
COPY --from=builder /usr/local/etc/php/conf.d /usr/local/etc/php/conf.d
|
||||||
|
COPY --from=builder /usr/local/bin/composer /usr/local/bin/composer
|
||||||
|
|
||||||
|
# Install required Alpine packages
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
nginx \
|
||||||
|
supervisor \
|
||||||
|
libpng-dev \
|
||||||
|
libzip-dev \
|
||||||
|
icu-dev \
|
||||||
|
postgresql-dev \
|
||||||
|
freetype-dev \
|
||||||
|
libjpeg-turbo-dev \
|
||||||
|
bash \
|
||||||
|
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||||
|
&& docker-php-ext-install -j$(nproc) \
|
||||||
|
gd pgsql zip pdo_mysql mysqli intl
|
||||||
|
|
||||||
|
# Remove default nginx config
|
||||||
|
RUN rm -rf /var/www/html \
|
||||||
|
&& ln -s /var/app /var/www/app
|
||||||
|
|
||||||
|
# Create required directories for supervisord
|
||||||
|
RUN mkdir -p /var/log/supervisor /var/run/supervisor
|
||||||
|
|
||||||
# Copy over configs
|
# Copy over configs
|
||||||
COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY ./docker/nginx.conf /etc/nginx/http.d/default.conf
|
||||||
COPY ./docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
COPY ./docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
# Set permissions and link storage
|
# Set permissions and link storage
|
||||||
RUN php artisan storage:link \
|
RUN php artisan storage:link \
|
||||||
&& chown -R www-data:www-data . \
|
&& chown -R www-data:www-data . \
|
||||||
&& chmod +x ./docker/entrypoint.sh
|
&& chmod +x ./docker/entrypoint.sh
|
||||||
|
|
||||||
# Serve on port 80
|
# Serve on port 80
|
||||||
@@ -59,4 +87,4 @@ EXPOSE 80
|
|||||||
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost/up || exit 1
|
HEALTHCHECK --interval=30s --timeout=10s --retries=3 CMD curl -f http://localhost/up || exit 1
|
||||||
|
|
||||||
# Run everything else
|
# Run everything else
|
||||||
ENTRYPOINT ["/bin/bash", "./docker/entrypoint.sh"]
|
ENTRYPOINT ["/bin/sh", "./docker/entrypoint.sh"]
|
||||||
|
|||||||
+17
-6
@@ -8,17 +8,23 @@ echo "CiAgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi
|
|||||||
echo -e "\n====================== Validating environment... ====================== "
|
echo -e "\n====================== Validating environment... ====================== "
|
||||||
|
|
||||||
# Ensure app storage directory is scaffolded
|
# Ensure app storage directory is scaffolded
|
||||||
mkdir -p storage/{{framework/cache,framework/sessions,framework/views},app,logs}
|
mkdir -p storage/framework/cache \
|
||||||
|
storage/framework/sessions \
|
||||||
|
storage/framework/views \
|
||||||
|
storage/app \
|
||||||
|
storage/logs
|
||||||
|
|
||||||
|
echo -e "\n > Storage directory scaffolding is OK... "
|
||||||
|
|
||||||
# Ensure storage directory is permissioned for www-data
|
# Ensure storage directory is permissioned for www-data
|
||||||
chmod -R 775 storage
|
chmod -R 775 storage
|
||||||
chown -R www-data:www-data storage
|
chown -R www-data:www-data storage
|
||||||
|
|
||||||
echo -e "\n > Storage directory scaffolding is OK... "
|
echo -e "\n > Permissions are OK... "
|
||||||
|
|
||||||
# Ensure app key is generated
|
# Ensure app key exists / generate if required
|
||||||
if [[ -z "$APP_KEY" ]]; then
|
KEY_FILE="storage/app/.key"
|
||||||
echo -e "\n > Oops! The required APP_KEY configuration is missing in your environment! "
|
if [ -z "$APP_KEY" ] && [ ! -s "$KEY_FILE" ]; then
|
||||||
|
|
||||||
draw_box() {
|
draw_box() {
|
||||||
local text="$1"
|
local text="$1"
|
||||||
@@ -30,7 +36,12 @@ if [[ -z "$APP_KEY" ]]; then
|
|||||||
echo "$border"
|
echo "$border"
|
||||||
}
|
}
|
||||||
|
|
||||||
export APP_KEY=$(php artisan key:generate --show)
|
export APP_KEY="$(php artisan key:generate --show)"
|
||||||
|
|
||||||
|
echo -e "\n > Oops! The required APP_KEY configuration is missing! Generated app key and saved in $KEY_FILE"
|
||||||
|
|
||||||
|
echo "$APP_KEY" > "$KEY_FILE"
|
||||||
|
|
||||||
draw_box $APP_KEY
|
draw_box $APP_KEY
|
||||||
else
|
else
|
||||||
echo -e "\n > APP_KEY is OK... "
|
echo -e "\n > APP_KEY is OK... "
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
use App\Models\Holding;
|
use App\Models\Holding;
|
||||||
use Livewire\Volt\Component;
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
new class extends Component {
|
new class extends Component
|
||||||
|
{
|
||||||
// props
|
// props
|
||||||
public Holding $holding;
|
public Holding $holding;
|
||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'transaction-updated' => '$refresh',
|
'transaction-updated' => '$refresh',
|
||||||
'transaction-saved' => '$refresh'
|
'transaction-saved' => '$refresh',
|
||||||
];
|
];
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
|
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|||||||
@@ -1,33 +1,38 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Models\Transaction;
|
|
||||||
use App\Models\Portfolio;
|
use App\Models\Portfolio;
|
||||||
use App\Rules\SymbolValidationRule;
|
use App\Models\Transaction;
|
||||||
use App\Rules\QuantityValidationRule;
|
use App\Rules\QuantityValidationRule;
|
||||||
use Illuminate\Support\Collection;
|
use App\Rules\SymbolValidationRule;
|
||||||
use Livewire\Attributes\{Computed};
|
use App\Traits\WithTrimStrings;
|
||||||
use Livewire\Volt\Component;
|
use Livewire\Volt\Component;
|
||||||
use Mary\Traits\Toast;
|
use Mary\Traits\Toast;
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use App\Traits\WithTrimStrings;
|
|
||||||
|
|
||||||
new class extends Component {
|
new class extends Component
|
||||||
|
{
|
||||||
use Toast;
|
use Toast;
|
||||||
use WithTrimStrings;
|
use WithTrimStrings;
|
||||||
|
|
||||||
// props
|
// props
|
||||||
public ?Portfolio $portfolio;
|
public ?Portfolio $portfolio;
|
||||||
|
|
||||||
public ?Transaction $transaction;
|
public ?Transaction $transaction;
|
||||||
|
|
||||||
public ?String $portfolio_id;
|
public ?string $portfolio_id;
|
||||||
public String $symbol;
|
|
||||||
public String $transaction_type;
|
|
||||||
public String $date;
|
|
||||||
public Float $quantity;
|
|
||||||
public ?Float $cost_basis;
|
|
||||||
public ?Float $sale_price;
|
|
||||||
|
|
||||||
public Bool $confirmingTransactionDeletion = false;
|
public string $symbol;
|
||||||
|
|
||||||
|
public string $transaction_type;
|
||||||
|
|
||||||
|
public string $date;
|
||||||
|
|
||||||
|
public float $quantity;
|
||||||
|
|
||||||
|
public ?float $cost_basis;
|
||||||
|
|
||||||
|
public ?float $sale_price;
|
||||||
|
|
||||||
|
public bool $confirmingTransactionDeletion = false;
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
public function rules()
|
public function rules()
|
||||||
@@ -36,19 +41,19 @@ new class extends Component {
|
|||||||
'symbol' => ['required', 'string', new SymbolValidationRule],
|
'symbol' => ['required', 'string', new SymbolValidationRule],
|
||||||
'transaction_type' => 'required|string|in:BUY,SELL',
|
'transaction_type' => 'required|string|in:BUY,SELL',
|
||||||
'portfolio_id' => 'required|exists:portfolios,id',
|
'portfolio_id' => 'required|exists:portfolios,id',
|
||||||
'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:' . now()->format('Y-m-d')],
|
'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:'.now()->format('Y-m-d')],
|
||||||
'quantity' => [
|
'quantity' => [
|
||||||
'required',
|
'required',
|
||||||
'numeric',
|
'numeric',
|
||||||
'min:0',
|
'min:0',
|
||||||
new QuantityValidationRule($this->portfolio, $this->symbol, $this->transaction_type, $this->date)
|
new QuantityValidationRule($this->portfolio, $this->symbol, $this->transaction_type, $this->date),
|
||||||
],
|
],
|
||||||
'cost_basis' => 'exclude_if:transaction_type,SELL|min:0|numeric',
|
'cost_basis' => 'exclude_if:transaction_type,SELL|min:0|numeric',
|
||||||
'sale_price' => 'exclude_if:transaction_type,BUY|min:0|numeric',
|
'sale_price' => 'exclude_if:transaction_type,BUY|min:0|numeric',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (isset($this->transaction)) {
|
if (isset($this->transaction)) {
|
||||||
|
|
||||||
@@ -59,7 +64,7 @@ new class extends Component {
|
|||||||
$this->quantity = $this->transaction->quantity;
|
$this->quantity = $this->transaction->quantity;
|
||||||
$this->cost_basis = $this->transaction->cost_basis;
|
$this->cost_basis = $this->transaction->cost_basis;
|
||||||
$this->sale_price = $this->transaction->sale_price;
|
$this->sale_price = $this->transaction->sale_price;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$this->transaction_type = 'BUY';
|
$this->transaction_type = 'BUY';
|
||||||
$this->portfolio_id = isset($this->portfolio) ? $this->portfolio->id : '';
|
$this->portfolio_id = isset($this->portfolio) ? $this->portfolio->id : '';
|
||||||
@@ -70,9 +75,8 @@ new class extends Component {
|
|||||||
public function update()
|
public function update()
|
||||||
{
|
{
|
||||||
$this->authorize('fullAccess', $this->portfolio);
|
$this->authorize('fullAccess', $this->portfolio);
|
||||||
|
|
||||||
$this->transaction->update($this->validate());
|
$this->transaction->update($this->validate());
|
||||||
// $this->transaction->owner_id = auth()->user()->id;
|
|
||||||
$this->transaction->save();
|
$this->transaction->save();
|
||||||
|
|
||||||
$this->success(__('Transaction updated'));
|
$this->success(__('Transaction updated'));
|
||||||
@@ -83,7 +87,7 @@ new class extends Component {
|
|||||||
|
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
if (!isset($this->portfolio)) {
|
if (! isset($this->portfolio)) {
|
||||||
$this->portfolio = Portfolio::find($this->portfolio_id);
|
$this->portfolio = Portfolio::find($this->portfolio_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +111,11 @@ new class extends Component {
|
|||||||
|
|
||||||
$this->success(__('Transaction deleted'), redirectTo: route('holding.show', ['portfolio' => $this->portfolio->id, 'symbol' => $this->symbol]));
|
$this->success(__('Transaction deleted'), redirectTo: route('holding.show', ['portfolio' => $this->portfolio->id, 'symbol' => $this->symbol]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatedSymbol($value)
|
||||||
|
{
|
||||||
|
$this->symbol = strtoupper($value);
|
||||||
|
}
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
<div class="" x-data="{ transaction_type: @entangle('transaction_type') }">
|
<div class="" x-data="{ transaction_type: @entangle('transaction_type') }">
|
||||||
|
|||||||
@@ -2,29 +2,31 @@
|
|||||||
|
|
||||||
use App\Models\DailyChange;
|
use App\Models\DailyChange;
|
||||||
use App\Models\Portfolio;
|
use App\Models\Portfolio;
|
||||||
use Livewire\Attributes\{Title, Rule};
|
|
||||||
use Livewire\Volt\Component;
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
new class extends Component {
|
new class extends Component
|
||||||
|
{
|
||||||
// props
|
// props
|
||||||
public ?Portfolio $portfolio;
|
public ?Portfolio $portfolio;
|
||||||
public String $name = 'portfolio';
|
|
||||||
public String $scope = 'YTD';
|
public string $name = 'portfolio';
|
||||||
public Array $scopeOptions = [
|
|
||||||
|
public string $scope = 'YTD';
|
||||||
|
|
||||||
|
public array $scopeOptions = [
|
||||||
['id' => '1M', 'name' => '1 month', 'method' => 'subMonths', 'args' => [1]],
|
['id' => '1M', 'name' => '1 month', 'method' => 'subMonths', 'args' => [1]],
|
||||||
['id' => '3M', 'name' => '3 months', 'method' => 'subMonths', 'args' => [3]],
|
['id' => '3M', 'name' => '3 months', 'method' => 'subMonths', 'args' => [3]],
|
||||||
['id' => 'YTD', 'name' => 'Year to date', 'method' => 'startOfYear', 'args' => []],
|
['id' => 'YTD', 'name' => 'Year to date', 'method' => 'startOfYear', 'args' => []],
|
||||||
['id' => '1Y', 'name' => '1 year', 'method' => 'subYears', 'args' => [1]],
|
['id' => '1Y', 'name' => '1 year', 'method' => 'subYears', 'args' => [1]],
|
||||||
['id' => '3Y', 'name' => '3 years', 'method' => 'subYears', 'args' => [3]],
|
['id' => '3Y', 'name' => '3 years', 'method' => 'subYears', 'args' => [3]],
|
||||||
['id' => 'ALL', 'name' => 'All time', 'method' => null]
|
['id' => 'ALL', 'name' => 'All time', 'method' => null],
|
||||||
];
|
];
|
||||||
|
|
||||||
// data
|
// data
|
||||||
public Array $chartSeries;
|
public array $chartSeries;
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->chartSeries = $this->generatePerformanceData();
|
$this->chartSeries = $this->generatePerformanceData();
|
||||||
}
|
}
|
||||||
@@ -33,52 +35,53 @@ new class extends Component {
|
|||||||
{
|
{
|
||||||
$filterMethod = collect($this->scopeOptions)->where('id', $this->scope)->first();
|
$filterMethod = collect($this->scopeOptions)->where('id', $this->scope)->first();
|
||||||
|
|
||||||
$dailyChangeQuery = DailyChange::myDailyChanges();
|
$dailyChangeQuery = DailyChange::myDailyChanges()->selectRaw('
|
||||||
|
date,
|
||||||
|
SUM(total_market_value) as total_market_value,
|
||||||
|
SUM(total_cost_basis) as total_cost_basis,
|
||||||
|
SUM(total_gain) as total_gain
|
||||||
|
/* ,
|
||||||
|
SUM(realized_gains) as realized_gains,
|
||||||
|
SUM(total_dividends_earned) as total_dividends_earned
|
||||||
|
*/
|
||||||
|
');
|
||||||
|
|
||||||
if (isset($this->portfolio)) {
|
if (isset($this->portfolio)) {
|
||||||
|
|
||||||
|
// portfolio
|
||||||
$dailyChangeQuery->portfolio($this->portfolio->id);
|
$dailyChangeQuery->portfolio($this->portfolio->id);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$dailyChangeQuery->selectRaw('
|
|
||||||
date,
|
|
||||||
SUM(total_market_value) as total_market_value,
|
|
||||||
SUM(total_cost_basis) as total_cost_basis,
|
|
||||||
SUM(total_gain) as total_gain
|
|
||||||
/* ,
|
|
||||||
SUM(realized_gains) as realized_gains,
|
|
||||||
SUM(total_dividends_earned) as total_dividends_earned
|
|
||||||
*/
|
|
||||||
')
|
|
||||||
->withoutWishlists()
|
|
||||||
->groupBy('date')
|
|
||||||
->orderBy('date');
|
|
||||||
|
|
||||||
|
// dashboard
|
||||||
|
$dailyChangeQuery->withoutWishlists();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($filterMethod['method']) {
|
if ($filterMethod['method']) {
|
||||||
|
|
||||||
$dailyChangeQuery->whereDate('date', '>=', now()->{$filterMethod['method']}(...$filterMethod['args']));
|
$dailyChangeQuery->whereDate('date', '>=', now()->{$filterMethod['method']}(...$filterMethod['args']));
|
||||||
}
|
}
|
||||||
// dd($dailyChangeQuery->toSql());
|
|
||||||
$dailyChange = $dailyChangeQuery->get();
|
$dailyChange = $dailyChangeQuery
|
||||||
|
->orderBy('date')
|
||||||
|
->groupBy('date')
|
||||||
|
->get();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'series' => [
|
'series' => [
|
||||||
[
|
[
|
||||||
'name' => __('Market Value'),
|
'name' => __('Market Value'),
|
||||||
'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_market_value])->toArray(),
|
'data' => $dailyChange->map(fn ($data) => [$data->date, $data->total_market_value])->toArray(),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => __('Cost Basis'),
|
'name' => __('Cost Basis'),
|
||||||
'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_cost_basis])->toArray(),
|
'data' => $dailyChange->map(fn ($data) => [$data->date, $data->total_cost_basis])->toArray(),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => __('Market Gain'),
|
'name' => __('Market Gain'),
|
||||||
'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_gain])->toArray()
|
'data' => $dailyChange->map(fn ($data) => [$data->date, $data->total_gain])->toArray(),
|
||||||
],
|
],
|
||||||
|
|
||||||
// [
|
// [
|
||||||
// 'name' => __('Dividends Earned'),
|
// 'name' => __('Dividends Earned'),
|
||||||
// 'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_dividends_earned])->toArray()
|
// 'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_dividends_earned])->toArray()
|
||||||
@@ -87,7 +90,7 @@ new class extends Component {
|
|||||||
// 'name' => __('Realized Gains'),
|
// 'name' => __('Realized Gains'),
|
||||||
// 'data' => $dailyChange->map(fn($data) => [$data->date, $data->realized_gains])->toArray()
|
// 'data' => $dailyChange->map(fn($data) => [$data->date, $data->realized_gains])->toArray()
|
||||||
// ],
|
// ],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +105,6 @@ new class extends Component {
|
|||||||
{
|
{
|
||||||
return collect($this->scopeOptions)->where('id', $scope)->first()['name'];
|
return collect($this->scopeOptions)->where('id', $scope)->first()['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
}; ?>
|
}; ?>
|
||||||
|
|
||||||
<x-card class="bg-slate-100 dark:bg-base-200 rounded-lg mb-6">
|
<x-card class="bg-slate-100 dark:bg-base-200 rounded-lg mb-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user