mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-22 06:39:39 +02:00
122 lines
3.7 KiB
Ruby
122 lines
3.7 KiB
Ruby
|
class Account::BalanceCalculator
|
||
|
def initialize(account, holdings: nil)
|
||
|
@account = account
|
||
|
@holdings = holdings || []
|
||
|
end
|
||
|
|
||
|
def calculate(reverse: false, start_date: nil)
|
||
|
cash_balances = reverse ? reverse_cash_balances : forward_cash_balances
|
||
|
|
||
|
cash_balances.map do |balance|
|
||
|
holdings_value = converted_holdings.select { |h| h.date == balance.date }.sum(&:amount)
|
||
|
balance.balance = balance.balance + holdings_value
|
||
|
balance
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
attr_reader :account, :holdings
|
||
|
|
||
|
def oldest_date
|
||
|
converted_entries.first ? converted_entries.first.date - 1.day : Date.current
|
||
|
end
|
||
|
|
||
|
def reverse_cash_balances
|
||
|
prior_balance = account.cash_balance
|
||
|
|
||
|
Date.current.downto(oldest_date).map do |date|
|
||
|
entries_for_date = converted_entries.select { |e| e.date == date }
|
||
|
holdings_for_date = converted_holdings.select { |h| h.date == date }
|
||
|
|
||
|
valuation = entries_for_date.find { |e| e.account_valuation? }
|
||
|
|
||
|
current_balance = if valuation
|
||
|
# To get this to a cash valuation, we back out holdings value on day
|
||
|
valuation.amount - holdings_for_date.sum(&:amount)
|
||
|
else
|
||
|
transactions = entries_for_date.select { |e| e.account_transaction? || e.account_trade? }
|
||
|
|
||
|
calculate_balance(prior_balance, transactions)
|
||
|
end
|
||
|
|
||
|
balance_record = Account::Balance.new(
|
||
|
account: account,
|
||
|
date: date,
|
||
|
balance: valuation ? current_balance : prior_balance,
|
||
|
cash_balance: valuation ? current_balance : prior_balance,
|
||
|
currency: account.currency
|
||
|
)
|
||
|
|
||
|
prior_balance = current_balance
|
||
|
|
||
|
balance_record
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def forward_cash_balances
|
||
|
prior_balance = 0
|
||
|
current_balance = nil
|
||
|
|
||
|
oldest_date.upto(Date.current).map do |date|
|
||
|
entries_for_date = converted_entries.select { |e| e.date == date }
|
||
|
holdings_for_date = converted_holdings.select { |h| h.date == date }
|
||
|
|
||
|
valuation = entries_for_date.find { |e| e.account_valuation? }
|
||
|
|
||
|
current_balance = if valuation
|
||
|
# To get this to a cash valuation, we back out holdings value on day
|
||
|
valuation.amount - holdings_for_date.sum(&:amount)
|
||
|
else
|
||
|
transactions = entries_for_date.select { |e| e.account_transaction? || e.account_trade? }
|
||
|
|
||
|
calculate_balance(prior_balance, transactions, inverse: true)
|
||
|
end
|
||
|
|
||
|
balance_record = Account::Balance.new(
|
||
|
account: account,
|
||
|
date: date,
|
||
|
balance: current_balance,
|
||
|
cash_balance: current_balance,
|
||
|
currency: account.currency
|
||
|
)
|
||
|
|
||
|
prior_balance = current_balance
|
||
|
|
||
|
balance_record
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def converted_entries
|
||
|
@converted_entries ||= @account.entries.order(:date).to_a.map do |e|
|
||
|
converted_entry = e.dup
|
||
|
converted_entry.amount = converted_entry.amount_money.exchange_to(
|
||
|
account.currency,
|
||
|
date: e.date,
|
||
|
fallback_rate: 1
|
||
|
).amount
|
||
|
converted_entry.currency = account.currency
|
||
|
converted_entry
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def converted_holdings
|
||
|
@converted_holdings ||= holdings.map do |h|
|
||
|
converted_holding = h.dup
|
||
|
converted_holding.amount = converted_holding.amount_money.exchange_to(
|
||
|
account.currency,
|
||
|
date: h.date,
|
||
|
fallback_rate: 1
|
||
|
).amount
|
||
|
converted_holding.currency = account.currency
|
||
|
converted_holding
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def calculate_balance(prior_balance, transactions, inverse: false)
|
||
|
flows = transactions.sum(&:amount)
|
||
|
negated = inverse ? account.asset? : account.liability?
|
||
|
flows *= -1 if negated
|
||
|
prior_balance + flows
|
||
|
end
|
||
|
end
|