mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-21 22:29:38 +02:00
holding: Add multi-currency support for average costs calculations. (#2153)
Fixes: #2051.
This commit is contained in:
parent
60c3a04a48
commit
8c10e87387
3 changed files with 30 additions and 11 deletions
|
@ -27,10 +27,18 @@ class Holding < ApplicationRecord
|
||||||
|
|
||||||
# Basic approximation of cost-basis
|
# Basic approximation of cost-basis
|
||||||
def avg_cost
|
def avg_cost
|
||||||
avg_cost = account.entries.trades
|
avg_cost = account.trades
|
||||||
.joins("INNER JOIN trades ON trades.id = entries.entryable_id")
|
.with_entry
|
||||||
.where("trades.security_id = ? AND trades.qty > 0 AND entries.date <= ?", security.id, date)
|
.joins(ActiveRecord::Base.sanitize_sql_array([
|
||||||
.average(:price)
|
"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)
|
Money.new(avg_cost || price, currency)
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,15 +19,26 @@ class HoldingTest < ActiveSupport::TestCase
|
||||||
assert_in_delta expected_nvda_weight, @nvda.weight, 0.001
|
assert_in_delta expected_nvda_weight, @nvda.weight, 0.001
|
||||||
end
|
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: 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(@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: 5, price: 128.00, date: 1.day.ago.to_date)
|
||||||
create_trade(@nvda.security, account: @account, qty: 30, price: 124.00, date: Date.current)
|
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((212.00 + 216.00).to_d / 2), @amzn.avg_cost
|
||||||
assert_equal Money.new((128.0 + 124.0) / 2), @nvda.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
|
end
|
||||||
|
|
||||||
test "calculates total return trend" do
|
test "calculates total return trend" do
|
||||||
|
|
|
@ -28,20 +28,20 @@ module EntriesTestHelper
|
||||||
Entry.create! entry_defaults.merge(attributes)
|
Entry.create! entry_defaults.merge(attributes)
|
||||||
end
|
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_price = price || Security::Price.find_by!(security: security, date: date).price
|
||||||
|
|
||||||
trade = Trade.new \
|
trade = Trade.new \
|
||||||
qty: qty,
|
qty: qty,
|
||||||
security: security,
|
security: security,
|
||||||
price: trade_price,
|
price: trade_price,
|
||||||
currency: "USD"
|
currency: currency
|
||||||
|
|
||||||
account.entries.create! \
|
account.entries.create! \
|
||||||
name: "Trade",
|
name: "Trade",
|
||||||
date: date,
|
date: date,
|
||||||
amount: qty * trade_price,
|
amount: qty * trade_price,
|
||||||
currency: "USD",
|
currency: currency,
|
||||||
entryable: trade
|
entryable: trade
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue