2024-08-10 13:30:19 -05:00
< ? php
2025-01-28 17:33:54 -06:00
declare ( strict_types = 1 );
2024-08-10 13:30:19 -05:00
namespace App\Models ;
2025-04-09 19:25:15 -05:00
use App\Traits\HasMarketData ;
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 ;
2025-01-28 17:14:49 -06:00
use Illuminate\Database\Eloquent\Model ;
2025-02-25 20:09:03 -06:00
use Illuminate\Support\Arr ;
2025-04-09 19:25:15 -05:00
use Illuminate\Support\Collection ;
2025-01-28 17:14:49 -06:00
use Illuminate\Support\Facades\DB ;
2024-08-10 13:30:19 -05:00
class Holding extends Model
{
use HasFactory ;
2025-04-09 19:25:15 -05:00
use HasMarketData ;
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' ,
2025-01-28 17:14:49 -06:00
'reinvest_dividends' ,
2024-08-10 13:30:19 -05:00
];
protected $casts = [
2025-04-09 19:25:15 -05:00
'reinvest_dividends' => 'boolean' ,
2024-08-10 13:30:19 -05:00
'splits_synced_at' => 'datetime' ,
2024-10-18 20:46:22 -05:00
'first_transaction_date' => 'datetime' ,
2025-04-09 19:25:15 -05:00
'quantity' => 'float' ,
'average_cost_basis' => 'float' ,
'total_cost_basis' => 'float' ,
'realized_gain_dollars' => 'float' ,
'dividends_earned' => 'float' ,
'total_gain_dollars' => 'float' ,
'market_gain_dollars' => 'float' ,
'total_market_value' => 'float' ,
'total_dividends_earned' => 'float' ,
'market_data_market_value' => 'float' ,
'market_data_fifty_two_week_low' => 'float' ,
'market_data_fifty_two_week_high' => 'float' ,
'market_gain_percent' => 'float' ,
2024-08-10 13:30:19 -05:00
];
/**
2024-08-17 21:33:09 -05:00
* Related transactions for holding
2024-08-10 13:30:19 -05:00
*
* @return void
*/
2025-01-28 17:14:49 -06:00
public function transactions ()
2024-08-10 13:30:19 -05:00
{
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
*/
2025-01-28 17:14:49 -06:00
public function dividends ()
2024-08-10 13:30:19 -05:00
{
2024-08-27 21:17:54 -05:00
return $this -> hasMany ( Dividend :: class , 'symbol' , 'symbol' )
2025-04-09 19:25:15 -05:00
-> select ([ 'dividends.symbol' , 'dividends.date' , 'dividends.dividend_amount' , 'dividends.dividend_amount_base' ])
2025-01-28 17:14:49 -06:00
-> selectRaw ( " SUM(
2025-02-25 20:09:03 -06:00
CASE WHEN transaction_type = 'BUY'
AND transactions.symbol = dividends.symbol
2024-08-27 21:17:54 -05:00
AND transactions.portfolio_id = ' $this->portfolio_id '
2024-09-23 15:09:35 -05:00
AND date(dividends.date) >= date(transactions.date)
2024-08-27 21:17:54 -05:00
THEN transactions.quantity
ELSE 0 END
) AS purchased " )
2025-01-28 17:14:49 -06:00
-> selectRaw ( " SUM(
2024-08-27 21:17:54 -05:00
CASE WHEN transaction_type = 'SELL'
2025-02-25 20:09:03 -06:00
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = ' $this->portfolio_id '
2024-09-23 15:09:35 -05:00
AND date(dividends.date) >= date(transactions.date)
2024-08-27 21:17:54 -05:00
THEN transactions.quantity
ELSE 0 END
) AS sold " )
2025-01-28 17:14:49 -06:00
-> selectRaw ( " SUM(
2025-02-25 20:09:03 -06:00
(CASE WHEN transaction_type = 'BUY'
AND transactions.symbol = dividends.symbol
2024-10-28 21:17:53 -05:00
AND transactions.portfolio_id = ' $this->portfolio_id '
2025-02-25 20:09:03 -06:00
AND date(transactions.date) <= date(dividends.date)
2024-09-23 15:09:35 -05:00
THEN transactions.quantity ELSE 0 END
2025-02-25 20:09:03 -06:00
- CASE WHEN transaction_type = 'SELL'
AND transactions.symbol = dividends.symbol
2024-10-28 21:17:53 -05:00
AND transactions.portfolio_id = ' $this->portfolio_id '
2025-02-25 20:09:03 -06:00
AND date(transactions.date) <= date(dividends.date)
2024-09-23 15:09:35 -05:00
THEN transactions.quantity ELSE 0 END)
* dividends.dividend_amount
2024-10-28 21:17:53 -05:00
) AS total_received " )
2025-04-09 19:25:15 -05:00
-> selectRaw ( " SUM(
(CASE WHEN transaction_type = 'BUY'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = ' $this->portfolio_id '
AND date(transactions.date) <= 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 date(transactions.date) <= date(dividends.date)
THEN transactions.quantity ELSE 0 END)
* dividends.dividend_amount_base
) AS total_received_base " )
2025-01-28 17:14:49 -06:00
-> join ( 'transactions' , 'transactions.symbol' , 'dividends.symbol' )
2025-04-09 19:25:15 -05:00
-> groupBy ([ 'dividends.symbol' , 'dividends.date' , 'dividends.dividend_amount' , 'dividends.dividend_amount_base' ])
2025-01-28 17:14:49 -06: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 ' " );
})
2025-03-10 21:17:24 -05:00
-> 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)
2025-04-09 19:25:15 -05:00
) * dividends.dividend_amount_base > 0 " );
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
*/
2025-01-28 17:14:49 -06:00
public function portfolio ()
2024-08-10 13:30:19 -05:00
{
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
*/
2025-01-28 17:14:49 -06:00
public function splits ()
2024-08-10 13:30:19 -05:00
{
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-10-31 12:09:06 -05:00
/**
* Related chats for holding
*
* @return void
*/
public function chats ()
{
2024-11-03 08:41:14 -06:00
return $this -> morphMany ( AiChat :: class , 'chatable' ) -> where ( 'user_id' , auth () -> user () -> id );
2024-10-31 12:09:06 -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' )
2025-01-28 17:14:49 -06:00
-> withAggregate ( 'market_data' , 'market_value' )
2025-04-09 19:25:15 -05:00
-> withAggregate ( 'market_data' , 'market_value_base' )
2025-01-28 17:14:49 -06:00
-> 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
}
2025-04-09 19:25:15 -05:00
/**
* Calculate performance for holding in its local currency
*/
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' )
2025-03-10 21:17:24 -05:00
-> selectRaw ( 'COALESCE(((market_data.market_value - holdings.average_cost_basis) / NULLIF(holdings.average_cost_basis, 0)) * 100, 0) AS market_gain_percent' );
2024-08-29 18:46:21 -05:00
}
2024-08-10 13:30:19 -05:00
public function scopePortfolio ( $query , $portfolio )
{
2024-09-11 22:00:37 -05:00
return $query -> where ( 'holdings.portfolio_id' , $portfolio );
2024-08-10 13:30:19 -05:00
}
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
}
2025-01-28 17:14:49 -06:00
public function scopeWithoutWishlists ( $query )
{
2024-10-24 20:14:14 -05:00
return $query -> whereHas ( 'portfolio' , function ( $query ) {
2025-01-28 17:14:49 -06:00
$query -> where ( 'portfolios.wishlist' , 0 );
});
2024-08-10 13:30:19 -05:00
}
2024-10-23 16:57:55 -05:00
public function scopeMyHoldings ( $query , $userId = null )
2024-08-10 13:30:19 -05:00
{
2025-01-28 17:14:49 -06:00
return $query -> whereHas ( 'portfolio' , function ( $query ) use ( $userId ) {
2024-10-23 16:57:55 -05:00
$query -> whereRelation ( 'users' , 'id' , $userId ? ? auth () -> user () -> id );
2024-08-10 13:30:19 -05:00
});
}
2025-04-09 19:25:15 -05:00
/**
* Scope which returns collection of performance metrics for holdings
*
* @param string $currency Allows casting to specified currency
*/
public function scopeGetPortfolioMetrics ( $query , $currency = null ) : Collection
{
$result = $query -> withPortfolioMetrics ( $currency ) -> get ();
return collect ([
'total_cost_basis' => $result -> sum ( 'total_cost_basis' ),
'total_market_value' => $result -> sum ( 'total_market_value' ),
'total_gain_dollars' => $result -> sum ( 'total_gain_dollars' ),
'realized_gain_dollars' => $result -> sum ( 'realized_gain_dollars' ),
'total_dividends_earned' => $result -> sum ( 'total_dividends_earned' ),
]);
}
/**
* Scope to collect performance metrics for holdings
*
* @param string $currency Allows casting to specified currency
*/
public function scopeWithPortfolioMetrics ( $query , $currency = null ) : mixed
2024-08-10 13:30:19 -05:00
{
2025-04-09 19:25:15 -05:00
$currency = $currency ? ? auth () -> user () -> getCurrency ();
return $query -> select ([
'holdings.symbol' ,
'holdings.portfolio_id' ,
'transactions_display.total_cost_basis' ,
'transactions_display.realized_gain_dollars' ,
'dividends_display.total_dividends_earned' ,
])
-> groupBy ([
'holdings.symbol' ,
'holdings.quantity' ,
'holdings.portfolio_id' ,
'cr.rate' ,
'transactions_display.total_cost_basis' ,
'transactions_display.realized_gain_dollars' ,
'dividends_display.total_dividends_earned' ,
'market_data.market_value_base' ,
])
-> leftJoin ( 'currency_rates as cr' , function ( $join ) use ( $currency ) {
$join -> where ( 'cr.currency' , '=' , $currency );
if ( config ( 'database.default' ) === 'sqlite' ) {
$join -> whereRaw ( " strftime('%Y-%m-%d', cr.date) = ? " , [ now () -> toDateString ()]);
} else {
$join -> on ( 'cr.date' , '=' , DB :: raw ( " ' " . now () -> toDateString () . " ' " ));
}
})
-> leftJoin ( 'market_data' , function ( $join ) {
$join -> on ( 'market_data.symbol' , '=' , 'holdings.symbol' );
})
-> selectRaw (
'holdings.quantity * market_data.market_value_base * COALESCE(cr.rate, 1) AS total_market_value'
)
-> selectRaw ( '(
holdings.quantity * market_data.market_value_base * COALESCE(cr.rate, 1)
) - transactions_display.total_cost_basis as total_gain_dollars' )
-> leftJoinSub (
DB :: table ( 'transactions' )
-> leftJoin ( 'currency_rates as cr' , function ( $join ) use ( $currency ) {
$join -> on ( 'cr.date' , '=' , 'transactions.date' )
-> where ( 'cr.currency' , '=' , $currency );
})
-> select ([ 'transactions.symbol' , 'transactions.portfolio_id' ])
-> leftJoinSub (
DB :: table ( 'transactions' )
-> leftJoin ( 'currency_rates as cr' , function ( $join ) use ( $currency ) {
$join
-> on ( 'cr.date' , '=' , 'transactions.date' )
-> where ( 'cr.currency' , '=' , $currency );
})
-> select ([
'transactions.symbol' ,
'transactions.portfolio_id' ,
'transactions.quantity' ,
'transactions.date' ,
])
-> selectRaw (
" (CASE
2025-07-12 00:40:37 -05:00
WHEN
transactions.transaction_type = 'BUY'
OR SUM(transactions.cost_basis_base) = 0
THEN
COALESCE(cr.rate, 1)
2025-04-09 19:25:15 -05:00
ELSE (
SELECT
SUM(COALESCE(cr2.rate, 1) * buy.cost_basis_base)
/ SUM(buy.cost_basis_base)
FROM transactions as buy
LEFT JOIN currency_rates as cr2
ON cr2.date = buy.date
AND cr2.currency = ' { $currency } '
WHERE buy.symbol = transactions.symbol
AND buy.portfolio_id = transactions.portfolio_id
AND buy.transaction_type = 'BUY'
AND buy.date <= transactions.date
) END)
AS rate "
)
-> selectRaw (
2025-07-17 20:38:29 -05:00
" CASE
WHEN transactions.transaction_type = 'BUY'
THEN transactions.quantity
ELSE -transactions.quantity
END
AS remaining_quantity "
2025-04-09 19:25:15 -05:00
)
-> groupBy ([
'transactions.symbol' ,
'transactions.date' ,
'transactions.portfolio_id' ,
'transactions.transaction_type' ,
'transactions.quantity' ,
'cr.rate' ,
]), 'cost_basis_display' , function ( $join ) {
$join -> on ( 'transactions.symbol' , '=' , 'cost_basis_display.symbol' )
-> on ( 'transactions.portfolio_id' , '=' , 'cost_basis_display.portfolio_id' )
-> on ( 'transactions.date' , '=' , 'cost_basis_display.date' );
})
-> selectRaw (
" SUM(CASE WHEN transactions.transaction_type = 'SELL' THEN (transactions.sale_price_base - transactions.cost_basis_base) * transactions.quantity * COALESCE(cr.rate, 1) ELSE 0 END) AS realized_gain_dollars "
)
-> selectRaw (
2025-07-17 20:38:29 -05:00
" SUM(CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.cost_basis_base * transactions.quantity * cost_basis_display.rate END) / SUM(CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity END) * SUM(cost_basis_display.remaining_quantity) AS total_cost_basis "
2025-04-09 19:25:15 -05:00
)
-> groupBy ([ 'transactions.symbol' , 'transactions.portfolio_id' ]),
'transactions_display' ,
function ( $join ) {
$join -> on ( 'holdings.symbol' , '=' , 'transactions_display.symbol' )
-> on ( 'holdings.portfolio_id' , '=' , 'transactions_display.portfolio_id' );
}
)
-> leftJoinSub (
DB :: table ( 'dividends' )
-> join ( 'transactions as tx' , function ( $join ) {
$join -> on ( 'tx.symbol' , '=' , 'dividends.symbol' )
-> on ( 'tx.date' , '<=' , 'dividends.date' );
})
-> leftJoin ( 'currency_rates as cr' , function ( $join ) use ( $currency ) {
$join -> on ( 'cr.date' , '=' , 'dividends.date' )
-> where ( 'cr.currency' , '=' , $currency );
})
-> select ([ 'dividends.symbol' ])
-> selectRaw (
" SUM(((CASE WHEN transaction_type = 'BUY' THEN tx.quantity ELSE 0 END) - (CASE WHEN transaction_type = 'SELL' THEN tx.quantity ELSE 0 END)) * dividends.dividend_amount_base * COALESCE(cr.rate, 1)) AS total_dividends_earned "
)
-> groupBy ([ 'dividends.symbol' ]),
'dividends_display' ,
function ( $join ) {
$join -> on ( 'holdings.symbol' , '=' , 'dividends_display.symbol' );
}
);
2024-08-10 13:30:19 -05:00
}
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 ([
2025-01-28 17:14:49 -06:00
'portfolio_id' => $this -> portfolio_id ,
2025-04-09 19:25:15 -05:00
'transactions.symbol' => $this -> symbol ,
2025-03-10 21:17:24 -05:00
]) -> 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 " )
2025-04-09 19:25:15 -05:00
-> selectRaw ( " SUM(CASE WHEN transaction_type = 'SELL' THEN (sale_price - cost_basis) * quantity ELSE 0 END) AS realized_gain_dollars " )
2025-03-10 21:17:24 -05:00
-> selectRaw ( " SUM(CASE WHEN transaction_type = 'BUY' THEN (quantity * cost_basis) ELSE 0 END) AS total_cost_basis " )
2025-01-28 17:14:49 -06:00
-> first ();
2024-08-30 20:22:28 -05:00
2025-04-09 19:25:15 -05:00
$total_quantity = round ( $query -> qty_purchases - $query -> qty_sales , 4 );
2024-09-06 23:15:43 -05:00
$average_cost_basis = (
2025-01-28 17:14:49 -06:00
$query -> qty_purchases > 0
&& $total_quantity > 0
2025-03-10 21:17:24 -05:00
) ? $query -> total_cost_basis / $query -> qty_purchases
: 0 ;
2024-08-30 20:22:28 -05:00
// update holding
$this -> fill ([
'quantity' => $total_quantity ,
'average_cost_basis' => $average_cost_basis ,
'total_cost_basis' => $total_quantity * $average_cost_basis ,
2025-04-09 19:25:15 -05:00
'realized_gain_dollars' => $query -> realized_gain_dollars ? ? 0 ,
2025-01-28 17:14:49 -06:00
'dividends_earned' => $this -> dividends -> sum ( 'total_received' ),
2024-08-30 20:22:28 -05:00
]);
$this -> save ();
}
2024-08-10 13:30:19 -05:00
2025-01-28 17:14:49 -06:00
public function qtyOwned ( ? \Illuminate\Support\Carbon $date = null )
2024-10-18 20:46:22 -05:00
{
2025-01-28 17:14:49 -06:00
if ( $date == null ) {
$date = now ();
}
2024-10-18 20:46:22 -05:00
$transactions = $this -> transactions -> where ( 'date' , '<=' , $date );
$purchases = $transactions -> where ( 'transaction_type' , 'BUY' ) -> sum ( 'quantity' );
$sales = $transactions -> where ( 'transaction_type' , 'SELL' ) -> sum ( 'quantity' );
return $purchases - $sales ;
}
2025-04-09 19:25:15 -05:00
/**
* Method that enables calculating daily performance for a given holding
*
* @return void
*/
2024-09-11 22:00:37 -05:00
public function dailyPerformance (
2025-01-28 17:14:49 -06:00
? \Illuminate\Support\Carbon $start_date = null ,
? \Illuminate\Support\Carbon $end_date = null ,
2024-09-11 22:00:37 -05:00
) {
2025-01-28 17:14:49 -06:00
if ( $start_date == null ) {
$start_date = now ();
}
if ( $end_date == null ) {
$end_date = now ();
}
2024-09-11 22:00:37 -05:00
2025-03-10 21:17:24 -05:00
// MySQL default interval
2025-01-28 17:14:49 -06:00
$date_interval = 'DATE_ADD(date, INTERVAL 1 DAY)' ;
2025-03-10 21:17:24 -05:00
$castNumberType = 'decimal' ;
2024-09-11 22:00:37 -05:00
2025-03-10 21:17:24 -05:00
// Use SQLite interval grammar
2024-09-11 22:00:37 -05:00
if ( config ( 'database.default' ) === 'sqlite' ) {
2025-01-28 17:14:49 -06:00
2024-09-11 22:00:37 -05:00
$date_interval = " date(date, '+1 day') " ;
2025-03-10 21:17:24 -05:00
}
// Default CTE time series query (for MySQL and SQLite)
$timeSeriesQuery = DB :: table ( DB :: raw ( " (
WITH RECURSIVE date_series AS (
2025-04-09 19:25:15 -05:00
SELECT ' { $start_date -> toDateString () } ' AS date
2025-03-10 21:17:24 -05:00
UNION ALL
SELECT $date_interval
FROM date_series
2025-04-09 19:25:15 -05:00
WHERE date < ' { $end_date -> toDateString () } '
2025-03-10 21:17:24 -05:00
)
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(
2025-04-09 19:25:15 -05:00
date ' { $start_date -> toDateString () } ',
date ' { $end_date -> toDateString () } ',
2025-03-10 21:17:24 -05:00
interval '1 day'
) as date_series " ));
$castNumberType = 'numeric' ;
}
// Set MySQL-like query CTE max iterations
if ( config ( 'database.default' ) === 'mysql' ) {
2024-10-28 21:55:56 -05:00
2025-02-25 20:09:03 -06:00
// 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;' ),
2025-02-25 20:16:13 -06:00
'0' , new \stdClass
) -> version ;
2025-02-25 20:09:03 -06:00
if ( stripos ( $versionString , 'MariaDB' ) !== false ) {
$max_recursion_var_name = 'max_recursive_iterations' ; // Must be MariaDB
}
DB :: statement ( " SET $max_recursion_var_name =1000000; " );
2024-09-11 22:00:37 -05:00
}
2025-03-10 21:17:24 -05:00
// 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
2025-01-28 17:14:49 -06:00
-> select ([
'date_series.date' ,
DB :: raw ( "
2025-03-10 21:17:24 -05:00
{ $quantityQuery } AS owned
" ),
2025-01-28 17:14:49 -06:00
DB :: raw ( "
2025-03-10 21:17:24 -05:00
CASE
WHEN ( { $quantityQuery } ) = 0 THEN 0
ELSE SUM(CASE
2025-04-09 19:25:15 -05:00
WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity * transactions.cost_basis_base
2025-03-10 21:17:24 -05:00
ELSE 0
END)
END AS cost_basis
" ),
2025-04-09 19:25:15 -05:00
DB :: raw ( " COALESCE(SUM(CASE WHEN transaction_type = 'SELL' THEN ((sale_price_base - cost_basis_base) * quantity) ELSE 0 END), 0) AS realized_gains " ),
2025-01-28 17:14:49 -06:00
])
-> leftJoin ( 'transactions' , function ( $join ) {
$join -> on ( DB :: raw ( 'DATE(transactions.date)' ), '<=' , 'date_series.date' )
-> where ( 'transactions.symbol' , '=' , $this -> symbol )
-> where ( 'transactions.portfolio_id' , '=' , $this -> portfolio_id );
})
-> groupBy ( 'date_series.date' )
-> orderBy ( 'date_series.date' )
-> get ()
-> keyBy ( 'date' );
2024-09-11 22:00:37 -05:00
}
2024-10-31 17:04:59 -05:00
public function getFormattedTransactions ()
{
$formattedTransactions = '' ;
2025-01-28 17:14:49 -06:00
foreach ( $this -> transactions -> sortByDesc ( 'date' ) as $transaction ) {
2025-04-09 19:25:15 -05:00
$formattedTransactions .= ' * ' . $transaction -> date -> toDateString ()
2025-01-28 17:14:49 -06:00
. ' ' . $transaction -> transaction_type
. ' ' . $transaction -> quantity
. ' @ ' . $transaction -> cost_basis
2024-10-31 17:04:59 -05:00
. " each \n \n " ;
}
2025-01-28 17:14:49 -06:00
2024-10-31 17:04:59 -05:00
return $formattedTransactions ;
}
2025-01-28 17:14:49 -06:00
}