Securities Lookup#
Securities lookup resolves a user-typed ticker or company name into a list of matching Security records, each bound to a specific market data provider. The entry point is SecuritiesController#index, which calls Security.search_provider(params[:q], country_code: ...). The heavy lifting lives in the Security::Provided concern mixed into the Security model.
Concurrent Multi-Provider Architecture#
search_provider fans out to all enabled providers simultaneously using Concurrent::Promises.future, so total wall time equals max(provider latencies) rather than their sum . A shared deadline enforces a per-provider timeout of 8 seconds (PROVIDER_SEARCH_TIMEOUT); slow providers time out individually without blocking results from faster ones .
Nine providers are registered under the :securities concept in Provider::Registry:
| Provider | Class | Notes |
|---|---|---|
| TwelveData | Provider::TwelveData | /symbol_search; excludes crypto |
| Yahoo Finance | Provider::YahooFinance | /v1/finance/search; deduplicates dual-listings |
| Tiingo | Provider::Tiingo | /tiingo/utilities/search |
| EODHD | Provider::Eodhd | /api/search/{symbol} |
| Alpha Vantage | Provider::AlphaVantage | SYMBOL_SEARCH function |
| MFAPI | Provider::Mfapi | Indian mutual funds only |
| Binance Public | Provider::BinancePublic | Crypto-only; complex base/quote matching |
| MOEX Public | Provider::MoexPublic | Russian equities via ISS API |
| Tinkoff Invest | Provider::TinkoffInvest | FindInstrument service; API-tradeable only |
Active providers are read from Setting.enabled_securities_providers at runtime — only configured, enabled providers participate in a given search .
Caching#
Each provider's results are cached independently for 5 minutes (SEARCH_CACHE_TTL) to avoid burning through rate limits on repeated keystrokes . The cache key is computed per-provider inside fetch_provider_results:
security_search:{provider_key}:{SYMBOL}:{SHA256(sorted_params)}
Nil responses from a provider are not cached (skip_nil: true), so a transient failure retries on the next request . Yahoo Finance also caches its own cookie/crumb authentication for up to 1 hour (MAX_CRUMB_CACHE_DURATION) .
Deduplication#
Two layers of deduplication apply:
-
Cross-provider dedup — each result is keyed by
"#{symbol}|#{exchange_operating_mic}|#{provider_key}"(uppercased). The same ticker on the same exchange can appear once per provider; the user's selection records which provider's feed they want inprice_provider. -
Intra-provider dedup (Yahoo Finance) — Yahoo can return the same company twice for dual-listed Indian equities (NSE + BSE).
deduplicate_dual_listingsretains only the preferred listing bypreference_rankfrom theEXCHANGE_CONFIGmap while preserving Yahoo's original result order .
Relevance Ranking#
After merging all provider results, rank_search_results sorts by a two-key tuple [relevance, country_match, ticker_upcase]:
| Score | Condition |
|---|---|
| 0 | Exact ticker match |
| 1 | Ticker starts with query |
| 2 | Company name contains query |
| 3 | Everything else |
Within the same relevance tier, securities from the user's country (country_code param) are ranked first (country_match = 0). The final list is capped at 30 results (MAX_SEARCH_RESULTS) .
Provider Interface Contract#
Every securities provider includes Provider::SecurityConcept and implements:
search_securities(symbol, country_code: nil, exchange_operating_mic: nil)— returns an array ofSecurityvalue objectsfetch_security_info(symbol:, exchange_operating_mic:)— returns aSecurityInfostruct (name, logo, website, description)fetch_security_price/fetch_security_prices— price data fetching
Each provider normalizes exchange codes to ISO MIC format. TwelveData returns MICs directly ; Yahoo Finance maps its own exchange codes via map_exchange_mic. TwelveData explicitly filters out "Digital Currency" / "DIGITAL_CURRENCY" rows so crypto isn't mixed into equity results .
A resolved security stores its originating provider in price_provider. The price_data_provider instance method re-resolves this on each price fetch; if the assigned provider has since been disabled, it returns nil rather than silently falling back to a different provider.