tradingview-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. tradingview_sdk-0.1.0/.github/workflows/ci.yml +28 -0
  2. tradingview_sdk-0.1.0/.github/workflows/release.yml +57 -0
  3. tradingview_sdk-0.1.0/.gitignore +9 -0
  4. tradingview_sdk-0.1.0/LICENSE +21 -0
  5. tradingview_sdk-0.1.0/PKG-INFO +267 -0
  6. tradingview_sdk-0.1.0/README.md +252 -0
  7. tradingview_sdk-0.1.0/examples/historical_bars.py +124 -0
  8. tradingview_sdk-0.1.0/examples/run_screener.py +165 -0
  9. tradingview_sdk-0.1.0/examples/search_and_quote.py +100 -0
  10. tradingview_sdk-0.1.0/examples/strategies.py +118 -0
  11. tradingview_sdk-0.1.0/examples/stream_bars.py +116 -0
  12. tradingview_sdk-0.1.0/examples/stream_quotes.py +108 -0
  13. tradingview_sdk-0.1.0/pyproject.toml +42 -0
  14. tradingview_sdk-0.1.0/scripts/generate_fields.py +491 -0
  15. tradingview_sdk-0.1.0/src/tradingview_sdk/__init__.py +117 -0
  16. tradingview_sdk-0.1.0/src/tradingview_sdk/_chart.py +118 -0
  17. tradingview_sdk-0.1.0/src/tradingview_sdk/_http.py +118 -0
  18. tradingview_sdk-0.1.0/src/tradingview_sdk/_protocol.py +78 -0
  19. tradingview_sdk-0.1.0/src/tradingview_sdk/_stream.py +305 -0
  20. tradingview_sdk-0.1.0/src/tradingview_sdk/_sync.py +37 -0
  21. tradingview_sdk-0.1.0/src/tradingview_sdk/auth.py +136 -0
  22. tradingview_sdk-0.1.0/src/tradingview_sdk/bar_stream.py +167 -0
  23. tradingview_sdk-0.1.0/src/tradingview_sdk/bars.py +230 -0
  24. tradingview_sdk-0.1.0/src/tradingview_sdk/client.py +337 -0
  25. tradingview_sdk-0.1.0/src/tradingview_sdk/errors.py +51 -0
  26. tradingview_sdk-0.1.0/src/tradingview_sdk/fields.py +2337 -0
  27. tradingview_sdk-0.1.0/src/tradingview_sdk/models.py +298 -0
  28. tradingview_sdk-0.1.0/src/tradingview_sdk/quotes.py +50 -0
  29. tradingview_sdk-0.1.0/src/tradingview_sdk/screener.py +269 -0
  30. tradingview_sdk-0.1.0/src/tradingview_sdk/scripts.py +307 -0
  31. tradingview_sdk-0.1.0/src/tradingview_sdk/search.py +59 -0
  32. tradingview_sdk-0.1.0/src/tradingview_sdk/ws.py +128 -0
  33. tradingview_sdk-0.1.0/tests/conftest.py +22 -0
  34. tradingview_sdk-0.1.0/tests/fixtures/bars_btcusdt.json +134 -0
  35. tradingview_sdk-0.1.0/tests/fixtures/listing.html +1611 -0
  36. tradingview_sdk-0.1.0/tests/fixtures/metainfo.json +1 -0
  37. tradingview_sdk-0.1.0/tests/fixtures/pine_source.json +1 -0
  38. tradingview_sdk-0.1.0/tests/fixtures/quote_aapl.json +1 -0
  39. tradingview_sdk-0.1.0/tests/fixtures/screener_scan.json +1 -0
  40. tradingview_sdk-0.1.0/tests/fixtures/script_page.html +656 -0
  41. tradingview_sdk-0.1.0/tests/fixtures/search_aapl.json +1 -0
  42. tradingview_sdk-0.1.0/tests/live/test_live_rest.py +66 -0
  43. tradingview_sdk-0.1.0/tests/live/test_live_scripts.py +39 -0
  44. tradingview_sdk-0.1.0/tests/live/test_live_ws.py +74 -0
  45. tradingview_sdk-0.1.0/tests/test_bars.py +189 -0
  46. tradingview_sdk-0.1.0/tests/test_bars_session.py +171 -0
  47. tradingview_sdk-0.1.0/tests/test_fields.py +108 -0
  48. tradingview_sdk-0.1.0/tests/test_http.py +40 -0
  49. tradingview_sdk-0.1.0/tests/test_parsers.py +181 -0
  50. tradingview_sdk-0.1.0/tests/test_protocol.py +88 -0
  51. tradingview_sdk-0.1.0/tests/test_quote_stream.py +238 -0
  52. tradingview_sdk-0.1.0/tests/test_screener_query.py +61 -0
  53. tradingview_sdk-0.1.0/uv.lock +599 -0
