| name | tv-price-refresh |
| plugin | tradingview |
| description | Pulls real-time prices for all portfolio positions. Uses TradingView Desktop if running (port 9222), falls back to yfinance automatically per ticker. Trigger on /tv-price-refresh, "refresh prices", or "get live prices".
|
| allowed-tools | Bash, Read |
TradingView Price Refresh Skill
Price Source Priority (enforced in tv_batch_quotes.py)
| Priority | Source | When used |
|---|
| 1 (primary) | TradingView watchlist via CDP | TradingView running (port 9222) |
| 2 (fallback) | yfinance fast_info.last_price | TV unreachable, or ticker not in watchlist |
tv_batch_quotes.py reads the live TV-Full Watchlist via CDP — a single watchlist for all hours,
but each row is session-aware: outside regular hours, TradingView freezes its regular "last" price
and surfaces the extended/overnight move in a separate cell (labeled "Pre-market", "Post-market", or
"Overnight via BOATS"). _select_effective_price() picks the current tradable price per a 3-tier
priority — regular hours -> extended hours -> overnight/BOATS — rather than reporting the frozen
regular-session price after close.
yfinance is only used when TradingView is not running.
What This Skill Does
- Checks TradingView Desktop status (non-blocking — uses fallback if unavailable)
- Loads all tickers from
portfolio.json and target-portfolio.json
- Fetches live quotes — TV watchlist (
TV-Full Watchlist) first, yfinance only for misses
- Prints a price table with live data, 1d change%, and source indicator
- Summarises how many quotes came from TradingView vs. yfinance fallback
Phase 1 — Health Check
python3 "$(find plugins/tradingview/scripts ~/.claude/plugins/cache -name tv_health_check.py -path "*/tradingview/*" 2>/dev/null | sort | tail -1)" --json
If TradingView is not running, do not stop — proceed with yfinance fallback.
Note at the top of output: [yfinance mode — TradingView not connected]
Phase 2 — Load Tickers
Load tickers from both sources and deduplicate:
cat investment_screener/backend/data/portfolio.json
cat investment_screener/backend/data/theses/target-portfolio.json
Combine symbols from both files into a deduplicated list.
Exclude USD_CASH (not a tradeable symbol).
Phase 3 — Fetch Quotes
python3 plugins/tradingview/scripts/tv_batch_quotes.py '["CRWV","NVDA","INTC",...]'
Phase 4 — Display Results
Format and print a price table:
╔══════════════════════════════════════════════════════════════════════╗
║ LIVE PRICE REFRESH [2026-05-07 14:32 ET] ║
╚══════════════════════════════════════════════════════════════════════╝
[Source: TradingView real-time | yfinance fallback]
Ticker Price 1d Change% Source
---------- --------- ---------- ----------
CRWV $115.26 -6.07% TradingView
NVDA $497.32 +1.23% TradingView
INTC $22.15 -0.45% yfinance
AAPL $189.50 +0.12% TradingView
...
Summary: 35 tickers | 33 real-time (TradingView) | 2 yfinance fallback | 0 errors
Highlight large moves (>5% either direction) with a marker.
Error Handling
| Situation | Response |
|---|
| TradingView not running | Note in header, use yfinance for all |
| yfinance fails for one ticker | Mark as "ERROR" in table, continue |
| portfolio.json missing | Report error, try target-portfolio.json only |
| Empty ticker list | Print message and exit cleanly |
Hard Rules
- Never fail entirely if TradingView is down — yfinance fallback must always work
- Always show the source for each price so the user knows what's real-time vs. delayed
- Never modify any portfolio files — this is a read-only operation
Related: Backend /refresh-prices Endpoint (routes/portfolio.ts)
The Express POST /refresh-prices endpoint (separate code path from this skill's CLI
tools) calls py_services/fetch_portfolio_heatmap.py, not tv_batch_quotes.py. Two rules
apply there — do not regress either when touching that endpoint or its Python script:
- Always use the most current available price, regular or extended hours.
fetch_portfolio_heatmap.py already implements this: TradingView watchlist-first via CDP
(TV-Full Watchlist, session-aware via tv_batch_quotes._select_effective_price() —
regular hours -> extended hours -> overnight/BOATS), falling back to yfinance
fast_info.last_price only when TV is unreachable. Never swap this for a plain
regularMarketPrice/last-close lookup — that would silently drop extended-hours coverage.
- A price refresh must also refresh the stored USD/CAD exchange rate at the same time.
Wave 3 Task 8:
POST /refresh-prices now calls
fetch_broker_data.py --refresh-exchange-rate (a lightweight balances-only CDP fetch via
refresh_exchange_rate_only()) in the same Promise.all as the price fetch, so the stored
rate in broker_exchange_rate never goes stale relative to freshly-refreshed USD prices.
This is separate from a full --snapshot broker sync — do not remove it under the
assumption that only a full sync should touch the exchange rate.