Add SnapTrade brokerage integration with full trade history support (#737)

* Introduce SnapTrade integration with models, migrations, views, and activity processing logic.

* Refactor SnapTrade activities processing: improve activity fetching flow, handle pending states, and update UI elements for enhanced user feedback.

* Update Brakeman ignore file to include intentional redirect for SnapTrade OAuth portal.

* Refactor SnapTrade models, views, and processing logic: add currency extraction helper, improve pending state handling, optimize migration checks, and enhance user feedback in UI.

* Remove encryption for SnapTrade `snaptrade_user_id`, as it is an identifier, not a secret.

* Introduce `SnaptradeConnectionCleanupJob` to asynchronously handle SnapTrade connection cleanup and improve i18n for SnapTrade item status messages.

* Update SnapTrade encryption: make `snaptrade_user_secret` non-deterministic to enhance security.

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
This commit is contained in:
LPW
2026-01-22 14:52:49 -05:00
committed by GitHub
parent 179552657c
commit a83f70425f
52 changed files with 4417 additions and 25 deletions

View File

@@ -105,6 +105,32 @@ module SyncStats
holdings_stats
end
# Collects trades statistics (investment activities like buy/sell).
#
# @param sync [Sync] The sync record to update
# @param account_ids [Array<String>] The account IDs to count trades for
# @param source [String] The trade source (e.g., "snaptrade", "plaid")
# @param window_start [Time, nil] Start of the sync window (defaults to sync.created_at or 30 minutes ago)
# @param window_end [Time, nil] End of the sync window (defaults to Time.current)
# @return [Hash] The trades stats that were collected
def collect_trades_stats(sync, account_ids:, source:, window_start: nil, window_end: nil)
return {} unless sync.respond_to?(:sync_stats)
return {} if account_ids.empty?
window_start ||= sync.created_at || 30.minutes.ago
window_end ||= Time.current
trade_scope = Entry.where(account_id: account_ids, source: source, entryable_type: "Trade")
trades_imported = trade_scope.where(created_at: window_start..window_end).count
trades_stats = {
"trades_imported" => trades_imported
}
merge_sync_stats(sync, trades_stats)
trades_stats
end
# Collects health/error statistics.
#
# @param sync [Sync] The sync record to update