{"record":{"id":"6d0dbd759bd319d5","repo":"we-promise/sure","slug":"unknown-moex-security-secid","errorCode":null,"errorMessage":"Unknown MOEX security: #{secid}","messagePattern":"Unknown MOEX security: #(.+?)","errorType":"exception","errorClass":"Provider::MoexPublic::InvalidSecurityPriceError","httpStatus":null,"severity":"error","filePath":"app/models/provider/moex_public.rb","lineNumber":322,"sourceCode":"      case market.to_s.downcase\n      when \"bonds\" then \"bond\"\n      when /index/ then \"index\"\n      else \"stock\"\n      end\n    end\n\n    # ================================\n    #     Board / engine resolution\n    # ================================\n\n    # Resolves a SECID to its primary trading board plus engine/market, currency,\n    # display name, and kind. Cached 24h — reference data that rarely changes.\n    def resolve_instrument(secid)\n      cached = Rails.cache.fetch(\"moex_public:instrument:#{secid}\", expires_in: INSTRUMENT_CACHE_TTL) do\n        body = get_json(\"/securities/#{secid}.json\", \"iss.meta\" => \"off\")\n        desc = description_map(body)\n        boards = rows_from(body, \"boards\")\n        raise InvalidSecurityPriceError, \"Unknown MOEX security: #{secid}\" if boards.empty?\n\n        board = choose_board(boards)\n        kind = security_kind(desc[\"GROUP\"] || desc[\"TYPE\"], desc[\"TYPE\"]) || market_kind(board[\"market\"])\n\n        {\n          secid: secid,\n          engine: board[\"engine\"].to_s,\n          market: board[\"market\"].to_s,\n          board: board[\"boardid\"].to_s,\n          currency: normalize_currency(board[\"currencyid\"].presence || desc[\"FACEUNIT\"].presence || desc[\"CURRENCYID\"]),\n          name: (desc[\"SHORTNAME\"].presence || desc[\"NAME\"].presence || secid).to_s,\n          kind: kind\n        }\n      end\n\n      cached.symbolize_keys\n    end\n","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/moex_public.rb#L304-L340","documentation":"Raised by Provider::MoexPublic#resolve_instrument (an InvalidSecurityPriceError) when the ISS /securities/{secid}.json response's boards block is empty — i.e. MOEX knows no trading board for that SECID. Resolution happens inside a Rails.cache.fetch with a 24h TTL, but a raise skips the cache write, so failures are re-queried every time. This fires before any price fetch, for both search and price paths.","triggerScenarios":"Passing a Yahoo-style symbol (SBER.ME, GAZP.ME) instead of the MOEX SECID (SBER, GAZP); a typo'd or case-mangled SECID; a delisted security whose boards were retired; SECIDs for instruments the provider filters (indices, futures, FX like USD000UTSTOM can lack usable boards depending on shape); OTC-only instruments with no board membership.","commonSituations":"Importing a symbol list sourced from another data vendor without normalization; migrating tickers after a company rename (old SECID stops resolving); user free-text symbol entry; cached-good instruments going stale after delisting (the 24h positive cache masks it for up to a day).","solutions":["Confirm the exact SECID on iss.moex.com/iss/securities?q={symbol} and use that value verbatim (e.g. SBER, not SBER.ME).","Resolve user-supplied symbols through the provider's search endpoint (which returns proper SECIDs) instead of trusting raw input.","Normalize input with normalize_secid and strip suffixes like .ME before resolving.","Handle delisted securities by retiring the Security record rather than re-resolving on every sync."],"exampleFix":"# before\nprovider.fetch_security_prices(symbol: \"SBER.ME\", ...)\n\n# after\nsecid = symbol.delete_suffix(\".ME\").upcase # or resolve via provider.search_securities first\nprovider.fetch_security_prices(symbol: secid, ...)","handlingStrategy":"validation","validationCode":"secid = symbol.to_s.upcase.delete_suffix(\".ME\")\nresolved = provider.search_securities(secid)&.data&.first # confirm the SECID exists before price calls\nraise ArgumentError, \"unresolvable MOEX symbol #{symbol}\" if resolved.nil?","typeGuard":null,"tryCatchPattern":"begin\n  provider.fetch_security_prices(symbol: secid, exchange_operating_mic: mic, start_date: from, end_date: to)\nrescue Provider::MoexPublic::InvalidSecurityPriceError => e\n  retire_security(symbol) if e.message.start_with?(\"Unknown MOEX security\")\nend","preventionTips":["Always resolve symbols through the provider's search endpoint instead of trusting external-vendor tickers.","Normalize away foreign suffixes (.ME, exchange codes) before resolving a SECID.","Retire (don't retry) securities whose boards disappear — delisting is permanent."],"tags":["moex","secid","symbol-resolution","invalid-symbol","delisted"],"backgroundTag":"unknown-security-symbol","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}