mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-03 04:25:21 +02:00
Enhance security information retrieval and handling (#1826)
* Enhance security information retrieval and handling - Add support for operating MIC codes in security info fetching - Update security uniqueness validation to handle unknown securities - Improve security creation and update logic in Plaid investment sync - Update combobox and view components to handle operating MIC codes - Add unknown flag for securities with incomplete information * Update schema.rb * Refactor the need for mic codes * Don't fetch prices unless a security has the necessary mic code * Deduplication * Lint * Update Securities and Plaid Investment Sync - Modify PlaidInvestmentSync to return plaid_security for USD cash - Add non-null constraint to Securities ticker column - Update Securities fixture to use exchange_operating_mic instead of exchange_mic --------- Signed-off-by: Josh Pigford <josh@joshpigford.com>
This commit is contained in:
parent
fb6c6fa6bb
commit
68d7cb5de6
15 changed files with 203 additions and 31 deletions
|
@ -111,13 +111,10 @@ class Account::TradeBuilder
|
|||
end
|
||||
|
||||
def security
|
||||
ticker_symbol, exchange_mic, exchange_acronym, exchange_country_code = ticker.split("|")
|
||||
ticker_symbol, exchange_operating_mic = ticker.split("|")
|
||||
|
||||
security = Security.find_or_create_by(ticker: ticker_symbol, exchange_mic: exchange_mic, country_code: exchange_country_code)
|
||||
security.update(exchange_acronym: exchange_acronym)
|
||||
|
||||
FetchSecurityInfoJob.perform_later(security.id)
|
||||
|
||||
security
|
||||
Security.find_or_create_by(ticker: ticker_symbol, exchange_operating_mic: exchange_operating_mic) do |s|
|
||||
FetchSecurityInfoJob.perform_later(s.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -231,12 +231,12 @@ class Demo::Generator
|
|||
|
||||
def load_securities!
|
||||
# Create an unknown security to simulate edge cases
|
||||
Security.create! ticker: "UNKNOWN", name: "Unknown Demo Stock", exchange_mic: "UNKNOWN"
|
||||
Security.create! ticker: "UNKNOWN", name: "Unknown Demo Stock"
|
||||
|
||||
securities = [
|
||||
{ ticker: "AAPL", exchange_mic: "NASDAQ", name: "Apple Inc.", reference_price: 210 },
|
||||
{ ticker: "TM", exchange_mic: "NYSE", name: "Toyota Motor Corporation", reference_price: 202 },
|
||||
{ ticker: "MSFT", exchange_mic: "NASDAQ", name: "Microsoft Corporation", reference_price: 455 }
|
||||
{ ticker: "AAPL", exchange_mic: "XNGS", exchange_operating_mic: "XNAS", name: "Apple Inc.", reference_price: 210 },
|
||||
{ ticker: "TM", exchange_mic: "XNYS", exchange_operating_mic: "XNYS", name: "Toyota Motor Corporation", reference_price: 202 },
|
||||
{ ticker: "MSFT", exchange_mic: "XNGS", exchange_operating_mic: "XNAS", name: "Microsoft Corporation", reference_price: 455 }
|
||||
]
|
||||
|
||||
securities.each do |security_attributes|
|
||||
|
|
|
@ -82,12 +82,15 @@ class PlaidInvestmentSync
|
|||
end
|
||||
|
||||
return [ nil, nil ] if plaid_security.nil? || plaid_security.ticker_symbol.blank?
|
||||
return [ nil, plaid_security ] if plaid_security.ticker_symbol == "CUR:USD" # internally, we do not consider cash a security and track it separately
|
||||
|
||||
operating_mic = plaid_security.market_identifier_code
|
||||
|
||||
# Find any matching security
|
||||
security = Security.find_or_create_by!(
|
||||
ticker: plaid_security.ticker_symbol,
|
||||
exchange_mic: plaid_security.market_identifier_code || "XNAS",
|
||||
country_code: "US"
|
||||
) unless plaid_security.ticker_symbol == "CUR:USD" # internally, we do not consider cash a security and track it separately
|
||||
exchange_operating_mic: operating_mic
|
||||
)
|
||||
|
||||
[ security, plaid_security ]
|
||||
end
|
||||
|
|
|
@ -145,6 +145,7 @@ class Provider::Synth
|
|||
logo_url: security.dig("logo_url"),
|
||||
exchange_acronym: security.dig("exchange", "acronym"),
|
||||
exchange_mic: security.dig("exchange", "mic_code"),
|
||||
exchange_operating_mic: security.dig("exchange", "operating_mic_code"),
|
||||
country_code: security.dig("exchange", "country_code")
|
||||
}
|
||||
end
|
||||
|
@ -155,9 +156,10 @@ class Provider::Synth
|
|||
raw_response: response
|
||||
end
|
||||
|
||||
def fetch_security_info(ticker:, mic_code:)
|
||||
def fetch_security_info(ticker:, mic_code: nil, operating_mic: nil)
|
||||
response = client.get("#{base_url}/tickers/#{ticker}") do |req|
|
||||
req.params["mic_code"] = mic_code
|
||||
req.params["mic_code"] = mic_code if mic_code.present?
|
||||
req.params["operating_mic"] = operating_mic if operating_mic.present?
|
||||
end
|
||||
|
||||
parsed = JSON.parse(response.body)
|
||||
|
|
|
@ -6,7 +6,7 @@ class Security < ApplicationRecord
|
|||
has_many :prices, dependent: :destroy
|
||||
|
||||
validates :ticker, presence: true
|
||||
validates :ticker, uniqueness: { scope: :exchange_mic, case_sensitive: false }
|
||||
validates :ticker, uniqueness: { scope: :exchange_operating_mic, case_sensitive: false }
|
||||
|
||||
class << self
|
||||
def search(query)
|
||||
|
@ -30,11 +30,15 @@ class Security < ApplicationRecord
|
|||
name: name,
|
||||
logo_url: logo_url,
|
||||
exchange_acronym: exchange_acronym,
|
||||
exchange_mic: exchange_mic,
|
||||
exchange_operating_mic: exchange_operating_mic,
|
||||
exchange_country_code: country_code
|
||||
)
|
||||
end
|
||||
|
||||
def has_prices?
|
||||
exchange_operating_mic.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def upcase_ticker
|
||||
|
|
|
@ -8,6 +8,7 @@ module Security::Price::Provided
|
|||
|
||||
def fetch_price_from_provider(security:, date:, cache: false)
|
||||
return nil unless security_prices_provider.present?
|
||||
return nil unless security.has_prices?
|
||||
|
||||
response = security_prices_provider.fetch_security_prices \
|
||||
ticker: security.ticker,
|
||||
|
@ -32,6 +33,7 @@ module Security::Price::Provided
|
|||
def fetch_prices_from_provider(security:, start_date:, end_date:, cache: false)
|
||||
return [] unless security_prices_provider.present?
|
||||
return [] unless security
|
||||
return [] unless security.has_prices?
|
||||
|
||||
response = security_prices_provider.fetch_security_prices \
|
||||
ticker: security.ticker,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
class Security::SynthComboboxOption
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :symbol, :name, :logo_url, :exchange_acronym, :exchange_mic, :exchange_country_code
|
||||
attr_accessor :symbol, :name, :logo_url, :exchange_acronym, :exchange_country_code, :exchange_operating_mic
|
||||
|
||||
def id
|
||||
"#{symbol}|#{exchange_mic}|#{exchange_acronym}|#{exchange_country_code}" # submitted by combobox as value
|
||||
"#{symbol}|#{exchange_operating_mic}" # submitted by combobox as value
|
||||
end
|
||||
|
||||
def to_combobox_display
|
||||
"#{symbol} - #{name} (#{exchange_acronym})" # shown in combobox input when selected
|
||||
display_code = exchange_acronym.presence || exchange_operating_mic
|
||||
"#{symbol} - #{name} (#{display_code})" # shown in combobox input when selected
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue