1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-05 05:25:24 +02:00

Show cash + holdings value for investment account view (#1046)

* Handle missing tickers in security price syncs

* Show combined cash and holdings value on account page

* Improve partial locals
This commit is contained in:
Zach Gollwitzer 2024-08-02 17:09:25 -04:00 committed by GitHub
parent 453a54e5e6
commit ea8309eedd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 168 additions and 33 deletions

View file

@ -28,8 +28,10 @@ class Account < ApplicationRecord
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
delegate :value, :series, to: :accountable
class << self
def by_group(period: Period.all, currency: Money.default_currency)
def by_group(period: Period.all, currency: Money.default_currency.iso_code)
grouped_accounts = { assets: ValueGroup.new("Assets", currency), liabilities: ValueGroup.new("Liabilities", currency) }
Accountable.by_classification.each do |classification, types|
@ -82,18 +84,6 @@ class Account < ApplicationRecord
classification == "asset" ? "up" : "down"
end
def series(period: Period.all, currency: self.currency)
balance_series = balances.in_period(period).where(currency: Money::Currency.new(currency).iso_code)
if balance_series.empty? && period.date_range.end == Date.current
TimeSeries.new([ { date: Date.current, value: balance_money.exchange_to(currency) } ])
else
TimeSeries.from_collection(balance_series, :balance_money)
end
rescue Money::ConversionError
TimeSeries.new([])
end
def update_balance!(balance)
valuation = entries.account_valuations.find_by(date: Date.current)

View file

@ -10,6 +10,8 @@ class Account::Holding < ApplicationRecord
scope :chronological, -> { order(:date) }
scope :current, -> { where(date: Date.current).order(amount: :desc) }
scope :in_period, ->(period) { period.date_range.nil? ? all : where(date: period.date_range) }
scope :known_value, -> { where.not(amount: nil) }
scope :for, ->(security) { where(security_id: security).order(:date) }
delegate :name, to: :security
@ -18,7 +20,7 @@ class Account::Holding < ApplicationRecord
def weight
return nil unless amount
portfolio_value = account.holdings.current.where.not(amount: nil).sum(&:amount)
portfolio_value = account.holdings.current.known_value.sum(&:amount)
portfolio_value.zero? ? 1 : amount / portfolio_value * 100
end

View file

@ -17,4 +17,20 @@ module Accountable
included do
has_one :account, as: :accountable, touch: true
end
def value
account.balance_money
end
def series(period: Period.all, currency: account.currency)
balance_series = account.balances.in_period(period).where(currency: currency)
if balance_series.empty? && period.date_range.end == Date.current
TimeSeries.new([ { date: Date.current, value: account.balance_money.exchange_to(currency) } ])
else
TimeSeries.from_collection(balance_series, :balance_money)
end
rescue Money::ConversionError
TimeSeries.new([])
end
end

View file

@ -13,4 +13,35 @@ class Investment < ApplicationRecord
[ "Roth 401k", "roth_401k" ],
[ "Angel", "angel" ]
].freeze
def value
account.balance_money + holdings_value
end
def holdings_value
account.holdings.current.known_value.sum(&:amount) || Money.new(0, account.currency)
end
def series(period: Period.all, currency: account.currency)
balance_series = account.balances.in_period(period).where(currency: currency)
holding_series = account.holdings.known_value.in_period(period).where(currency: currency)
holdings_by_date = holding_series.group_by(&:date).transform_values do |holdings|
holdings.sum(&:amount)
end
combined_series = balance_series.map do |balance|
holding_amount = holdings_by_date[balance.date] || 0
{ date: balance.date, value: Money.new(balance.balance + holding_amount, currency) }
end
if combined_series.empty? && period.date_range.end == Date.current
TimeSeries.new([ { date: Date.current, value: self.value.exchange_to(currency) } ])
else
TimeSeries.new(combined_series)
end
rescue Money::ConversionError
TimeSeries.new([])
end
end

View file

@ -24,6 +24,11 @@ class Provider::Synth
prices: prices,
success?: true,
raw_response: prices.to_json
rescue StandardError => error
SecurityPriceResponse.new \
success?: false,
error: error,
raw_response: error
end
def fetch_exchange_rate(from:, to:, date:)