2025-05-15 10:19:56 -04:00
class Holding :: ReverseCalculator
2025-06-26 16:57:17 -04:00
attr_reader :account , :portfolio_snapshot
2025-05-15 10:19:56 -04:00
2025-06-26 16:57:17 -04:00
def initialize ( account , portfolio_snapshot : )
2025-05-15 10:19:56 -04:00
@account = account
2025-06-26 16:57:17 -04:00
@portfolio_snapshot = portfolio_snapshot
2025-05-15 10:19:56 -04:00
end
def calculate
Rails . logger . tagged ( " Holding::ReverseCalculator " ) do
holdings = calculate_holdings
Holding . gapfill ( holdings )
end
end
2025-03-07 17:35:55 -05:00
private
# Reverse calculators will use the existing holdings as a source of security ids and prices
# since it is common for a provider to supply "current day" holdings but not all the historical
# trades that make up those holdings.
def portfolio_cache
2025-04-14 11:40:34 -04:00
@portfolio_cache || = Holding :: PortfolioCache . new ( account , use_holdings : true )
2025-03-07 17:35:55 -05:00
end
def calculate_holdings
2025-06-26 16:57:17 -04:00
# Start with the portfolio snapshot passed in from the materializer
current_portfolio = portfolio_snapshot . to_h
2025-03-07 17:35:55 -05:00
previous_portfolio = { }
holdings = [ ]
Date . current . downto ( account . start_date ) . each do | date |
today_trades = portfolio_cache . get_trades ( date : date )
previous_portfolio = transform_portfolio ( current_portfolio , today_trades , direction : :reverse )
2025-05-06 09:25:49 -04:00
# If current day, always use holding prices (since that's what Plaid gives us). For historical values, use market data (since Plaid doesn't supply historical prices)
holdings += build_holdings ( current_portfolio , date , price_source : date == Date . current ? " holding " : nil )
2025-03-07 17:35:55 -05:00
current_portfolio = previous_portfolio
end
holdings
end
2025-05-15 10:19:56 -04:00
def transform_portfolio ( previous_portfolio , trade_entries , direction : :forward )
new_quantities = previous_portfolio . dup
trade_entries . each do | trade_entry |
trade = trade_entry . entryable
security_id = trade . security_id
qty_change = trade . qty
qty_change = qty_change * - 1 if direction == :reverse
new_quantities [ security_id ] = ( new_quantities [ security_id ] || 0 ) + qty_change
end
new_quantities
end
def build_holdings ( portfolio , date , price_source : nil )
portfolio . map do | security_id , qty |
price = portfolio_cache . get_price ( security_id , date , source : price_source )
if price . nil?
next
end
Holding . new (
account_id : account . id ,
security_id : security_id ,
date : date ,
qty : qty ,
price : price . price ,
currency : price . currency ,
amount : qty * price . price
)
end . compact
end
2025-03-07 17:35:55 -05:00
end