adds feature to backfill daily change

This commit is contained in:
hackerESQ
2024-09-11 22:00:37 -05:00
parent a13d2d4323
commit c0c83ebdbd
4 changed files with 243 additions and 5 deletions
+47 -3
View File
@@ -30,6 +30,7 @@ class Holding extends Model
protected $casts = [
'splits_synced_at' => 'datetime',
'first_transaction_date' => 'datetime'
];
protected $attributes = [
@@ -133,7 +134,7 @@ class Holding extends Model
public function scopePortfolio($query, $portfolio)
{
return $query->where('portfolio_id', $portfolio);
return $query->where('holdings.portfolio_id', $portfolio);
}
public function scopeSymbol($query, $symbol)
@@ -220,6 +221,49 @@ class Holding extends Model
$this->save();
}
}
public function dailyPerformance(
\Illuminate\Support\Carbon $start_date = null,
\Illuminate\Support\Carbon $end_date = null,
) {
if ($start_date == null) $start_date = now();
if ($end_date == null) $end_date = now();
$date_interval = "DATE_ADD(date, INTERVAL 1 DAY)";
if (config('database.default') === 'sqlite') {
$date_interval = "date(date, '+1 day')";
}
return DB::table(DB::raw("(
WITH RECURSIVE date_series AS (
SELECT '{$start_date->format('Y-m-d')}' AS date
UNION ALL
SELECT $date_interval
FROM date_series
WHERE date < '{$end_date->format('Y-m-d')}'
)
SELECT date_series.date
FROM date_series
) as date_series")
)
->select([
'date_series.date',
DB::raw("
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) AS `owned`
"),
DB::raw("COALESCE(SUM(CASE WHEN transaction_type = 'BUY' THEN (quantity * cost_basis) ELSE 0 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) {
$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');
}
}
+69 -1
View File
@@ -2,7 +2,10 @@
namespace App\Models;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Model;
use App\Interfaces\MarketData\MarketDataInterface;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -74,7 +77,8 @@ class Portfolio extends Model
return $this->users()->firstWhere('owner', 1)?->id;
}
public static function syncUsers(self $model) {
public static function syncUsers(self $model)
{
// make sure we don't remove owner access
$user_id[$model->owner_id ?? auth()->user()->id] = ['owner' => true];
@@ -86,4 +90,68 @@ class Portfolio extends Model
// save
$model->users()->sync($user_id);
}
public function syncDailyChanges(): void
{
$holdings = $this->holdings()
->join('transactions', function($join) {
$join->on('transactions.symbol', '=', 'holdings.symbol')
->where('transactions.portfolio_id', '=', $this->id);
})
->select('holdings.*', DB::raw('min(transactions.date) as first_transaction_date')) // get first transaction date
->groupBy('holdings.symbol')
->get();
$total_performance = [];
$holdings->each(function($holding) use (&$total_performance) {
$all_history = app(MarketDataInterface::class)->history('ACME', $holding->first_transaction_date, now());
$quantity = $holding->dailyPerformance($holding->first_transaction_date, now());
$dividends = $holding->dividends->keyBy(function ($dividend, $key) {
return $dividend['date']->format('Y-m-d');
})->toArray();
$dividends_earned = 0;
$daily_performance = [];
$all_history->sortBy('date')->each(function ($history, $date) use ($quantity, $dividends, &$daily_performance, &$dividends_earned) {
$close = Arr::get($history, 'close', 0);
$total_market_value = $quantity->get($date)->owned * $close;
$dividend = Arr::get($dividends, $date, []);
$dividends_earned += $quantity->get($date)->owned * Arr::get($dividend, 'dividend_amount', 0);
$daily_performance[$date] = [
'date' => $date,
'portfolio_id' => $this->id,
'total_market_value' => $total_market_value,
'total_cost_basis' => $quantity->get($date)->cost_basis,
'total_gain' => $total_market_value - $quantity->get($date)->cost_basis,
'realized_gains' => $quantity->get($date)->realized_gains,
'total_dividends_earned' => $dividends_earned
];
});
foreach ($daily_performance as $date => $performance) {
if (!isset($total_performance[$date])) {
$total_performance[$date] = $performance;
} else {
$total_performance[$date]['total_market_value'] += $performance['total_market_value'];
$total_performance[$date]['total_cost_basis'] += $performance['total_cost_basis'];
$total_performance[$date]['total_gain'] += $performance['total_gain'];
$total_performance[$date]['realized_gains'] += $performance['realized_gains'];
$total_performance[$date]['total_dividends_earned'] += $performance['total_dividends_earned'];
}
}
});
$this->daily_change()->delete();
DailyChange::insert($total_performance);
}
}