simplify tests and daily change calculations

This commit is contained in:
hackerESQ
2024-09-23 15:09:35 -05:00
parent d0fbf44fa0
commit 7d119a8c24
8 changed files with 77 additions and 65 deletions
+15 -23
View File
@@ -71,7 +71,7 @@ class Holding extends Model
CASE WHEN transaction_type = 'BUY'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = '$this->portfolio_id'
AND dividends.date >= transactions.date
AND date(dividends.date) >= date(transactions.date)
THEN transactions.quantity
ELSE 0 END
) AS purchased")
@@ -79,10 +79,19 @@ class Holding extends Model
CASE WHEN transaction_type = 'SELL'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = '$this->portfolio_id'
AND dividends.date >= transactions.date
AND date(dividends.date) >= date(transactions.date)
THEN transactions.quantity
ELSE 0 END
) AS sold")
->selectRaw('SUM(
(CASE WHEN transaction_type = "BUY"
AND date(transactions.date) <= date(dividends.date)
THEN transactions.quantity ELSE 0 END
- CASE WHEN transaction_type = "SELL"
AND date(transactions.date) <= date(dividends.date)
THEN transactions.quantity ELSE 0 END)
* dividends.dividend_amount
) AS total_received')
->join('transactions', 'transactions.symbol', 'dividends.symbol')
->groupBy(['dividends.symbol','dividends.date','dividends.dividend_amount'])
->orderBy('dividends.date', 'DESC')
@@ -186,32 +195,15 @@ class Holding extends Model
? $query->total_cost_basis / $query->qty_purchases
: 0;
// pull dividend data joined with holdings/transactions
$dividends = Dividend::select('holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount')
->selectRaw('
(COALESCE(CASE WHEN transactions.transaction_type = "BUY"
AND date(transactions.date) <= date(dividends.date)
THEN transactions.quantity ELSE 0 END, 0)
- COALESCE(CASE WHEN transactions.transaction_type = "SELL"
AND date(transactions.date) <= date(dividends.date)
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')
->where('dividends.symbol', $this->symbol)
->where('transactions.portfolio_id', $this->portfolio_id)
->groupBy('holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount', 'total_received')
->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->total_sale_price > 0 ? $query->total_sale_price - ($query->qty_sales * ($query->total_cost_basis / $query->qty_purchases)) : 0,
'dividends_earned' => $dividends->sum('total_received')
'realized_gain_dollars' => $query->qty_purchases > 0 && $query->total_sale_price > 0
? $query->total_sale_price - ($query->qty_sales * ($query->total_cost_basis / $query->qty_purchases))
: 0,
'dividends_earned' => $this->dividends->sum('total_received')
]);
$this->save();
+11 -9
View File
@@ -127,15 +127,17 @@ class Portfolio extends Model
$total_market_value = $performance->owned * $close;
$dividends_earned += $performance->owned * ($dividends->get($date)?->dividend_amount ?? 0);
$daily[$date] = [
'date' => $date,
'portfolio_id' => $this->id,
'total_market_value' => $total_market_value,
'total_cost_basis' => $performance->cost_basis,
'total_gain' => $total_market_value - $performance->cost_basis,
'realized_gains' => $performance->realized_gains,
'total_dividends_earned' => $dividends_earned
];
if (Carbon::parse($date)->isWeekday()) {
$daily[$date] = [
'date' => $date,
'portfolio_id' => $this->id,
'total_market_value' => $total_market_value,
'total_cost_basis' => $performance->cost_basis,
'total_gain' => $total_market_value - $performance->cost_basis,
'realized_gains' => $performance->realized_gains,
'total_dividends_earned' => $dividends_earned
];
}
});
foreach ($daily as $date => $performance) {
@@ -17,7 +17,7 @@
'enabled' => false
]
],
'colors' => ['#3185FC', '#48435C', '#9792E3', '#00E396', '#B74F6F', ],
'colors' => ['#3185FC', '#48435C', '#9792E3', '#00E396', '#B74F6F'],
'stroke' => [
'curve' => "smooth",
'width' => 3
@@ -11,7 +11,7 @@ new class extends Component {
public ?Portfolio $portfolio;
public String $name = 'portfolio';
public String $scope = 'YTD';
public Array $options = [
public Array $scopeOptions = [
['id' => '1M', 'name' => '1 month', 'method' => 'subMonths', 'args' => [1]],
['id' => '3M', 'name' => '3 months', 'method' => 'subMonths', 'args' => [3]],
['id' => 'YTD', 'name' => 'Year to date', 'method' => 'startOfYear', 'args' => []],
@@ -31,7 +31,7 @@ new class extends Component {
public function generatePerformanceData()
{
$filterMethod = collect($this->options)->where('id', $this->scope)->first();
$filterMethod = collect($this->scopeOptions)->where('id', $this->scope)->first();
$dailyChangeQuery = DailyChange::query();
@@ -41,14 +41,15 @@ new class extends Component {
} else {
$dailyChangeQuery->selectRaw('date,
$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'
)->groupBy('date');
SUM(total_gain) as total_gain,
--SUM(realized_gains) as realized_gains,
--SUM(total_dividends_earned) as total_dividends_earned
')->groupBy('date');
// SUM(total_dividends_earned) as total_dividends_earned,
// SUM(realized_gains) as realized_gains
}
if ($filterMethod['method']) {
@@ -72,10 +73,11 @@ new class extends Component {
'name' => __('Market Gain'),
'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_gain])->toArray()
],
[
'name' => __('Dividends Earned'),
'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_dividends_earned])->toArray()
],
// [
// 'name' => __('Dividends Earned'),
// 'data' => $dailyChange->map(fn($data) => [$data->date, $data->total_dividends_earned])->toArray()
// ],
// [
// 'name' => __('Realized Gains'),
// 'data' => $dailyChange->map(fn($data) => [$data->date, $data->realized_gains])->toArray()
@@ -93,7 +95,7 @@ new class extends Component {
public function getScopeName($scope)
{
return collect($this->options)->where('id', $scope)->first()['name'];
return collect($this->scopeOptions)->where('id', $scope)->first()['name'];
}
}; ?>
@@ -115,7 +117,7 @@ new class extends Component {
<x-dropdown title="{{ __('Choose time period') }}" label="{{ $scope }}" class="btn-ghost btn-sm" x-bind:disabled="loading">
@foreach($options as $option)
@foreach($scopeOptions as $option)
<x-menu-item
title="{{ $option['name'] }}"
+2 -2
View File
@@ -21,8 +21,8 @@ class CaptureDailyChangeTest extends TestCase
$this->actingAs($user = User::factory()->create());
$this->portfolio = Portfolio::factory()->create();
Transaction::factory(5)->buy()->portfolio($this->portfolio->id)->symbol('AAPL')->create();
$this->transaction = Transaction::factory()->sell()->portfolio($this->portfolio->id)->symbol('AAPL')->create();
Transaction::factory(5)->buy()->lastYear()->portfolio($this->portfolio->id)->symbol('AAPL')->create();
$this->transaction = Transaction::factory()->sell()->lastMonth()->portfolio($this->portfolio->id)->symbol('AAPL')->create();
}
/**
+4 -4
View File
@@ -55,9 +55,9 @@ class DashboardTest extends TestCase
$this->actingAs($user = User::factory()->create());
$portfolio = Portfolio::factory()->create();
Transaction::factory(5)->buy()->portfolio($portfolio->id)->symbol('AAPL')->create();
$transaction = Transaction::factory()->sell()->portfolio($portfolio->id)->symbol('AAPL')->create();
Transaction::factory(5)->buy()->lastYear()->portfolio($portfolio->id)->symbol('AAPL')->create();
$transaction = Transaction::factory()->sell()->lastMonth()->portfolio($portfolio->id)->symbol('AAPL')->create();
$metrics = Holding::query()
->myHoldings()
@@ -65,7 +65,7 @@ class DashboardTest extends TestCase
->first();
$this->assertEqualsWithDelta(
$transaction->sale_price - $transaction->cost_basis,
$transaction->sale_price - $transaction->cost_basis,
$metrics->realized_gain_dollars,
0.01
);
+25 -9
View File
@@ -5,6 +5,7 @@ namespace Tests;
use Tests\TestCase;
use App\Models\User;
use App\Models\Holding;
use Carbon\CarbonPeriod;
use App\Models\Portfolio;
use App\Models\DailyChange;
use App\Models\Transaction;
@@ -33,7 +34,7 @@ class SyncDailyChangeTest extends TestCase
$portfolio->syncDailyChanges();
$count_of_daily_changes = $portfolio->daily_change()->count('date');
$days_between_now_and_first_trans = (int) now()->diffInDays($portfolio->transactions()->min('date'), true) + 1;
$days_between_now_and_first_trans = (int) CarbonPeriod::create($portfolio->transactions()->min('date'), now())->filter('isWeekday')->count();
$this->assertEquals($count_of_daily_changes, $days_between_now_and_first_trans);
}
@@ -49,19 +50,28 @@ class SyncDailyChangeTest extends TestCase
$first_transaction = Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create();
Artisan::call('sync:daily-change', ['portfolio_id' => $portfolio->id]);
$holding = Holding::symbol('ACME')->portfolio($portfolio->id)->first();
$daily_change = DailyChange::whereDate('date', $first_transaction->date)->first();
$daily_change = DailyChange::whereDate('date', '<=', $first_transaction->date->addDays(2))
->whereDate('date', '>=', $first_transaction->date->subDays(2))
->orderByDesc('date')
->first();
$this->assertEquals($holding->average_cost_basis, $daily_change->total_cost_basis);
$second_transaction = Transaction::factory()->buy()->lastYear()->portfolio($portfolio->id)->symbol('ACME')->create();
Artisan::call('sync:daily-change', ['portfolio_id' => $portfolio->id]);
$daily_change = DailyChange::whereDate('date', $second_transaction->date)->first();
$daily_change = DailyChange::whereDate('date', '<=', $second_transaction->date->addDays(2))
->whereDate('date', '>=', $second_transaction->date->subDays(2))
->orderByDesc('date')
->first();
$this->assertEqualsWithDelta($first_transaction->cost_basis + $second_transaction->cost_basis, $daily_change->total_cost_basis, 0.01);
$third_transaction = Transaction::factory(2)->sell()->lastMonth()->portfolio($portfolio->id)->symbol('ACME')->create()->first();
Artisan::call('sync:daily-change', ['portfolio_id' => $portfolio->id]);
$daily_change = DailyChange::whereDate('date', $third_transaction->date)->first();
$daily_change = DailyChange::whereDate('date', '<=', $third_transaction->date->addDays(2))
->whereDate('date', '>=', $third_transaction->date->subDays(2))
->orderByDesc('date')
->first();
$this->assertEquals(0, $daily_change->total_cost_basis);
}
@@ -99,12 +109,14 @@ class SyncDailyChangeTest extends TestCase
$this->assertEquals($day_before->realized_gains, 0);
$day_after = DailyChange::query()
$after = DailyChange::query()
->portfolio($portfolio->id)
->whereDate('date', $sale_transaction->date->addDays(1))
->whereDate('date', '<=', $sale_transaction->date->addDays(2))
->whereDate('date', '>=', $sale_transaction->date->subDays(2))
->orderByDesc('date')
->first();
$this->assertEqualsWithDelta($day_after->realized_gains, $realized_gain, 0.01);
$this->assertEqualsWithDelta($after->realized_gains, $realized_gain, 0.01);
}
public function test_dividends_captured_in_daily_change_sync(): void
@@ -124,7 +136,9 @@ class SyncDailyChangeTest extends TestCase
$first_dividend_change = DailyChange::query()
->portfolio($portfolio->id)
->whereDate('date', $dividends->first()->date)
->whereDate('date', '<=', $dividends->first()->date->addDays(2))
->whereDate('date', '>=', $dividends->first()->date->subDays(2))
->orderByDesc('date')
->first();
$owned = $dividends->first()->purchased - $dividends->first()->sold;
@@ -133,7 +147,9 @@ class SyncDailyChangeTest extends TestCase
$last_dividend_change = DailyChange::query()
->portfolio($portfolio->id)
->whereDate('date', $dividends->last()->date)
->whereDate('date', '<=', $dividends->last()->date->addDays(2))
->whereDate('date', '>=', $dividends->last()->date->subDays(2))
->orderByDesc('date')
->first();
$total_dividends = $dividends->reduce(function (?float $carry, $dividend) {
+4 -4
View File
@@ -30,9 +30,9 @@ class TransactionsTest extends TestCase
{
$this->actingAs($user = User::factory()->create());
Transaction::factory(5)->buy()->symbol('AAPL')->create();
Transaction::factory(5)->buy()->lastYear()->symbol('AAPL')->create();
$transaction = Transaction::factory()->sell()->symbol('AAPL')->create();
$transaction = Transaction::factory()->sell()->lastMonth()->symbol('AAPL')->create();
$this->assertNotNull($transaction->cost_basis);
}
@@ -56,8 +56,8 @@ class TransactionsTest extends TestCase
$portfolio = Portfolio::factory()->create();
Transaction::factory(5)->buy()->portfolio($portfolio->id)->symbol('AAPL')->create();
$transaction = Transaction::factory()->sell()->portfolio($portfolio->id)->symbol('AAPL')->create();
Transaction::factory(5)->buy()->lastYear()->portfolio($portfolio->id)->symbol('AAPL')->create();
$transaction = Transaction::factory()->sell()->lastMonth()->portfolio($portfolio->id)->symbol('AAPL')->create();
$this->assertDatabaseHas('holdings', [
'portfolio_id' => $portfolio->id,