idx-cli/FEATURE_SPEC.md

11 KiB

Feature Spec: MSN Finance Full Coverage

Branch: feat/msn-full Status: Draft — pending review Reference: origin/dev/rubick (Go implementation by rubick)


Background

The Rust CLI currently supports two MSN endpoints:

  • Finance/Quotesquote()
  • api.msn.com/keyratiosfundamentals()

The rubick Go project (friend's scraper) demonstrates a much wider set of MSN Finance endpoints covering equities, financials, earnings, charts, sentiment, insights, and news — all using the same public API key. This spec defines the full porting roadmap from Go → Rust.

MSN API key (public, embedded in MSN Money website):

0QfOX3Vn51YCzitbLaRkTTBadtWpgTN8NZLW0C1SEM

Base URLs:

  • https://assets.msn.com/service/ — core market data (Quotes, Charts, Equities, Earnings, Sentiment, Screener)
  • https://api.msn.com/msn/v0/pages/finance/ — extended data (keyratios, insights, newsfeed)
  • https://services.bingapis.com/contentservices-finance.hedgefunddataprovider/api/v1/ — Bing ownership data

Endpoints to Implement

P0 — Core Completeness

1. Finance/Equities — Company Profile

  • Method: GET
  • URL: {MSN_ASSETS_BASE_URL}Finance/Equities?apikey={key}&ids={id}&wrapodata=false
  • Returns: EquityData — company name, description, sector, industry, website, employees, address, officers/executives
  • CLI use: idx stock profile BBCA or folded into info subcommand
  • Rust struct:
pub struct EquityData {
    pub id: String,
    pub symbol: String,
    pub short_name: String,
    pub long_name: String,
    pub description: String,
    pub sector: String,
    pub industry: String,
    pub website: String,
    pub employees: i64,
    pub address: String,
    pub city: String,
    pub country: String,
    pub phone: String,
    pub officers: Vec<Officer>,
}

pub struct Officer {
    pub name: String,
    pub title: String,
    pub age: Option<i32>,
    pub year_born: Option<i32>,
    pub total_pay: Option<i64>,
}
  • Complexity: Low

2. Finance/Equities/financialstatements — Financial Statements

  • Method: GET
  • URL: {MSN_ASSETS_BASE_URL}Finance/Equities/financialstatements?apikey={key}&ids={id}&wrapodata=false
  • Returns: Balance sheet, cash flow, income statement — each as a map of {field: value} keyed by line item name, with period metadata (reportDate, endDate, currency, source)
  • CLI use: idx stock financials BBCA [--statement income|balance|cashflow]
  • Note: Fields are dynamic (map-based), not fixed columns — render as table with row=line item, col=period if multiple periods returned
  • Rust struct:
pub struct FinancialStatements {
    pub instrument: InstrumentInfo,
    pub balance_sheet: Option<BalanceSheet>,
    pub cash_flow: Option<CashFlow>,
    pub income_statement: Option<IncomeStatement>,
}

pub struct BalanceSheet {
    pub current_assets: HashMap<String, f64>,
    pub long_term_assets: HashMap<String, f64>,
    pub current_liabilities: HashMap<String, f64>,
    pub equity: HashMap<String, f64>,
    pub currency: String,
    pub report_date: String,
    pub end_date: String,
}

// Similar pattern for CashFlow (financing/investing/operating) and IncomeStatement
  • Complexity: Medium (dynamic maps → table rendering)

P1 — High Analyst Value

3. Finance/Events/Earnings — Earnings History & Forecast

  • Method: GET
  • URL: {MSN_ASSETS_BASE_URL}Finance/Events/Earnings?apikey={key}&ids={id}&wrapodata=false
  • Returns:
    • EpsLastYear, RevenueLastYear
    • Forecast.annual — 2 forward years: EpsForecast, RevenueForecast, GAAP/Normalized consensus
    • Forecast.quarterly — next 4 quarters with same fields + EarningReleaseDate
    • History.annual — 5 years: EpsActual, EpsSurprise, EpsSurprisePercent, RevenueActual, RevenueSurprise
    • History.quarterly — ~12 quarters of actuals + surprises
  • CLI use: idx stock earnings BBCA [--forecast|--history] [--annual|--quarterly]
  • Rust struct:
pub struct EarningsReport {
    pub eps_last_year: f64,
    pub revenue_last_year: f64,
    pub forecast: EarningsForecast,
    pub history: EarningsHistory,
}

pub struct EarningsData {
    pub eps_actual: Option<f64>,
    pub eps_forecast: Option<f64>,
    pub eps_surprise: Option<f64>,
    pub eps_surprise_pct: Option<f64>,
    pub revenue_actual: Option<f64>,
    pub revenue_forecast: Option<f64>,
    pub revenue_surprise: Option<f64>,
    pub earning_release_date: Option<String>,
    pub period_type: String, // e.g. "Q42025", "2025"
}
  • Complexity: Medium (nested map keyed by period string)

4. Finance/Charts — Price Chart / OHLCV History

  • Method: GET
  • URL: {MSN_ASSETS_BASE_URL}Finance/Charts?apikey={key}&ids={id}&chartType={type}&wrapodata=false
  • Chart types: 1D, 1W, 1M, 3M, 6M, 1Y, 3Y, 5Y, MAX
  • Returns: Series of ChartPoint { time, open, high, low, close, price, volume }
  • Note: This unblocks the history() provider method — current implementation explicitly returns Unsupported. MSN charts don't guarantee OHLCV on all timeframes (1D is often price-only), so parse defensively.
  • CLI use: idx stock history BBCA --period 3M (existing command, just needs this wired up)
  • Rust struct:
pub struct ChartPoint {
    pub time: String,
    pub open: Option<f64>,
    pub high: Option<f64>,
    pub low: Option<f64>,
    pub close: Option<f64>,
    pub price: f64,
    pub volume: Option<i64>,
}
  • Complexity: Medium (parse series array, handle missing OHLCV gracefully)

P2 — Enrichment Layer

5. Finance/SentimentBrowser — Crowd Sentiment

  • Method: GET
  • URL: {MSN_ASSETS_BASE_URL}Finance/SentimentBrowser?apikey={key}&ids={id}&wrapodata=false
  • Returns: Per-period sentiment stats: bullish/bearish/neutral counts, time range name (e.g., "1D", "1W", "1M")
  • CLI use: idx stock sentiment BBCA
  • Rust struct:
pub struct SentimentData {
    pub symbol: String,
    pub statistics: Vec<SentimentPeriod>,
}

pub struct SentimentPeriod {
    pub time_range: String,   // "1D", "1W", "1M"
    pub bullish: i32,
    pub bearish: i32,
    pub neutral: i32,
}
  • Complexity: Low

6. api.msn.com/insights — AI-Generated Insights

  • Method: GET
  • URL: {MSN_API_BASE_URL}insights?apikey={key}&ids={id}&wrapodata=false
  • Returns: Summary text, highlights array, risks array, last updated timestamp
  • CLI use: idx stock insights BBCA
  • Rust struct:
pub struct InsightData {
    pub id: String,
    pub summary: String,
    pub highlights: Vec<String>,
    pub risks: Vec<String>,
    pub last_updated: String,
}
  • Complexity: Low

7. MSN/Feed/me — Stock News Feed

  • Method: GET
  • URL: {MSN_API_BASE_URL} + entity feed params with stock ID
  • Returns: News cards: title, URL, abstract, provider name, publish time, read time
  • CLI use: idx stock news BBCA [--limit 10]
  • Rust struct:
pub struct NewsItem {
    pub id: String,
    pub title: String,
    pub url: String,
    pub description: String,
    pub provider: String,
    pub published_at: String,
    pub read_time_min: Option<i32>,
}
  • Complexity: Medium (URL construction + response parsing needs rubick reference)

8. Finance/Screener — IDX Universe Screener

  • Method: POST
  • URL: {MSN_ASSETS_BASE_URL}Finance/Screener?apikey={key}&wrapodata=false
  • Body: { filter: [{key, keyGroup, isRange}], order: {key, dir}, returnValueType: [...], screenerType: "...", limit: 50 }
  • Returns: List of stocks with quote data (price, change, market cap, volume, 52w hi/lo, YTD return)
  • CLI use: idx screen [--preset top-gainers|top-losers|most-active|...]
  • Complexity: Medium (POST body construction, preset filter definitions)

P3 — Optional / Future

9. Bing Ownership API — Institutional Holders

  • Base: https://services.bingapis.com/contentservices-finance.hedgefunddataprovider/api/v1/
  • Endpoints:
    • GetSecurityTopShareHolders
    • GetSecurityTopBuyers / GetSecurityTopSellers
    • GetSecurityTopNewShareHolders / GetSecurityTopExitedShareHolders
  • CLI use: idx stock holders BBCA [--buyers|--sellers|--new|--exited]
  • Note: Separate base URL, may need different auth/headers than MSN. Validate working before implementing.
  • Complexity: Low-Medium

Implementation Plan

Phase 1 — Extend src/api/msn/

  1. Add fetch_equities(symbol) to client.rs
  2. Add fetch_financial_statements(symbol) to client.rs
  3. Add fetch_earnings(symbol) to client.rs
  4. Add fetch_charts(symbol, period) to client.rs
  5. Add corresponding parse functions to parse.rs
  6. Expose via new methods on MsnProvider in mod.rs

Phase 2 — New Rust structs in src/api/msn/types.rs (new file)

  • Extract shared types (currently inline in parse.rs) into dedicated types.rs
  • Add all new structs listed above

Phase 3 — Wire CLI commands in src/cli/stocks.rs

New subcommands to add:

idx stock profile <SYMBOL>        # Company info + officers
idx stock financials <SYMBOL>     # Income / balance / cashflow
idx stock earnings <SYMBOL>       # EPS history + forecast
idx stock sentiment <SYMBOL>      # Crowd sentiment
idx stock insights <SYMBOL>       # AI highlights + risks
idx stock news <SYMBOL>           # News feed
idx screen                        # IDX screener (separate top-level command)

And unblock existing:

idx stock history <SYMBOL>        # Wire MSN charts (currently Unsupported)

Phase 4 — Output formatting

  • Table output for financials (line item rows, period columns)
  • Compact output for earnings (actual vs forecast vs surprise %)
  • JSON output flag --json should work for all new commands

Open Questions

  1. Chart OHLCV completeness — rubick notes that MSN charts don't always return full OHLCV on short timeframes (e.g., 1D is price-only). Do we want to keep history() returning Unsupported for MSN and add a separate charts() method, or silently map price → close for compatibility?

  2. Financial statements period count — The API returns one period per call (most recent). Do we want to add a bulk-fetch loop (e.g., fetch last 4 quarters separately) or just expose single-period for now?

  3. News feed URL construction — needs exact param structure from rubick's GetNewsFeed() Go implementation. Worth a closer look before implementing.

  4. Screener presets — rubick defines filter key constants (e.g., "st_list_topperfs", "st_reg_id"). Need to decide which presets to expose as CLI flags and what the default screener view looks like.

  5. Provider trait extensionquote(), fundamentals(), history() are currently defined on Provider trait. New methods (earnings, profile, etc.) are MSN-specific — do we extend the trait or expose them as inherent methods on MsnProvider only?


Files to Touch

src/api/msn/
  client.rs      — add fetch_* methods
  mod.rs         — expose new provider methods
  parse.rs       — add parse_* functions
  types.rs       — NEW: shared type definitions

src/cli/
  stocks.rs      — add new subcommands + output formatting

tests/
  cli.rs         — integration tests for new commands
  fixtures/      — add response fixtures for new endpoints

Drafted by Ciphercat based on rubick Go implementation analysis + live MSN API verification.