mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-10 07:55:21 +02:00
Finish balance calculators
This commit is contained in:
parent
3a2263ea99
commit
791683aca0
4 changed files with 38 additions and 103 deletions
|
@ -5,6 +5,12 @@ class Account::Balance::BaseCalculator
|
|||
@account = account
|
||||
end
|
||||
|
||||
def calculate
|
||||
Rails.logger.tagged(self.class.name) do
|
||||
calculate_balances
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
CashBalance = Data.define(:date, :balance)
|
||||
|
||||
|
@ -12,12 +18,12 @@ class Account::Balance::BaseCalculator
|
|||
@sync_cache ||= Account::Balance::SyncCache.new(account)
|
||||
end
|
||||
|
||||
def build_balance(amount, date)
|
||||
def build_balance(date, cash_balance, holdings_value)
|
||||
Account::Balance.new(
|
||||
account: account,
|
||||
date: date,
|
||||
balance: amount,
|
||||
cash_balance: amount,
|
||||
balance: holdings_value + cash_balance,
|
||||
cash_balance: cash_balance,
|
||||
currency: account.currency
|
||||
)
|
||||
end
|
||||
|
|
|
@ -1,42 +1,29 @@
|
|||
class Account::Balance::ForwardCalculator < Account::Balance::BaseCalculator
|
||||
def calculate
|
||||
Rails.logger.tagged("Account::BalanceCalculator") do
|
||||
calculate_cash_balances
|
||||
calculate_balances
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def calculate_balances
|
||||
@balances = []
|
||||
|
||||
@balances = @cash_balances.map do |balance|
|
||||
holdings = sync_cache.get_holdings(balance.date)
|
||||
holdings_value = holdings.sum(&:amount)
|
||||
build_balance(balance.balance + holdings_value, balance.date)
|
||||
end.compact
|
||||
end
|
||||
|
||||
def calculate_cash_balances
|
||||
prior_cash_balance = 0
|
||||
current_cash_balance = nil
|
||||
|
||||
@cash_balances = []
|
||||
@balances = []
|
||||
|
||||
account.start_date.upto(Date.current).each do |date|
|
||||
entries = sync_cache.get_entries(date)
|
||||
holdings = sync_cache.get_holdings(date)
|
||||
holdings_value = holdings.sum(&:amount)
|
||||
valuation = sync_cache.get_valuation(date)
|
||||
|
||||
current_cash_balance = if valuation
|
||||
# To get this to a cash valuation, we back out holdings value on day
|
||||
valuation.amount - holdings.sum(&:amount)
|
||||
# Since a valuation means "total balance" (which includes holdings), we back out holdings value to get cash balance
|
||||
valuation.amount - holdings_value
|
||||
else
|
||||
calculate_next_balance(prior_cash_balance, entries, direction: :forward)
|
||||
end
|
||||
|
||||
@cash_balances << CashBalance.new(date, current_cash_balance)
|
||||
@balances << build_balance(date, current_cash_balance, holdings_value)
|
||||
|
||||
prior_cash_balance = current_cash_balance
|
||||
end
|
||||
|
||||
@balances
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,91 +1,33 @@
|
|||
class Account::Balance::ReverseCalculator
|
||||
attr_reader :account, :holdings
|
||||
|
||||
def initialize(account, holdings: nil)
|
||||
@account = account
|
||||
@holdings = holdings || []
|
||||
end
|
||||
|
||||
def calculate
|
||||
Rails.logger.tagged("Account::BalanceCalculator") do
|
||||
Rails.logger.info("Calculating cash balances with strategy: reverse sync")
|
||||
cash_balances = calculate_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.compact
|
||||
end
|
||||
end
|
||||
|
||||
class Account::Balance::ReverseCalculator < Account::Balance::BaseCalculator
|
||||
private
|
||||
def oldest_date
|
||||
converted_entries.first ? converted_entries.first.date - 1.day : Date.current
|
||||
end
|
||||
|
||||
def calculate_balances
|
||||
prior_balance = account.cash_balance
|
||||
todays_cash_balance = account.cash_balance
|
||||
yesterdays_cash_balance = nil
|
||||
|
||||
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 }
|
||||
@balances = []
|
||||
|
||||
valuation = entries_for_date.find { |e| e.account_valuation? }
|
||||
Date.current.downto(account.start_date).map do |date|
|
||||
entries = sync_cache.get_entries(date)
|
||||
holdings = sync_cache.get_holdings(date)
|
||||
holdings_value = holdings.sum(&:amount)
|
||||
valuation = sync_cache.get_valuation(date)
|
||||
|
||||
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)
|
||||
yesterdays_cash_balance = if valuation
|
||||
# Since a valuation means "total balance" (which includes holdings), we back out holdings value to get cash balance
|
||||
valuation.amount - holdings_value
|
||||
else
|
||||
transactions = entries_for_date.select { |e| e.account_transaction? || e.account_trade? }
|
||||
|
||||
calculate_balance(prior_balance, transactions)
|
||||
calculate_next_balance(todays_cash_balance, entries, direction: :reverse)
|
||||
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
|
||||
)
|
||||
if valuation.present?
|
||||
@balances << build_balance(date, yesterdays_cash_balance, holdings_value)
|
||||
else
|
||||
@balances << build_balance(date, todays_cash_balance, holdings_value)
|
||||
end
|
||||
|
||||
prior_balance = current_balance
|
||||
|
||||
balance_record
|
||||
todays_cash_balance = yesterdays_cash_balance
|
||||
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
|
||||
@balances
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ class Account::Balance::ReverseCalculatorTest < ActiveSupport::TestCase
|
|||
test "no entries sync" do
|
||||
assert_equal 0, @account.balances.count
|
||||
|
||||
expected = [ @account.balance ]
|
||||
expected = [ @account.balance, @account.balance ]
|
||||
calculated = Account::Balance::ReverseCalculator.new(@account).calculate
|
||||
|
||||
assert_equal expected, calculated.map(&:balance)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue