tradingview_screener.query

  1from __future__ import annotations
  2
  3__all__ = ['Query', 'Column']
  4
  5import pprint
  6from typing import TypedDict, Any, Literal
  7
  8import requests
  9import pandas as pd
 10
 11from tradingview_screener.constants import COLUMNS, MARKETS, HEADERS, URL
 12
 13
 14class FilterOperationDict(TypedDict):
 15    left: str
 16    operation: Literal[
 17        'greater',
 18        'egreater',
 19        'less',
 20        'eless',
 21        'equal',
 22        'nequal',
 23        'in_range',
 24        'not_in_range',
 25        'match',  # the same as: `LOWER(col) LIKE '%pattern%'`
 26        'crosses',
 27        'crosses_above',
 28        'crosses_below',
 29    ]
 30    right: Any
 31
 32
 33class SortByDict(TypedDict):
 34    sortBy: str
 35    sortOrder: Literal['asc', 'desc']
 36
 37
 38class QueryDict(TypedDict):
 39    """
 40    The fields that can be passed to the tradingview scan API
 41    """
 42
 43    # TODO: test which optional ...
 44    markets: list[str]
 45    symbols: dict
 46    options: dict
 47    columns: list[str]
 48    filter: list[FilterOperationDict]
 49    sort: SortByDict
 50    range: list[int]  # a with two integers, i.e. `[0, 100]`
 51
 52
 53class Column:
 54    """
 55    A Column object represents a field in the tradingview stock screener,
 56    and it's used in SELECT queries and WHERE queries with the `Query` object.
 57
 58    A `Column` supports all the comparison operations:
 59    `<`, `<=`, `>`, `>=`, `==`, `!=`, and also other methods like `between()`, `isin()`, etc.
 60
 61    Examples:
 62
 63    Some of the operations that you can do with `Column` objects:
 64    >>> Column('close') >= 2.5
 65    >>> Column('close').between(2.5, 15)
 66    >>> Column('high') > Column('VWAP')
 67    >>> Column('close').between(Column('EMA5'), Column('EMA20')
 68    >>> Column('type').isin(['stock', 'fund'])
 69    >>> Column('description').like('apple')  # the same as `description LIKE '%apple%'`
 70    """
 71
 72    def __init__(self, name: str) -> None:
 73        """
 74        Create a column object from a given column name
 75
 76        :param name: string, should be either a key or a value from the `COLUMNS` dictionary
 77        """
 78        # if `name` is a dictionary key: get its value. otherwise make sure that it's a
 79        # dictionary value.
 80        self.name = COLUMNS.get(name, name)
 81
 82    # disable this method and do the column/field validation through the server
 83    # @classmethod
 84    # def from_unknown_name(cls, name: str) -> Column:
 85    #     """
 86    #     Create a column object from a column name that isn't in the `COLUMNS` dictionary
 87    #
 88    #     :param name: string, column name
 89    #     :return: Column
 90    #     """
 91    #     # close is just a temporary column, so it won't raise an error at `__init__`
 92    #     column = cls(name='close')
 93    #     column.name = name
 94    #     return column
 95
 96    @staticmethod
 97    def _extract_value(obj) -> ...:
 98        if isinstance(obj, Column):
 99            return obj.name
100        return obj
101
102    def __gt__(self, other) -> FilterOperationDict:
103        return FilterOperationDict(
104            left=self.name, operation='greater', right=self._extract_value(other)
105        )
106
107    def __ge__(self, other) -> FilterOperationDict:
108        return FilterOperationDict(
109            left=self.name, operation='egreater', right=self._extract_value(other)
110        )
111
112    def __lt__(self, other) -> FilterOperationDict:
113        return FilterOperationDict(
114            left=self.name, operation='less', right=self._extract_value(other)
115        )
116
117    def __le__(self, other) -> FilterOperationDict:
118        return FilterOperationDict(
119            left=self.name, operation='eless', right=self._extract_value(other)
120        )
121
122    def __eq__(self, other) -> FilterOperationDict:
123        return FilterOperationDict(
124            left=self.name, operation='equal', right=self._extract_value(other)
125        )
126
127    def __ne__(self, other) -> FilterOperationDict:
128        return FilterOperationDict(
129            left=self.name, operation='nequal', right=self._extract_value(other)
130        )
131
132    def crosses(self, other) -> FilterOperationDict:
133        return FilterOperationDict(
134            left=self.name, operation='crosses', right=self._extract_value(other)
135        )
136
137    def crosses_above(self, other) -> FilterOperationDict:
138        return FilterOperationDict(
139            left=self.name, operation='crosses_above', right=self._extract_value(other)
140        )
141
142    def crosses_below(self, other) -> FilterOperationDict:
143        return FilterOperationDict(
144            left=self.name, operation='crosses_below', right=self._extract_value(other)
145        )
146
147    def between(self, left, right) -> FilterOperationDict:
148        return FilterOperationDict(
149            left=self.name,
150            operation='in_range',
151            right=[self._extract_value(left), self._extract_value(right)],
152        )
153
154    def not_between(self, left, right) -> FilterOperationDict:
155        return FilterOperationDict(
156            left=self.name,
157            operation='not_in_range',
158            right=[self._extract_value(left), self._extract_value(right)],
159        )
160
161    def isin(self, values) -> FilterOperationDict:
162        return FilterOperationDict(left=self.name, operation='in_range', right=list(values))
163
164    def like(self, other) -> FilterOperationDict:
165        return FilterOperationDict(
166            left=self.name, operation='match', right=self._extract_value(other)
167        )
168
169    def __repr__(self) -> str:
170        return f'< Column({self.name!r}) >'
171
172
173class Query:
174    """
175    This class allows you to perform SQL-like queries on the tradingview stock-screener.
176
177    The `Query` object reppresents a query that can be made to the official tradingview API, and it
178    stores all the data as JSON internally.
179
180    Examples:
181
182    To perform a simple query all you have to do is:
183    >>> from tradingview_screener import Query
184    >>> Query().get_scanner_data()
185    (18060,
186              ticker  name   close     volume  market_cap_basic
187     0      AMEX:SPY   SPY  410.68  107367671               NaN
188     1    NASDAQ:QQQ   QQQ  345.31   63475390               NaN
189     2   NASDAQ:TSLA  TSLA  207.30   94879471      6.589904e+11
190     3   NASDAQ:NVDA  NVDA  405.00   41677185      1.000350e+12
191     4   NASDAQ:AMZN  AMZN  127.74  125309313      1.310658e+12
192     ..          ...   ...     ...        ...               ...
193     45     NYSE:UNH   UNH  524.66    2585616      4.859952e+11
194     46  NASDAQ:DXCM  DXCM   89.29   14954605      3.449933e+10
195     47      NYSE:MA    MA  364.08    3624883      3.429080e+11
196     48    NYSE:ABBV  ABBV  138.93    9427212      2.452179e+11
197     49     AMEX:XLK   XLK  161.12    8115780               NaN
198     [50 rows x 5 columns])
199
200    The `get_scanner_data()` method will return a tuple with the first element being the number of
201    records that were found (like a `COUNT(*)`), and the second element contains the data that was
202    found as a DataFrame.
203
204    ---
205
206    By default, the `Query` will select the columns: `name`, `close`, `volume`, `market_cap_basic`,
207    but you override that
208    >>> (Query()
209    ...  .select('open', 'high', 'low', 'VWAP', 'MACD.macd', 'RSI', 'Price to Earnings Ratio (TTM)')
210    ...  .get_scanner_data())
211    (18060,
212              ticker    open     high  ...  MACD.macd        RSI  price_earnings_ttm
213     0      AMEX:SPY  414.19  414.600  ...  -5.397135  29.113396                 NaN
214     1    NASDAQ:QQQ  346.43  348.840  ...  -4.321482  34.335449                 NaN
215     2   NASDAQ:TSLA  210.60  212.410  ... -12.224250  28.777229           66.752536
216     3   NASDAQ:NVDA  411.30  412.060  ...  -8.738986  37.845668           97.835540
217     4   NASDAQ:AMZN  126.20  130.020  ...  -2.025378  48.665666           66.697995
218     ..          ...     ...      ...  ...        ...        ...                 ...
219     45     NYSE:UNH  525.99  527.740  ...   6.448129  54.614775           22.770713
220     46  NASDAQ:DXCM   92.73   92.988  ...  -2.376942  52.908093           98.914368
221     47      NYSE:MA  366.49  368.285  ...  -7.496065  22.614078           31.711800
222     48    NYSE:ABBV  138.77  143.000  ...  -1.708497  27.117232           37.960054
223     49     AMEX:XLK  161.17  162.750  ...  -1.520828  36.868658                 NaN
224     [50 rows x 8 columns])
225
226    You can find the 250+ columns available in `tradingview_screener.constants.COLUMNS`.
227
228    Now let's do some queries using the `WHERE` statement, select all the stocks that the `close` is
229    bigger or equal than 350
230    >>> (Query()
231    ...  .select('close', 'volume', '52 Week High')
232    ...  .where(Column('close') >= 350)
233    ...  .get_scanner_data())
234    (159,
235              ticker      close     volume  price_52_week_high
236     0      AMEX:SPY     410.68  107367671              459.44
237     1   NASDAQ:NVDA     405.00   41677185              502.66
238     2    NYSE:BRK.A  503375.05       7910           566569.97
239     3      AMEX:IVV     412.55    5604525              461.88
240     4      AMEX:VOO     377.32    5638752              422.15
241     ..          ...        ...        ...                 ...
242     45  NASDAQ:EQIX     710.39     338549              821.63
243     46     NYSE:MCK     448.03     527406              465.90
244     47     NYSE:MTD     976.25     241733             1615.97
245     48  NASDAQ:CTAS     496.41     464631              525.37
246     49   NASDAQ:ROP     475.57     450141              508.90
247     [50 rows x 4 columns])
248
249    You can even use other columns in these kind of operations
250    >>> (Query()
251    ...  .select('close', 'VWAP')
252    ...  .where(Column('close') >= Column('VWAP'))
253    ...  .get_scanner_data())
254    (9044,
255               ticker   close        VWAP
256     0    NASDAQ:AAPL  168.22  168.003333
257     1    NASDAQ:META  296.73  296.336667
258     2   NASDAQ:GOOGL  122.17  121.895233
259     3     NASDAQ:AMD   96.43   96.123333
260     4    NASDAQ:GOOG  123.40  123.100000
261     ..           ...     ...         ...
262     45       NYSE:GD  238.25  238.043333
263     46     NYSE:GOLD   16.33   16.196667
264     47      AMEX:SLV   21.18   21.041667
265     48      AMEX:VXX   27.08   26.553333
266     49      NYSE:SLB   55.83   55.676667
267     [50 rows x 3 columns])
268
269    Let's find all the stocks that the price is between the EMA 5 and 20, and the type is a stock
270    or fund
271    >>> (Query()
272    ...  .select('close', 'volume', 'EMA5', 'EMA20', 'type')
273    ...  .where(
274    ...     Column('close').between(Column('EMA5'), Column('EMA20')),
275    ...     Column('type').isin(['stock', 'fund'])
276    ...  )
277    ...  .get_scanner_data())
278    (1730,
279              ticker   close     volume        EMA5       EMA20   type
280     0   NASDAQ:AMZN  127.74  125309313  125.033517  127.795142  stock
281     1      AMEX:HYG   72.36   35621800   72.340776   72.671058   fund
282     2      AMEX:LQD   99.61   21362859   99.554272  100.346388   fund
283     3    NASDAQ:IEF   90.08   11628236   89.856804   90.391503   fund
284     4      NYSE:SYK  261.91    3783608  261.775130  266.343290  stock
285     ..          ...     ...        ...         ...         ...    ...
286     45     NYSE:EMN   72.58    1562328   71.088034   72.835394  stock
287     46     NYSE:KIM   16.87    6609420   16.858920   17.096582   fund
288     47  NASDAQ:COLM   71.34    1516675   71.073116   71.658864  stock
289     48     NYSE:AOS   67.81    1586796   67.561619   67.903168  stock
290     49  NASDAQ:IGIB   47.81    2073538   47.761338   48.026795   fund
291     [50 rows x 6 columns])
292
293    There are also the `ORDER BY`, `OFFSET`, and `LIMIT` statements.
294    Let's select all the tickers with a market cap between 1M and 50M, that have a relative volume
295    bigger than 1.2, and that the MACD is positive
296    >>> (Query()
297    ...  .select('name', 'close', 'volume', 'relative_volume_10d_calc')
298    ...  .where(
299    ...      Column('market_cap_basic').between(1_000_000, 50_000_000),
300    ...      Column('relative_volume_10d_calc') > 1.2,
301    ...      Column('MACD.macd') >= Column('MACD.signal')
302    ...  )
303    ...  .order_by('volume', ascending=False)
304    ...  .offset(5)
305    ...  .limit(15)
306    ...  .get_scanner_data())
307    (393,
308             ticker  name   close    volume  relative_volume_10d_calc
309     0     OTC:YCRM  YCRM  0.0120  19626514                  1.887942
310     1     OTC:PLPL  PLPL  0.0002  17959914                  3.026059
311     2  NASDAQ:ABVC  ABVC  1.3800  16295824                  1.967505
312     3     OTC:TLSS  TLSS  0.0009  15671157                  1.877976
313     4     OTC:GVSI  GVSI  0.0128  14609774                  2.640792
314     5     OTC:IGEX  IGEX  0.0012  14285592                  1.274861
315     6     OTC:EEGI  EEGI  0.0004  12094000                  2.224749
316     7   NASDAQ:GLG   GLG  0.0591   9811974                  1.990526
317     8  NASDAQ:TCRT  TCRT  0.0890   8262894                  2.630553
318     9     OTC:INKW  INKW  0.0027   7196404                  1.497134)
319
320    To avoid rewriting the same query again and again, you can save the query to a variable and
321    just call `get_scanner_data()` again and again to get the latest data:
322    >>> top_50_bullish = (Query()
323    ...  .select('name', 'close', 'volume', 'relative_volume_10d_calc')
324    ...  .where(
325    ...      Column('market_cap_basic').between(1_000_000, 50_000_000),
326    ...      Column('relative_volume_10d_calc') > 1.2,
327    ...      Column('MACD.macd') >= Column('MACD.signal')
328    ...  )
329    ...  .order_by('volume', ascending=False)
330    ...  .limit(50))
331    >>> top_50_bullish.get_scanner_data()
332    (393,
333              ticker   name     close     volume  relative_volume_10d_calc
334     0      OTC:BEGI   BEGI  0.001050  127874055                  3.349924
335     1      OTC:HCMC   HCMC  0.000100  126992562                  1.206231
336     2      OTC:HEMP   HEMP  0.000150  101382713                  1.775458
337     3      OTC:SONG   SONG  0.000800   92640779                  1.805721
338     4      OTC:APRU   APRU  0.001575   38104499                 29.028958
339     ..          ...    ...       ...        ...                       ...
340     45    OTC:BSHPF  BSHPF  0.001000     525000                  1.280899
341     46     OTC:GRHI   GRHI  0.033000     507266                  1.845738
342     47    OTC:OMGGF  OMGGF  0.035300     505000                  4.290059
343     48  NASDAQ:GBNH   GBNH  0.273000     500412                  9.076764
344     49    OTC:CLRMF  CLRMF  0.032500     496049                 17.560935
345     [50 rows x 5 columns])
346    """
347
348    def __init__(self) -> None:
349        # noinspection PyTypeChecker
350        self.query: QueryDict = {
351            'markets': ['america'],
352            'symbols': {'query': {'types': []}, 'tickers': []},
353            'options': {'lang': 'en'},
354            'columns': ['name', 'close', 'volume', 'market_cap_basic'],
355            # 'filter': ...,
356            'sort': {'sortBy': 'Value.Traded', 'sortOrder': 'desc'},
357            'range': [0, 50],
358        }
359        self.url = 'https://scanner.tradingview.com/america/scan'
360
361    def set_markets(self, *markets: str) -> Query:
362        """
363        This method allows you to select the market/s which you want to query.
364
365        By default, the screener will only scan US equities, but you can change it to scan any
366        or even multiple markets, that includes a list of 67 countries, and also the following 
367        commodities: `bonds`, `cfd`, `coin`, `crypto`, `economics2`, `euronext`, `forex`,
368        `futures`, `options`.
369
370        You may choose any value from `tradingview_screener.constants.MARKETS`.
371
372        Examples:
373
374        By default, the screener will search the `america` market
375        >>> default_columns = ['close', 'market', 'country', 'currency']
376        >>> Query().select(*default_columns).get_scanner_data()
377        (17898,
378                  ticker     close   market        country currency
379         0      AMEX:SPY  419.9900  america  United States      USD
380         1   NASDAQ:TSLA  201.7201  america  United States      USD
381         2   NASDAQ:NVDA  416.3800  america  United States      USD
382         3    NASDAQ:AMD  106.4499  america  United States      USD
383         4    NASDAQ:QQQ  353.4000  america  United States      USD
384         ..          ...       ...      ...            ...      ...
385         45  NASDAQ:ADBE  538.0000  america  United States      USD
386         46      NYSE:BA  188.9000  america  United States      USD
387         47  NASDAQ:SBUX   90.9100  america  United States      USD
388         48     NYSE:HUM  500.6350  america  United States      USD
389         49     NYSE:CAT  227.3400  america  United States      USD
390         [50 rows x 5 columns])
391
392        But you can change it (note the difference between `market` and `country`)
393        >>> (Query()
394        ...  .select(*default_columns)
395        ...  .set_markets('italy')
396        ...  .get_scanner_data())
397        (2346,
398                ticker    close market      country currency
399         0     MIL:UCG  23.9150  italy        Italy      EUR
400         1     MIL:ISP   2.4910  italy        Italy      EUR
401         2   MIL:STLAM  17.9420  italy  Netherlands      EUR
402         3    MIL:ENEL   6.0330  italy        Italy      EUR
403         4     MIL:ENI  15.4800  italy        Italy      EUR
404         ..        ...      ...    ...          ...      ...
405         45    MIL:UNI   5.1440  italy        Italy      EUR
406         46   MIL:3OIS   0.4311  italy      Ireland      EUR
407         47   MIL:3SIL  35.2300  italy      Ireland      EUR
408         48   MIL:IWDE  69.1300  italy      Ireland      EUR
409         49   MIL:QQQS  19.2840  italy      Ireland      EUR
410         [50 rows x 5 columns])
411
412        You can also select multiple markets
413        >>> (Query()
414        ...  .select(*default_columns)
415        ...  .set_markets('america', 'israel', 'hongkong', 'switzerland')
416        ...  .get_scanner_data())
417        (23964,
418                   ticker      close    market        country currency
419         0       AMEX:SPY   420.1617   america  United States      USD
420         1    NASDAQ:TSLA   201.2000   america  United States      USD
421         2    NASDAQ:NVDA   416.7825   america  United States      USD
422         3     NASDAQ:AMD   106.6600   america  United States      USD
423         4     NASDAQ:QQQ   353.7985   america  United States      USD
424         ..           ...        ...       ...            ...      ...
425         45  NASDAQ:GOOGL   124.9200   america  United States      USD
426         46     HKEX:1211   233.2000  hongkong          China      HKD
427         47     TASE:ALHE  1995.0000    israel         Israel      ILA
428         48      AMEX:BIL    91.4398   america  United States      USD
429         49   NASDAQ:GOOG   126.1500   america  United States      USD
430         [50 rows x 5 columns])
431
432        You may also select different financial instruments
433        >>> (Query()
434        ...  .select('close', 'market')
435        ...  .set_markets('cfd', 'crypto', 'futures', 'options')
436        ...  .get_scanner_data())
437        (118076,
438                                 ticker  ...   market
439         0          UNISWAP3ETH:WETHVGT  ...   crypto
440         1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
441         2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
442         3          UNISWAP3ETH:USDTBRD  ...   crypto
443         4         UNISWAP3ETH:WBTCAUSD  ...   crypto
444         ..                         ...  ...      ...
445         45               NSE:IDEAF2024  ...  futures
446         46         NSE:INDUSTOWERX2023  ...  futures
447         47            NSE:INDUSTOWER1!  ...  futures
448         48                  BIST:XU100  ...      cfd
449         49              BYBIT:BTCUSD.P  ...   crypto
450         [50 rows x 3 columns])
451
452        To select all the avaialble markets you can do this trick
453        >>> from tradingview_screener.constants import MARKETS
454        >>> len(MARKETS)
455        76
456        >>> (Query()
457        ...  .select('close', 'market')
458        ...  .set_markets(*MARKETS)
459        ...  .get_scanner_data())  # notice how many records we find: over 240k
460        (241514,
461                                 ticker  ...   market
462         0          UNISWAP3ETH:WETHVGT  ...   crypto
463         1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
464         2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
465         3          UNISWAP3ETH:USDTBRD  ...   crypto
466         4         UNISWAP3ETH:WBTCAUSD  ...   crypto
467         ..                         ...  ...      ...
468         45               NSE:IDEAF2024  ...  futures
469         46            NSE:INDUSTOWER1!  ...  futures
470         47         NSE:INDUSTOWERX2023  ...  futures
471         48                  BIST:XU100  ...      cfd
472         49              BYBIT:BTCUSD.P  ...   crypto
473
474         [50 rows x 3 columns])
475
476        :param markets: one or more markets from `tradingview_screener.constants.MARKETS`
477        :return: Self
478        """
479        if len(markets) == 1:
480            market = markets[0]
481            assert market in MARKETS
482
483            self.url = URL.format(market=market)
484            self.query['markets'] = [market]
485
486        elif len(markets) >= 1:
487            for m in markets:
488                assert m in MARKETS
489
490            self.url = URL.format(market='global')
491            self.query['markets'] = list(markets)
492
493        return self
494
495    def set_tickers(self, *tickers: str) -> Query:
496        """
497        Set the tickers you wish to receive information on.
498
499        Examples:
500
501        >>> Query().limit(5).get_scanner_data()
502        (17879,
503                 ticker  name   close     volume  market_cap_basic
504         0  NASDAQ:TSLA  TSLA  248.50  118559595      7.887376e+11
505         1     AMEX:SPY   SPY  445.52   62066984               NaN
506         2  NASDAQ:NVDA  NVDA  455.72   47389801      1.125628e+12
507         3   NASDAQ:QQQ   QQQ  372.58   35846281               NaN
508         4  NASDAQ:AAPL  AAPL  178.18   65600673      2.785707e+12)
509
510        >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd')
511        >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data()
512        (2,
513                 ticker  name   market   close     volume        VWAP  MACD.macd
514         0  NASDAQ:TSLA  TSLA  america  248.50  118559595  250.563333   0.730376
515         1  NASDAQ:NVDA  NVDA  america  455.72   47389801  458.163333   7.927189)
516
517        >>> q.set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX').get_scanner_data()
518        (4,
519              ticker  name   market     close    volume          VWAP    MACD.macd
520         0  HOSE:VIX   VIX  vietnam  19800.00  26292400  19883.333333  1291.359459
521         1  AMEX:SPY   SPY  america    445.52  62066984    445.720000     0.484263
522         2  NYSE:GME   GME  america     17.71   4693902     17.853333    -0.660342
523         3  MIL:RACE  RACE    italy    279.30    246547    279.033327    -1.398701)
524
525        :param tickers: One or more tickers, syntax: `exchange:symbol`
526        :return: Self
527        """
528        # no need to select the market if we specify the symbol we want
529        # noinspection PyTypedDict
530        self.query.pop('markets', None)
531
532        self.query['symbols'] = {'tickers': list(tickers)}
533        self.url = 'https://scanner.tradingview.com/global/scan'
534        return self
535
536    def select(self, *columns: Column | str) -> Query:
537        self.query['columns'] = [
538            col.name if isinstance(col, Column) else Column(col).name for col in columns
539        ]
540        return self
541
542    def where(self, *expressions: FilterOperationDict) -> Query:
543        self.query['filter'] = list(expressions)  # convert tuple[dict] -> list[dict]
544        return self
545
546    def order_by(self, column: Column | str, ascending: bool = True) -> Query:
547        column = column.name if isinstance(column, Column) else Column(column).name
548        sort_order = 'asc' if ascending else 'desc'
549        # noinspection PyTypeChecker
550        self.query['sort'] = SortByDict(sortBy=column, sortOrder=sort_order)
551        return self
552
553    def offset(self, offset: int) -> Query:
554        self.query['range'][0] = offset
555        return self
556
557    def limit(self, limit: int) -> Query:
558        self.query['range'][1] = limit
559        return self
560
561    # def set_options(self, options) -> None:
562    #     raise NotImplementedError
563
564    def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]:
565        """
566        Perform a POST web-request and return the data from the API as a DataFrame.
567
568        Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`,
569        this can be very useful if you want to pass your own headers/cookies.
570
571        (if you have paid for a live data add-on with TradingView, you want to pass your own
572        headers and cookies to access that real-time data)
573
574        :param kwargs: kwargs to pass to `requests.post()`
575        :return: a tuple consisting of: (total_count, dataframe)
576        """
577        kwargs.setdefault('headers', HEADERS)
578        kwargs.setdefault('timeout', 20)
579        r = requests.post(self.url, json=self.query, **kwargs)
580
581        if r.status_code >= 400:
582            # add the body to the error message for debugging purposes
583            r.reason += f'\n Body: {r.text}\n'
584            r.raise_for_status()
585
586        json_obj = r.json()
587        rows_count = json_obj['totalCount']
588        data = json_obj['data']
589
590        df = pd.DataFrame(
591            data=([row['s'], *row['d']] for row in data),
592            columns=['ticker', *self.query.get('columns', ())],
593        )
594        return rows_count, df
595
596    def copy(self) -> Query:
597        new = Query()
598        new.query = self.query.copy()
599        return new
600
601    def __repr__(self) -> str:
602        return f'< {pprint.pformat(self.query)} >'
603
604    def __eq__(self, other) -> bool:
605        return isinstance(other, Query) and self.query == other.query
class Query:
174class Query:
175    """
176    This class allows you to perform SQL-like queries on the tradingview stock-screener.
177
178    The `Query` object reppresents a query that can be made to the official tradingview API, and it
179    stores all the data as JSON internally.
180
181    Examples:
182
183    To perform a simple query all you have to do is:
184    >>> from tradingview_screener import Query
185    >>> Query().get_scanner_data()
186    (18060,
187              ticker  name   close     volume  market_cap_basic
188     0      AMEX:SPY   SPY  410.68  107367671               NaN
189     1    NASDAQ:QQQ   QQQ  345.31   63475390               NaN
190     2   NASDAQ:TSLA  TSLA  207.30   94879471      6.589904e+11
191     3   NASDAQ:NVDA  NVDA  405.00   41677185      1.000350e+12
192     4   NASDAQ:AMZN  AMZN  127.74  125309313      1.310658e+12
193     ..          ...   ...     ...        ...               ...
194     45     NYSE:UNH   UNH  524.66    2585616      4.859952e+11
195     46  NASDAQ:DXCM  DXCM   89.29   14954605      3.449933e+10
196     47      NYSE:MA    MA  364.08    3624883      3.429080e+11
197     48    NYSE:ABBV  ABBV  138.93    9427212      2.452179e+11
198     49     AMEX:XLK   XLK  161.12    8115780               NaN
199     [50 rows x 5 columns])
200
201    The `get_scanner_data()` method will return a tuple with the first element being the number of
202    records that were found (like a `COUNT(*)`), and the second element contains the data that was
203    found as a DataFrame.
204
205    ---
206
207    By default, the `Query` will select the columns: `name`, `close`, `volume`, `market_cap_basic`,
208    but you override that
209    >>> (Query()
210    ...  .select('open', 'high', 'low', 'VWAP', 'MACD.macd', 'RSI', 'Price to Earnings Ratio (TTM)')
211    ...  .get_scanner_data())
212    (18060,
213              ticker    open     high  ...  MACD.macd        RSI  price_earnings_ttm
214     0      AMEX:SPY  414.19  414.600  ...  -5.397135  29.113396                 NaN
215     1    NASDAQ:QQQ  346.43  348.840  ...  -4.321482  34.335449                 NaN
216     2   NASDAQ:TSLA  210.60  212.410  ... -12.224250  28.777229           66.752536
217     3   NASDAQ:NVDA  411.30  412.060  ...  -8.738986  37.845668           97.835540
218     4   NASDAQ:AMZN  126.20  130.020  ...  -2.025378  48.665666           66.697995
219     ..          ...     ...      ...  ...        ...        ...                 ...
220     45     NYSE:UNH  525.99  527.740  ...   6.448129  54.614775           22.770713
221     46  NASDAQ:DXCM   92.73   92.988  ...  -2.376942  52.908093           98.914368
222     47      NYSE:MA  366.49  368.285  ...  -7.496065  22.614078           31.711800
223     48    NYSE:ABBV  138.77  143.000  ...  -1.708497  27.117232           37.960054
224     49     AMEX:XLK  161.17  162.750  ...  -1.520828  36.868658                 NaN
225     [50 rows x 8 columns])
226
227    You can find the 250+ columns available in `tradingview_screener.constants.COLUMNS`.
228
229    Now let's do some queries using the `WHERE` statement, select all the stocks that the `close` is
230    bigger or equal than 350
231    >>> (Query()
232    ...  .select('close', 'volume', '52 Week High')
233    ...  .where(Column('close') >= 350)
234    ...  .get_scanner_data())
235    (159,
236              ticker      close     volume  price_52_week_high
237     0      AMEX:SPY     410.68  107367671              459.44
238     1   NASDAQ:NVDA     405.00   41677185              502.66
239     2    NYSE:BRK.A  503375.05       7910           566569.97
240     3      AMEX:IVV     412.55    5604525              461.88
241     4      AMEX:VOO     377.32    5638752              422.15
242     ..          ...        ...        ...                 ...
243     45  NASDAQ:EQIX     710.39     338549              821.63
244     46     NYSE:MCK     448.03     527406              465.90
245     47     NYSE:MTD     976.25     241733             1615.97
246     48  NASDAQ:CTAS     496.41     464631              525.37
247     49   NASDAQ:ROP     475.57     450141              508.90
248     [50 rows x 4 columns])
249
250    You can even use other columns in these kind of operations
251    >>> (Query()
252    ...  .select('close', 'VWAP')
253    ...  .where(Column('close') >= Column('VWAP'))
254    ...  .get_scanner_data())
255    (9044,
256               ticker   close        VWAP
257     0    NASDAQ:AAPL  168.22  168.003333
258     1    NASDAQ:META  296.73  296.336667
259     2   NASDAQ:GOOGL  122.17  121.895233
260     3     NASDAQ:AMD   96.43   96.123333
261     4    NASDAQ:GOOG  123.40  123.100000
262     ..           ...     ...         ...
263     45       NYSE:GD  238.25  238.043333
264     46     NYSE:GOLD   16.33   16.196667
265     47      AMEX:SLV   21.18   21.041667
266     48      AMEX:VXX   27.08   26.553333
267     49      NYSE:SLB   55.83   55.676667
268     [50 rows x 3 columns])
269
270    Let's find all the stocks that the price is between the EMA 5 and 20, and the type is a stock
271    or fund
272    >>> (Query()
273    ...  .select('close', 'volume', 'EMA5', 'EMA20', 'type')
274    ...  .where(
275    ...     Column('close').between(Column('EMA5'), Column('EMA20')),
276    ...     Column('type').isin(['stock', 'fund'])
277    ...  )
278    ...  .get_scanner_data())
279    (1730,
280              ticker   close     volume        EMA5       EMA20   type
281     0   NASDAQ:AMZN  127.74  125309313  125.033517  127.795142  stock
282     1      AMEX:HYG   72.36   35621800   72.340776   72.671058   fund
283     2      AMEX:LQD   99.61   21362859   99.554272  100.346388   fund
284     3    NASDAQ:IEF   90.08   11628236   89.856804   90.391503   fund
285     4      NYSE:SYK  261.91    3783608  261.775130  266.343290  stock
286     ..          ...     ...        ...         ...         ...    ...
287     45     NYSE:EMN   72.58    1562328   71.088034   72.835394  stock
288     46     NYSE:KIM   16.87    6609420   16.858920   17.096582   fund
289     47  NASDAQ:COLM   71.34    1516675   71.073116   71.658864  stock
290     48     NYSE:AOS   67.81    1586796   67.561619   67.903168  stock
291     49  NASDAQ:IGIB   47.81    2073538   47.761338   48.026795   fund
292     [50 rows x 6 columns])
293
294    There are also the `ORDER BY`, `OFFSET`, and `LIMIT` statements.
295    Let's select all the tickers with a market cap between 1M and 50M, that have a relative volume
296    bigger than 1.2, and that the MACD is positive
297    >>> (Query()
298    ...  .select('name', 'close', 'volume', 'relative_volume_10d_calc')
299    ...  .where(
300    ...      Column('market_cap_basic').between(1_000_000, 50_000_000),
301    ...      Column('relative_volume_10d_calc') > 1.2,
302    ...      Column('MACD.macd') >= Column('MACD.signal')
303    ...  )
304    ...  .order_by('volume', ascending=False)
305    ...  .offset(5)
306    ...  .limit(15)
307    ...  .get_scanner_data())
308    (393,
309             ticker  name   close    volume  relative_volume_10d_calc
310     0     OTC:YCRM  YCRM  0.0120  19626514                  1.887942
311     1     OTC:PLPL  PLPL  0.0002  17959914                  3.026059
312     2  NASDAQ:ABVC  ABVC  1.3800  16295824                  1.967505
313     3     OTC:TLSS  TLSS  0.0009  15671157                  1.877976
314     4     OTC:GVSI  GVSI  0.0128  14609774                  2.640792
315     5     OTC:IGEX  IGEX  0.0012  14285592                  1.274861
316     6     OTC:EEGI  EEGI  0.0004  12094000                  2.224749
317     7   NASDAQ:GLG   GLG  0.0591   9811974                  1.990526
318     8  NASDAQ:TCRT  TCRT  0.0890   8262894                  2.630553
319     9     OTC:INKW  INKW  0.0027   7196404                  1.497134)
320
321    To avoid rewriting the same query again and again, you can save the query to a variable and
322    just call `get_scanner_data()` again and again to get the latest data:
323    >>> top_50_bullish = (Query()
324    ...  .select('name', 'close', 'volume', 'relative_volume_10d_calc')
325    ...  .where(
326    ...      Column('market_cap_basic').between(1_000_000, 50_000_000),
327    ...      Column('relative_volume_10d_calc') > 1.2,
328    ...      Column('MACD.macd') >= Column('MACD.signal')
329    ...  )
330    ...  .order_by('volume', ascending=False)
331    ...  .limit(50))
332    >>> top_50_bullish.get_scanner_data()
333    (393,
334              ticker   name     close     volume  relative_volume_10d_calc
335     0      OTC:BEGI   BEGI  0.001050  127874055                  3.349924
336     1      OTC:HCMC   HCMC  0.000100  126992562                  1.206231
337     2      OTC:HEMP   HEMP  0.000150  101382713                  1.775458
338     3      OTC:SONG   SONG  0.000800   92640779                  1.805721
339     4      OTC:APRU   APRU  0.001575   38104499                 29.028958
340     ..          ...    ...       ...        ...                       ...
341     45    OTC:BSHPF  BSHPF  0.001000     525000                  1.280899
342     46     OTC:GRHI   GRHI  0.033000     507266                  1.845738
343     47    OTC:OMGGF  OMGGF  0.035300     505000                  4.290059
344     48  NASDAQ:GBNH   GBNH  0.273000     500412                  9.076764
345     49    OTC:CLRMF  CLRMF  0.032500     496049                 17.560935
346     [50 rows x 5 columns])
347    """
348
349    def __init__(self) -> None:
350        # noinspection PyTypeChecker
351        self.query: QueryDict = {
352            'markets': ['america'],
353            'symbols': {'query': {'types': []}, 'tickers': []},
354            'options': {'lang': 'en'},
355            'columns': ['name', 'close', 'volume', 'market_cap_basic'],
356            # 'filter': ...,
357            'sort': {'sortBy': 'Value.Traded', 'sortOrder': 'desc'},
358            'range': [0, 50],
359        }
360        self.url = 'https://scanner.tradingview.com/america/scan'
361
362    def set_markets(self, *markets: str) -> Query:
363        """
364        This method allows you to select the market/s which you want to query.
365
366        By default, the screener will only scan US equities, but you can change it to scan any
367        or even multiple markets, that includes a list of 67 countries, and also the following 
368        commodities: `bonds`, `cfd`, `coin`, `crypto`, `economics2`, `euronext`, `forex`,
369        `futures`, `options`.
370
371        You may choose any value from `tradingview_screener.constants.MARKETS`.
372
373        Examples:
374
375        By default, the screener will search the `america` market
376        >>> default_columns = ['close', 'market', 'country', 'currency']
377        >>> Query().select(*default_columns).get_scanner_data()
378        (17898,
379                  ticker     close   market        country currency
380         0      AMEX:SPY  419.9900  america  United States      USD
381         1   NASDAQ:TSLA  201.7201  america  United States      USD
382         2   NASDAQ:NVDA  416.3800  america  United States      USD
383         3    NASDAQ:AMD  106.4499  america  United States      USD
384         4    NASDAQ:QQQ  353.4000  america  United States      USD
385         ..          ...       ...      ...            ...      ...
386         45  NASDAQ:ADBE  538.0000  america  United States      USD
387         46      NYSE:BA  188.9000  america  United States      USD
388         47  NASDAQ:SBUX   90.9100  america  United States      USD
389         48     NYSE:HUM  500.6350  america  United States      USD
390         49     NYSE:CAT  227.3400  america  United States      USD
391         [50 rows x 5 columns])
392
393        But you can change it (note the difference between `market` and `country`)
394        >>> (Query()
395        ...  .select(*default_columns)
396        ...  .set_markets('italy')
397        ...  .get_scanner_data())
398        (2346,
399                ticker    close market      country currency
400         0     MIL:UCG  23.9150  italy        Italy      EUR
401         1     MIL:ISP   2.4910  italy        Italy      EUR
402         2   MIL:STLAM  17.9420  italy  Netherlands      EUR
403         3    MIL:ENEL   6.0330  italy        Italy      EUR
404         4     MIL:ENI  15.4800  italy        Italy      EUR
405         ..        ...      ...    ...          ...      ...
406         45    MIL:UNI   5.1440  italy        Italy      EUR
407         46   MIL:3OIS   0.4311  italy      Ireland      EUR
408         47   MIL:3SIL  35.2300  italy      Ireland      EUR
409         48   MIL:IWDE  69.1300  italy      Ireland      EUR
410         49   MIL:QQQS  19.2840  italy      Ireland      EUR
411         [50 rows x 5 columns])
412
413        You can also select multiple markets
414        >>> (Query()
415        ...  .select(*default_columns)
416        ...  .set_markets('america', 'israel', 'hongkong', 'switzerland')
417        ...  .get_scanner_data())
418        (23964,
419                   ticker      close    market        country currency
420         0       AMEX:SPY   420.1617   america  United States      USD
421         1    NASDAQ:TSLA   201.2000   america  United States      USD
422         2    NASDAQ:NVDA   416.7825   america  United States      USD
423         3     NASDAQ:AMD   106.6600   america  United States      USD
424         4     NASDAQ:QQQ   353.7985   america  United States      USD
425         ..           ...        ...       ...            ...      ...
426         45  NASDAQ:GOOGL   124.9200   america  United States      USD
427         46     HKEX:1211   233.2000  hongkong          China      HKD
428         47     TASE:ALHE  1995.0000    israel         Israel      ILA
429         48      AMEX:BIL    91.4398   america  United States      USD
430         49   NASDAQ:GOOG   126.1500   america  United States      USD
431         [50 rows x 5 columns])
432
433        You may also select different financial instruments
434        >>> (Query()
435        ...  .select('close', 'market')
436        ...  .set_markets('cfd', 'crypto', 'futures', 'options')
437        ...  .get_scanner_data())
438        (118076,
439                                 ticker  ...   market
440         0          UNISWAP3ETH:WETHVGT  ...   crypto
441         1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
442         2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
443         3          UNISWAP3ETH:USDTBRD  ...   crypto
444         4         UNISWAP3ETH:WBTCAUSD  ...   crypto
445         ..                         ...  ...      ...
446         45               NSE:IDEAF2024  ...  futures
447         46         NSE:INDUSTOWERX2023  ...  futures
448         47            NSE:INDUSTOWER1!  ...  futures
449         48                  BIST:XU100  ...      cfd
450         49              BYBIT:BTCUSD.P  ...   crypto
451         [50 rows x 3 columns])
452
453        To select all the avaialble markets you can do this trick
454        >>> from tradingview_screener.constants import MARKETS
455        >>> len(MARKETS)
456        76
457        >>> (Query()
458        ...  .select('close', 'market')
459        ...  .set_markets(*MARKETS)
460        ...  .get_scanner_data())  # notice how many records we find: over 240k
461        (241514,
462                                 ticker  ...   market
463         0          UNISWAP3ETH:WETHVGT  ...   crypto
464         1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
465         2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
466         3          UNISWAP3ETH:USDTBRD  ...   crypto
467         4         UNISWAP3ETH:WBTCAUSD  ...   crypto
468         ..                         ...  ...      ...
469         45               NSE:IDEAF2024  ...  futures
470         46            NSE:INDUSTOWER1!  ...  futures
471         47         NSE:INDUSTOWERX2023  ...  futures
472         48                  BIST:XU100  ...      cfd
473         49              BYBIT:BTCUSD.P  ...   crypto
474
475         [50 rows x 3 columns])
476
477        :param markets: one or more markets from `tradingview_screener.constants.MARKETS`
478        :return: Self
479        """
480        if len(markets) == 1:
481            market = markets[0]
482            assert market in MARKETS
483
484            self.url = URL.format(market=market)
485            self.query['markets'] = [market]
486
487        elif len(markets) >= 1:
488            for m in markets:
489                assert m in MARKETS
490
491            self.url = URL.format(market='global')
492            self.query['markets'] = list(markets)
493
494        return self
495
496    def set_tickers(self, *tickers: str) -> Query:
497        """
498        Set the tickers you wish to receive information on.
499
500        Examples:
501
502        >>> Query().limit(5).get_scanner_data()
503        (17879,
504                 ticker  name   close     volume  market_cap_basic
505         0  NASDAQ:TSLA  TSLA  248.50  118559595      7.887376e+11
506         1     AMEX:SPY   SPY  445.52   62066984               NaN
507         2  NASDAQ:NVDA  NVDA  455.72   47389801      1.125628e+12
508         3   NASDAQ:QQQ   QQQ  372.58   35846281               NaN
509         4  NASDAQ:AAPL  AAPL  178.18   65600673      2.785707e+12)
510
511        >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd')
512        >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data()
513        (2,
514                 ticker  name   market   close     volume        VWAP  MACD.macd
515         0  NASDAQ:TSLA  TSLA  america  248.50  118559595  250.563333   0.730376
516         1  NASDAQ:NVDA  NVDA  america  455.72   47389801  458.163333   7.927189)
517
518        >>> q.set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX').get_scanner_data()
519        (4,
520              ticker  name   market     close    volume          VWAP    MACD.macd
521         0  HOSE:VIX   VIX  vietnam  19800.00  26292400  19883.333333  1291.359459
522         1  AMEX:SPY   SPY  america    445.52  62066984    445.720000     0.484263
523         2  NYSE:GME   GME  america     17.71   4693902     17.853333    -0.660342
524         3  MIL:RACE  RACE    italy    279.30    246547    279.033327    -1.398701)
525
526        :param tickers: One or more tickers, syntax: `exchange:symbol`
527        :return: Self
528        """
529        # no need to select the market if we specify the symbol we want
530        # noinspection PyTypedDict
531        self.query.pop('markets', None)
532
533        self.query['symbols'] = {'tickers': list(tickers)}
534        self.url = 'https://scanner.tradingview.com/global/scan'
535        return self
536
537    def select(self, *columns: Column | str) -> Query:
538        self.query['columns'] = [
539            col.name if isinstance(col, Column) else Column(col).name for col in columns
540        ]
541        return self
542
543    def where(self, *expressions: FilterOperationDict) -> Query:
544        self.query['filter'] = list(expressions)  # convert tuple[dict] -> list[dict]
545        return self
546
547    def order_by(self, column: Column | str, ascending: bool = True) -> Query:
548        column = column.name if isinstance(column, Column) else Column(column).name
549        sort_order = 'asc' if ascending else 'desc'
550        # noinspection PyTypeChecker
551        self.query['sort'] = SortByDict(sortBy=column, sortOrder=sort_order)
552        return self
553
554    def offset(self, offset: int) -> Query:
555        self.query['range'][0] = offset
556        return self
557
558    def limit(self, limit: int) -> Query:
559        self.query['range'][1] = limit
560        return self
561
562    # def set_options(self, options) -> None:
563    #     raise NotImplementedError
564
565    def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]:
566        """
567        Perform a POST web-request and return the data from the API as a DataFrame.
568
569        Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`,
570        this can be very useful if you want to pass your own headers/cookies.
571
572        (if you have paid for a live data add-on with TradingView, you want to pass your own
573        headers and cookies to access that real-time data)
574
575        :param kwargs: kwargs to pass to `requests.post()`
576        :return: a tuple consisting of: (total_count, dataframe)
577        """
578        kwargs.setdefault('headers', HEADERS)
579        kwargs.setdefault('timeout', 20)
580        r = requests.post(self.url, json=self.query, **kwargs)
581
582        if r.status_code >= 400:
583            # add the body to the error message for debugging purposes
584            r.reason += f'\n Body: {r.text}\n'
585            r.raise_for_status()
586
587        json_obj = r.json()
588        rows_count = json_obj['totalCount']
589        data = json_obj['data']
590
591        df = pd.DataFrame(
592            data=([row['s'], *row['d']] for row in data),
593            columns=['ticker', *self.query.get('columns', ())],
594        )
595        return rows_count, df
596
597    def copy(self) -> Query:
598        new = Query()
599        new.query = self.query.copy()
600        return new
601
602    def __repr__(self) -> str:
603        return f'< {pprint.pformat(self.query)} >'
604
605    def __eq__(self, other) -> bool:
606        return isinstance(other, Query) and self.query == other.query

This class allows you to perform SQL-like queries on the tradingview stock-screener.

The Query object reppresents a query that can be made to the official tradingview API, and it stores all the data as JSON internally.

Examples:

To perform a simple query all you have to do is:

>>> from tradingview_screener import Query
>>> Query().get_scanner_data()
(18060,
          ticker  name   close     volume  market_cap_basic
 0      AMEX:SPY   SPY  410.68  107367671               NaN
 1    NASDAQ:QQQ   QQQ  345.31   63475390               NaN
 2   NASDAQ:TSLA  TSLA  207.30   94879471      6.589904e+11
 3   NASDAQ:NVDA  NVDA  405.00   41677185      1.000350e+12
 4   NASDAQ:AMZN  AMZN  127.74  125309313      1.310658e+12
 ..          ...   ...     ...        ...               ...
 45     NYSE:UNH   UNH  524.66    2585616      4.859952e+11
 46  NASDAQ:DXCM  DXCM   89.29   14954605      3.449933e+10
 47      NYSE:MA    MA  364.08    3624883      3.429080e+11
 48    NYSE:ABBV  ABBV  138.93    9427212      2.452179e+11
 49     AMEX:XLK   XLK  161.12    8115780               NaN
 [50 rows x 5 columns])

The get_scanner_data() method will return a tuple with the first element being the number of records that were found (like a COUNT(*)), and the second element contains the data that was found as a DataFrame.


By default, the Query will select the columns: name, close, volume, market_cap_basic, but you override that

>>> (Query()
...  .select('open', 'high', 'low', 'VWAP', 'MACD.macd', 'RSI', 'Price to Earnings Ratio (TTM)')
...  .get_scanner_data())
(18060,
          ticker    open     high  ...  MACD.macd        RSI  price_earnings_ttm
 0      AMEX:SPY  414.19  414.600  ...  -5.397135  29.113396                 NaN
 1    NASDAQ:QQQ  346.43  348.840  ...  -4.321482  34.335449                 NaN
 2   NASDAQ:TSLA  210.60  212.410  ... -12.224250  28.777229           66.752536
 3   NASDAQ:NVDA  411.30  412.060  ...  -8.738986  37.845668           97.835540
 4   NASDAQ:AMZN  126.20  130.020  ...  -2.025378  48.665666           66.697995
 ..          ...     ...      ...  ...        ...        ...                 ...
 45     NYSE:UNH  525.99  527.740  ...   6.448129  54.614775           22.770713
 46  NASDAQ:DXCM   92.73   92.988  ...  -2.376942  52.908093           98.914368
 47      NYSE:MA  366.49  368.285  ...  -7.496065  22.614078           31.711800
 48    NYSE:ABBV  138.77  143.000  ...  -1.708497  27.117232           37.960054
 49     AMEX:XLK  161.17  162.750  ...  -1.520828  36.868658                 NaN
 [50 rows x 8 columns])

You can find the 250+ columns available in tradingview_screener.constants.COLUMNS.

Now let's do some queries using the WHERE statement, select all the stocks that the close is bigger or equal than 350

>>> (Query()
...  .select('close', 'volume', '52 Week High')
...  .where(Column('close') >= 350)
...  .get_scanner_data())
(159,
          ticker      close     volume  price_52_week_high
 0      AMEX:SPY     410.68  107367671              459.44
 1   NASDAQ:NVDA     405.00   41677185              502.66
 2    NYSE:BRK.A  503375.05       7910           566569.97
 3      AMEX:IVV     412.55    5604525              461.88
 4      AMEX:VOO     377.32    5638752              422.15
 ..          ...        ...        ...                 ...
 45  NASDAQ:EQIX     710.39     338549              821.63
 46     NYSE:MCK     448.03     527406              465.90
 47     NYSE:MTD     976.25     241733             1615.97
 48  NASDAQ:CTAS     496.41     464631              525.37
 49   NASDAQ:ROP     475.57     450141              508.90
 [50 rows x 4 columns])

You can even use other columns in these kind of operations

>>> (Query()
...  .select('close', 'VWAP')
...  .where(Column('close') >= Column('VWAP'))
...  .get_scanner_data())
(9044,
           ticker   close        VWAP
 0    NASDAQ:AAPL  168.22  168.003333
 1    NASDAQ:META  296.73  296.336667
 2   NASDAQ:GOOGL  122.17  121.895233
 3     NASDAQ:AMD   96.43   96.123333
 4    NASDAQ:GOOG  123.40  123.100000
 ..           ...     ...         ...
 45       NYSE:GD  238.25  238.043333
 46     NYSE:GOLD   16.33   16.196667
 47      AMEX:SLV   21.18   21.041667
 48      AMEX:VXX   27.08   26.553333
 49      NYSE:SLB   55.83   55.676667
 [50 rows x 3 columns])

Let's find all the stocks that the price is between the EMA 5 and 20, and the type is a stock or fund

>>> (Query()
...  .select('close', 'volume', 'EMA5', 'EMA20', 'type')
...  .where(
...     Column('close').between(Column('EMA5'), Column('EMA20')),
...     Column('type').isin(['stock', 'fund'])
...  )
...  .get_scanner_data())
(1730,
          ticker   close     volume        EMA5       EMA20   type
 0   NASDAQ:AMZN  127.74  125309313  125.033517  127.795142  stock
 1      AMEX:HYG   72.36   35621800   72.340776   72.671058   fund
 2      AMEX:LQD   99.61   21362859   99.554272  100.346388   fund
 3    NASDAQ:IEF   90.08   11628236   89.856804   90.391503   fund
 4      NYSE:SYK  261.91    3783608  261.775130  266.343290  stock
 ..          ...     ...        ...         ...         ...    ...
 45     NYSE:EMN   72.58    1562328   71.088034   72.835394  stock
 46     NYSE:KIM   16.87    6609420   16.858920   17.096582   fund
 47  NASDAQ:COLM   71.34    1516675   71.073116   71.658864  stock
 48     NYSE:AOS   67.81    1586796   67.561619   67.903168  stock
 49  NASDAQ:IGIB   47.81    2073538   47.761338   48.026795   fund
 [50 rows x 6 columns])

There are also the ORDER BY, OFFSET, and LIMIT statements. Let's select all the tickers with a market cap between 1M and 50M, that have a relative volume bigger than 1.2, and that the MACD is positive

>>> (Query()
...  .select('name', 'close', 'volume', 'relative_volume_10d_calc')
...  .where(
...      Column('market_cap_basic').between(1_000_000, 50_000_000),
...      Column('relative_volume_10d_calc') > 1.2,
...      Column('MACD.macd') >= Column('MACD.signal')
...  )
...  .order_by('volume', ascending=False)
...  .offset(5)
...  .limit(15)
...  .get_scanner_data())
(393,
         ticker  name   close    volume  relative_volume_10d_calc
 0     OTC:YCRM  YCRM  0.0120  19626514                  1.887942
 1     OTC:PLPL  PLPL  0.0002  17959914                  3.026059
 2  NASDAQ:ABVC  ABVC  1.3800  16295824                  1.967505
 3     OTC:TLSS  TLSS  0.0009  15671157                  1.877976
 4     OTC:GVSI  GVSI  0.0128  14609774                  2.640792
 5     OTC:IGEX  IGEX  0.0012  14285592                  1.274861
 6     OTC:EEGI  EEGI  0.0004  12094000                  2.224749
 7   NASDAQ:GLG   GLG  0.0591   9811974                  1.990526
 8  NASDAQ:TCRT  TCRT  0.0890   8262894                  2.630553
 9     OTC:INKW  INKW  0.0027   7196404                  1.497134)

To avoid rewriting the same query again and again, you can save the query to a variable and just call get_scanner_data() again and again to get the latest data:

>>> top_50_bullish = (Query()
...  .select('name', 'close', 'volume', 'relative_volume_10d_calc')
...  .where(
...      Column('market_cap_basic').between(1_000_000, 50_000_000),
...      Column('relative_volume_10d_calc') > 1.2,
...      Column('MACD.macd') >= Column('MACD.signal')
...  )
...  .order_by('volume', ascending=False)
...  .limit(50))
>>> top_50_bullish.get_scanner_data()
(393,
          ticker   name     close     volume  relative_volume_10d_calc
 0      OTC:BEGI   BEGI  0.001050  127874055                  3.349924
 1      OTC:HCMC   HCMC  0.000100  126992562                  1.206231
 2      OTC:HEMP   HEMP  0.000150  101382713                  1.775458
 3      OTC:SONG   SONG  0.000800   92640779                  1.805721
 4      OTC:APRU   APRU  0.001575   38104499                 29.028958
 ..          ...    ...       ...        ...                       ...
 45    OTC:BSHPF  BSHPF  0.001000     525000                  1.280899
 46     OTC:GRHI   GRHI  0.033000     507266                  1.845738
 47    OTC:OMGGF  OMGGF  0.035300     505000                  4.290059
 48  NASDAQ:GBNH   GBNH  0.273000     500412                  9.076764
 49    OTC:CLRMF  CLRMF  0.032500     496049                 17.560935
 [50 rows x 5 columns])
query: tradingview_screener.query.QueryDict
url
def set_markets(self, *markets: str) -> Query:
362    def set_markets(self, *markets: str) -> Query:
363        """
364        This method allows you to select the market/s which you want to query.
365
366        By default, the screener will only scan US equities, but you can change it to scan any
367        or even multiple markets, that includes a list of 67 countries, and also the following 
368        commodities: `bonds`, `cfd`, `coin`, `crypto`, `economics2`, `euronext`, `forex`,
369        `futures`, `options`.
370
371        You may choose any value from `tradingview_screener.constants.MARKETS`.
372
373        Examples:
374
375        By default, the screener will search the `america` market
376        >>> default_columns = ['close', 'market', 'country', 'currency']
377        >>> Query().select(*default_columns).get_scanner_data()
378        (17898,
379                  ticker     close   market        country currency
380         0      AMEX:SPY  419.9900  america  United States      USD
381         1   NASDAQ:TSLA  201.7201  america  United States      USD
382         2   NASDAQ:NVDA  416.3800  america  United States      USD
383         3    NASDAQ:AMD  106.4499  america  United States      USD
384         4    NASDAQ:QQQ  353.4000  america  United States      USD
385         ..          ...       ...      ...            ...      ...
386         45  NASDAQ:ADBE  538.0000  america  United States      USD
387         46      NYSE:BA  188.9000  america  United States      USD
388         47  NASDAQ:SBUX   90.9100  america  United States      USD
389         48     NYSE:HUM  500.6350  america  United States      USD
390         49     NYSE:CAT  227.3400  america  United States      USD
391         [50 rows x 5 columns])
392
393        But you can change it (note the difference between `market` and `country`)
394        >>> (Query()
395        ...  .select(*default_columns)
396        ...  .set_markets('italy')
397        ...  .get_scanner_data())
398        (2346,
399                ticker    close market      country currency
400         0     MIL:UCG  23.9150  italy        Italy      EUR
401         1     MIL:ISP   2.4910  italy        Italy      EUR
402         2   MIL:STLAM  17.9420  italy  Netherlands      EUR
403         3    MIL:ENEL   6.0330  italy        Italy      EUR
404         4     MIL:ENI  15.4800  italy        Italy      EUR
405         ..        ...      ...    ...          ...      ...
406         45    MIL:UNI   5.1440  italy        Italy      EUR
407         46   MIL:3OIS   0.4311  italy      Ireland      EUR
408         47   MIL:3SIL  35.2300  italy      Ireland      EUR
409         48   MIL:IWDE  69.1300  italy      Ireland      EUR
410         49   MIL:QQQS  19.2840  italy      Ireland      EUR
411         [50 rows x 5 columns])
412
413        You can also select multiple markets
414        >>> (Query()
415        ...  .select(*default_columns)
416        ...  .set_markets('america', 'israel', 'hongkong', 'switzerland')
417        ...  .get_scanner_data())
418        (23964,
419                   ticker      close    market        country currency
420         0       AMEX:SPY   420.1617   america  United States      USD
421         1    NASDAQ:TSLA   201.2000   america  United States      USD
422         2    NASDAQ:NVDA   416.7825   america  United States      USD
423         3     NASDAQ:AMD   106.6600   america  United States      USD
424         4     NASDAQ:QQQ   353.7985   america  United States      USD
425         ..           ...        ...       ...            ...      ...
426         45  NASDAQ:GOOGL   124.9200   america  United States      USD
427         46     HKEX:1211   233.2000  hongkong          China      HKD
428         47     TASE:ALHE  1995.0000    israel         Israel      ILA
429         48      AMEX:BIL    91.4398   america  United States      USD
430         49   NASDAQ:GOOG   126.1500   america  United States      USD
431         [50 rows x 5 columns])
432
433        You may also select different financial instruments
434        >>> (Query()
435        ...  .select('close', 'market')
436        ...  .set_markets('cfd', 'crypto', 'futures', 'options')
437        ...  .get_scanner_data())
438        (118076,
439                                 ticker  ...   market
440         0          UNISWAP3ETH:WETHVGT  ...   crypto
441         1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
442         2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
443         3          UNISWAP3ETH:USDTBRD  ...   crypto
444         4         UNISWAP3ETH:WBTCAUSD  ...   crypto
445         ..                         ...  ...      ...
446         45               NSE:IDEAF2024  ...  futures
447         46         NSE:INDUSTOWERX2023  ...  futures
448         47            NSE:INDUSTOWER1!  ...  futures
449         48                  BIST:XU100  ...      cfd
450         49              BYBIT:BTCUSD.P  ...   crypto
451         [50 rows x 3 columns])
452
453        To select all the avaialble markets you can do this trick
454        >>> from tradingview_screener.constants import MARKETS
455        >>> len(MARKETS)
456        76
457        >>> (Query()
458        ...  .select('close', 'market')
459        ...  .set_markets(*MARKETS)
460        ...  .get_scanner_data())  # notice how many records we find: over 240k
461        (241514,
462                                 ticker  ...   market
463         0          UNISWAP3ETH:WETHVGT  ...   crypto
464         1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
465         2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
466         3          UNISWAP3ETH:USDTBRD  ...   crypto
467         4         UNISWAP3ETH:WBTCAUSD  ...   crypto
468         ..                         ...  ...      ...
469         45               NSE:IDEAF2024  ...  futures
470         46            NSE:INDUSTOWER1!  ...  futures
471         47         NSE:INDUSTOWERX2023  ...  futures
472         48                  BIST:XU100  ...      cfd
473         49              BYBIT:BTCUSD.P  ...   crypto
474
475         [50 rows x 3 columns])
476
477        :param markets: one or more markets from `tradingview_screener.constants.MARKETS`
478        :return: Self
479        """
480        if len(markets) == 1:
481            market = markets[0]
482            assert market in MARKETS
483
484            self.url = URL.format(market=market)
485            self.query['markets'] = [market]
486
487        elif len(markets) >= 1:
488            for m in markets:
489                assert m in MARKETS
490
491            self.url = URL.format(market='global')
492            self.query['markets'] = list(markets)
493
494        return self

This method allows you to select the market/s which you want to query.

By default, the screener will only scan US equities, but you can change it to scan any or even multiple markets, that includes a list of 67 countries, and also the following commodities: bonds, cfd, coin, crypto, economics2, euronext, forex, futures, options.

You may choose any value from tradingview_screener.constants.MARKETS.

Examples:

By default, the screener will search the america market

>>> default_columns = ['close', 'market', 'country', 'currency']
>>> Query().select(*default_columns).get_scanner_data()
(17898,
          ticker     close   market        country currency
 0      AMEX:SPY  419.9900  america  United States      USD
 1   NASDAQ:TSLA  201.7201  america  United States      USD
 2   NASDAQ:NVDA  416.3800  america  United States      USD
 3    NASDAQ:AMD  106.4499  america  United States      USD
 4    NASDAQ:QQQ  353.4000  america  United States      USD
 ..          ...       ...      ...            ...      ...
 45  NASDAQ:ADBE  538.0000  america  United States      USD
 46      NYSE:BA  188.9000  america  United States      USD
 47  NASDAQ:SBUX   90.9100  america  United States      USD
 48     NYSE:HUM  500.6350  america  United States      USD
 49     NYSE:CAT  227.3400  america  United States      USD
 [50 rows x 5 columns])

But you can change it (note the difference between market and country)

>>> (Query()
...  .select(*default_columns)
...  .set_markets('italy')
...  .get_scanner_data())
(2346,
        ticker    close market      country currency
 0     MIL:UCG  23.9150  italy        Italy      EUR
 1     MIL:ISP   2.4910  italy        Italy      EUR
 2   MIL:STLAM  17.9420  italy  Netherlands      EUR
 3    MIL:ENEL   6.0330  italy        Italy      EUR
 4     MIL:ENI  15.4800  italy        Italy      EUR
 ..        ...      ...    ...          ...      ...
 45    MIL:UNI   5.1440  italy        Italy      EUR
 46   MIL:3OIS   0.4311  italy      Ireland      EUR
 47   MIL:3SIL  35.2300  italy      Ireland      EUR
 48   MIL:IWDE  69.1300  italy      Ireland      EUR
 49   MIL:QQQS  19.2840  italy      Ireland      EUR
 [50 rows x 5 columns])

You can also select multiple markets

>>> (Query()
...  .select(*default_columns)
...  .set_markets('america', 'israel', 'hongkong', 'switzerland')
...  .get_scanner_data())
(23964,
           ticker      close    market        country currency
 0       AMEX:SPY   420.1617   america  United States      USD
 1    NASDAQ:TSLA   201.2000   america  United States      USD
 2    NASDAQ:NVDA   416.7825   america  United States      USD
 3     NASDAQ:AMD   106.6600   america  United States      USD
 4     NASDAQ:QQQ   353.7985   america  United States      USD
 ..           ...        ...       ...            ...      ...
 45  NASDAQ:GOOGL   124.9200   america  United States      USD
 46     HKEX:1211   233.2000  hongkong          China      HKD
 47     TASE:ALHE  1995.0000    israel         Israel      ILA
 48      AMEX:BIL    91.4398   america  United States      USD
 49   NASDAQ:GOOG   126.1500   america  United States      USD
 [50 rows x 5 columns])

You may also select different financial instruments

>>> (Query()
...  .select('close', 'market')
...  .set_markets('cfd', 'crypto', 'futures', 'options')
...  .get_scanner_data())
(118076,
                         ticker  ...   market
 0          UNISWAP3ETH:WETHVGT  ...   crypto
 1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
 2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
 3          UNISWAP3ETH:USDTBRD  ...   crypto
 4         UNISWAP3ETH:WBTCAUSD  ...   crypto
 ..                         ...  ...      ...
 45               NSE:IDEAF2024  ...  futures
 46         NSE:INDUSTOWERX2023  ...  futures
 47            NSE:INDUSTOWER1!  ...  futures
 48                  BIST:XU100  ...      cfd
 49              BYBIT:BTCUSD.P  ...   crypto
 [50 rows x 3 columns])

To select all the avaialble markets you can do this trick

>>> from tradingview_screener.constants import MARKETS
>>> len(MARKETS)
76
>>> (Query()
...  .select('close', 'market')
...  .set_markets(*MARKETS)
...  .get_scanner_data())  # notice how many records we find: over 240k
(241514,
                         ticker  ...   market
 0          UNISWAP3ETH:WETHVGT  ...   crypto
 1   UNISWAP3POLYGON:BONKWMATIC  ...   crypto
 2   UNISWAP3ARBITRUM:WETHTROVE  ...   crypto
 3          UNISWAP3ETH:USDTBRD  ...   crypto
 4         UNISWAP3ETH:WBTCAUSD  ...   crypto
 ..                         ...  ...      ...
 45               NSE:IDEAF2024  ...  futures
 46            NSE:INDUSTOWER1!  ...  futures
 47         NSE:INDUSTOWERX2023  ...  futures
 48                  BIST:XU100  ...      cfd
 49              BYBIT:BTCUSD.P  ...   crypto

[50 rows x 3 columns])

Parameters
Returns

Self

def set_tickers(self, *tickers: str) -> Query:
496    def set_tickers(self, *tickers: str) -> Query:
497        """
498        Set the tickers you wish to receive information on.
499
500        Examples:
501
502        >>> Query().limit(5).get_scanner_data()
503        (17879,
504                 ticker  name   close     volume  market_cap_basic
505         0  NASDAQ:TSLA  TSLA  248.50  118559595      7.887376e+11
506         1     AMEX:SPY   SPY  445.52   62066984               NaN
507         2  NASDAQ:NVDA  NVDA  455.72   47389801      1.125628e+12
508         3   NASDAQ:QQQ   QQQ  372.58   35846281               NaN
509         4  NASDAQ:AAPL  AAPL  178.18   65600673      2.785707e+12)
510
511        >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd')
512        >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data()
513        (2,
514                 ticker  name   market   close     volume        VWAP  MACD.macd
515         0  NASDAQ:TSLA  TSLA  america  248.50  118559595  250.563333   0.730376
516         1  NASDAQ:NVDA  NVDA  america  455.72   47389801  458.163333   7.927189)
517
518        >>> q.set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX').get_scanner_data()
519        (4,
520              ticker  name   market     close    volume          VWAP    MACD.macd
521         0  HOSE:VIX   VIX  vietnam  19800.00  26292400  19883.333333  1291.359459
522         1  AMEX:SPY   SPY  america    445.52  62066984    445.720000     0.484263
523         2  NYSE:GME   GME  america     17.71   4693902     17.853333    -0.660342
524         3  MIL:RACE  RACE    italy    279.30    246547    279.033327    -1.398701)
525
526        :param tickers: One or more tickers, syntax: `exchange:symbol`
527        :return: Self
528        """
529        # no need to select the market if we specify the symbol we want
530        # noinspection PyTypedDict
531        self.query.pop('markets', None)
532
533        self.query['symbols'] = {'tickers': list(tickers)}
534        self.url = 'https://scanner.tradingview.com/global/scan'
535        return self

Set the tickers you wish to receive information on.

Examples:

>>> Query().limit(5).get_scanner_data()
(17879,
         ticker  name   close     volume  market_cap_basic
 0  NASDAQ:TSLA  TSLA  248.50  118559595      7.887376e+11
 1     AMEX:SPY   SPY  445.52   62066984               NaN
 2  NASDAQ:NVDA  NVDA  455.72   47389801      1.125628e+12
 3   NASDAQ:QQQ   QQQ  372.58   35846281               NaN
 4  NASDAQ:AAPL  AAPL  178.18   65600673      2.785707e+12)
>>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd')
>>> q.set_tickers('NASDAQ:TSLA').get_scanner_data()
(2,
         ticker  name   market   close     volume        VWAP  MACD.macd
 0  NASDAQ:TSLA  TSLA  america  248.50  118559595  250.563333   0.730376
 1  NASDAQ:NVDA  NVDA  america  455.72   47389801  458.163333   7.927189)
>>> q.set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX').get_scanner_data()
(4,
      ticker  name   market     close    volume          VWAP    MACD.macd
 0  HOSE:VIX   VIX  vietnam  19800.00  26292400  19883.333333  1291.359459
 1  AMEX:SPY   SPY  america    445.52  62066984    445.720000     0.484263
 2  NYSE:GME   GME  america     17.71   4693902     17.853333    -0.660342
 3  MIL:RACE  RACE    italy    279.30    246547    279.033327    -1.398701)
Parameters
  • **tickers: One or more tickers, syntax: exchange**: symbol
Returns

Self

def select( self, *columns: Column | str) -> Query:
537    def select(self, *columns: Column | str) -> Query:
538        self.query['columns'] = [
539            col.name if isinstance(col, Column) else Column(col).name for col in columns
540        ]
541        return self
def where( self, *expressions: tradingview_screener.query.FilterOperationDict) -> Query:
543    def where(self, *expressions: FilterOperationDict) -> Query:
544        self.query['filter'] = list(expressions)  # convert tuple[dict] -> list[dict]
545        return self
def order_by( self, column: Column | str, ascending: bool = True) -> Query:
547    def order_by(self, column: Column | str, ascending: bool = True) -> Query:
548        column = column.name if isinstance(column, Column) else Column(column).name
549        sort_order = 'asc' if ascending else 'desc'
550        # noinspection PyTypeChecker
551        self.query['sort'] = SortByDict(sortBy=column, sortOrder=sort_order)
552        return self
def offset(self, offset: int) -> Query:
554    def offset(self, offset: int) -> Query:
555        self.query['range'][0] = offset
556        return self
def limit(self, limit: int) -> Query:
558    def limit(self, limit: int) -> Query:
559        self.query['range'][1] = limit
560        return self
def get_scanner_data(self, **kwargs) -> tuple[int, pandas.core.frame.DataFrame]:
565    def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]:
566        """
567        Perform a POST web-request and return the data from the API as a DataFrame.
568
569        Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`,
570        this can be very useful if you want to pass your own headers/cookies.
571
572        (if you have paid for a live data add-on with TradingView, you want to pass your own
573        headers and cookies to access that real-time data)
574
575        :param kwargs: kwargs to pass to `requests.post()`
576        :return: a tuple consisting of: (total_count, dataframe)
577        """
578        kwargs.setdefault('headers', HEADERS)
579        kwargs.setdefault('timeout', 20)
580        r = requests.post(self.url, json=self.query, **kwargs)
581
582        if r.status_code >= 400:
583            # add the body to the error message for debugging purposes
584            r.reason += f'\n Body: {r.text}\n'
585            r.raise_for_status()
586
587        json_obj = r.json()
588        rows_count = json_obj['totalCount']
589        data = json_obj['data']
590
591        df = pd.DataFrame(
592            data=([row['s'], *row['d']] for row in data),
593            columns=['ticker', *self.query.get('columns', ())],
594        )
595        return rows_count, df

Perform a POST web-request and return the data from the API as a DataFrame.

Note that you can pass extra keyword-arguments that will be forwarded to requests.post(), this can be very useful if you want to pass your own headers/cookies.

(if you have paid for a live data add-on with TradingView, you want to pass your own headers and cookies to access that real-time data)

Parameters
  • kwargs: kwargs to pass to requests.post()
Returns

a tuple consisting of: (total_count, dataframe)

def copy(self) -> Query:
597    def copy(self) -> Query:
598        new = Query()
599        new.query = self.query.copy()
600        return new
class Column:
 54class Column:
 55    """
 56    A Column object represents a field in the tradingview stock screener,
 57    and it's used in SELECT queries and WHERE queries with the `Query` object.
 58
 59    A `Column` supports all the comparison operations:
 60    `<`, `<=`, `>`, `>=`, `==`, `!=`, and also other methods like `between()`, `isin()`, etc.
 61
 62    Examples:
 63
 64    Some of the operations that you can do with `Column` objects:
 65    >>> Column('close') >= 2.5
 66    >>> Column('close').between(2.5, 15)
 67    >>> Column('high') > Column('VWAP')
 68    >>> Column('close').between(Column('EMA5'), Column('EMA20')
 69    >>> Column('type').isin(['stock', 'fund'])
 70    >>> Column('description').like('apple')  # the same as `description LIKE '%apple%'`
 71    """
 72
 73    def __init__(self, name: str) -> None:
 74        """
 75        Create a column object from a given column name
 76
 77        :param name: string, should be either a key or a value from the `COLUMNS` dictionary
 78        """
 79        # if `name` is a dictionary key: get its value. otherwise make sure that it's a
 80        # dictionary value.
 81        self.name = COLUMNS.get(name, name)
 82
 83    # disable this method and do the column/field validation through the server
 84    # @classmethod
 85    # def from_unknown_name(cls, name: str) -> Column:
 86    #     """
 87    #     Create a column object from a column name that isn't in the `COLUMNS` dictionary
 88    #
 89    #     :param name: string, column name
 90    #     :return: Column
 91    #     """
 92    #     # close is just a temporary column, so it won't raise an error at `__init__`
 93    #     column = cls(name='close')
 94    #     column.name = name
 95    #     return column
 96
 97    @staticmethod
 98    def _extract_value(obj) -> ...:
 99        if isinstance(obj, Column):
100            return obj.name
101        return obj
102
103    def __gt__(self, other) -> FilterOperationDict:
104        return FilterOperationDict(
105            left=self.name, operation='greater', right=self._extract_value(other)
106        )
107
108    def __ge__(self, other) -> FilterOperationDict:
109        return FilterOperationDict(
110            left=self.name, operation='egreater', right=self._extract_value(other)
111        )
112
113    def __lt__(self, other) -> FilterOperationDict:
114        return FilterOperationDict(
115            left=self.name, operation='less', right=self._extract_value(other)
116        )
117
118    def __le__(self, other) -> FilterOperationDict:
119        return FilterOperationDict(
120            left=self.name, operation='eless', right=self._extract_value(other)
121        )
122
123    def __eq__(self, other) -> FilterOperationDict:
124        return FilterOperationDict(
125            left=self.name, operation='equal', right=self._extract_value(other)
126        )
127
128    def __ne__(self, other) -> FilterOperationDict:
129        return FilterOperationDict(
130            left=self.name, operation='nequal', right=self._extract_value(other)
131        )
132
133    def crosses(self, other) -> FilterOperationDict:
134        return FilterOperationDict(
135            left=self.name, operation='crosses', right=self._extract_value(other)
136        )
137
138    def crosses_above(self, other) -> FilterOperationDict:
139        return FilterOperationDict(
140            left=self.name, operation='crosses_above', right=self._extract_value(other)
141        )
142
143    def crosses_below(self, other) -> FilterOperationDict:
144        return FilterOperationDict(
145            left=self.name, operation='crosses_below', right=self._extract_value(other)
146        )
147
148    def between(self, left, right) -> FilterOperationDict:
149        return FilterOperationDict(
150            left=self.name,
151            operation='in_range',
152            right=[self._extract_value(left), self._extract_value(right)],
153        )
154
155    def not_between(self, left, right) -> FilterOperationDict:
156        return FilterOperationDict(
157            left=self.name,
158            operation='not_in_range',
159            right=[self._extract_value(left), self._extract_value(right)],
160        )
161
162    def isin(self, values) -> FilterOperationDict:
163        return FilterOperationDict(left=self.name, operation='in_range', right=list(values))
164
165    def like(self, other) -> FilterOperationDict:
166        return FilterOperationDict(
167            left=self.name, operation='match', right=self._extract_value(other)
168        )
169
170    def __repr__(self) -> str:
171        return f'< Column({self.name!r}) >'

A Column object represents a field in the tradingview stock screener, and it's used in SELECT queries and WHERE queries with the Query object.

A Column supports all the comparison operations: <, <=, >, >=, ==, !=, and also other methods like between(), isin(), etc.

Examples:

Some of the operations that you can do with Column objects:

>>> Column('close') >= 2.5
>>> Column('close').between(2.5, 15)
>>> Column('high') > Column('VWAP')
>>> Column('close').between(Column('EMA5'), Column('EMA20')
>>> Column('type').isin(['stock', 'fund'])
>>> Column('description').like('apple')  # the same as `description LIKE '%apple%'`
Column(name: str)
73    def __init__(self, name: str) -> None:
74        """
75        Create a column object from a given column name
76
77        :param name: string, should be either a key or a value from the `COLUMNS` dictionary
78        """
79        # if `name` is a dictionary key: get its value. otherwise make sure that it's a
80        # dictionary value.
81        self.name = COLUMNS.get(name, name)

Create a column object from a given column name

Parameters
  • name: string, should be either a key or a value from the COLUMNS dictionary
name
def crosses(self, other) -> tradingview_screener.query.FilterOperationDict:
133    def crosses(self, other) -> FilterOperationDict:
134        return FilterOperationDict(
135            left=self.name, operation='crosses', right=self._extract_value(other)
136        )
def crosses_above(self, other) -> tradingview_screener.query.FilterOperationDict:
138    def crosses_above(self, other) -> FilterOperationDict:
139        return FilterOperationDict(
140            left=self.name, operation='crosses_above', right=self._extract_value(other)
141        )
def crosses_below(self, other) -> tradingview_screener.query.FilterOperationDict:
143    def crosses_below(self, other) -> FilterOperationDict:
144        return FilterOperationDict(
145            left=self.name, operation='crosses_below', right=self._extract_value(other)
146        )
def between(self, left, right) -> tradingview_screener.query.FilterOperationDict:
148    def between(self, left, right) -> FilterOperationDict:
149        return FilterOperationDict(
150            left=self.name,
151            operation='in_range',
152            right=[self._extract_value(left), self._extract_value(right)],
153        )
def not_between(self, left, right) -> tradingview_screener.query.FilterOperationDict:
155    def not_between(self, left, right) -> FilterOperationDict:
156        return FilterOperationDict(
157            left=self.name,
158            operation='not_in_range',
159            right=[self._extract_value(left), self._extract_value(right)],
160        )
def isin(self, values) -> tradingview_screener.query.FilterOperationDict:
162    def isin(self, values) -> FilterOperationDict:
163        return FilterOperationDict(left=self.name, operation='in_range', right=list(values))
def like(self, other) -> tradingview_screener.query.FilterOperationDict:
165    def like(self, other) -> FilterOperationDict:
166        return FilterOperationDict(
167            left=self.name, operation='match', right=self._extract_value(other)
168        )