mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-04 21:15:19 +02:00
Populate holdings for "offline" securities properly (#1958)
* Placeholder logic for missing prices * Generate holdings properly for "offline" securities * Separate forward and reverse calculators for holdings and balances * Remove unnecessary currency conversion during sync * Clearer sync process * Move price caching logic to dedicated model * Base holding calculator * Base calculator for balances * Finish balance calculators * Better naming * Logs cleanup * Remove stale data type * Remove stale test * Fix price lookup logic for holdings sync * Fix Plaid item sync regression * Remove temp logging * Calculate cash and holdings series * Add holdings, cash, and balance series dropdown for investments
This commit is contained in:
parent
26762477a3
commit
eac5d5e663
35 changed files with 1109 additions and 808 deletions
|
@ -2,7 +2,9 @@ module Account::Chartable
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def balance_series(currency:, period: Period.last_30_days, favorable_direction: "up")
|
||||
def balance_series(currency:, period: Period.last_30_days, favorable_direction: "up", view: :balance)
|
||||
raise ArgumentError, "Invalid view type" unless [ :balance, :cash_balance, :holdings_balance ].include?(view.to_sym)
|
||||
|
||||
balances = Account::Balance.find_by_sql([
|
||||
balance_series_query,
|
||||
{
|
||||
|
@ -21,8 +23,8 @@ module Account::Chartable
|
|||
date: curr.date,
|
||||
date_formatted: I18n.l(curr.date, format: :long),
|
||||
trend: Trend.new(
|
||||
current: Money.new(curr.balance, currency),
|
||||
previous: prev.nil? ? nil : Money.new(prev.balance, currency),
|
||||
current: Money.new(balance_value_for(curr, view), currency),
|
||||
previous: prev.nil? ? nil : Money.new(balance_value_for(prev, view), currency),
|
||||
favorable_direction: favorable_direction
|
||||
)
|
||||
)
|
||||
|
@ -33,8 +35,8 @@ module Account::Chartable
|
|||
end_date: period.end_date,
|
||||
interval: period.interval,
|
||||
trend: Trend.new(
|
||||
current: Money.new(balances.last&.balance || 0, currency),
|
||||
previous: Money.new(balances.first&.balance || 0, currency),
|
||||
current: Money.new(balance_value_for(balances.last, view) || 0, currency),
|
||||
previous: Money.new(balance_value_for(balances.first, view) || 0, currency),
|
||||
favorable_direction: favorable_direction
|
||||
),
|
||||
values: values
|
||||
|
@ -52,6 +54,8 @@ module Account::Chartable
|
|||
SELECT
|
||||
d.date,
|
||||
SUM(CASE WHEN accounts.classification = 'asset' THEN ab.balance ELSE -ab.balance END * COALESCE(er.rate, 1)) as balance,
|
||||
SUM(CASE WHEN accounts.classification = 'asset' THEN ab.cash_balance ELSE -ab.cash_balance END * COALESCE(er.rate, 1)) as cash_balance,
|
||||
SUM(CASE WHEN accounts.classification = 'asset' THEN ab.balance - ab.cash_balance ELSE 0 END * COALESCE(er.rate, 1)) as holdings_balance,
|
||||
COUNT(CASE WHEN accounts.currency <> :target_currency AND er.rate IS NULL THEN 1 END) as missing_rates
|
||||
FROM dates d
|
||||
LEFT JOIN accounts ON accounts.id IN (#{all.select(:id).to_sql})
|
||||
|
@ -70,26 +74,46 @@ module Account::Chartable
|
|||
SQL
|
||||
end
|
||||
|
||||
def balance_value_for(balance_record, view)
|
||||
return 0 if balance_record.nil?
|
||||
|
||||
case view.to_sym
|
||||
when :balance then balance_record.balance
|
||||
when :cash_balance then balance_record.cash_balance
|
||||
when :holdings_balance then balance_record.holdings_balance
|
||||
else
|
||||
raise ArgumentError, "Invalid view type: #{view}"
|
||||
end
|
||||
end
|
||||
|
||||
def invert_balances(balances)
|
||||
balances.map do |balance|
|
||||
balance.balance = -balance.balance
|
||||
balance.cash_balance = -balance.cash_balance
|
||||
balance.holdings_balance = -balance.holdings_balance
|
||||
balance
|
||||
end
|
||||
end
|
||||
|
||||
def gapfill_balances(balances)
|
||||
gapfilled = []
|
||||
prev = nil
|
||||
|
||||
prev_balance = nil
|
||||
|
||||
[ nil, *balances ].each_cons(2).each_with_index do |(prev, curr), index|
|
||||
if index == 0 && curr.balance.nil?
|
||||
curr.balance = 0 # Ensure all series start with a non-nil balance
|
||||
elsif curr.balance.nil?
|
||||
curr.balance = prev.balance
|
||||
balances.each do |curr|
|
||||
if prev.nil?
|
||||
# Initialize first record with zeros if nil
|
||||
curr.balance ||= 0
|
||||
curr.cash_balance ||= 0
|
||||
curr.holdings_balance ||= 0
|
||||
else
|
||||
# Copy previous values for nil fields
|
||||
curr.balance ||= prev.balance
|
||||
curr.cash_balance ||= prev.cash_balance
|
||||
curr.holdings_balance ||= prev.holdings_balance
|
||||
end
|
||||
|
||||
gapfilled << curr
|
||||
prev = curr
|
||||
end
|
||||
|
||||
gapfilled
|
||||
|
@ -100,11 +124,20 @@ module Account::Chartable
|
|||
classification == "asset" ? "up" : "down"
|
||||
end
|
||||
|
||||
def balance_series(period: Period.last_30_days)
|
||||
def balance_series(period: Period.last_30_days, view: :balance)
|
||||
self.class.where(id: self.id).balance_series(
|
||||
currency: currency,
|
||||
period: period,
|
||||
view: view,
|
||||
favorable_direction: favorable_direction
|
||||
)
|
||||
end
|
||||
|
||||
def sparkline_series
|
||||
cache_key = family.build_cache_key("#{id}_sparkline")
|
||||
|
||||
Rails.cache.fetch(cache_key) do
|
||||
balance_series
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue