diff --git a/app/models/holding.rb b/app/models/holding.rb index fb9b001e..af659f3e 100644 --- a/app/models/holding.rb +++ b/app/models/holding.rb @@ -27,10 +27,18 @@ class Holding < ApplicationRecord # Basic approximation of cost-basis def avg_cost - avg_cost = account.entries.trades - .joins("INNER JOIN trades ON trades.id = entries.entryable_id") - .where("trades.security_id = ? AND trades.qty > 0 AND entries.date <= ?", security.id, date) - .average(:price) + avg_cost = account.trades + .with_entry + .joins(ActiveRecord::Base.sanitize_sql_array([ + "LEFT JOIN exchange_rates ON ( + exchange_rates.date = entries.date AND + exchange_rates.from_currency = trades.currency AND + exchange_rates.to_currency = ? + )", account.currency + ])) + .where(security_id: security.id) + .where("trades.qty > 0 AND entries.date <= ?", date) + .average("trades.price * COALESCE(exchange_rates.rate, 1)") Money.new(avg_cost || price, currency) end diff --git a/test/models/holding_test.rb b/test/models/holding_test.rb index 53fddf95..c3cc9a7a 100644 --- a/test/models/holding_test.rb +++ b/test/models/holding_test.rb @@ -19,15 +19,26 @@ class HoldingTest < ActiveSupport::TestCase assert_in_delta expected_nvda_weight, @nvda.weight, 0.001 end - test "calculates simple average cost basis" do + 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) - assert_equal Money.new((212.0 + 216.0) / 2), @amzn.avg_cost - assert_equal Money.new((128.0 + 124.0) / 2), @nvda.avg_cost + assert_equal Money.new((212.00 + 216.00).to_d / 2), @amzn.avg_cost + assert_equal Money.new((128.00 + 124.00).to_d / 2), @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") + + assert_equal Money.new((212.00 + 216.00).to_d / 2, "CAD").exchange_to("USD", fallback_rate: 1), @amzn.avg_cost + assert_equal Money.new((128.00 + 124.00).to_d / 2, "CAD").exchange_to("USD", fallback_rate: 1), @nvda.avg_cost end test "calculates total return trend" do diff --git a/test/support/entries_test_helper.rb b/test/support/entries_test_helper.rb index c17b8065..908f9676 100644 --- a/test/support/entries_test_helper.rb +++ b/test/support/entries_test_helper.rb @@ -28,20 +28,20 @@ module EntriesTestHelper Entry.create! entry_defaults.merge(attributes) end - def create_trade(security, account:, qty:, date:, price: nil) + def create_trade(security, account:, qty:, date:, price: nil, currency: "USD") trade_price = price || Security::Price.find_by!(security: security, date: date).price trade = Trade.new \ qty: qty, security: security, - price: trade_price, - currency: "USD" + price: trade_price, + currency: currency account.entries.create! \ name: "Trade", date: date, amount: qty * trade_price, - currency: "USD", + currency: currency, entryable: trade end end