Populate holdings for "offline" securities properly #1958

Merged
zachgoll merged 20 commits from 1952-bug-some-stocks-do-not-appear-among-holdings into main 2025-03-08 06:35:55 +08:00
zachgoll commented 2025-03-06 06:47:24 +08:00 (Migrated from github.com)

https://github.com/user-attachments/assets/618d72cb-2ba2-40ad-8509-cdd7f96c53ce

Changes

Offline tickers

Fixes #1952 where "offline" ticker symbols were not generating holdings. Changes include:

  • Offline tickers will use the "most recent" price (given by the user) to calculate historical values
  • Cash balances now show when there are no holdings in the account (previously, it showed empty holdings area)

Sync code organization

The previous balance and holdings sync classes had a ton of mixed responsibilities and it was getting hard to maintain and tweak (as needed in the case of this bug).

The flow dependencies are now more clear:

  • Syncers - a "syncer" (Account::Balance::Syncer, Account::Holding::Syncer) is responsible for:
    • Calculating
    • Persisting
    • Purging
  • Calculators - a "calculator" is solely responsible for calculating the values that need to be persisted

In short, an Account is a Syncable, which calls the sync method to create a Sync resource. A Sync resource is polymorphic and can sync a Family, PlaidItem, or Account. Each Sync calls sync_data, which for an Account will call various operations, including sync_balances. The sync_balances operation will use Account::Balance::Syncer to calculate (via Account::Balance::Calculator) the balances, which depends on Account::Holding::Syncer.

account.sync -> Sync -> account.sync_data -> Account::Balance::Syncer -> Account::Holding::Syncer

Additional account concerns

  • Account::Linkable - more clearly identifies a "Linked" account (connected to a data source like Plaid) and an "Unlinked" or "Offline" account (manual data)
  • Account::Enrichable - used for any code related to enhancing the data of an account from an external data source

This PR hatweaks the holdings calculator so that "offline" tickers (that our data provider doesn't have prices for) will still generate holding values. The fallback logic is as-follows:

  1. If our provider has a daily price, use it for daily historical values
  2. If provider is missing price, find the most recent trade for the security and use the "spot" price at the time of trade for daily historicals
https://github.com/user-attachments/assets/618d72cb-2ba2-40ad-8509-cdd7f96c53ce ## Changes ### Offline tickers Fixes #1952 where "offline" ticker symbols were not generating holdings. Changes include: - Offline tickers will use the "most recent" price (given by the user) to calculate historical values - Cash balances now show when there are no holdings in the account (previously, it showed empty holdings area) ### Sync code organization The previous balance and holdings sync classes had a ton of mixed responsibilities and it was getting hard to maintain and tweak (as needed in the case of this bug). The flow dependencies are now more clear: - **Syncers** - a "syncer" (`Account::Balance::Syncer`, `Account::Holding::Syncer`) is responsible for: - Calculating - Persisting - Purging - **Calculators** - a "calculator" is solely responsible for calculating the values that need to be persisted In short, an `Account` is a `Syncable`, which calls the `sync` method to create a `Sync` resource. A `Sync` resource is polymorphic and can sync a `Family`, `PlaidItem`, or `Account`. Each `Sync` calls `sync_data`, which for an `Account` will call various operations, including `sync_balances`. The `sync_balances` operation will use `Account::Balance::Syncer` to calculate (via `Account::Balance::Calculator`) the balances, which depends on `Account::Holding::Syncer`. `account.sync` -> `Sync` -> `account.sync_data` -> `Account::Balance::Syncer` -> `Account::Holding::Syncer` ### Additional account concerns - `Account::Linkable` - more clearly identifies a "Linked" account (connected to a data source like Plaid) and an "Unlinked" or "Offline" account (manual data) - `Account::Enrichable` - used for any code related to enhancing the data of an account from an external data source This PR hatweaks the holdings calculator so that "offline" tickers (that our data provider doesn't have prices for) will still generate holding values. The fallback logic is as-follows: 1. If our provider has a daily price, use it for daily historical values 2. If provider is missing price, find the _most recent_ trade for the security and use the "spot" price at the time of trade for daily historicals
Sign in to join this conversation.