Back to Worldmonitor

TradingView Screener Integration Guide

docs/tradingview-screener-integration.md

2.10.027.5 KB
Original Source

TradingView Screener Integration Guide

Purpose: Reference document for extending WorldMonitor's finance hub with TradingView Screener data.


Table of Contents

  1. Overview
  2. Libraries
  3. What Data Is Available
  4. Architecture Fit (Gold Standard)
  5. Integration Patterns
  6. Panel Extensions
  7. Field Reference
  8. Query Cookbook
  9. Rate Limiting & Production Notes
  10. Implementation Checklist

Overview

TradingView exposes an undocumented but stable /screener API at https://scanner.tradingview.com/{market}/scan. Two open-source libraries wrap it:

LibraryLanguageRepo
tradingview-screenerPython../TradingView-Screener/
tradingview-screener-tsTypeScripthttps://github.com/Anny26022/TradingView-Screener-ts

Both provide:

  • 3,000+ fields across stocks, crypto, forex, futures, bonds
  • SQL-like Query builder with filter, sort, paginate
  • 87 markets/exchanges (geographic + asset class)
  • Timeframe variants per field (1m → 1M)
  • No API key required for delayed/public data

The TypeScript library is the one relevant for WorldMonitor. It has full parity with the Python version (41/41 operations pass) plus a self-hosted REST server option.


Libraries

TypeScript: tradingview-screener-ts

bash
npm install tradingview-screener-ts
typescript
import { Query, col, And, Or } from 'tradingview-screener-ts'

const [total, rows] = await new Query()
  .set_markets('crypto')
  .select('name', 'close', 'volume', 'market_cap_basic', 'change')
  .order_by('market_cap_basic', false)
  .limit(50)
  .get_scanner_data()
// rows: [{ ticker: 'BINANCE:BTCUSDT', name: 'Bitcoin', close: 62000, ... }]

Key types:

typescript
interface ScreenerRowDict { s: string; d: unknown[] }
interface ScreenerDict    { totalCount: number; data: ScreenerRowDict[] | null }

// get_scanner_data returns:
[number, Record<string, unknown>[]]
// [totalCount, [{ ticker, col1, col2, ... }, ...]]

Timeframe variants: append |{tf} to any field name.

typescript
'close'       // daily
'close|1'     // 1-minute
'close|5'     // 5-minute
'close|60'    // 1-hour
'close|240'   // 4-hour
'close|1W'    // weekly
'close|1M'    // monthly

All filter methods on col():

typescript
col('close').gt(100)              col('close').ge(100)
col('close').lt(100)              col('close').le(100)
col('close').eq(100)              col('close').ne(100)
col('close').between(10, 50)      col('close').not_between(10, 50)
col('type').isin(['stock'])       col('type').not_in(['etf'])
col('tags').has(['value'])        col('tags').has_none_of(['etf'])
col('close').crosses(col('EMA20'))
col('close').crosses_above(col('EMA20'))
col('close').crosses_below(col('EMA20'))
col('close').above_pct(col('SMA200'), 0.05)
col('close').below_pct(col('SMA200'), 0.05)
col('name').like('Apple%')        col('name').not_like('Apple%')
col('eps').empty()                col('eps').not_empty()
col('date').in_day_range(0, 0)    col('date').in_week_range(0, 2)

What Data Is Available

By Asset Class

Market KeyFieldsWith TimeframesNotes
america1,0033,514US stocks
crypto5253,094BTC/ETH/etc.
forex4392,950FX pairs
cfd4392,950CFDs
futures394394Commodities, index futures
bonds153180Government/corporate bonds
coin5183,029Spot crypto

Data Categories Available

CategoryExample Fields
Price / OHLCVclose, open, high, low, volume
Changechange (%), change_abs ($), change_from_open, gap
Market capmarket_cap_basic
TechnicalsRSI, MACD.macd, MACD.signal, MACD.hist, BB.upper, BB.lower, BB.mid
Moving averagesEMA5, EMA20, EMA50, EMA100, EMA200, SMA20, SMA50, SMA200
Volume analysisrelative_volume_10d_calc, Value.Traded, average_volume_10d_calc
Fundamentalsprice_earnings_ttm, earnings_per_share_basic_ttm, dividend_yield_recent, book_value_per_share
52-weekprice_52_week_high, price_52_week_low, High.All, Low.All
VWAPVWAP
Classificationtype, typespecs, sector, industry, country, exchange, currency
Statusactive_symbol, is_primary, update_mode
Indicesindex (which indices the stock belongs to)
Analyst ratingsRecommend.All, Recommend.MA, Recommend.Other
Beta/riskbeta_1_year
Pre/post marketpremarket_change, premarket_volume, postmarket_change
Earningsearnings_release_next_trading_date_fq, earnings_per_share_forecast_next_fq
Crypto-specific24h_vol_change, circulating_supply, total_supply, 24h_close_change

Architecture Fit (Gold Standard)

WorldMonitor's data flow: Railway seeds Redis → Vercel reads Redis → Frontend RPC

TradingView Screener calls belong in the Railway AIS relay (scripts/ais-relay.cjs), alongside existing CoinGecko/Yahoo/Finnhub calls.

Railway ais-relay.cjs
  └── seedTvStockScreener()     → market:tv-screener:stocks:v1    (TTL 5m)
  └── seedTvCryptoScreener()    → market:tv-screener:crypto:v1    (TTL 5m)
  └── seedTvForexScreener()     → market:tv-screener:forex:v1     (TTL 5m)
  └── seedTvTechnicals()        → market:tv-technicals:v1         (TTL 5m)
  └── seedTvEarningsCalendar()  → market:tv-earnings:v1           (TTL 1h)
  └── seedTvSectorSummary()     → market:tv-sectors:v1            (TTL 5m)

Vercel RPC handlers (read-only from Redis):
  └── list-tv-stock-screener.ts
  └── list-tv-crypto-screener.ts
  └── list-tv-forex-screener.ts
  └── get-tv-technicals.ts
  └── list-tv-earnings.ts
  └── list-tv-sectors.ts

Frontend → circuit breaker → RPC → Redis

No TradingView calls from Vercel edge. All upstream calls are Railway-side.

The TS library (tradingview-screener-ts) runs inside the Railway relay Node.js process.


Integration Patterns

Seed Script Pattern (matches existing seed-crypto-quotes.mjs)

javascript
// scripts/seed-tv-stock-screener.mjs
import { Query, col } from 'tradingview-screener-ts';
import { runSeed, CHROME_UA } from './_seed-utils.mjs';

const TV_KEY = 'market:tv-screener:stocks:v1';
const CACHE_TTL = 300; // 5 minutes

async function seedTvStockScreener() {
  const [total, rows] = await new Query()
    .select(
      'name', 'close', 'change', 'volume', 'market_cap_basic',
      'relative_volume_10d_calc', 'RSI', 'sector', 'country'
    )
    .where(
      col('market_cap_basic').gt(1_000_000_000),
      col('active_symbol').eq(true),
      col('is_primary').eq(true)
    )
    .order_by('Value.Traded', false)
    .limit(100)
    .get_scanner_data();

  if (!rows.length) throw new Error('TradingView returned no stock data');

  await runSeed(TV_KEY, { rows, total }, CACHE_TTL);
}

seedTvStockScreener().catch(err => {
  console.error('FATAL:', err.message);
  process.exit(1);
});

Relay Integration Pattern

javascript
// In scripts/ais-relay.cjs — add to seedAllMarketData():

const { Query, col } = require('tradingview-screener-ts');

async function seedTvStockScreener() {
  try {
    const [total, rows] = await new Query()
      .select('name', 'close', 'change', 'volume', 'market_cap_basic', 'RSI', 'sector')
      .where(col('market_cap_basic').gt(1_000_000_000), col('is_primary').eq(true))
      .order_by('Value.Traded', false)
      .limit(100)
      .get_scanner_data();

    if (rows.length > 0) {
      await redisSet('market:tv-screener:stocks:v1', JSON.stringify({ rows, total }), 'EX', 300);
      console.log(`[TV Screener] Seeded ${rows.length} stocks`);
    }
  } catch (err) {
    console.error('[TV Screener] Failed:', err.message);
  }
}

Server Handler Pattern (read-only from Redis)

