Files
sure/test/models/holding_test.rb
2025-11-23 20:43:55 +05:30

112 lines
4.2 KiB
Ruby

require "test_helper"
require "ostruct"
require "bigdecimal"
class HoldingTest < ActiveSupport::TestCase
include EntriesTestHelper, SecuritiesTestHelper
setup do
@account = families(:empty).accounts.create!(name: "Test Brokerage", balance: 20000, cash_balance: 0, currency: "USD", accountable: Investment.new)
# Current day holding instances
@amzn, @nvda = load_holdings
end
test "calculates portfolio weight" do
expected_amzn_weight = 3240.0 / @account.balance * 100
expected_nvda_weight = 3720.0 / @account.balance * 100
assert_in_delta expected_amzn_weight, @amzn.weight, 0.001
assert_in_delta expected_nvda_weight, @nvda.weight, 0.001
end
test "calculates average cost basis" do
create_trade(@amzn.security, account: @account, qty: 10, price: 212.00, date: 1.day.ago.to_date)
create_trade(@amzn.security, account: @account, qty: 15, price: 216.00, date: Date.current)
create_trade(@nvda.security, account: @account, qty: 5, price: 128.00, date: 1.day.ago.to_date)
create_trade(@nvda.security, account: @account, qty: 30, price: 124.00, date: Date.current)
# expected weighted averages (quantity-weighted)
amzn_total = BigDecimal("10") * BigDecimal("212.00") + BigDecimal("15") * BigDecimal("216.00")
amzn_qty = BigDecimal("10") + BigDecimal("15")
expected_amzn = amzn_total / amzn_qty
nvda_total = BigDecimal("5") * BigDecimal("128.00") + BigDecimal("30") * BigDecimal("124.00")
nvda_qty = BigDecimal("5") + BigDecimal("30")
expected_nvda = nvda_total / nvda_qty
assert_equal Money.new(expected_amzn), @amzn.avg_cost
assert_equal Money.new(expected_nvda), @nvda.avg_cost
end
test "calculates average cost basis from another currency" do
create_trade(@amzn.security, account: @account, qty: 10, price: 212.00, date: 1.day.ago.to_date, currency: "CAD")
create_trade(@amzn.security, account: @account, qty: 15, price: 216.00, date: Date.current, currency: "CAD")
create_trade(@nvda.security, account: @account, qty: 5, price: 128.00, date: 1.day.ago.to_date, currency: "CAD")
create_trade(@nvda.security, account: @account, qty: 30, price: 124.00, date: Date.current, currency: "CAD")
# compute expected: sum(price * qty * rate) / sum(qty)
amzn_total_usd = BigDecimal("10") * BigDecimal("212.00") * BigDecimal("1") +
BigDecimal("15") * BigDecimal("216.00") * BigDecimal("1")
amzn_qty = BigDecimal("10") + BigDecimal("15")
expected_amzn_usd = amzn_total_usd / amzn_qty
nvda_total_usd = BigDecimal("5") * BigDecimal("128.00") * BigDecimal("1") +
BigDecimal("30") * BigDecimal("124.00") * BigDecimal("1")
nvda_qty = BigDecimal("5") + BigDecimal("30")
expected_nvda_usd = nvda_total_usd / nvda_qty
assert_equal Money.new(expected_amzn_usd, "CAD").exchange_to("USD", fallback_rate: 1), @amzn.avg_cost
assert_equal Money.new(expected_nvda_usd, "CAD").exchange_to("USD", fallback_rate: 1), @nvda.avg_cost
end
test "calculates total return trend" do
@amzn.stubs(:avg_cost).returns(Money.new(214.00))
@nvda.stubs(:avg_cost).returns(Money.new(126.00))
# Gained $30, or 0.93%
assert_equal Money.new(30), @amzn.trend.value
assert_in_delta 0.9, @amzn.trend.percent, 0.001
# Lost $60, or -1.59%
assert_equal Money.new(-60), @nvda.trend.value
assert_in_delta -1.6, @nvda.trend.percent, 0.001
end
private
def load_holdings
security1 = create_security("AMZN", prices: [
{ date: 1.day.ago.to_date, price: 212.00 },
{ date: Date.current, price: 216.00 }
])
security2 = create_security("NVDA", prices: [
{ date: 1.day.ago.to_date, price: 128.00 },
{ date: Date.current, price: 124.00 }
])
create_holding(security1, 1.day.ago.to_date, 10)
amzn = create_holding(security1, Date.current, 15)
create_holding(security2, 1.day.ago.to_date, 5)
nvda = create_holding(security2, Date.current, 30)
[ amzn, nvda ]
end
def create_holding(security, date, qty)
price = Security::Price.find_by(date: date, security: security).price
@account.holdings.create! \
date: date,
security: security,
qty: qty,
price: price,
amount: qty * price,
currency: "USD"
end
end