I needed to extract every drawing -- tweets, trendlines, arrows, callouts, fibs, the lot -- from my TradingView account (SFWTrades) and import them into a custom trading workstation I'm building. The account had been accumulating chart annotations since 2022 across 489 symbols, 4 chart layouts, and multiple chart panels. The goal: get it all out, structured as JSON, ready for import.
The Wrong Way: 6,664 API Calls Over 62 Minutes
I started this project by pasting a detailed spec into Claude Code on the web and asking it to plan the extraction. It came back with a per-symbol strategy: take every ticker from my watchlists (~1,600 symbols), resolve exchange prefixes, then query TradingView's chart-storage API for each symbol x layout combination:
GET https://charts-storage.tradingview.com/charts-storage/get/layout/{layoutId}/sources
?chart_id=1&layout_id={layoutId}&jwt={jwt}&symbol={EXCHANGE:SYMBOL}
1,666 symbols x 4 layouts = 6,664 API calls. Rate-limited at 2 requests/second with jitter, that's 62 minutes of patience. The script worked -- it found 825 drawings across 281 symbols, with progress tracking, resume support, exponential backoff on 429s, the whole nine yards. Solid engineering for a fundamentally wrong approach.
The problem: Claude never questioned whether the symbol parameter was mandatory. It assumed you had to query one symbol at a time because the spec said so, and the spec said so because I wrote it after reading the API docs at face value. Neither of us tried dropping the filter to see what happened.
The Aha Moment: AAOI
After the 62-minute run finished, I started validating. I knew I had drawings on AAOI (Applied Optics) -- but AAOI wasn't in any of my watchlists. The entire extraction strategy was built around "symbols in watchlists," which meant anything I'd ever drawn on but hadn't watchlisted was invisible.
This led to the obvious experiment: what if we just... don't pass the symbol filter?
GET .../sources?chart_id=1&layout_id=AqIsJwS7&jwt={jwt}
No symbol parameter. The API returned everything: 938 sources across 406 symbols in a single response. Every drawing on that layout, regardless of symbol. Including AAOI.
Suddenly the extraction was 1 API call per layout+chart_id instead of 1,666.
The Hidden Third Bucket: _shared Sources
Even after the no-filter discovery, I was still missing drawings. A user on my TradingView chart had arrow marks on SOUN (SoundHound) from December 2024 -- visible right there on the chart, confirmed as real drawing objects, not indicator output. But the API returned nothing for SOUN except one tweet.
This sent me on a long debugging session: testing chart_ids 0 through 16, checking different exchange prefixes, trying to extract the JWT from a running process's memory (blocked by ptrace protection), dumping the chart page HTML and grepping for "ArrowMark." Nothing.
The breakthrough came from a HAR file captured from the browser's Network tab. TradingView's chart page makes three distinct source requests:
chart_id=1(or 2, etc.) -- per-chart-panel drawings, usually symbol-bound (tweets, predictions, ghost feeds)chart_id=_shared-- cross-chart drawings that persist regardless of which symbol is active (trendlines, arrows, callouts, risk/reward setups)user/sources-- user-level drawings
We'd been querying #1 exclusively. The _shared bucket on layout AqIsJwS7 alone contained 589 additional drawings -- 184 trendlines, 95 horizontal lines, 74 callouts, 55 arrows, and more. This was the bulk of the analytical work. The SOUN arrows were there all along, filed under chart_id=_shared.
The Right Way: 13 API Calls in Seconds
The complete extraction:
For each layout (AqIsJwS7, tBoQUgW3, Y7HM8XK2, Dv9bPTYH):
GET .../sources?chart_id=1 (no symbol filter)
GET .../sources?chart_id=2 (no symbol filter)
GET .../sources?chart_id=_shared
GET .../user/sources
13 requests. A few seconds. 1,752 drawings across 489 symbols, including hidden objects (returned with visible: false), all drawing types, all exchanges, all timeframes. Complete.
Final Numbers
| Source | Drawings |
|---|---|
| AqIsJwS7 chart 1 | 938 |
| AqIsJwS7 chart 2 | 77 |
| AqIsJwS7 shared | 589 |
| Dv9bPTYH chart 1 | 86 |
| Dv9bPTYH chart 2 | 37 |
| Dv9bPTYH shared | 23 |
| Y7HM8XK2 shared | 1 |
| User sources | 1 |
| Total | 1,752 |
Drawing types found: tweets (876), trendlines (239), horizontal lines (234), callouts (76), arrows up/down (69), vertical lines (51), brushes (31), crosslines (24), risk/reward (22), predictions (10), ghost feeds (9), VWAP (16), circles (13), bezier curves (9), text (20), rectangles (4), fib retracements (1), head & shoulders (3), parallel channels (3), projections (2), images (1), and more.
What TradingView Stores Per Tweet
One thing I learned: TradingView's tweet annotations store surprisingly little. Seven fields per tweet:
{
"id": 2047082658606297355,
"createdAt": 1775659322,
"text": "You really do not know how big this really is for $INTC...",
"username": "@imnotharsh",
"user": "Harsh",
"profileImageUrl": "https://pbs.twimg.com/..._normal.jpg",
"tweetUrl": "https://x.com/imnotharsh/status/2047082658606297355"
}
Text is truncated (typically around 300 characters). No quoted tweets, no images, no thread context. If a tweet quotes another tweet with an image -- which is common for fintwit analysis posts -- you get only the outer tweet's partial text with an opaque https://t.co/... link. Enriching all 876 tweets with full content from the X API is the next project.
Lessons
- Test your assumptions about API behavior before building elaborate workarounds. One curl call without the
symbolparameter would have saved an hour of extraction time and significant complexity. The per-symbol script was 300 lines; the full-dump approach is 13 HTTP requests. - Watch the network tab. The
chart_id=_sharedbucket was only discoverable by observing what the browser actually requests. API documentation (to the extent TradingView has any for this internal endpoint) wouldn't have told you about it. - Session cookies travel with the JWT. The charts-storage API requires both the JWT token and the session cookies. The JWT alone silently returns empty results -- no error, just
{"success": true, "payload": {"sources": {}}}. This cost another debugging detour.
The trading workstation can now display all 1,752 annotations natively. The extraction script is 13 API calls that run in seconds, fully resumable, and trivially re-runnable to pick up new drawings.
Enjoyed this post?
Get notified when I publish something new. No spam, unsubscribe anytime.