typescript
// server/worldmonitor/market/v1/list-tv-stock-screener.ts
import { getCachedJson } from '@/_shared/redis';
import type { ListTvStockScreenerRequest, ListTvStockScreenerResponse } from '@generated/...';

const CACHE_KEY = 'market:tv-screener:stocks:v1';

export async function listTvStockScreener(
  _req: ListTvStockScreenerRequest
): Promise<ListTvStockScreenerResponse> {
  const data = await getCachedJson<{ rows: TvStockRow[]; total: number }>(CACHE_KEY, true);
  return { stocks: data?.rows ?? [], total: data?.total ?? 0 };
}

Panel Extensions

1. Stock Screener Panel

New panel idea: Filterable table of top stocks by market cap, volume, RSI.

Data needed:

typescript
new Query()
  .select('name', 'close', 'change', 'change_abs', 'volume',
          'market_cap_basic', 'RSI', 'relative_volume_10d_calc',
          'sector', 'country', 'exchange')
  .where(
    col('market_cap_basic').gt(1_000_000_000),  // >$1B market cap
    col('is_primary').eq(true),
    col('active_symbol').eq(true)
  )
  .order_by('Value.Traded', false)
  .limit(100)

Fields per row:

  • Ticker, Name, Price, Change %, Volume, Market Cap, RSI, Rel. Volume, Sector

2. Enhanced Crypto Panel

Upgrade existing CryptoPanel with TradingView data (richer than CoinGecko):

typescript
new Query()
  .set_markets('crypto')
  .select('name', 'close', 'change', 'change|1W', 'volume',
          'market_cap_basic', 'RSI', 'Recommend.All',
          'relative_volume_10d_calc', '24h_vol_change')
  .order_by('market_cap_basic', false)
  .limit(50)

New fields vs current: Analyst recommendation, multi-timeframe change, RSI, relative volume.


3. Sector Performance Panel

Real-time sector heatmap (US stock sectors + international):

typescript
// One query per sector, or use sector field + aggregate
new Query()
  .select('sector', 'change')
  .where(
    col('market_cap_basic').gt(500_000_000),
    col('type').isin(['stock']),
    col('exchange').not_in(['OTC']),
    col('is_primary').eq(true)
  )
  .order_by('Value.Traded', false)
  .limit(2000)
// Then group by 'sector' and average 'change' on the Railway side

Sectors available: Technology, Healthcare, Financials, Consumer Cyclical, Industrials, Communication Services, Consumer Defensive, Energy, Basic Materials, Real Estate, Utilities.


4. Forex Panel

New panel: Major currency pairs with 24h change.

typescript
new Query()
  .set_markets('forex')
  .select('name', 'close', 'change', 'change_abs', 'volume',
          'RSI', 'MACD.macd', 'EMA20', 'BB.upper', 'BB.lower')
  .set_tickers(
    'FX:EURUSD', 'FX:GBPUSD', 'FX:USDJPY', 'FX:USDCHF',
    'FX:AUDUSD', 'FX:USDCAD', 'FX:NZDUSD', 'FX:USDHKD',
    'FX:USDCNH', 'FX:EURGBP'
  )
  .get_scanner_data()

5. Commodity Futures Panel

Upgrade existing CommoditiesPanel with futures data:

typescript
new Query()
  .set_markets('futures')
  .select('name', 'close', 'change', 'change_abs', 'volume',
          'open', 'high', 'low', 'price_52_week_high', 'price_52_week_low')
  .set_tickers(
    'NYMEX:CL1!',  // WTI Crude Oil
    'NYMEX:NG1!',  // Natural Gas
    'COMEX:GC1!',  // Gold
    'COMEX:SI1!',  // Silver
    'CBOT:ZW1!',   // Wheat
    'CBOT:ZC1!',   // Corn
    'CBOT:ZS1!',   // Soybeans
    'NYMEX:HO1!',  // Heating Oil
    'NYMEX:RB1!',  // RBOB Gasoline
    'COMEX:HG1!',  // Copper
    'COMEX:PL1!',  // Platinum
    'COMEX:PA1!',  // Palladium
  )
  .get_scanner_data()

6. Technical Signals Panel

New panel: Stocks/crypto with notable technical setups.

typescript
// Golden cross: EMA50 crossed above EMA200
new Query()
  .select('name', 'close', 'change', 'EMA50', 'EMA200', 'volume', 'RSI')
  .where(
    col('EMA50').crosses_above(col('EMA200')),
    col('volume').gt(500_000),
    col('market_cap_basic').gt(500_000_000)
  )
  .limit(20)
  .get_scanner_data()

// Oversold (RSI < 30) with positive change
new Query()
  .select('name', 'close', 'change', 'RSI', 'volume', 'market_cap_basic')
  .where(
    col('RSI').between(20, 30),
    col('change').gt(0),
    col('volume').gt(1_000_000)
  )
  .limit(20)
  .get_scanner_data()

// Strong buy recommendations
new Query()
  .select('name', 'close', 'Recommend.All', 'RSI', 'MACD.macd')
  .where(col('Recommend.All').between(0.5, 1.0))
  .order_by('Recommend.All', false)
  .limit(20)
  .get_scanner_data()

7. Earnings Calendar Panel

New panel: Upcoming earnings dates.

typescript
new Query()
  .select('name', 'close', 'change', 'market_cap_basic',
          'earnings_release_next_trading_date_fq',
          'earnings_per_share_forecast_next_fq',
          'earnings_per_share_basic_ttm')
  .where(
    col('earnings_release_next_trading_date_fq').in_day_range(0, 7),
    col('market_cap_basic').gt(1_000_000_000)
  )
  .order_by('market_cap_basic', false)
  .limit(50)
  .get_scanner_data()

8. Relative Strength / Hot Sectors

Ideas for the AI Forecasts panel or new "Market Pulse" panel:

typescript
// Top movers today
new Query()
  .select('name', 'close', 'change', 'volume', 'relative_volume_10d_calc', 'sector')
  .where(
    col('change').gt(5),                         // up 5%+
    col('volume').gt(1_000_000),
    col('relative_volume_10d_calc').gt(2)        // 2x normal volume
  )
  .order_by('change', false)
  .limit(20)
  .get_scanner_data()

// Pre-market movers
new Query()
  .select('name', 'close', 'premarket_change', 'premarket_volume', 'market_cap_basic')
  .where(
    col('premarket_change').not_empty(),
    col('premarket_volume').gt(100_000),
    col('market_cap_basic').gt(500_000_000)
  )
  .order_by('premarket_change', false)
  .limit(20)
  .get_scanner_data()

9. Bond / Yield Panel

New panel: Government bond yields.

typescript
new Query()
  .set_markets('bonds')
  .select('name', 'close', 'change', 'yield', 'duration', 'country')
  .set_tickers(
    'TVC:US10Y',   // US 10-year yield
    'TVC:US02Y',   // US 2-year yield
    'TVC:US30Y',   // US 30-year yield
    'TVC:DE10Y',   // German 10Y
    'TVC:GB10Y',   // UK 10Y
    'TVC:JP10Y',   // Japan 10Y
    'TVC:IT10Y',   // Italy 10Y
    'TVC:CN10Y',   // China 10Y
  )
  .get_scanner_data()

Field Reference

Price & Volume

FieldDescriptionType
closeCurrent/closing priceprice
openOpening priceprice
highDay highprice
lowDay lowprice
volumeVolumenumber
Value.TradedDollar volume tradedfundamental_price
average_volume_10d_calc10-day avg volumenumber
relative_volume_10d_calcRelative volume vs 10d avgnumber

Change

FieldDescriptionType
change% change todaypercent
change_absAbsolute $ changeprice
change_from_open% change from openpercent
gapGap % from prev closepercent
premarket_changePre-market % changepercent
postmarket_changeAfter-hours % changepercent

Technicals

FieldDescriptionType
RSIRSI(14)number
RSI[1]RSI prev barnumber
MACD.macdMACD linenumber
MACD.signalSignal linenumber
MACD.histHistogramnumber
BB.upperBollinger upperprice
BB.lowerBollinger lowerprice
BB.midBollinger mid (SMA20)price
VWAPVWAPprice
stochastic_kStochastic %Knumber
stochastic_dStochastic %Dnumber
ATRAverage True Rangenumber
Recommend.AllCombined TV rating (-1 to 1)number
Recommend.MAMA recommendationnumber
Recommend.OtherOscillator recommendationnumber

Moving Averages

FieldDescription
EMA5, EMA10, EMA20Exponential MAs
EMA50, EMA100, EMA200Longer-term EMAs
SMA5, SMA10, SMA20Simple MAs
SMA50, SMA100, SMA200Longer-term SMAs
HullMA9Hull MA
VWMAVolume-weighted MA

Fundamentals (Stocks)

FieldDescriptionType
market_cap_basicMarket capfundamental_price
price_earnings_ttmP/E rationumber
earnings_per_share_basic_ttmEPS (TTM)fundamental_price
dividend_yield_recentDividend yield %percent
dividends_yield_currentCurrent dividend yieldpercent
book_value_per_shareBook value/sharefundamental_price
price_book_fqPrice/Booknumber
price_sales_currentPrice/Salesnumber
debt_to_equityDebt/Equity rationumber
return_on_equityROE %percent
gross_marginGross margin %percent
net_income_marginNet margin %percent
beta_1_yearBeta vs marketnumber
price_52_week_high52-week highprice
price_52_week_low52-week lowprice
number_of_employeesHeadcountnumber
sectorSector classificationtext
industryIndustrytext

Crypto-Specific

FieldDescriptionType
market_cap_basicMarket capfundamental_price
24h_vol_change24h volume change %percent
circulating_supplyCirculating supplynumber
total_supplyTotal supplynumber
24h_close_change24h close change %percent

Classification

FieldDescription
nameTicker symbol
descriptionCompany/asset name
typestock, fund, dr, bond, crypto
typespecsSub-type array: ['common'], ['etf'], ['etn']
exchangeExchange: NASDAQ, NYSE, BINANCE, etc.
countryCountry code
currencyQuote currency
update_modestreaming, delayed_streaming_900
is_primaryPrimary listing (not ADR/secondary)
active_symbolTraded today

Query Cookbook

Top 50 Most Traded US Stocks

typescript
const [total, rows] = await new Query()
  .select('name', 'description', 'close', 'change', 'volume', 'market_cap_basic', 'sector')
  .where(
    col('is_primary').eq(true),
    col('active_symbol').eq(true),
    col('exchange').not_in(['OTC'])
  )
  .order_by('Value.Traded', false)
  .limit(50)
  .get_scanner_data()

Top Crypto by Market Cap

typescript
const [total, rows] = await new Query()
  .set_markets('crypto')
  .select('name', 'close', 'change', 'market_cap_basic', 'volume', 'RSI')
  .order_by('market_cap_basic', false)
  .limit(50)
  .get_scanner_data()

S&P 500 Components

typescript
const [total, rows] = await new Query()
  .set_index('SP;SPX')
  .select('name', 'close', 'change', 'volume', 'market_cap_basic', 'sector', 'RSI')
  .order_by('market_cap_basic', false)
  .limit(505)
  .get_scanner_data()

Sector Performance (US)

typescript
// Fetch all large-cap stocks with sector; aggregate by sector on Railway side
const [total, rows] = await new Query()
  .select('sector', 'change', 'market_cap_basic')
  .where(
    col('market_cap_basic').gt(500_000_000),
    col('type').isin(['stock']),
    col('is_primary').eq(true),
    col('exchange').not_in(['OTC']),
    col('sector').not_empty()
  )
  .limit(2000)
  .get_scanner_data()

// Group and average on Railway:
const sectorChange = Object.entries(
  rows.reduce((acc, r) => {
    const s = r.sector as string
    if (!acc[s]) acc[s] = { sum: 0, count: 0 }
    acc[s].sum += (r.change as number) ?? 0
    acc[s].count++
    return acc
  }, {} as Record<string, { sum: number; count: number }>)
).map(([name, { sum, count }]) => ({ name, change: sum / count }))

Forex Major Pairs

typescript
const [total, rows] = await new Query()
  .set_markets('forex')
  .set_tickers(
    'FX:EURUSD', 'FX:GBPUSD', 'FX:USDJPY', 'FX:USDCHF',
    'FX:AUDUSD', 'FX:USDCAD', 'FX:NZDUSD', 'FX:EURGBP',
    'FX:EURJPY', 'FX:GBPJPY'
  )
  .select('name', 'close', 'change', 'change_abs', 'high', 'low', 'RSI')
  .get_scanner_data()

Commodity Futures

typescript
const [total, rows] = await new Query()
  .set_markets('futures')
  .set_tickers(
    'NYMEX:CL1!', 'NYMEX:NG1!', 'COMEX:GC1!', 'COMEX:SI1!',
    'CBOT:ZW1!', 'CBOT:ZC1!', 'CBOT:ZS1!', 'COMEX:HG1!'
  )
  .select('name', 'close', 'change', 'high', 'low', 'volume')
  .get_scanner_data()

Multi-Timeframe Screener (1D + 1W change)

typescript
const [total, rows] = await new Query()
  .select('name', 'close', 'change', 'change|1W', 'change|1M',
          'RSI', 'RSI|1W', 'volume', 'market_cap_basic')
  .where(col('market_cap_basic').gt(1_000_000_000))
  .order_by('Value.Traded', false)
  .limit(50)
  .get_scanner_data()

Upcoming Earnings (7 Days)

typescript
const [total, rows] = await new Query()
  .select('name', 'close', 'change', 'market_cap_basic',
          'earnings_release_next_trading_date_fq',
          'earnings_per_share_forecast_next_fq')
  .where(
    col('earnings_release_next_trading_date_fq').in_day_range(0, 7),
    col('market_cap_basic').gt(1_000_000_000)
  )
  .order_by('market_cap_basic', false)
  .limit(50)
  .get_scanner_data()

Technical Breakouts

typescript
// Price above 52-week high
const [total, rows] = await new Query()
  .select('name', 'close', 'change', 'volume', 'price_52_week_high')
  .where(
    col('close').above_pct(col('price_52_week_high'), 1.0),  // AT or above 52wk high
    col('volume').gt(1_000_000)
  )
  .order_by('change', false)
  .limit(20)
  .get_scanner_data()

// Golden cross (EMA50 > EMA200)
const [total2, rows2] = await new Query()
  .select('name', 'close', 'change', 'EMA50', 'EMA200', 'volume')
  .where(
    col('EMA50').crosses_above(col('EMA200')),
    col('volume').gt(500_000)
  )
  .limit(20)
  .get_scanner_data()

Oversold / Strong Buy Setups

typescript
// RSI oversold with positive change
const [total, rows] = await new Query()
  .select('name', 'close', 'change', 'RSI', 'Recommend.All')
  .where(
    col('RSI').between(20, 35),
    col('change').gt(0),
    col('volume').gt(500_000),
    col('market_cap_basic').gt(500_000_000)
  )
  .limit(20)
  .get_scanner_data()

Rate Limiting & Production Notes

TradingView API Behavior

  • No documented rate limits. In practice, bans are possible with high-frequency polling.
  • Auth: Unauthenticated requests get delayed data (15-min for some exchanges). Pass a valid TradingView session cookie for real-time.
  • Max limit: Up to 100,000 rows technically possible but not recommended.
  • No retry logic built-in — implement your own for 429/5xx.

WorldMonitor-Specific Recommendations

ConcernRecommendation
Polling interval5 min (matches existing MARKET_SEED_INTERVAL_MS = 300_000)
TTL5 min for price data, 1h for earnings/fundamentals
Per-call limits100 rows for screener, 500 for index components
CoinGecko overlapKeep CoinGecko for crypto token panels (richer DeFi/AI/Other data); TradingView for top-N crypto
Finnhub/Yahoo overlapTradingView can supplement stock quotes; keep Finnhub/Yahoo as primary for existing panels
Railway concurrencyRun TV calls sequentially inside seedAllMarketData(), not Promise.all
Circuit breakerAdd a tvScreenerBreaker alongside existing stock/crypto breakers
No auth initiallyDelayed data is fine for WorldMonitor's use case
User-AgentLibrary automatically mimics Chrome headers; do not override

Sample Relay Integration Timing

Assuming existing relay runs seedAllMarketData() every 5 min with 3 existing CoinGecko calls:

Seed FunctionAvg DurationCoinGecko?
seedCryptoQuotes~1sYes
seedStablecoins~1sYes
seedCryptoSectors~1.5sYes
seedTokenPanels~1.5sYes
seedTvStocks (new)~0.5sNo (TradingView)
seedTvCrypto (new)~0.5sNo (TradingView)
seedTvForex (new)~0.5sNo (TradingView)

TradingView calls are independent of CoinGecko rate limits. Each call completes in 200-800ms.


Implementation Checklist

Phase A: Stock Screener Panel

  • npm install tradingview-screener-ts in Railway relay package
  • Add seedTvStockScreener() to scripts/ais-relay.cjs
  • Add market:tv-screener:stocks:v1 to server/_shared/cache-keys.ts
  • Create proto/worldmonitor/market/v1/list_tv_stock_screener.proto
  • Create server/worldmonitor/market/v1/list-tv-stock-screener.ts
  • Register in handler.ts and service.proto
  • Run buf generate
  • Add tvStocksBreaker to src/services/market/index.ts
  • Create TvStockScreenerPanel in src/components/MarketPanel.ts
  • Register panel in src/config/panels.ts
  • Wire in panel-layout.ts and data-loader.ts
  • Add to api/bootstrap.js and cache-keys.ts BOOTSTRAP_TIERS
  • Add cache tier to server/gateway.ts
  • Sync scripts/shared/ if needed
  • npm run typecheck && npm run test:data → all pass

Phase B: Sector Performance Upgrade

  • Add seedTvSectorSummary() to relay (aggregates by sector server-side)
  • Upgrade existing HeatmapPanel to use TradingView sector data
  • Keep existing get-sector-summary handler as fallback

Phase C: Forex Panel

  • Add seedTvForexPairs() to relay
  • New ForexPanel component
  • Register in FINANCE_PANELS

Phase D: Enhanced Commodity Futures

  • Add seedTvCommodityFutures() (replaces/supplements Yahoo commodities)
  • Upgrade CommoditiesPanel rendering with futures data

Phase E: Earnings Calendar Panel

  • Add seedTvEarningsCalendar() (TTL 1h, not 5min)
  • New EarningsCalendarPanel component
  • Show next 7 days of earnings for large-cap stocks

Appendix: Useful TradingView Market Identifiers

Major US Indices (for set_index())

SymbolIndex
SP;SPXS&P 500
DJ;DJIDow Jones
NASDAQ;NDXNasdaq 100
SP;MIDS&P MidCap 400
SP;SMLS&P SmallCap 600
RUSSELL;RUTRussell 2000

Futures Tickers

TickerCommodity
NYMEX:CL1!WTI Crude Oil
NYMEX:NG1!Natural Gas
COMEX:GC1!Gold
COMEX:SI1!Silver
COMEX:HG1!Copper
COMEX:PL1!Platinum
CBOT:ZW1!Wheat
CBOT:ZC1!Corn
CBOT:ZS1!Soybeans
CME:ES1!S&P 500 E-mini
CME:NQ1!Nasdaq E-mini
CME:RTY1!Russell 2000 E-mini
EUREX:FDAX1!DAX Futures
SGX:CN1!CSI 300 Futures

Government Bonds / Yields

TickerDescription
TVC:US02YUS 2-Year Yield
TVC:US10YUS 10-Year Yield
TVC:US30YUS 30-Year Yield
TVC:DE10YGerman 10-Year Bund
TVC:GB10YUK Gilt 10-Year
TVC:JP10YJapan JGB 10-Year
TVC:IT10YItaly BTP 10-Year
TVC:FR10YFrance OAT 10-Year
TVC:CN10YChina 10-Year

Forex Pairs

TickerPair
FX:EURUSDEUR/USD
FX:GBPUSDGBP/USD
FX:USDJPYUSD/JPY
FX:USDCHFUSD/CHF
FX:AUDUSDAUD/USD
FX:USDCADUSD/CAD
FX:NZDUSDNZD/USD
FX:USDCNHUSD/CNH (offshore RMB)
FX:USDINRUSD/INR
FX:USDBRLUSD/BRL
FX:USDTRYUSD/TRY
FX:USDRUBUSD/RUB
FX:USDZARUSD/ZAR

Document generated 2026-03-20. TradingView API is undocumented and subject to change. Field availability and market identifiers should be verified against the library's live metadata endpoint before production use: GET /api/v1/metadata/fields?universe={market}