mirror of
https://github.com/we-promise/sure
synced 2026-04-25 17:15:07 +02:00
* Replace Maybe for Sure in select code areas * Make sure passwords are consistent * Remove (admin|member) from demo data first name * Database and schema names finally to `sure` * Fix broken test * Another (benchmarking) database name to `sure_*` * More rebranding to Sure * Missed this Maybe mention in the same page * Random nitpicks and more Maybes * Demo data accounts and more Maybes * Test data account updates * Impersonation test accounts * Consistency with `compose.example.yml`
94 lines
3.4 KiB
Ruby
94 lines
3.4 KiB
Ruby
# Resolves a Plaid security to an internal Security record, or nil
|
|
class PlaidAccount::Investments::SecurityResolver
|
|
UnresolvablePlaidSecurityError = Class.new(StandardError)
|
|
|
|
def initialize(plaid_account)
|
|
@plaid_account = plaid_account
|
|
@security_cache = {}
|
|
end
|
|
|
|
# Resolves an internal Security record for a given Plaid security ID
|
|
def resolve(plaid_security_id:)
|
|
response = @security_cache[plaid_security_id]
|
|
return response if response.present?
|
|
|
|
plaid_security = get_plaid_security(plaid_security_id)
|
|
|
|
if plaid_security.nil?
|
|
report_unresolvable_security(plaid_security_id)
|
|
response = Response.new(security: nil, cash_equivalent?: false, brokerage_cash?: false)
|
|
elsif brokerage_cash?(plaid_security)
|
|
response = Response.new(security: nil, cash_equivalent?: true, brokerage_cash?: true)
|
|
else
|
|
security = Security::Resolver.new(
|
|
plaid_security["ticker_symbol"],
|
|
exchange_operating_mic: plaid_security["market_identifier_code"]
|
|
).resolve
|
|
|
|
response = Response.new(
|
|
security: security,
|
|
cash_equivalent?: cash_equivalent?(plaid_security),
|
|
brokerage_cash?: false
|
|
)
|
|
end
|
|
|
|
@security_cache[plaid_security_id] = response
|
|
|
|
response
|
|
end
|
|
|
|
private
|
|
attr_reader :plaid_account, :security_cache
|
|
|
|
Response = Struct.new(:security, :cash_equivalent?, :brokerage_cash?, keyword_init: true)
|
|
|
|
def securities
|
|
plaid_account.raw_investments_payload["securities"] || []
|
|
end
|
|
|
|
# Tries to find security, or returns the "proxy security" (common with options contracts that have underlying securities)
|
|
def get_plaid_security(plaid_security_id)
|
|
security = securities.find { |s| s["security_id"] == plaid_security_id && s["ticker_symbol"].present? }
|
|
|
|
return security if security.present?
|
|
|
|
securities.find { |s| s["proxy_security_id"] == plaid_security_id }
|
|
end
|
|
|
|
def report_unresolvable_security(plaid_security_id)
|
|
Sentry.capture_exception(UnresolvablePlaidSecurityError.new("Could not resolve Plaid security from provided data")) do |scope|
|
|
scope.set_context("plaid_security", {
|
|
plaid_security_id: plaid_security_id
|
|
})
|
|
end
|
|
end
|
|
|
|
# Plaid treats "brokerage cash" differently than us. Internally, Sure treats "brokerage cash"
|
|
# as "uninvested cash" (i.e. cash that doesn't have a corresponding Security and can be withdrawn).
|
|
#
|
|
# Plaid treats everything as a "holding" with a corresponding Security. For example, "brokerage cash" (USD)
|
|
# in Plaids data model would be represented as:
|
|
#
|
|
# - A Security with ticker `CUR:USD`
|
|
# - A holding, linked to the `CUR:USD` Security, with an institution price of $1
|
|
#
|
|
# Internally, we store brokerage cash balance as `account.cash_balance`, NOT as a holding + security.
|
|
# This allows us to properly build historical cash balances and holdings values separately and accurately.
|
|
#
|
|
# These help identify these "special case" securities for various calculations.
|
|
#
|
|
def known_plaid_brokerage_cash_tickers
|
|
[ "CUR:USD" ]
|
|
end
|
|
|
|
def brokerage_cash?(plaid_security)
|
|
return false unless plaid_security["ticker_symbol"].present?
|
|
known_plaid_brokerage_cash_tickers.include?(plaid_security["ticker_symbol"])
|
|
end
|
|
|
|
def cash_equivalent?(plaid_security)
|
|
return false unless plaid_security["type"].present?
|
|
plaid_security["type"] == "cash" || plaid_security["is_cash_equivalent"] == true
|
|
end
|
|
end
|