@@ -0,0 +1,28 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ['3.11', '3.12']
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v6
21
+ with:
22
+ enable-cache: true
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ run: uv python install ${{ matrix.python-version }}
26
+
27
+ - name: Run offline tests
28
+ run: uv run --python ${{ matrix.python-version }} pytest
@@ -0,0 +1,57 @@
1
+ name: release
2
+
3
+ # Tag a version (vX.Y.Z) -> build, publish to PyPI, then create the GitHub Release.
4
+ #
5
+ # Ordered for atomicity: every cheap check runs first as a gate; PyPI (the one
6
+ # irreversible step) publishes only after they pass and is made idempotent with
7
+ # --check-url; the reversible GitHub Release is created last, so its existence
8
+ # means the whole release succeeded. The single job is safe to re-run.
9
+ on:
10
+ push:
11
+ tags:
12
+ - 'v*.*.*'
13
+
14
+ jobs:
15
+ release:
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: write # needed to create the GitHub Release
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v6
24
+ with:
25
+ enable-cache: true
26
+
27
+ - name: Install dependencies
28
+ run: uv sync
29
+
30
+ # -- gate: nothing external happens until all of these pass --------------
31
+ - name: Verify tag matches package version
32
+ run: |
33
+ TAG="${GITHUB_REF_NAME#v}"
34
+ PKG="$(grep -m1 -E '^version[[:space:]]*=' pyproject.toml | sed -E 's/.*"([^"]+)".*/\1/')"
35
+ echo "tag=$TAG package=$PKG"
36
+ if [ "$TAG" != "$PKG" ]; then
37
+ echo "::error::Tag v$TAG does not match pyproject version $PKG"
38
+ exit 1
39
+ fi
40
+
41
+ - name: Run offline tests
42
+ run: uv run pytest
43
+
44
+ - name: Build sdist and wheel
45
+ run: uv build
46
+
47
+ # -- irreversible commit: PyPI (idempotent via --check-url) --------------
48
+ - name: Publish to PyPI
49
+ run: uv publish --check-url https://pypi.org/simple/ --token "${{ secrets.PYPI_API_TOKEN }}"
50
+
51
+ # -- reversible marker: GitHub Release, created last ---------------------
52
+ - name: Create GitHub Release
53
+ uses: softprops/action-gh-release@v2
54
+ with:
55
+ files: dist/*
56
+ generate_release_notes: true
57
+ fail_on_unmatched_files: true
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .env
9
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 greatricky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,267 @@
1
+ Metadata-Version: 2.4
2
+ Name: tradingview-sdk
3
+ Version: 0.1.0
4
+ Summary: Unofficial Python SDK for TradingView: quotes, streaming prices, historical bars, screeners, and community strategies
5
+ Author-email: greatricky <greatricky@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: selectolax>=0.3
11
+ Requires-Dist: websockets>=12.0
12
+ Provides-Extra: pandas
13
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # tradingview-sdk
17
+
18
+ Unofficial Python SDK for TradingView's web APIs:
19
+
20
+ - **Instrument information** — symbol search with exchange, type, ISIN/CUSIP, currency
21
+ - **Latest quotes** — one REST call for price, change, volume, bid/ask, fundamentals
22
+ - **Streaming prices** — websocket client with dynamic subscribe/unsubscribe of many tickers
23
+ - **Historical bars (OHLCV)** — daily/intraday candles over any date range as typed `BarSet`/`Bar` objects (optional pandas `.to_dataframe()`), plus a live bar stream
24
+ - **Stock & ETF screeners** — programmatic equivalents of [tradingview.com/screener](https://www.tradingview.com/screener/) and [tradingview.com/etf-screener](https://www.tradingview.com/etf-screener/) with a fluent query builder
25
+ - **Community strategies** — list open-source strategies, fetch the published backtest **strategy report** (net profit, profit factor, drawdown, trade list, equity curves) and the full **Pine source code**
26
+
27
+ > ⚠️ This SDK uses TradingView's private web endpoints, which are undocumented and may change without notice. It is not affiliated with or endorsed by TradingView. Use responsibly and respect their terms of service.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ uv sync # from this repo (dev)
33
+ # or
34
+ pip install .
35
+ pip install ".[pandas]" # adds pandas, for BarSet.to_dataframe()
36
+ ```
37
+
38
+ Requires Python 3.11+. Dependencies: `httpx`, `websockets`, `selectolax`. `pandas` is an optional extra — everything works without it except `BarSet.to_dataframe()`, which imports it lazily.
39
+
40
+ ## Quickstart
41
+
42
+ ### Instrument info & quotes (sync)
43
+
44
+ ```python
45
+ from tradingview_sdk import TradingView
46
+
47
+ with TradingView() as tv:
48
+ # 1. Instrument information
49
+ for info in tv.search_symbols("AAPL"):
50
+ print(info.full_symbol, info.type, info.isin, info.description)
51
+
52
+ # 2. Latest quote — accepts "NASDAQ:AAPL" or a bare "AAPL"
53
+ quote = tv.get_quote("NASDAQ:AAPL")
54
+ print(quote.last, quote.change, quote.volume, quote["market_cap_basic"])
55
+ ```
56
+
57
+ Every method also exists on `AsyncTradingView` with an identical signature:
58
+
59
+ ```python
60
+ from tradingview_sdk import AsyncTradingView
61
+
62
+ async with AsyncTradingView() as tv:
63
+ quote = await tv.get_quote("NASDAQ:AAPL")
64
+ ```
65
+
66
+ ### Streaming prices (websocket)
67
+
68
+ ```python
69
+ import asyncio
70
+ from tradingview_sdk import QuoteStream
71
+
72
+ async def main():
73
+ async with QuoteStream() as stream:
74
+ await stream.subscribe("NASDAQ:AAPL", "BINANCE:BTCUSDT")
75
+
76
+ async for update in stream.updates():
77
+ print(update.symbol, update.last_price, update.changes)
78
+
79
+ # add/remove tickers at any time, mid-stream
80
+ await stream.unsubscribe("NASDAQ:AAPL")
81
+ await stream.subscribe("NASDAQ:TSLA", "BINANCE:ETHUSDT")
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ - `stream.updates("NASDAQ:TSLA")` — filtered iterator for specific symbols; multiple concurrent iterators are fine.
87
+ - `stream.on_update(callback)` — sync or async callback alternative; returns an unregister function.
88
+ - `stream.snapshot("NASDAQ:AAPL")` — latest merged field values.
89
+ - Reconnects automatically with exponential backoff and **resubscribes everything** after a drop.
90
+
91
+ ### Historical bars (OHLCV)
92
+
93
+ ```python
94
+ from datetime import date, datetime, timezone
95
+ from tradingview_sdk import TradingView, Interval
96
+
97
+ with TradingView() as tv:
98
+ # Index — most-recent N candles (accepts "EXCHANGE:TICKER" or a bare ticker)
99
+ spx = tv.get_bars("SP:SPX", Interval.DAY, bars=300)
100
+ print(spx.symbol, spx.interval, len(spx))
101
+ print(spx.last.close, spx.closes[-5:])
102
+
103
+ # Stock — a daily range; a plain date is the natural input here
104
+ # (no time-of-day / tz needed). `start` wins over `bars`, paging back to reach it.
105
+ aapl = tv.get_bars("NASDAQ:AAPL", Interval.DAY, start=date(2026, 1, 1))
106
+
107
+ # Crypto — an intraday, timezone-aware window. The tz matters because these
108
+ # bars are intraday, and BTCUSDT trades 24/7 so every hour is populated.
109
+ btc = tv.get_bars("BINANCE:BTCUSDT", "60", # "60" = 60-minute bars
110
+ start=datetime(2026, 8, 1, 8, 0, tzinfo=timezone.utc),
111
+ end=datetime(2026, 8, 8, tzinfo=timezone.utc))
112
+
113
+ df = btc.to_dataframe() # optional: pandas DataFrame indexed by UTC time
114
+ ```
115
+
116
+ - `bars=N` returns the N most-recent candles; pass `start`/`end` to fetch a range instead — the SDK paginates automatically. Use a timezone-aware `datetime` for intraday precision, or a plain `date` (or epoch seconds) for daily ranges.
117
+ - `interval` accepts the `Interval` enum (`Interval.MIN_5`, `Interval.HOUR_1`, `Interval.DAY`, `Interval.WEEK`, …) or any raw TradingView resolution string (`"1"`, `"5"`, `"60"`, `"240"`, `"1D"`, `"1W"`, `"1M"`).
118
+ - `adjustment` mirrors TradingView's single chart "ADJ" toggle (plain strings `"splits"`/`"dividends"` work too):
119
+ - `Adjustment.SPLITS` → split-adjusted only (dividends left in) — the default.
120
+ - `Adjustment.DIVIDENDS` → split-adjusted and dividend-adjusted.
121
+
122
+ Splits are always applied (there is no split-off mode), and the latest bar is identical either way — only historical bars change.
123
+ - Each `Bar` has `time` (epoch seconds, UTC), `open`/`high`/`low`/`close`/`volume`, and a `.datetime` (aware UTC). `BarSet` is iterable/indexable with `.last`, `.closes`, `.times`, … and a lazy `.to_dataframe()` (needs pandas only if you call it). Bar times are always UTC, and a naive `start`/`end` `datetime` is interpreted as UTC.
124
+ - Bars load over a websocket chart session; anonymous access returns delayed data (log in for realtime — see [Authentication](#authentication-optional)).
125
+
126
+ **Streaming bars** — `BarStream` mirrors `QuoteStream` for live, updating candles:
127
+
128
+ ```python
129
+ import asyncio
130
+ from tradingview_sdk import BarStream
131
+
132
+ async def main():
133
+ async with BarStream() as stream:
134
+ await stream.subscribe("BINANCE:BTCUSDT", "1") # 1-minute bars
135
+ async for u in stream.updates():
136
+ print(u.symbol, u.interval, u.bar.close, "closed" if u.closed else "forming")
137
+
138
+ asyncio.run(main())
139
+ ```
140
+
141
+ Each `BarUpdate` carries the latest `bar` and `closed` — `False` while the bar is still forming, `True` once a newer bar has started.
142
+
143
+ ### Screeners
144
+
145
+ ```python
146
+ from tradingview_sdk import TradingView, ScreenerQuery, Filter
147
+
148
+ with TradingView() as tv:
149
+ stocks = tv.screen_stocks() # defaults mirror the web stock screener
150
+ etfs = tv.screen_etfs() # defaults mirror the web ETF screener
151
+ print(stocks.total_count, etfs.total_count)
152
+
153
+ # Custom query
154
+ query = (
155
+ ScreenerQuery()
156
+ .where(
157
+ Filter.gt("market_cap_basic", 10e9),
158
+ Filter.between("price_earnings_ttm", 0, 15),
159
+ Filter.gt("volume", 2_000_000),
160
+ )
161
+ .select("name", "close", "change", "market_cap_basic", "price_earnings_ttm", "sector")
162
+ .order_by("market_cap_basic")
163
+ .limit(100)
164
+ )
165
+ for row in tv.screen(query):
166
+ print(row.symbol, row["price_earnings_ttm"])
167
+ ```
168
+
169
+ Any column/filter field the web screener supports works here. Use `.market("germany")`, `.market("crypto")`, etc. for other markets.
170
+
171
+ #### Field catalog
172
+
173
+ All ~1,100 screener fields are available as a typed enum with metadata (generated from TradingView's scanner `metainfo` endpoint — regenerate with `uv run python scripts/generate_fields.py`):
174
+
175
+ ```python
176
+ from tradingview_sdk import Field, FIELDS, field_info, search_fields
177
+
178
+ Field.MARKET_CAP_BASIC # == "market_cap_basic", usable anywhere a field name goes
179
+ Field.RSI.info.description # "Relative Strength Index (14). Range 0-100; <30 oversold, >70 overbought."
180
+ FIELDS["sector"].values # allowed values: ("Commercial Services", ..., "Utilities")
181
+ FIELDS["change"].type # FieldType.PERCENT (12.5 means 12.5%)
182
+ FIELDS["change"].timeframes # ("1", "5", "15", "30", "60", "120", "240", "1W", "1M")
183
+ Field.CHANGE.tf("60") # "change|60" — change on the 60-minute chart
184
+ search_fields("dividend yield") # find fields by name/description substring
185
+ ```
186
+
187
+ Each `FieldInfo` carries: `name`, `type` (semantics: `percent` = percentage points, `price`/`fundamental_price` = monetary, `time` = UNIX timestamp, `num_slice` = per-period history array), a human-readable `description` (hand-curated for the ~120 most-used fields, auto-derived otherwise), `values` (the allowed values for enumerated text fields like `sector`, `industry`, `exchange`, `type`, `typespecs`), and `timeframes` (fields that exist per chart timeframe via the `"name|tf"` suffix). Some fund classification fields (`asset_class`, `focus`, `niche`, `weighting_scheme`) return internal ids — select the `.tr`-suffixed twin column (e.g. `"asset_class.tr"`) for readable labels.
188
+
189
+ ### Community strategies
190
+
191
+ ```python
192
+ from tradingview_sdk import TradingView
193
+
194
+ with TradingView() as tv:
195
+ # List open-source strategies (paginated; also: tv.iter_strategies())
196
+ page = tv.list_strategies()
197
+ for card in page:
198
+ print(card.title, card.author, card.likes, card.url)
199
+
200
+ # Full detail: metadata + published strategy report + Pine source
201
+ s = tv.get_strategy("eUCT3oSF-WW-Pro-Flow-Zones-Miracle-V4") # slug or full URL
202
+ print(s.title, s.chart_symbol, s.chart_interval)
203
+
204
+ r = s.report # the "Strategy report" from the script page
205
+ print(r.all.net_profit, r.all.total_trades, r.all.profit_factor)
206
+ print(r.max_drawdown, r.sharpe_ratio, r.sortino_ratio)
207
+ print(r.trades[:2]) # individual trades
208
+ print(r.buy_hold_curve[:5]) # buy & hold equity curve
209
+
210
+ print(s.source.source_text) # full Pine source (open-source scripts)
211
+ ```
212
+
213
+ Protected/invite-only scripts raise `AuthRequiredError` for the source; the report and metadata still parse when the page publishes them.
214
+
215
+ ## Authentication (optional)
216
+
217
+ Everything works anonymously. Logging in upgrades websocket data from delayed to realtime for exchanges you have data entitlements for, per your TradingView account.
218
+
219
+ Copy the `sessionid` and `sessionid_sign` cookies from a logged-in browser (DevTools → Application → Cookies → tradingview.com), then either:
220
+
221
+ ```bash
222
+ export TV_SESSION_ID="..."
223
+ export TV_SESSION_ID_SIGN="..."
224
+ ```
225
+
226
+ (picked up automatically), or pass explicitly:
227
+
228
+ ```python
229
+ from tradingview_sdk import Credentials, QuoteStream, TradingView
230
+
231
+ creds = Credentials(session_id="...", session_sign="...")
232
+ tv = TradingView(credentials=creds)
233
+ stream = QuoteStream(credentials=creds)
234
+ ```
235
+
236
+ ## Development
237
+
238
+ ```bash
239
+ uv sync
240
+ uv run pytest # offline tests (protocol codec, parsers vs recorded fixtures, fake WS server)
241
+ uv run pytest -m live # live smoke tests against real endpoints
242
+ uv run python examples/historical_bars.py
243
+ uv run python examples/stream_quotes.py
244
+ ```
245
+
246
+ Package layout: pure request-builders/parsers per endpoint (`search.py`, `quotes.py`, `screener.py`, `scripts.py`, `_chart.py`), thin sync/async facades (`client.py`), and async websocket clients (`ws.py` for quotes, `bars.py`/`bar_stream.py` for OHLCV) over the `~m~`-framed TradingView protocol (`_protocol.py`).
247
+
248
+ ### Releasing to PyPI
249
+
250
+ Tagging a version runs `.github/workflows/release.yml`, which builds and publishes to PyPI, then creates the matching GitHub Release. To cut a release:
251
+
252
+ ```bash
253
+ # bump `version` in pyproject.toml (the single source of truth), then:
254
+ git commit -am "Release v0.2.0"
255
+ git tag v0.2.0
256
+ git push origin main --tags
257
+ ```
258
+
259
+ The workflow guards that the tag matches the package version, runs the offline tests, publishes to PyPI (idempotently), and attaches the built artifacts to the GitHub Release.
260
+
261
+ ## Error handling
262
+
263
+ All errors derive from `TradingViewError`: `HTTPStatusError` (with `RateLimitError` for 429 and `AuthRequiredError` for 401/403), `SymbolNotFoundError`, `ParseError` (markup drift), `ProtocolError`, and `StreamClosedError`. Every model keeps the raw payload on `.raw` so new upstream fields remain accessible.
264
+
265
+ ## License
266
+
267
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,252 @@
1
+ # tradingview-sdk
2
+
3
+ Unofficial Python SDK for TradingView's web APIs:
4
+
5
+ - **Instrument information** — symbol search with exchange, type, ISIN/CUSIP, currency
6
+ - **Latest quotes** — one REST call for price, change, volume, bid/ask, fundamentals
7
+ - **Streaming prices** — websocket client with dynamic subscribe/unsubscribe of many tickers
8
+ - **Historical bars (OHLCV)** — daily/intraday candles over any date range as typed `BarSet`/`Bar` objects (optional pandas `.to_dataframe()`), plus a live bar stream
9
+ - **Stock & ETF screeners** — programmatic equivalents of [tradingview.com/screener](https://www.tradingview.com/screener/) and [tradingview.com/etf-screener](https://www.tradingview.com/etf-screener/) with a fluent query builder
10
+ - **Community strategies** — list open-source strategies, fetch the published backtest **strategy report** (net profit, profit factor, drawdown, trade list, equity curves) and the full **Pine source code**
11
+
12
+ > ⚠️ This SDK uses TradingView's private web endpoints, which are undocumented and may change without notice. It is not affiliated with or endorsed by TradingView. Use responsibly and respect their terms of service.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ uv sync # from this repo (dev)
18
+ # or
19
+ pip install .
20
+ pip install ".[pandas]" # adds pandas, for BarSet.to_dataframe()
21
+ ```
22
+
23
+ Requires Python 3.11+. Dependencies: `httpx`, `websockets`, `selectolax`. `pandas` is an optional extra — everything works without it except `BarSet.to_dataframe()`, which imports it lazily.
24
+
25
+ ## Quickstart
26
+
27
+ ### Instrument info & quotes (sync)
28
+
29
+ ```python
30
+ from tradingview_sdk import TradingView
31
+
32
+ with TradingView() as tv:
33
+ # 1. Instrument information
34
+ for info in tv.search_symbols("AAPL"):
35
+ print(info.full_symbol, info.type, info.isin, info.description)
36
+
37
+ # 2. Latest quote — accepts "NASDAQ:AAPL" or a bare "AAPL"
38
+ quote = tv.get_quote("NASDAQ:AAPL")
39
+ print(quote.last, quote.change, quote.volume, quote["market_cap_basic"])
40
+ ```
41
+
42
+ Every method also exists on `AsyncTradingView` with an identical signature:
43
+
44
+ ```python
45
+ from tradingview_sdk import AsyncTradingView
46
+
47
+ async with AsyncTradingView() as tv:
48
+ quote = await tv.get_quote("NASDAQ:AAPL")
49
+ ```
50
+
51
+ ### Streaming prices (websocket)
52
+
53
+ ```python
54
+ import asyncio
55
+ from tradingview_sdk import QuoteStream
56
+
57
+ async def main():
58
+ async with QuoteStream() as stream:
59
+ await stream.subscribe("NASDAQ:AAPL", "BINANCE:BTCUSDT")
60
+
61
+ async for update in stream.updates():
62
+ print(update.symbol, update.last_price, update.changes)
63
+
64
+ # add/remove tickers at any time, mid-stream
65
+ await stream.unsubscribe("NASDAQ:AAPL")
66
+ await stream.subscribe("NASDAQ:TSLA", "BINANCE:ETHUSDT")
67
+
68
+ asyncio.run(main())
69
+ ```
70
+
71
+ - `stream.updates("NASDAQ:TSLA")` — filtered iterator for specific symbols; multiple concurrent iterators are fine.
72
+ - `stream.on_update(callback)` — sync or async callback alternative; returns an unregister function.
73
+ - `stream.snapshot("NASDAQ:AAPL")` — latest merged field values.
74
+ - Reconnects automatically with exponential backoff and **resubscribes everything** after a drop.
75
+
76
+ ### Historical bars (OHLCV)
77
+
78
+ ```python
79
+ from datetime import date, datetime, timezone
80
+ from tradingview_sdk import TradingView, Interval
81
+
82
+ with TradingView() as tv:
83
+ # Index — most-recent N candles (accepts "EXCHANGE:TICKER" or a bare ticker)
84
+ spx = tv.get_bars("SP:SPX", Interval.DAY, bars=300)
85
+ print(spx.symbol, spx.interval, len(spx))
86
+ print(spx.last.close, spx.closes[-5:])
87
+
88
+ # Stock — a daily range; a plain date is the natural input here
89
+ # (no time-of-day / tz needed). `start` wins over `bars`, paging back to reach it.
90
+ aapl = tv.get_bars("NASDAQ:AAPL", Interval.DAY, start=date(2026, 1, 1))
91
+
92
+ # Crypto — an intraday, timezone-aware window. The tz matters because these
93
+ # bars are intraday, and BTCUSDT trades 24/7 so every hour is populated.
94
+ btc = tv.get_bars("BINANCE:BTCUSDT", "60", # "60" = 60-minute bars
95
+ start=datetime(2026, 8, 1, 8, 0, tzinfo=timezone.utc),
96
+ end=datetime(2026, 8, 8, tzinfo=timezone.utc))
97
+
98
+ df = btc.to_dataframe() # optional: pandas DataFrame indexed by UTC time
99
+ ```
100
+
101
+ - `bars=N` returns the N most-recent candles; pass `start`/`end` to fetch a range instead — the SDK paginates automatically. Use a timezone-aware `datetime` for intraday precision, or a plain `date` (or epoch seconds) for daily ranges.
102
+ - `interval` accepts the `Interval` enum (`Interval.MIN_5`, `Interval.HOUR_1`, `Interval.DAY`, `Interval.WEEK`, …) or any raw TradingView resolution string (`"1"`, `"5"`, `"60"`, `"240"`, `"1D"`, `"1W"`, `"1M"`).
103
+ - `adjustment` mirrors TradingView's single chart "ADJ" toggle (plain strings `"splits"`/`"dividends"` work too):
104
+ - `Adjustment.SPLITS` → split-adjusted only (dividends left in) — the default.
105
+ - `Adjustment.DIVIDENDS` → split-adjusted and dividend-adjusted.
106
+
107
+ Splits are always applied (there is no split-off mode), and the latest bar is identical either way — only historical bars change.
108
+ - Each `Bar` has `time` (epoch seconds, UTC), `open`/`high`/`low`/`close`/`volume`, and a `.datetime` (aware UTC). `BarSet` is iterable/indexable with `.last`, `.closes`, `.times`, … and a lazy `.to_dataframe()` (needs pandas only if you call it). Bar times are always UTC, and a naive `start`/`end` `datetime` is interpreted as UTC.
109
+ - Bars load over a websocket chart session; anonymous access returns delayed data (log in for realtime — see [Authentication](#authentication-optional)).
110
+
111
+ **Streaming bars** — `BarStream` mirrors `QuoteStream` for live, updating candles:
112
+
113
+ ```python
114
+ import asyncio
115
+ from tradingview_sdk import BarStream
116
+
117
+ async def main():
118
+ async with BarStream() as stream:
119
+ await stream.subscribe("BINANCE:BTCUSDT", "1") # 1-minute bars
120
+ async for u in stream.updates():
121
+ print(u.symbol, u.interval, u.bar.close, "closed" if u.closed else "forming")
122
+
123
+ asyncio.run(main())
124
+ ```
125
+
126
+ Each `BarUpdate` carries the latest `bar` and `closed` — `False` while the bar is still forming, `True` once a newer bar has started.
127
+
128
+ ### Screeners
129
+
130
+ ```python
131
+ from tradingview_sdk import TradingView, ScreenerQuery, Filter
132
+
133
+ with TradingView() as tv:
134
+ stocks = tv.screen_stocks() # defaults mirror the web stock screener
135
+ etfs = tv.screen_etfs() # defaults mirror the web ETF screener
136
+ print(stocks.total_count, etfs.total_count)
137
+
138
+ # Custom query
139
+ query = (
140
+ ScreenerQuery()
141
+ .where(
142
+ Filter.gt("market_cap_basic", 10e9),
143
+ Filter.between("price_earnings_ttm", 0, 15),
144
+ Filter.gt("volume", 2_000_000),
145
+ )
146
+ .select("name", "close", "change", "market_cap_basic", "price_earnings_ttm", "sector")
147
+ .order_by("market_cap_basic")
148
+ .limit(100)
149
+ )
150
+ for row in tv.screen(query):
151
+ print(row.symbol, row["price_earnings_ttm"])
152
+ ```
153
+
154
+ Any column/filter field the web screener supports works here. Use `.market("germany")`, `.market("crypto")`, etc. for other markets.
155
+
156
+ #### Field catalog
157
+
158
+ All ~1,100 screener fields are available as a typed enum with metadata (generated from TradingView's scanner `metainfo` endpoint — regenerate with `uv run python scripts/generate_fields.py`):
159
+
160
+ ```python
161
+ from tradingview_sdk import Field, FIELDS, field_info, search_fields
162
+
163
+ Field.MARKET_CAP_BASIC # == "market_cap_basic", usable anywhere a field name goes
164
+ Field.RSI.info.description # "Relative Strength Index (14). Range 0-100; <30 oversold, >70 overbought."
165
+ FIELDS["sector"].values # allowed values: ("Commercial Services", ..., "Utilities")
166
+ FIELDS["change"].type # FieldType.PERCENT (12.5 means 12.5%)
167
+ FIELDS["change"].timeframes # ("1", "5", "15", "30", "60", "120", "240", "1W", "1M")
168
+ Field.CHANGE.tf("60") # "change|60" — change on the 60-minute chart
169
+ search_fields("dividend yield") # find fields by name/description substring
170
+ ```
171
+
172
+ Each `FieldInfo` carries: `name`, `type` (semantics: `percent` = percentage points, `price`/`fundamental_price` = monetary, `time` = UNIX timestamp, `num_slice` = per-period history array), a human-readable `description` (hand-curated for the ~120 most-used fields, auto-derived otherwise), `values` (the allowed values for enumerated text fields like `sector`, `industry`, `exchange`, `type`, `typespecs`), and `timeframes` (fields that exist per chart timeframe via the `"name|tf"` suffix). Some fund classification fields (`asset_class`, `focus`, `niche`, `weighting_scheme`) return internal ids — select the `.tr`-suffixed twin column (e.g. `"asset_class.tr"`) for readable labels.
173
+
174
+ ### Community strategies
175
+
176
+ ```python
177
+ from tradingview_sdk import TradingView
178
+
179
+ with TradingView() as tv:
180
+ # List open-source strategies (paginated; also: tv.iter_strategies())
181
+ page = tv.list_strategies()
182
+ for card in page:
183
+ print(card.title, card.author, card.likes, card.url)
184
+
185
+ # Full detail: metadata + published strategy report + Pine source
186
+ s = tv.get_strategy("eUCT3oSF-WW-Pro-Flow-Zones-Miracle-V4") # slug or full URL
187
+ print(s.title, s.chart_symbol, s.chart_interval)
188
+
189
+ r = s.report # the "Strategy report" from the script page
190
+ print(r.all.net_profit, r.all.total_trades, r.all.profit_factor)
191
+ print(r.max_drawdown, r.sharpe_ratio, r.sortino_ratio)
192
+ print(r.trades[:2]) # individual trades
193
+ print(r.buy_hold_curve[:5]) # buy & hold equity curve
194
+
195
+ print(s.source.source_text) # full Pine source (open-source scripts)
196
+ ```
197
+
198
+ Protected/invite-only scripts raise `AuthRequiredError` for the source; the report and metadata still parse when the page publishes them.
199
+
200
+ ## Authentication (optional)
201
+
202
+ Everything works anonymously. Logging in upgrades websocket data from delayed to realtime for exchanges you have data entitlements for, per your TradingView account.
203
+
204
+ Copy the `sessionid` and `sessionid_sign` cookies from a logged-in browser (DevTools → Application → Cookies → tradingview.com), then either:
205
+
206
+ ```bash
207
+ export TV_SESSION_ID="..."
208
+ export TV_SESSION_ID_SIGN="..."
209
+ ```
210
+
211
+ (picked up automatically), or pass explicitly:
212
+
213
+ ```python
214
+ from tradingview_sdk import Credentials, QuoteStream, TradingView
215
+
216
+ creds = Credentials(session_id="...", session_sign="...")
217
+ tv = TradingView(credentials=creds)
218
+ stream = QuoteStream(credentials=creds)
219
+ ```
220
+
221
+ ## Development
222
+
223
+ ```bash
224
+ uv sync
225
+ uv run pytest # offline tests (protocol codec, parsers vs recorded fixtures, fake WS server)
226
+ uv run pytest -m live # live smoke tests against real endpoints
227
+ uv run python examples/historical_bars.py
228
+ uv run python examples/stream_quotes.py
229
+ ```
230
+
231
+ Package layout: pure request-builders/parsers per endpoint (`search.py`, `quotes.py`, `screener.py`, `scripts.py`, `_chart.py`), thin sync/async facades (`client.py`), and async websocket clients (`ws.py` for quotes, `bars.py`/`bar_stream.py` for OHLCV) over the `~m~`-framed TradingView protocol (`_protocol.py`).
232
+
233
+ ### Releasing to PyPI
234
+
235
+ Tagging a version runs `.github/workflows/release.yml`, which builds and publishes to PyPI, then creates the matching GitHub Release. To cut a release:
236
+
237
+ ```bash
238
+ # bump `version` in pyproject.toml (the single source of truth), then:
239
+ git commit -am "Release v0.2.0"
240
+ git tag v0.2.0
241
+ git push origin main --tags
242
+ ```
243
+
244
+ The workflow guards that the tag matches the package version, runs the offline tests, publishes to PyPI (idempotently), and attaches the built artifacts to the GitHub Release.
245
+
246
+ ## Error handling
247
+
248
+ All errors derive from `TradingViewError`: `HTTPStatusError` (with `RateLimitError` for 429 and `AuthRequiredError` for 401/403), `SymbolNotFoundError`, `ParseError` (markup drift), `ProtocolError`, and `StreamClosedError`. Every model keeps the raw payload on `.raw` so new upstream fields remain accessible.
249
+
250
+ ## License
251
+
252
+ MIT — see [LICENSE](LICENSE).