2024-08-10 13:30:19 -05:00
< ? php
namespace App\Models ;
2024-08-27 21:17:54 -05:00
use App\Models\Split ;
2024-08-10 13:30:19 -05:00
use App\Models\Dividend ;
2024-08-27 21:17:54 -05:00
use App\Models\Portfolio ;
use App\Models\MarketData ;
use App\Models\Transaction ;
2024-08-10 13:30:19 -05:00
use Illuminate\Database\Eloquent\Model ;
2024-08-17 18:40:50 -05:00
use Illuminate\Database\Eloquent\Concerns\HasUuids ;
2024-08-10 13:30:19 -05:00
use Illuminate\Database\Eloquent\Factories\HasFactory ;
class Holding extends Model
{
use HasFactory ;
2024-08-17 18:40:50 -05:00
use HasUuids ;
2024-08-10 13:30:19 -05:00
protected $fillable = [
'portfolio_id' ,
'symbol' ,
'quantity' ,
'average_cost_basis' ,
'total_cost_basis' ,
2024-08-21 20:42:32 -05:00
'realized_gain_dollars' ,
2024-08-10 13:30:19 -05:00
'dividends_earned' ,
'splits_synced_at' ,
];
protected $casts = [
'splits_synced_at' => 'datetime' ,
];
2024-08-26 22:13:00 -05:00
protected $attributes = [
'realized_gain_dollars' => 0 ,
'dividends_earned' => 0 ,
];
2024-08-10 13:30:19 -05:00
/**
2024-08-17 21:33:09 -05:00
* Market data for holding
2024-08-10 13:30:19 -05:00
*
* @return void
*/
public function market_data ()
{
return $this -> hasOne ( MarketData :: class , 'symbol' , 'symbol' );
}
/**
2024-08-17 21:33:09 -05:00
* Related transactions for holding
2024-08-10 13:30:19 -05:00
*
* @return void
*/
public function transactions ()
{
2024-08-30 20:58:00 -05:00
return $this -> hasManyThrough ( Transaction :: class , Portfolio :: class , 'id' , 'portfolio_id' , 'portfolio_id' , 'id' ) -> orderBy ( 'date' , 'DESC' );
2024-08-10 13:30:19 -05:00
}
/**
2024-08-17 21:33:09 -05:00
* Related dividends for holding
2024-08-10 13:30:19 -05:00
*
* @return void
*/
public function dividends ()
{
2024-08-27 21:17:54 -05:00
return $this -> hasMany ( Dividend :: class , 'symbol' , 'symbol' )
2024-08-29 22:26:04 -05:00
-> select ([ 'dividends.symbol' , 'dividends.date' , 'dividends.dividend_amount' ])
2024-08-27 21:17:54 -05:00
-> selectRaw ( " SUM(
CASE WHEN transaction_type = 'BUY'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = ' $this->portfolio_id '
AND dividends.date >= transactions.date
THEN transactions.quantity
ELSE 0 END
) AS purchased " )
-> selectRaw ( " SUM(
CASE WHEN transaction_type = 'SELL'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = ' $this->portfolio_id '
AND dividends.date >= transactions.date
THEN transactions.quantity
ELSE 0 END
) AS sold " )
-> join ( 'transactions' , 'transactions.symbol' , 'dividends.symbol' )
2024-08-29 22:26:04 -05:00
-> groupBy ([ 'dividends.symbol' , 'dividends.date' , 'dividends.dividend_amount' ])
2024-08-27 21:17:54 -05:00
-> orderBy ( 'dividends.date' , 'DESC' )
-> where ( 'dividends.date' , '>=' , function ( $query ) {
$query -> selectRaw ( 'min(transactions.date)' )
-> from ( 'transactions' )
-> whereRaw ( " transactions.portfolio_id = ' $this->portfolio_id ' " )
-> whereRaw ( " transactions.symbol = ' $this->symbol ' " );
});
2024-08-10 13:30:19 -05:00
}
/**
2024-08-17 21:33:09 -05:00
* Related portfolio for holding
2024-08-10 13:30:19 -05:00
*
* @return void
*/
public function portfolio ()
{
return $this -> belongsTo ( Portfolio :: class );
}
/**
2024-08-17 21:33:09 -05:00
* Related splits for holding
2024-08-10 13:30:19 -05:00
*
* @return void
*/
public function splits ()
{
2024-08-27 21:17:54 -05:00
return $this -> hasMany ( Split :: class , 'symbol' , 'symbol' )
-> orderBy ( 'date' , 'DESC' );
2024-08-10 13:30:19 -05:00
}
2024-08-28 15:19:20 -05:00
public function scopeWithMarketData ( $query )
{
2024-08-28 23:32:01 -05:00
return $query -> withAggregate ( 'market_data' , 'name' )
-> withAggregate ( 'market_data' , 'market_value' )
-> withAggregate ( 'market_data' , 'fifty_two_week_low' )
-> withAggregate ( 'market_data' , 'fifty_two_week_high' )
-> withAggregate ( 'market_data' , 'updated_at' )
-> join ( 'market_data' , 'holdings.symbol' , 'market_data.symbol' );
2024-08-28 15:19:20 -05:00
}
2024-08-29 18:46:21 -05:00
public function scopeWithPerformance ( $query )
{
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.average_cost_basis), 0) AS market_gain_percent' );
}
2024-08-10 13:30:19 -05:00
public function scopePortfolio ( $query , $portfolio )
{
return $query -> where ( 'portfolio_id' , $portfolio );
}
2024-08-27 21:17:54 -05:00
public function scopeSymbol ( $query , $symbol )
{
2024-08-28 22:06:47 -05:00
return $query -> where ( 'holdings.symbol' , $symbol );
2024-08-27 21:17:54 -05:00
}
2024-08-10 13:30:19 -05:00
public function scopeWithoutWishlists ( $query ) {
return $query -> join ( 'portfolios' , 'portfolios.id' , 'holdings.portfolio_id' )
-> where ( 'portfolios.wishlist' , 0 );
}
public function scopeMyHoldings ( $query )
{
return $query -> whereHas ( 'portfolio' , function ( $query ) {
$query -> whereRelation ( 'users' , 'id' , auth () -> user () -> id );
});
}
2024-08-29 18:46:21 -05:00
public function scopeWithPortfolioMetrics ( $query )
2024-08-10 13:30:19 -05:00
{
2024-08-29 18:46:21 -05:00
return $query -> selectRaw ( 'COALESCE(SUM(holdings.dividends_earned),0) AS total_dividends_earned' )
2024-08-21 20:42:32 -05:00
-> selectRaw ( 'COALESCE(SUM(holdings.realized_gain_dollars),0) AS realized_gain_dollars' )
-> selectRaw ( '@total_market_value:=COALESCE(SUM(holdings.quantity * market_data.market_value),0) AS total_market_value' )
-> selectRaw ( '@sum_total_cost_basis:=COALESCE(SUM(holdings.total_cost_basis),0) AS total_cost_basis' )
-> selectRaw ( '@total_gain_dollars:=COALESCE((@total_market_value - @sum_total_cost_basis),0) AS total_gain_dollars' )
2024-08-26 22:13:00 -05:00
// ->selectRaw('COALESCE((@total_gain_dollars / @sum_total_cost_basis) * 100,0) AS total_gain_percent')
2024-08-10 13:30:19 -05:00
-> join ( 'market_data' , 'market_data.symbol' , 'holdings.symbol' );
}
2024-08-30 20:22:28 -05:00
2024-08-30 21:58:38 -05:00
public function syncTransactionsAndDividends ()
2024-08-30 20:22:28 -05:00
{
// pull existing transaction data
$query = Transaction :: where ([
'portfolio_id' => $this -> portfolio_id ,
'symbol' => $this -> symbol ,
]) -> 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 = "BUY" THEN (quantity * cost_basis) ELSE 0 END) AS `cost_basis`' )
-> selectRaw ( 'SUM(CASE WHEN transaction_type = "SELL" THEN ((sale_price - cost_basis) * quantity) ELSE 0 END) AS `realized_gains`' )
-> first ();
$total_quantity = $query -> qty_purchases - $query -> qty_sales ;
$average_cost_basis = $query -> qty_purchases > 0
? $query -> cost_basis / $query -> qty_purchases
: 0 ;
// pull dividend data joined with holdings/transactions
$dividends = Dividend :: where ([
'dividends.symbol' => $this -> symbol ,
])
-> select ([ 'holdings.portfolio_id' , 'dividends.date' , 'dividends.symbol' , 'dividends.dividend_amount' ])
-> selectRaw ( '@purchased:=(SELECT coalesce(SUM(quantity),0) FROM transactions WHERE transactions.transaction_type = "BUY" AND transactions.symbol = dividends.symbol AND date(transactions.date) <= date(dividends.date) AND holdings.portfolio_id = transactions.portfolio_id ) AS `purchased`' )
-> selectRaw ( '@sold:=(SELECT coalesce(SUM(quantity),0) FROM transactions WHERE transactions.transaction_type = "SELL" AND transactions.symbol = dividends.symbol AND date(transactions.date) <= date(dividends.date) AND holdings.portfolio_id = transactions.portfolio_id ) AS `sold`' )
-> selectRaw ( '@owned:=(@purchased - @sold) AS `owned`' )
-> selectRaw ( '@dividends_received:=(@owned * dividends.dividend_amount) AS `dividends_received`' )
-> join ( 'transactions' , 'transactions.symbol' , 'dividends.symbol' )
-> join ( 'holdings' , 'transactions.portfolio_id' , 'holdings.portfolio_id' )
-> groupBy ([ 'holdings.portfolio_id' , 'dividends.date' , 'dividends.symbol' , 'dividends.dividend_amount' ])
-> get ();
// update holding
$this -> fill ([
'quantity' => $total_quantity ,
'average_cost_basis' => $average_cost_basis ,
'total_cost_basis' => $total_quantity * $average_cost_basis ,
'realized_gain_dollars' => $query -> realized_gains ,
'dividends_earned' => $dividends -> where ( 'portfolio_id' , $this -> portfolio_id ) -> sum ( 'dividends_received' )
]);
$this -> save ();
}
2024-08-10 13:30:19 -05:00
}