fix: holding calculations
This commit is contained in:
+60
-34
@@ -246,8 +246,6 @@ class Holding extends Model
|
|||||||
return $query->select([
|
return $query->select([
|
||||||
'holdings.symbol',
|
'holdings.symbol',
|
||||||
'holdings.portfolio_id',
|
'holdings.portfolio_id',
|
||||||
'transactions_display.total_cost_basis',
|
|
||||||
'transactions_display.realized_gain_dollars',
|
|
||||||
'dividends_display.total_dividends_earned',
|
'dividends_display.total_dividends_earned',
|
||||||
])
|
])
|
||||||
->groupBy([
|
->groupBy([
|
||||||
@@ -255,8 +253,6 @@ class Holding extends Model
|
|||||||
'holdings.quantity',
|
'holdings.quantity',
|
||||||
'holdings.portfolio_id',
|
'holdings.portfolio_id',
|
||||||
'cr.rate',
|
'cr.rate',
|
||||||
'transactions_display.total_cost_basis',
|
|
||||||
'transactions_display.realized_gain_dollars',
|
|
||||||
'dividends_display.total_dividends_earned',
|
'dividends_display.total_dividends_earned',
|
||||||
'market_data.market_value_base',
|
'market_data.market_value_base',
|
||||||
])
|
])
|
||||||
@@ -264,10 +260,10 @@ class Holding extends Model
|
|||||||
$join->where('cr.currency', '=', $currency);
|
$join->where('cr.currency', '=', $currency);
|
||||||
|
|
||||||
if (config('database.default') === 'sqlite') {
|
if (config('database.default') === 'sqlite') {
|
||||||
|
$join->whereRaw("strftime('%Y-%m-%d', cr.date) = ?", [
|
||||||
$join->whereRaw("strftime('%Y-%m-%d', cr.date) = ?", [now()->toDateString()]);
|
now()->toDateString(),
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$join->on('cr.date', '=', DB::raw("'".now()->toDateString()."'"));
|
$join->on('cr.date', '=', DB::raw("'".now()->toDateString()."'"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -277,16 +273,29 @@ class Holding extends Model
|
|||||||
->selectRaw(
|
->selectRaw(
|
||||||
'holdings.quantity * market_data.market_value_base * COALESCE(cr.rate, 1) AS total_market_value'
|
'holdings.quantity * market_data.market_value_base * COALESCE(cr.rate, 1) AS total_market_value'
|
||||||
)
|
)
|
||||||
->selectRaw('(
|
->selectRaw(
|
||||||
|
'SUM(transactions_display.realized_gain_dollars) * COALESCE(cr.rate, 1) AS realized_gain_dollars'
|
||||||
|
)
|
||||||
|
->selectRaw(
|
||||||
|
'(SUM(transactions_display.total_cost_basis) / SUM(transactions_display.total_purchases)) * holdings.quantity AS total_cost_basis'
|
||||||
|
)
|
||||||
|
->selectRaw(
|
||||||
|
'(
|
||||||
holdings.quantity * market_data.market_value_base * COALESCE(cr.rate, 1)
|
holdings.quantity * market_data.market_value_base * COALESCE(cr.rate, 1)
|
||||||
) - transactions_display.total_cost_basis as total_gain_dollars')
|
) - (SUM(transactions_display.total_cost_basis) / SUM(transactions_display.total_purchases)) * holdings.quantity AS total_gain_dollars'
|
||||||
|
)
|
||||||
->leftJoinSub(
|
->leftJoinSub(
|
||||||
DB::table('transactions')
|
DB::table('transactions')
|
||||||
->leftJoin('currency_rates as cr', function ($join) use ($currency) {
|
->leftJoin('currency_rates as cr', function ($join) use ($currency) {
|
||||||
$join->on('cr.date', '=', 'transactions.date')
|
$join
|
||||||
|
->on('cr.date', '=', 'transactions.date')
|
||||||
->where('cr.currency', '=', $currency);
|
->where('cr.currency', '=', $currency);
|
||||||
})
|
})
|
||||||
->select(['transactions.symbol', 'transactions.portfolio_id'])
|
->select([
|
||||||
|
'transactions.id',
|
||||||
|
'transactions.symbol',
|
||||||
|
'transactions.portfolio_id',
|
||||||
|
])
|
||||||
->leftJoinSub(
|
->leftJoinSub(
|
||||||
DB::table('transactions')
|
DB::table('transactions')
|
||||||
->leftJoin('currency_rates as cr', function ($join) use ($currency) {
|
->leftJoin('currency_rates as cr', function ($join) use ($currency) {
|
||||||
@@ -298,10 +307,11 @@ class Holding extends Model
|
|||||||
'transactions.symbol',
|
'transactions.symbol',
|
||||||
'transactions.portfolio_id',
|
'transactions.portfolio_id',
|
||||||
'transactions.quantity',
|
'transactions.quantity',
|
||||||
|
'transactions.cost_basis_base',
|
||||||
'transactions.date',
|
'transactions.date',
|
||||||
])
|
])
|
||||||
->selectRaw(
|
->selectRaw("
|
||||||
"(CASE
|
(CASE
|
||||||
WHEN
|
WHEN
|
||||||
transactions.transaction_type = 'BUY'
|
transactions.transaction_type = 'BUY'
|
||||||
OR SUM(transactions.cost_basis_base) = 0
|
OR SUM(transactions.cost_basis_base) = 0
|
||||||
@@ -320,49 +330,64 @@ class Holding extends Model
|
|||||||
AND buy.transaction_type = 'BUY'
|
AND buy.transaction_type = 'BUY'
|
||||||
AND buy.date <= transactions.date
|
AND buy.date <= transactions.date
|
||||||
) END)
|
) END)
|
||||||
AS rate"
|
AS rate")
|
||||||
)
|
|
||||||
->selectRaw(
|
|
||||||
"CASE
|
|
||||||
WHEN transactions.transaction_type = 'BUY'
|
|
||||||
THEN transactions.quantity
|
|
||||||
ELSE -transactions.quantity
|
|
||||||
END
|
|
||||||
AS remaining_quantity"
|
|
||||||
)
|
|
||||||
->groupBy([
|
->groupBy([
|
||||||
|
'transactions.id',
|
||||||
'transactions.symbol',
|
'transactions.symbol',
|
||||||
'transactions.date',
|
'transactions.date',
|
||||||
'transactions.portfolio_id',
|
'transactions.portfolio_id',
|
||||||
'transactions.transaction_type',
|
'transactions.transaction_type',
|
||||||
|
'transactions.cost_basis_base',
|
||||||
'transactions.quantity',
|
'transactions.quantity',
|
||||||
'cr.rate',
|
'cr.rate',
|
||||||
]), 'cost_basis_display', function ($join) {
|
]),
|
||||||
$join->on('transactions.symbol', '=', 'cost_basis_display.symbol')
|
'cost_basis_display',
|
||||||
->on('transactions.portfolio_id', '=', 'cost_basis_display.portfolio_id')
|
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');
|
->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(
|
->selectRaw(
|
||||||
"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"
|
"CASE WHEN transactions.transaction_type = 'SELL' THEN (transactions.sale_price_base - transactions.cost_basis_base) * transactions.quantity * COALESCE(cr.rate, 1) END AS realized_gain_dollars"
|
||||||
)
|
)
|
||||||
->groupBy(['transactions.symbol', 'transactions.portfolio_id']),
|
->selectRaw(
|
||||||
|
"CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.cost_basis_base * transactions.quantity * cost_basis_display.rate END AS total_cost_basis"
|
||||||
|
)
|
||||||
|
->selectRaw(
|
||||||
|
"CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity END AS total_purchases"
|
||||||
|
)
|
||||||
|
->groupBy([
|
||||||
|
'transactions.id',
|
||||||
|
'transactions.symbol',
|
||||||
|
'transactions.portfolio_id',
|
||||||
|
'transactions.cost_basis_base',
|
||||||
|
'transactions.quantity',
|
||||||
|
'cost_basis_display.rate',
|
||||||
|
'cr.rate',
|
||||||
|
]),
|
||||||
'transactions_display',
|
'transactions_display',
|
||||||
function ($join) {
|
function ($join) {
|
||||||
$join->on('holdings.symbol', '=', 'transactions_display.symbol')
|
$join
|
||||||
|
->on('holdings.symbol', '=', 'transactions_display.symbol')
|
||||||
->on('holdings.portfolio_id', '=', 'transactions_display.portfolio_id');
|
->on('holdings.portfolio_id', '=', 'transactions_display.portfolio_id');
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
->leftJoinSub(
|
->leftJoinSub(
|
||||||
DB::table('dividends')
|
DB::table('dividends')
|
||||||
->join('transactions as tx', function ($join) {
|
->join('transactions as tx', function ($join) {
|
||||||
$join->on('tx.symbol', '=', 'dividends.symbol')
|
$join
|
||||||
|
->on('tx.symbol', '=', 'dividends.symbol')
|
||||||
->on('tx.date', '<=', 'dividends.date');
|
->on('tx.date', '<=', 'dividends.date');
|
||||||
})
|
})
|
||||||
->leftJoin('currency_rates as cr', function ($join) use ($currency) {
|
->leftJoin('currency_rates as cr', function ($join) use ($currency) {
|
||||||
$join->on('cr.date', '=', 'dividends.date')
|
$join
|
||||||
|
->on('cr.date', '=', 'dividends.date')
|
||||||
->where('cr.currency', '=', $currency);
|
->where('cr.currency', '=', $currency);
|
||||||
})
|
})
|
||||||
->select(['dividends.symbol'])
|
->select(['dividends.symbol'])
|
||||||
@@ -375,6 +400,7 @@ class Holding extends Model
|
|||||||
$join->on('holdings.symbol', '=', 'dividends_display.symbol');
|
$join->on('holdings.symbol', '=', 'dividends_display.symbol');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncTransactionsAndDividends()
|
public function syncTransactionsAndDividends()
|
||||||
|
|||||||
@@ -43,4 +43,20 @@ class HoldingsTest extends TestCase
|
|||||||
$holding = Holding::query()->getPortfolioMetrics();
|
$holding = Holding::query()->getPortfolioMetrics();
|
||||||
$this->assertEquals(0, $holding->get('total_cost_basis'));
|
$this->assertEquals(0, $holding->get('total_cost_basis'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_calculates_cost_bases_on_same_day_buy_sell_transaction(): void
|
||||||
|
{
|
||||||
|
$this->actingAs($user = User::factory()->create());
|
||||||
|
|
||||||
|
$portfolio = Portfolio::factory()->create();
|
||||||
|
|
||||||
|
Transaction::factory(2)->buy()->lastYear()->costBasis(100)->portfolio($portfolio->id)->symbol('AAPL')->create();
|
||||||
|
Transaction::factory(2)->buy()->lastYear()->costBasis(300)->portfolio($portfolio->id)->symbol('AAPL')->create();
|
||||||
|
|
||||||
|
Transaction::factory()->sell()->lastYear()->portfolio($portfolio->id)->symbol('AAPL')->create();
|
||||||
|
Transaction::factory()->sell()->recent()->portfolio($portfolio->id)->symbol('AAPL')->create();
|
||||||
|
|
||||||
|
$holding = Holding::query()->getPortfolioMetrics();
|
||||||
|
$this->assertEquals(400, $holding->get('total_cost_basis'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user