Adds Provider::TinkoffInvest, a token-based securities provider built on the
public T-Invest REST gateway (invest-public-api.tinkoff.ru/rest). It serves
prices for Russian instruments (shares, ETF/БПИФ, bonds) and, crucially, brand
logos via the T-Invest CDN — the authoritative logo source for MOEX
instruments, which ISS (MoexPublic) does not provide.
- Registry: register `tinkoff_invest` under the :securities concept; token via
ENV TINKOFF_INVEST_API_KEY or encrypted Setting.tinkoff_invest_api_key.
- Logos independent of the price provider: Security#import_brand_logo consults
T-Invest for a logo whenever a token is configured (after the price-provider
metadata fetch, so it never short-circuits website_url backfill). Gated on
token presence, not the securities checklist.
- display_logo_url: with no website domain, a stored provider logo (T-Invest)
now beats the ticker-only Brandfetch lettermark; when a domain exists,
Brandfetch still wins (unchanged).
- MoexPublic no longer reports moex.com as the issuer website — it's the
exchange, not the issuer, and would make Brandfetch render the exchange logo
for every instrument and shadow the real brand logo.
- Prices: GetCandles (daily, paged) + GetLastPrices; Quotation units+nano/1e9;
bonds priced as percent-of-par x nominal (missing nominal raises, not 0).
- Settings: encrypted token field (always shown) + provider checkbox + en locale.
- Tests for search/info/logo-url/prices/bond/incomplete-candle and display logic.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(prices): add Moscow Exchange (MOEX ISS) securities + FX provider
Add Provider::MoexPublic, a keyless provider built on the free MOEX ISS API
(https://iss.moex.com/iss), modeled on Provider::BinancePublic.
Securities: shares, funds/ETF/БПИФ (e.g. LQDT), and bonds (OFZ + corporate).
Bonds are priced clean — LAST% × FACEVALUE / 100 in the instrument currency,
with per-row FACEVALUE for amortizing issues; NKD/accrued coupon excluded.
Exchange rates: also implements ExchangeRateConcept for RUB↔{USD,EUR,CNY} via
selt TOM instruments (USD000UTSTOM/EUR_RUB__TOM/CNYRUB_TOM); the selt quote is
X/RUB, inverted for RUB→X, nil for non-RUB-crossed pairs.
Details:
- Board/engine resolution via the ISS primary-board flag with a hardcoded
priority fallback (TQBR, TQTF, TQOB, TQCB, …).
- Instrument currency from CURRENCYID/FACEUNIT (handles USD/CNY eurobonds &
FX funds), normalizing legacy SUR/RUR → RUB; default RUB.
- Full history via from/till + start= pagination; current price fallback chain
LAST → MARKETPRICE → LCURRENTPRICE → LCLOSEPRICE → PREVPRICE → latest history
close.
- Bare SECID identity, exchange_operating_mic=MISX, country_code=nil (wildcard
like Binance); search accepts .ME/.MOEX/.MISX/.MCX aliases and ISIN.
- RateLimitable throttling, SslConfigurable, Faraday retry/timeouts; all public
methods wrapped in with_provider_response.
Wired into Provider::Registry for both :securities and :exchange_rates, the
hosting provider-selection UI, locales, and config/exchanges.yml (MISX).
Docker-tested (devcontainer, Ruby 3.4.9): 29 new tests green, full provider
suite + i18n green, rubocop clean; smoke-tested against live ISS (SBER price,
OFZ clean price, USD/RUB FX).
* fix(moex): address review — FX weekend lookback, dead branch, translated hints
- fetch_exchange_rate now fetches a 10-day lookback window (not just the exact
day) so a weekend/holiday request resolves to the prior trading day's close,
matching Yahoo's behavior (Codex P2).
- Remove dead identical if/else branches in history_row_price (CodeRabbit).
- Translate moex_public_hint into ca/fr/hu/vi/zh-CN instead of English copy
(CodeRabbit).
- Add a test covering the FX prior-trading-day lookback.
* fix(moex): guard ISS date parsing; doc TQTE in board priority
Address maintainer review (jjmata):
- parse_iss_date wraps Date.parse so a malformed ISS TRADEDATE skips just that
row (with a contextual log warning) instead of failing the whole history/FX
fetch. Used in history_row_price and fx_history.
- Add TQTE to the BOARD_PRIORITY doc comment (it was in the constant but missing
from the comment).
- Add a test covering the unparseable-date skip.