mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 15:35:22 +02:00
Fix Plaid cash balance double counting
This commit is contained in:
parent
8b857e9c8a
commit
89cfac4d95
3 changed files with 44 additions and 1 deletions
|
@ -21,7 +21,13 @@ class Balance::ReverseCalculator < Balance::BaseCalculator
|
|||
if valuation.present?
|
||||
@balances << build_balance(date, previous_cash_balance, holdings_value)
|
||||
else
|
||||
@balances << build_balance(date, current_cash_balance, holdings_value)
|
||||
# If date is today, we don't distinguish cash vs. total since provider's are inconsistent with treatment
|
||||
# of the cash component. Instead, just set the balance equal to the "total value" reported by the provider
|
||||
if date == Date.current
|
||||
@balances << build_balance(date, account.balance, 0)
|
||||
else
|
||||
@balances << build_balance(date, current_cash_balance, holdings_value)
|
||||
end
|
||||
end
|
||||
|
||||
current_cash_balance = previous_cash_balance
|
||||
|
|
|
@ -11,6 +11,7 @@ class PlaidInvestmentSync
|
|||
@securities = securities
|
||||
|
||||
PlaidAccount.transaction do
|
||||
normalize_cash_balance!
|
||||
sync_transactions!
|
||||
sync_holdings!
|
||||
end
|
||||
|
@ -19,6 +20,23 @@ class PlaidInvestmentSync
|
|||
private
|
||||
attr_reader :transactions, :holdings, :securities
|
||||
|
||||
# Plaid considers "brokerage cash" and "cash equivalent holdings" to all be part of "cash balance"
|
||||
# Internally, we DO NOT.
|
||||
# Maybe clearly distinguishes between "brokerage cash" vs. "holdings (i.e. invested cash)"
|
||||
# For this reason, we must back out cash + cash equivalent holdings from the reported cash balance to avoid double counting
|
||||
def normalize_cash_balance!
|
||||
non_cash_holdings = holdings.reject do |h|
|
||||
_internal_security, plaid_security = get_security(h.security_id, securities)
|
||||
plaid_security&.type == "cash"
|
||||
end
|
||||
|
||||
non_cash_holdings_value = non_cash_holdings.sum { |h| h.quantity * h.institution_price }
|
||||
|
||||
plaid_account.account.update!(
|
||||
cash_balance: plaid_account.account.cash_balance - non_cash_holdings_value
|
||||
)
|
||||
end
|
||||
|
||||
def sync_transactions!
|
||||
transactions.each do |transaction|
|
||||
security, plaid_security = get_security(transaction.security_id, securities)
|
||||
|
|
|
@ -120,4 +120,23 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
|
|||
|
||||
assert_equal expected, calculated
|
||||
end
|
||||
|
||||
test "uses provider reported holdings and cash value on current day" do
|
||||
aapl = securities(:aapl)
|
||||
|
||||
# Implied holdings value of $1,000 from provider
|
||||
@account.update!(cash_balance: 19000, balance: 20000)
|
||||
|
||||
# Create a holding that differs in value from provider ($2,000 vs. the $1,000 reported by provider)
|
||||
Holding.create!(date: Date.current, account: @account, security: aapl, qty: 10, price: 100, amount: 2000, currency: "USD")
|
||||
Holding.create!(date: 1.day.ago.to_date, account: @account, security: aapl, qty: 10, price: 100, amount: 2000, currency: "USD")
|
||||
|
||||
# Today reports the provider value. Yesterday, provider won't give us any data, so we MUST look at the generated holdings value
|
||||
# to calculate the end balance ($19,000 cash + $2,000 holdings = $21,000 total value)
|
||||
expected = [ 21000, 20000 ]
|
||||
|
||||
calculated = Balance::ReverseCalculator.new(@account).calculate.sort_by(&:date).map(&:balance)
|
||||
|
||||
assert_equal expected, calculated
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue