Skip to content

feat(tradingview): add tradingview#1432

Open
jbriaux wants to merge 3 commits into
mvanhorn:mainfrom
jbriaux:feat/tradingview
Open

feat(tradingview): add tradingview#1432
jbriaux wants to merge 3 commits into
mvanhorn:mainfrom
jbriaux:feat/tradingview

Conversation

@jbriaux

@jbriaux jbriaux commented Jul 3, 2026

Copy link
Copy Markdown

tradingview

Resolve any ticker and see its price in USD and EUR in one command — stocks, crypto, and forex, no API key.

API: tradingview | Category: payments | Press version: 4.27.1
Spec: internal (live-probed public endpoints: scanner.tradingview.com, symbol-search.tradingview.com)

Publication Path

New print

CLI Shape

$ tradingview-pp-cli --help
Tradingview CLI — Resolve any ticker and see its price in USD and EUR in one command — stocks, crypto, and forex, no API key.

Available Commands:
  convert       Convert an amount between currencies using TradingView's own live forex rates.
  doctor        Check CLI health
  market        Get last price and currency for a fully-qualified EXCHANGE:TICKER
  quote         See any ticker's last price in its native currency, USD, and EUR in a single command.
  search        Search TradingView symbols: resolve a name or ticker to fully-qualified EXCHANGE:TICKER candidates.
  symbols       Search symbols by text (returns exchange, type, currency)
  sync          Sync API data to local SQLite for offline search and analysis
  watchlist     Track a local list of symbols and snapshot their quotes for offline use.
  which         Find the command that implements a capability
  workflow      Compound workflows that combine multiple API operations

Agent mode: add --agent to any command for JSON output + non-interactive mode.

Novel Commands

Command Name Description
quote Multi-currency quote See any ticker's last price in its native currency, USD, and EUR in a single command.
convert TradingView-rate conversion Convert an amount between currencies using TradingView's own live forex rates.
watchlist Local watchlist Track a list of symbols locally and snapshot their USD/EUR quotes to SQLite for offline querying.
watchlist changes Snapshot drift Show how each watched symbol moved in USD between its two most recent snapshots.

What This CLI Does

TradingView CLI turns a symbol into an answer: search resolves a query to a fully-qualified EXCHANGE:TICKER, quote returns the last price in its native currency plus USD and EUR, and convert does fiat conversion using TradingView's own forex rates. Terminal-first, agent-native --json, no data-API subscription required for the public endpoints. A local watchlist tracks symbols and snapshots their USD/EUR prices to SQLite for offline querying and drift.

TradingView has no official public data API; this CLI is built from live-probed public endpoints (symbol search + the universal scanner /symbol price endpoint + FX_IDC forex rates). No authentication is required at runtime. Crypto quote currencies (USDT/USDC) are treated as USD-pegged and surfaced explicitly.

Manuscripts

Validation Results

Check Result
Manifest PASS
Phase 5 PASS
go mod tidy PASS
govulncheck (this CLI only, reachable findings) PASS (no vulnerabilities)
go vet PASS
go build PASS
--help PASS
--version PASS
verify-skill PASS
Manuscripts PRESENT

Publish Live Gate

Full live dogfood reran at publish time against the real TradingView endpoints and passed (matrix 55/55, level full, auth: none). Proof: Phase 5 Acceptance Marker

Notes

Three hand-customizations over generated output are recorded under .printing-press-patches/:

  • tradingview-search-command — replaced the generator's broken framework FTS search (it targeted the root scanner host instead of the symbol-search host and 404'd) with the working symbol resolver, aliased lookup.
  • tradingview-watchlist — added the local SQLite watchlist (add/list/sync/quotes/changes).
  • tradingview-mcp-trim — hid redundant/no-op commands from the MCP surface.

🤖 Generated with Claude Code (Printing Press)

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a new tradingview CLI to the library that resolves any ticker and returns its price in native currency, USD, and EUR — with no API key — by live-probing TradingView's public scanner and symbol-search endpoints. The four issues flagged in the previous review round (non-atomic watchlist sync, unbounded snapshot growth, offline remove mismatch, and stale price cache) are all addressed in this version.

  • Novel commands: quote (multi-currency price), convert (live FX rates), and watchlist (local SQLite snapshot store with sync/quotes/changes subcommands).
  • Core design: fetchRawQuote uses GetNoCache to bypass the 5-minute response cache for every price call; fetchForexRate handles direct/inverse/cross-via-USD fallback; watchlist sync fetches all quotes before opening a transaction, then writes and prunes atomically.
  • Remaining nit: both latestSnapshot and the changes command sort by ts DESC while the pruning logic sorts by rowid DESC; two syncs within the same RFC3339 second would produce identical timestamps, making the "two most recent snapshots" query non-deterministic.

Confidence Score: 5/5

Safe to merge; the previous round's blocking concerns are all resolved and the only remaining finding is a latent ordering inconsistency in the watchlist snapshot queries.

All four issues from the prior review (non-atomic sync writes, unbounded snapshot table, offline remove mismatch, stale price cache for quotes) are correctly addressed. The two remaining comments describe a non-deterministic snapshot-pair selection that only triggers when two syncs run within the same RFC3339 second — an edge case with negligible real-world exposure and no data loss risk.

library/payments/tradingview/internal/cli/watchlist.go — the changes and latestSnapshot queries sort by ts while the pruning query sorts by rowid; these should use the same ordering column.

Important Files Changed

Filename Overview
library/payments/tradingview/internal/cli/watchlist.go Novel watchlist command with SQLite-backed snapshots; sync is now properly wrapped in a transaction with keepPerSymbol=200 pruning, and remove has an offline LIKE fallback. Two queries (latestSnapshot and changes) sort by ts while pruning sorts by rowid, creating a non-deterministic ordering if two syncs land in the same RFC3339 second.
library/payments/tradingview/internal/cli/tradingview_market.go Hand-authored core: symbol resolution, raw quote fetch (GetNoCache — correctly bypasses 5-min cache), and forex rate resolution with direct/inverse/cross-via-USD fallback chain. Logic is sound.
library/payments/tradingview/internal/cli/quote.go Multi-currency quote command; fetches native price then converts to USD and EUR via FX_IDC rates. USD/EUR math (priceEUR = priceUSD / EURUSD) is correct. USD-pegged stablecoins handled via isUSDLike.
library/payments/tradingview/internal/client/client.go Generated HTTP client; 5-min GET cache is correctly bypassed for price endpoints via GetNoCache. Cross-host auth stripping on redirects is properly implemented.
library/payments/tradingview/internal/cli/convert.go Currency conversion using TradingView FX_IDC rates; delegates to fetchForexRate which handles direct/inverse/cross pairs. Clean implementation.
library/payments/tradingview/internal/store/store.go SQLite store with schema versioning; uses modernc.org/sqlite (pure Go, no CGO). Write-serialization via writeMu, WAL-compatible read paths.
library/payments/tradingview/internal/cli/lookup.go Search/lookup command wrapping searchSymbols; handles type and exchange filters. Clean and straightforward.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    User([User]) --> quote["quote <symbol>"]
    User --> convert["convert <amount> <from> <to>"]
    User --> search["search <query>"]
    User --> wl["watchlist subcommands"]

    quote --> resolveSymbol["resolveSymbol()\nsymbol-search.tradingview.com"]
    resolveSymbol --> fetchRawQuote["fetchRawQuote()\nscanner.tradingview.com/symbol\n[GetNoCache — bypasses 5-min cache]"]
    fetchRawQuote --> isUSD{native currency\nUSD-like?}
    isUSD -- yes --> eurusd["fetchRawQuote(FX_IDC:EURUSD)\n→ priceEUR = priceUSD / EURUSD"]
    isUSD -- no --> fetchForexRate["fetchForexRate(cur→USD)\ndirect / inverse / cross-via-USD"]
    fetchForexRate --> eurusd
    eurusd --> quoteView["quoteView{price, USD, EUR}"]

    convert --> fetchForexRate2["fetchForexRate(from→to)"]
    fetchForexRate2 --> convertView["convertView{rate, result}"]

    search --> searchSymbols["searchSymbols()\nsymbol-search.tradingview.com\n[c.Get — 5-min cache OK]"]

    wl --> wladd["add: resolveSymbol + buildQuoteView → INSERT watchlist"]
    wl --> wlsync["sync: fetch all quotes (network)\nthen INSERT + PRUNE in single tx\n[keepPerSymbol=200, ORDER BY rowid DESC]"]
    wl --> wlchanges["changes: SELECT … ORDER BY ts DESC LIMIT 2\n⚠ ordering differs from prune (rowid)"]
    wlsync --> SQLite[(SQLite\nwatchlist +\nquote_snapshots)]
    wlchanges --> SQLite
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    User([User]) --> quote["quote <symbol>"]
    User --> convert["convert <amount> <from> <to>"]
    User --> search["search <query>"]
    User --> wl["watchlist subcommands"]

    quote --> resolveSymbol["resolveSymbol()\nsymbol-search.tradingview.com"]
    resolveSymbol --> fetchRawQuote["fetchRawQuote()\nscanner.tradingview.com/symbol\n[GetNoCache — bypasses 5-min cache]"]
    fetchRawQuote --> isUSD{native currency\nUSD-like?}
    isUSD -- yes --> eurusd["fetchRawQuote(FX_IDC:EURUSD)\n→ priceEUR = priceUSD / EURUSD"]
    isUSD -- no --> fetchForexRate["fetchForexRate(cur→USD)\ndirect / inverse / cross-via-USD"]
    fetchForexRate --> eurusd
    eurusd --> quoteView["quoteView{price, USD, EUR}"]

    convert --> fetchForexRate2["fetchForexRate(from→to)"]
    fetchForexRate2 --> convertView["convertView{rate, result}"]

    search --> searchSymbols["searchSymbols()\nsymbol-search.tradingview.com\n[c.Get — 5-min cache OK]"]

    wl --> wladd["add: resolveSymbol + buildQuoteView → INSERT watchlist"]
    wl --> wlsync["sync: fetch all quotes (network)\nthen INSERT + PRUNE in single tx\n[keepPerSymbol=200, ORDER BY rowid DESC]"]
    wl --> wlchanges["changes: SELECT … ORDER BY ts DESC LIMIT 2\n⚠ ordering differs from prune (rowid)"]
    wlsync --> SQLite[(SQLite\nwatchlist +\nquote_snapshots)]
    wlchanges --> SQLite
Loading

Reviews (2): Last reviewed commit: "fix(tradingview): correct module import ..." | Re-trigger Greptile

Comment on lines +424 to +431

// verifyShortCircuitEnvelope returns the synthetic JSON body that
// stands in for a real mutating response when do() short-circuits in
// verify mode. The __pp_verify_synthetic__ sentinel is namespace-
// reserved (no real API uses __pp_*) so downstream consumers
// (validate-narrative, agent inspections) can key on one obvious field
// instead of trying to disambiguate common literals like status:"noop".
// method and path are echoed back as diagnostic prose for human/agent

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 5-minute response cache silently serves stale prices

readCache uses a hardcoded 5-minute TTL for every GET, including live price requests from fetchRawQuote. quote NASDAQ:AAPL invoked twice within five minutes returns the cached response without any indication that the data is stale. The command's help text promises "last price," so a user that re-runs it during a fast-moving session would silently receive a price that could be several minutes old. Consider expiring price-endpoint responses more aggressively (e.g., 30–60 seconds), surfacing a cache-age note in human output, or defaulting --no-cache for the quote and convert commands.

Fix in Codex Fix in Claude Code Fix in Cursor Fix in Conductor

Comment thread library/payments/tradingview/internal/cli/watchlist.go
Comment thread library/payments/tradingview/internal/cli/watchlist.go
Comment thread library/payments/tradingview/internal/cli/watchlist.go
@jbriaux

jbriaux commented Jul 3, 2026

Copy link
Copy Markdown
Author

Thanks for the review. Addressed all four findings in the follow-up commits:

  • Stale cached prices (client.go) — fetchRawQuote now uses the no-cache GET path so quote/convert/watchlist always fetch a fresh last price (still writes the cache on success).
  • Offline remove of bare tickers (watchlist.go) — a bare ticker now matches any stored EXCHANGE:<ticker> via a LIKE fallback, so removal works even when symbol-search is unreachable.
  • sync outside a transaction (watchlist.go) — all network fetches happen first, then every snapshot insert + prune runs inside a single transaction that commits atomically (rollback on interruption).
  • Unbounded quote_snapshots (watchlist.go) — each sync prunes to the 200 most recent snapshots per symbol (by rowid).

Verified live: transactional double-sync, bare-ticker offline remove, and fresh back-to-back quotes all behave as expected; go build/go vet/go test ./internal/cli/ clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant