tradingview_screener.query
1from __future__ import annotations 2 3__all__ = ['And', 'Or', 'Query'] 4 5import pprint 6from typing import TYPE_CHECKING 7 8import requests 9 10from tradingview_screener.column import Column 11 12if TYPE_CHECKING: 13 import pandas as pd 14 from typing import Literal, Any 15 from typing_extensions import Self 16 from tradingview_screener.models import ( 17 QueryDict, 18 SortByDict, 19 ScreenerDict, 20 FilterOperationDict, 21 OperationDict, 22 ) 23 24 25DEFAULT_RANGE = [0, 50] 26URL = 'https://scanner.tradingview.com/{market}/scan' 27HEADERS = { 28 'authority': 'scanner.tradingview.com', 29 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"', 30 'accept': 'text/plain, */*; q=0.01', 31 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 32 'sec-ch-ua-mobile': '?0', 33 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' 34 'Chrome/98.0.4758.102 Safari/537.36', 35 'sec-ch-ua-platform': '"Windows"', 36 'origin': 'https://www.tradingview.com', 37 'sec-fetch-site': 'same-site', 38 'sec-fetch-mode': 'cors', 39 'sec-fetch-dest': 'empty', 40 'referer': 'https://www.tradingview.com/', 41 'accept-language': 'en-US,en;q=0.9,it;q=0.8', 42} 43 44 45def _impl_and_or_chaining( 46 expressions: tuple[FilterOperationDict | OperationDict, ...], operator: Literal['and', 'or'] 47) -> OperationDict: 48 # we want to wrap all the `FilterOperationDict` expressions with `{'expression': expr}`, 49 # to know if it's an instance of `FilterOperationDict` we simply check if it has the `left` key, 50 # which no other TypedDict has. 51 lst = [] 52 for expr in expressions: 53 if 'left' in expr: # if isinstance(expr, FilterOperationDict): ... 54 lst.append({'expression': expr}) 55 else: 56 lst.append(expr) 57 return {'operation': {'operator': operator, 'operands': lst}} 58 59 60def And(*expressions: FilterOperationDict | OperationDict) -> OperationDict: 61 return _impl_and_or_chaining(expressions, operator='and') 62 63 64def Or(*expressions: FilterOperationDict | OperationDict) -> OperationDict: 65 return _impl_and_or_chaining(expressions, operator='or') 66 67 68class Query: 69 """ 70 This class allows you to perform SQL-like queries on the tradingview stock-screener. 71 72 The `Query` object reppresents a query that can be made to the official tradingview API, and it 73 stores all the data as JSON internally. 74 75 Examples: 76 77 To perform a simple query all you have to do is: 78 >>> from tradingview_screener import Query 79 >>> Query().get_scanner_data() 80 (18060, 81 ticker name close volume market_cap_basic 82 0 AMEX:SPY SPY 410.68 107367671 NaN 83 1 NASDAQ:QQQ QQQ 345.31 63475390 NaN 84 2 NASDAQ:TSLA TSLA 207.30 94879471 6.589904e+11 85 3 NASDAQ:NVDA NVDA 405.00 41677185 1.000350e+12 86 4 NASDAQ:AMZN AMZN 127.74 125309313 1.310658e+12 87 .. ... ... ... ... ... 88 45 NYSE:UNH UNH 524.66 2585616 4.859952e+11 89 46 NASDAQ:DXCM DXCM 89.29 14954605 3.449933e+10 90 47 NYSE:MA MA 364.08 3624883 3.429080e+11 91 48 NYSE:ABBV ABBV 138.93 9427212 2.452179e+11 92 49 AMEX:XLK XLK 161.12 8115780 NaN 93 [50 rows x 5 columns]) 94 95 The `get_scanner_data()` method will return a tuple with the first element being the number of 96 records that were found (like a `COUNT(*)`), and the second element contains the data that was 97 found as a DataFrame. 98 99 --- 100 101 By default, the `Query` will select the columns: `name`, `close`, `volume`, `market_cap_basic`, 102 but you override that 103 >>> (Query() 104 ... .select('open', 'high', 'low', 'VWAP', 'MACD.macd', 'RSI', 'Price to Earnings Ratio (TTM)') 105 ... .get_scanner_data()) 106 (18060, 107 ticker open high ... MACD.macd RSI price_earnings_ttm 108 0 AMEX:SPY 414.19 414.600 ... -5.397135 29.113396 NaN 109 1 NASDAQ:QQQ 346.43 348.840 ... -4.321482 34.335449 NaN 110 2 NASDAQ:TSLA 210.60 212.410 ... -12.224250 28.777229 66.752536 111 3 NASDAQ:NVDA 411.30 412.060 ... -8.738986 37.845668 97.835540 112 4 NASDAQ:AMZN 126.20 130.020 ... -2.025378 48.665666 66.697995 113 .. ... ... ... ... ... ... ... 114 45 NYSE:UNH 525.99 527.740 ... 6.448129 54.614775 22.770713 115 46 NASDAQ:DXCM 92.73 92.988 ... -2.376942 52.908093 98.914368 116 47 NYSE:MA 366.49 368.285 ... -7.496065 22.614078 31.711800 117 48 NYSE:ABBV 138.77 143.000 ... -1.708497 27.117232 37.960054 118 49 AMEX:XLK 161.17 162.750 ... -1.520828 36.868658 NaN 119 [50 rows x 8 columns]) 120 121 You can find the 250+ columns available in `tradingview_screener.constants.COLUMNS`. 122 123 Now let's do some queries using the `WHERE` statement, select all the stocks that the `close` is 124 bigger or equal than 350 125 >>> (Query() 126 ... .select('close', 'volume', '52 Week High') 127 ... .where(Column('close') >= 350) 128 ... .get_scanner_data()) 129 (159, 130 ticker close volume price_52_week_high 131 0 AMEX:SPY 410.68 107367671 459.44 132 1 NASDAQ:NVDA 405.00 41677185 502.66 133 2 NYSE:BRK.A 503375.05 7910 566569.97 134 3 AMEX:IVV 412.55 5604525 461.88 135 4 AMEX:VOO 377.32 5638752 422.15 136 .. ... ... ... ... 137 45 NASDAQ:EQIX 710.39 338549 821.63 138 46 NYSE:MCK 448.03 527406 465.90 139 47 NYSE:MTD 976.25 241733 1615.97 140 48 NASDAQ:CTAS 496.41 464631 525.37 141 49 NASDAQ:ROP 475.57 450141 508.90 142 [50 rows x 4 columns]) 143 144 You can even use other columns in these kind of operations 145 >>> (Query() 146 ... .select('close', 'VWAP') 147 ... .where(Column('close') >= Column('VWAP')) 148 ... .get_scanner_data()) 149 (9044, 150 ticker close VWAP 151 0 NASDAQ:AAPL 168.22 168.003333 152 1 NASDAQ:META 296.73 296.336667 153 2 NASDAQ:GOOGL 122.17 121.895233 154 3 NASDAQ:AMD 96.43 96.123333 155 4 NASDAQ:GOOG 123.40 123.100000 156 .. ... ... ... 157 45 NYSE:GD 238.25 238.043333 158 46 NYSE:GOLD 16.33 16.196667 159 47 AMEX:SLV 21.18 21.041667 160 48 AMEX:VXX 27.08 26.553333 161 49 NYSE:SLB 55.83 55.676667 162 [50 rows x 3 columns]) 163 164 Let's find all the stocks that the price is between the EMA 5 and 20, and the type is a stock 165 or fund 166 >>> (Query() 167 ... .select('close', 'volume', 'EMA5', 'EMA20', 'type') 168 ... .where( 169 ... Column('close').between(Column('EMA5'), Column('EMA20')), 170 ... Column('type').isin(['stock', 'fund']) 171 ... ) 172 ... .get_scanner_data()) 173 (1730, 174 ticker close volume EMA5 EMA20 type 175 0 NASDAQ:AMZN 127.74 125309313 125.033517 127.795142 stock 176 1 AMEX:HYG 72.36 35621800 72.340776 72.671058 fund 177 2 AMEX:LQD 99.61 21362859 99.554272 100.346388 fund 178 3 NASDAQ:IEF 90.08 11628236 89.856804 90.391503 fund 179 4 NYSE:SYK 261.91 3783608 261.775130 266.343290 stock 180 .. ... ... ... ... ... ... 181 45 NYSE:EMN 72.58 1562328 71.088034 72.835394 stock 182 46 NYSE:KIM 16.87 6609420 16.858920 17.096582 fund 183 47 NASDAQ:COLM 71.34 1516675 71.073116 71.658864 stock 184 48 NYSE:AOS 67.81 1586796 67.561619 67.903168 stock 185 49 NASDAQ:IGIB 47.81 2073538 47.761338 48.026795 fund 186 [50 rows x 6 columns]) 187 188 There are also the `ORDER BY`, `OFFSET`, and `LIMIT` statements. 189 Let's select all the tickers with a market cap between 1M and 50M, that have a relative volume 190 bigger than 1.2, and that the MACD is positive 191 >>> (Query() 192 ... .select('name', 'close', 'volume', 'relative_volume_10d_calc') 193 ... .where( 194 ... Column('market_cap_basic').between(1_000_000, 50_000_000), 195 ... Column('relative_volume_10d_calc') > 1.2, 196 ... Column('MACD.macd') >= Column('MACD.signal') 197 ... ) 198 ... .order_by('volume', ascending=False) 199 ... .offset(5) 200 ... .limit(15) 201 ... .get_scanner_data()) 202 (393, 203 ticker name close volume relative_volume_10d_calc 204 0 OTC:YCRM YCRM 0.0120 19626514 1.887942 205 1 OTC:PLPL PLPL 0.0002 17959914 3.026059 206 2 NASDAQ:ABVC ABVC 1.3800 16295824 1.967505 207 3 OTC:TLSS TLSS 0.0009 15671157 1.877976 208 4 OTC:GVSI GVSI 0.0128 14609774 2.640792 209 5 OTC:IGEX IGEX 0.0012 14285592 1.274861 210 6 OTC:EEGI EEGI 0.0004 12094000 2.224749 211 7 NASDAQ:GLG GLG 0.0591 9811974 1.990526 212 8 NASDAQ:TCRT TCRT 0.0890 8262894 2.630553 213 9 OTC:INKW INKW 0.0027 7196404 1.497134) 214 215 To avoid rewriting the same query again and again, you can save the query to a variable and 216 just call `get_scanner_data()` again and again to get the latest data: 217 >>> top_50_bullish = (Query() 218 ... .select('name', 'close', 'volume', 'relative_volume_10d_calc') 219 ... .where( 220 ... Column('market_cap_basic').between(1_000_000, 50_000_000), 221 ... Column('relative_volume_10d_calc') > 1.2, 222 ... Column('MACD.macd') >= Column('MACD.signal') 223 ... ) 224 ... .order_by('volume', ascending=False) 225 ... .limit(50)) 226 >>> top_50_bullish.get_scanner_data() 227 (393, 228 ticker name close volume relative_volume_10d_calc 229 0 OTC:BEGI BEGI 0.001050 127874055 3.349924 230 1 OTC:HCMC HCMC 0.000100 126992562 1.206231 231 2 OTC:HEMP HEMP 0.000150 101382713 1.775458 232 3 OTC:SONG SONG 0.000800 92640779 1.805721 233 4 OTC:APRU APRU 0.001575 38104499 29.028958 234 .. ... ... ... ... ... 235 45 OTC:BSHPF BSHPF 0.001000 525000 1.280899 236 46 OTC:GRHI GRHI 0.033000 507266 1.845738 237 47 OTC:OMGGF OMGGF 0.035300 505000 4.290059 238 48 NASDAQ:GBNH GBNH 0.273000 500412 9.076764 239 49 OTC:CLRMF CLRMF 0.032500 496049 17.560935 240 [50 rows x 5 columns]) 241 """ 242 243 def __init__(self) -> None: 244 # noinspection PyTypeChecker 245 self.query: QueryDict = { 246 'markets': ['america'], 247 'symbols': {'query': {'types': []}, 'tickers': []}, 248 'options': {'lang': 'en'}, 249 'columns': ['name', 'close', 'volume', 'market_cap_basic'], 250 # 'filter': ..., 251 'sort': {'sortBy': 'Value.Traded', 'sortOrder': 'desc'}, 252 'range': DEFAULT_RANGE.copy(), 253 } 254 self.url = 'https://scanner.tradingview.com/america/scan' 255 256 def select(self, *columns: Column | str) -> Self: 257 self.query['columns'] = [ 258 col.name if isinstance(col, Column) else Column(col).name for col in columns 259 ] 260 return self 261 262 def where(self, *expressions: FilterOperationDict) -> Self: 263 """ 264 Filter screener (expressions are joined with the AND operator) 265 """ 266 self.query['filter'] = list(expressions) # convert tuple[dict] -> list[dict] 267 return self 268 269 def where2(self, operation: OperationDict) -> Self: 270 """ 271 Filter screener using AND/OR operators (nested expressions also allowed) 272 273 Rules: 274 1. The argument passed to `where2()` **must** be wrapped in `And()` or `Or()`. 275 2. `And()` and `Or()` can accept one or more conditions as arguments. 276 3. Conditions can be simple (e.g., `Column('field') == 'value'`) or complex, allowing nesting of `And()` and `Or()` to create intricate logical filters. 277 4. Unlike the `where()` method, which only supports chaining conditions with the `AND` operator, `where2()` allows mixing and nesting of `AND` and `OR` operators. 278 279 Examples: 280 281 1. **Combining conditions with `OR` and nested `AND`:** 282 ```python 283 Query() 284 .select('type', 'typespecs') 285 .where2( 286 Or( 287 And(Column('type') == 'stock', Column('typespecs').has(['common', 'preferred'])), 288 And(Column('type') == 'fund', Column('typespecs').has_none_of(['etf'])), 289 Column('type') == 'dr', 290 ) 291 ) 292 ``` 293 294 This query filters entities where: 295 - The `type` is `'stock'` and `typespecs` contains `'common'` or `'preferred'`, **OR** 296 - The `type` is `'fund'` and `typespecs` does not contain `'etf'`, **OR** 297 - The `type` is `'dr'`. 298 299 2. **Mixing conditions with `OR`:** 300 ```python 301 Query().where2( 302 Or( 303 And(col('type') == 'stock', col('typespecs').has(['common'])), 304 col('type') == 'fund', 305 ) 306 ) 307 ``` 308 This query filters entities where: 309 - The `type` is `'stock'` and `typespecs` contains `'common'`, **OR** 310 - The `type` is `'fund'`. 311 312 3. **Combining conditions with `AND`:** 313 ```python 314 Query() 315 .set_markets('crypto') 316 .where2( 317 And( 318 col('exchange').isin(['UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff']), 319 col('currency_id') == 'USD', 320 ) 321 ) 322 ``` 323 This query filters entities in the `'crypto'` market where: 324 - The `exchange` is one of `'UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff'`, **AND** 325 - The `currency_id` is `'USD'`. 326 """ 327 self.query['filter2'] = operation['operation'] 328 return self 329 330 def order_by( 331 self, column: Column | str, ascending: bool = True, nulls_first: bool = False 332 ) -> Self: 333 """ 334 Applies sorting to the query results based on the specified column. 335 336 Examples: 337 338 >>> Query().order_by('volume', ascending=False) # sort descending 339 >>> Query().order_by('close', ascending=True) 340 >>> Query().order_by('dividends_yield_current', ascending=False, nulls_first=False) 341 342 :param column: Either a `Column` object or a string with the column name. 343 :param ascending: Set to True for ascending order (default), or False for descending. 344 :param nulls_first: If True, places `None` values at the beginning of the results. Defaults 345 to False. 346 :return: The updated query object. 347 """ 348 dct: SortByDict = { 349 'sortBy': column.name if isinstance(column, Column) else column, 350 'sortOrder': 'asc' if ascending else 'desc', 351 'nullsFirst': nulls_first, 352 } 353 self.query['sort'] = dct 354 return self 355 356 def limit(self, limit: int) -> Self: 357 self.query.setdefault('range', DEFAULT_RANGE.copy())[1] = limit 358 return self 359 360 def offset(self, offset: int) -> Self: 361 self.query.setdefault('range', DEFAULT_RANGE.copy())[0] = offset 362 return self 363 364 def set_markets(self, *markets: str) -> Self: 365 """ 366 This method allows you to select the market/s which you want to query. 367 368 By default, the screener will only scan US equities, but you can change it to scan any 369 market or country, that includes a list of 67 countries, and also the following 370 asset classes: `bonds`, `cfd`, `coin`, `crypto`, `euronext`, `forex`, 371 `futures`, `options`. 372 373 You may choose any value from `tradingview_screener.constants.MARKETS`. 374 375 If you select multiple countries, you might want to 376 377 Examples: 378 379 By default, the screener will show results from the `america` market, but you can 380 change it (note the difference between `market` and `country`) 381 >>> columns = ['close', 'market', 'country', 'currency'] 382 >>> (Query() 383 ... .select(*columns) 384 ... .set_markets('italy') 385 ... .get_scanner_data()) 386 (2346, 387 ticker close market country currency 388 0 MIL:UCG 23.9150 italy Italy EUR 389 1 MIL:ISP 2.4910 italy Italy EUR 390 2 MIL:STLAM 17.9420 italy Netherlands EUR 391 3 MIL:ENEL 6.0330 italy Italy EUR 392 4 MIL:ENI 15.4800 italy Italy EUR 393 .. ... ... ... ... ... 394 45 MIL:UNI 5.1440 italy Italy EUR 395 46 MIL:3OIS 0.4311 italy Ireland EUR 396 47 MIL:3SIL 35.2300 italy Ireland EUR 397 48 MIL:IWDE 69.1300 italy Ireland EUR 398 49 MIL:QQQS 19.2840 italy Ireland EUR 399 [50 rows x 5 columns]) 400 401 You can also select multiple markets 402 >>> (Query() 403 ... .select(*columns) 404 ... .set_markets('america', 'israel', 'hongkong', 'switzerland') 405 ... .get_scanner_data()) 406 (23964, 407 ticker close market country currency 408 0 AMEX:SPY 420.1617 america United States USD 409 1 NASDAQ:TSLA 201.2000 america United States USD 410 2 NASDAQ:NVDA 416.7825 america United States USD 411 3 NASDAQ:AMD 106.6600 america United States USD 412 4 NASDAQ:QQQ 353.7985 america United States USD 413 .. ... ... ... ... ... 414 45 NASDAQ:GOOGL 124.9200 america United States USD 415 46 HKEX:1211 233.2000 hongkong China HKD 416 47 TASE:ALHE 1995.0000 israel Israel ILA 417 48 AMEX:BIL 91.4398 america United States USD 418 49 NASDAQ:GOOG 126.1500 america United States USD 419 [50 rows x 5 columns]) 420 421 You may also select different financial instruments 422 >>> (Query() 423 ... .select('close', 'market') 424 ... .set_markets('cfd', 'crypto', 'forex', 'futures') 425 ... .get_scanner_data()) 426 (118076, 427 ticker ... market 428 0 UNISWAP3ETH:JUSTICEUSDT ... crypto 429 1 UNISWAP3ETH:UAHGUSDT ... crypto 430 2 UNISWAP3ETH:KENDUWETH ... crypto 431 3 UNISWAP3ETH:MATICSTMATIC ... crypto 432 4 UNISWAP3ETH:WETHETHM ... crypto 433 .. ... ... ... 434 45 UNISWAP:MUSICAIWETH_1F5304.USD ... crypto 435 46 CRYPTOCAP:FIL ... cfd 436 47 CRYPTOCAP:SUI ... cfd 437 48 CRYPTOCAP:ARBI ... cfd 438 49 CRYPTOCAP:OP ... cfd 439 [50 rows x 3 columns]) 440 441 :param markets: one or more markets from `tradingview_screener.constants.MARKETS` 442 :return: Self 443 """ 444 if len(markets) == 1: 445 market = markets[0] 446 self.url = URL.format(market=market) 447 self.query['markets'] = [market] 448 else: # len(markets) == 0 or len(markets) > 1 449 self.url = URL.format(market='global') 450 self.query['markets'] = list(markets) 451 452 return self 453 454 def set_tickers(self, *tickers: str) -> Self: 455 """ 456 Set the tickers you wish to receive information on. 457 458 Note that this resets the markets and sets the URL market to `global`. 459 460 Examples: 461 462 >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd') 463 >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data() 464 (1, 465 ticker name market close volume VWAP MACD.macd 466 0 NASDAQ:TSLA TSLA america 186 3519931 185.53 2.371601) 467 468 To set tickers from multiple markets we need to update the markets that include them: 469 >>> (Query() 470 ... .set_markets('america', 'italy', 'vietnam') 471 ... .set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX') 472 ... .get_scanner_data()) 473 (4, 474 ticker name close volume market_cap_basic 475 0 HOSE:VIX VIX 16700.00 33192500 4.568961e+08 476 1 AMEX:SPY SPY 544.35 1883562 NaN 477 2 NYSE:GME GME 23.80 3116758 1.014398e+10 478 3 MIL:RACE RACE 393.30 122878 1.006221e+11) 479 480 :param tickers: One or more tickers, syntax: `exchange:symbol` 481 :return: Self 482 """ 483 self.query.setdefault('symbols', {})['tickers'] = list(tickers) 484 self.set_markets() 485 return self 486 487 def set_index(self, *indexes: str) -> Self: 488 """ 489 Scan only the equities that are in in the given index (or indexes). 490 491 Note that this resets the markets and sets the URL market to `global`. 492 493 Examples: 494 495 >>> Query().set_index('SYML:SP;SPX').get_scanner_data() 496 (503, 497 ticker name close volume market_cap_basic 498 0 NASDAQ:NVDA NVDA 1208.88 41238122 2.973644e+12 499 1 NASDAQ:AAPL AAPL 196.89 53103705 3.019127e+12 500 2 NASDAQ:TSLA TSLA 177.48 56244929 5.660185e+11 501 3 NASDAQ:AMD AMD 167.87 44795240 2.713306e+11 502 4 NASDAQ:MSFT MSFT 423.85 13621485 3.150183e+12 503 5 NASDAQ:AMZN AMZN 184.30 28021473 1.917941e+12 504 6 NASDAQ:META META 492.96 9379199 1.250410e+12 505 7 NASDAQ:GOOGL GOOGL 174.46 19660698 2.164346e+12 506 8 NASDAQ:SMCI SMCI 769.11 3444852 4.503641e+10 507 9 NASDAQ:GOOG GOOG 175.95 14716134 2.164346e+12 508 10 NASDAQ:AVGO AVGO 1406.64 1785876 6.518669e+11) 509 510 You can set multiple indices as well, like the NIFTY 50 and UK 100 Index. 511 >>> Query().set_index('SYML:NSE;NIFTY', 'SYML:TVC;UKX').get_scanner_data() 512 (150, 513 ticker name close volume market_cap_basic 514 0 NSE:INFY INFY 1533.600000 24075302 7.623654e+10 515 1 LSE:AZN AZN 12556.000000 2903032 2.489770e+11 516 2 NSE:HDFCBANK HDFCBANK 1573.350000 18356108 1.432600e+11 517 3 NSE:RELIANCE RELIANCE 2939.899900 9279348 2.381518e+11 518 4 LSE:LSEG LSEG 9432.000000 2321053 6.395329e+10 519 5 NSE:BAJFINANCE BAJFINANCE 7191.399900 2984052 5.329685e+10 520 6 LSE:BARC BARC 217.250000 96238723 4.133010e+10 521 7 NSE:SBIN SBIN 829.950010 25061284 8.869327e+10 522 8 NSE:LT LT 3532.500000 5879660 5.816100e+10 523 9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11) 524 525 You can find the full list of indices in [`constants.INDICES`](constants.html#INDICES), 526 just note that the syntax is 527 `SYML:{source};{symbol}`. 528 529 :param indexes: One or more strings representing the financial indexes to filter by 530 :return: An instance of the `Query` class with the filter applied 531 """ 532 self.query.setdefault('preset', 'index_components_market_pages') 533 self.query.setdefault('symbols', {})['symbolset'] = list(indexes) 534 # reset markets list and URL to `/global` 535 self.set_markets() 536 return self 537 538 # def set_currency(self, currency: Literal['symbol', 'market'] | str) -> Self: 539 # """ 540 # Change the currency of the screener. 541 # 542 # Note that this changes *only* the currency of the columns of type `fundamental_price`, 543 # for example: `market_cap_basic`, `net_income`, `total_debt`. Other columns like `close` and 544 # `Value.Traded` won't change, because they are of a different type. 545 # 546 # This can be particularly useful if you are querying tickers across different markets. 547 # 548 # Examples: 549 # 550 # >>> Query().set_currency('symbol') # convert to symbol currency 551 # >>> Query().set_currency('market') # convert to market currency 552 # >>> Query().set_currency('usd') # or another currency 553 # >>> Query().set_currency('jpy') 554 # >>> Query().set_currency('eur') 555 # """ 556 # # symbol currency -> self.query['price_conversion'] = {'to_symbol': True} 557 # # market currency -> self.query['price_conversion'] = {'to_symbol': False} 558 # # USD or other currency -> self.query['price_conversion'] = {'to_currency': 'usd'} 559 # if currency == 'symbol': 560 # self.query['price_conversion'] = {'to_symbol': True} 561 # elif currency == 'market': 562 # self.query['price_conversion'] = {'to_symbol': False} 563 # else: 564 # self.query['price_conversion'] = {'to_currency': currency} 565 # return self 566 567 def set_property(self, key: str, value: Any) -> Self: 568 self.query[key] = value 569 return self 570 571 def get_scanner_data_raw(self, **kwargs) -> ScreenerDict: 572 """ 573 Perform a POST web-request and return the data from the API (dictionary). 574 575 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 576 this can be very useful if you want to pass your own headers/cookies. 577 578 >>> Query().select('close', 'volume').limit(5).get_scanner_data_raw() 579 { 580 'totalCount': 17559, 581 'data': [ 582 {'s': 'NASDAQ:NVDA', 'd': [116.14, 312636630]}, 583 {'s': 'AMEX:SPY', 'd': [542.04, 52331224]}, 584 {'s': 'NASDAQ:QQQ', 'd': [462.58, 40084156]}, 585 {'s': 'NASDAQ:TSLA', 'd': [207.83, 76247251]}, 586 {'s': 'NASDAQ:SBUX', 'd': [95.9, 157211696]}, 587 ], 588 } 589 """ 590 self.query.setdefault('range', DEFAULT_RANGE.copy()) 591 592 kwargs.setdefault('headers', HEADERS) 593 kwargs.setdefault('timeout', 20) 594 r = requests.post(self.url, json=self.query, **kwargs) 595 596 if not r.ok: 597 # add the body to the error message for debugging purposes 598 r.reason += f'\n Body: {r.text}\n' 599 r.raise_for_status() 600 601 return r.json() 602 603 def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]: 604 """ 605 Perform a POST web-request and return the data from the API as a DataFrame (along with 606 the number of rows/tickers that matched your query). 607 608 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 609 this can be very useful if you want to pass your own headers/cookies. 610 611 ### Live/Delayed data 612 613 Note that to get live-data you have to authenticate, which is done by passing your cookies. 614 Have a look in the README at the "Real-Time Data Access" sections. 615 616 :param kwargs: kwargs to pass to `requests.post()` 617 :return: a tuple consisting of: (total_count, dataframe) 618 """ 619 import pandas as pd 620 621 json_obj = self.get_scanner_data_raw(**kwargs) 622 rows_count = json_obj['totalCount'] 623 data = json_obj['data'] 624 625 df = pd.DataFrame( 626 data=([row['s'], *row['d']] for row in data), 627 columns=['ticker', *self.query.get('columns', ())], # pyright: ignore [reportArgumentType] 628 ) 629 return rows_count, df 630 631 def copy(self) -> Query: 632 new = Query() 633 new.query = self.query.copy() 634 return new 635 636 def __repr__(self) -> str: 637 return f'< {pprint.pformat(self.query)}\n url={self.url!r} >' 638 639 def __eq__(self, other) -> bool: 640 return isinstance(other, Query) and self.query == other.query and self.url == other.url 641 642 643# TODO: Query should have no defaults (except limit), and a separate module should have all the 644# default screeners 645# TODO: add all presets
69class Query: 70 """ 71 This class allows you to perform SQL-like queries on the tradingview stock-screener. 72 73 The `Query` object reppresents a query that can be made to the official tradingview API, and it 74 stores all the data as JSON internally. 75 76 Examples: 77 78 To perform a simple query all you have to do is: 79 >>> from tradingview_screener import Query 80 >>> Query().get_scanner_data() 81 (18060, 82 ticker name close volume market_cap_basic 83 0 AMEX:SPY SPY 410.68 107367671 NaN 84 1 NASDAQ:QQQ QQQ 345.31 63475390 NaN 85 2 NASDAQ:TSLA TSLA 207.30 94879471 6.589904e+11 86 3 NASDAQ:NVDA NVDA 405.00 41677185 1.000350e+12 87 4 NASDAQ:AMZN AMZN 127.74 125309313 1.310658e+12 88 .. ... ... ... ... ... 89 45 NYSE:UNH UNH 524.66 2585616 4.859952e+11 90 46 NASDAQ:DXCM DXCM 89.29 14954605 3.449933e+10 91 47 NYSE:MA MA 364.08 3624883 3.429080e+11 92 48 NYSE:ABBV ABBV 138.93 9427212 2.452179e+11 93 49 AMEX:XLK XLK 161.12 8115780 NaN 94 [50 rows x 5 columns]) 95 96 The `get_scanner_data()` method will return a tuple with the first element being the number of 97 records that were found (like a `COUNT(*)`), and the second element contains the data that was 98 found as a DataFrame. 99 100 --- 101 102 By default, the `Query` will select the columns: `name`, `close`, `volume`, `market_cap_basic`, 103 but you override that 104 >>> (Query() 105 ... .select('open', 'high', 'low', 'VWAP', 'MACD.macd', 'RSI', 'Price to Earnings Ratio (TTM)') 106 ... .get_scanner_data()) 107 (18060, 108 ticker open high ... MACD.macd RSI price_earnings_ttm 109 0 AMEX:SPY 414.19 414.600 ... -5.397135 29.113396 NaN 110 1 NASDAQ:QQQ 346.43 348.840 ... -4.321482 34.335449 NaN 111 2 NASDAQ:TSLA 210.60 212.410 ... -12.224250 28.777229 66.752536 112 3 NASDAQ:NVDA 411.30 412.060 ... -8.738986 37.845668 97.835540 113 4 NASDAQ:AMZN 126.20 130.020 ... -2.025378 48.665666 66.697995 114 .. ... ... ... ... ... ... ... 115 45 NYSE:UNH 525.99 527.740 ... 6.448129 54.614775 22.770713 116 46 NASDAQ:DXCM 92.73 92.988 ... -2.376942 52.908093 98.914368 117 47 NYSE:MA 366.49 368.285 ... -7.496065 22.614078 31.711800 118 48 NYSE:ABBV 138.77 143.000 ... -1.708497 27.117232 37.960054 119 49 AMEX:XLK 161.17 162.750 ... -1.520828 36.868658 NaN 120 [50 rows x 8 columns]) 121 122 You can find the 250+ columns available in `tradingview_screener.constants.COLUMNS`. 123 124 Now let's do some queries using the `WHERE` statement, select all the stocks that the `close` is 125 bigger or equal than 350 126 >>> (Query() 127 ... .select('close', 'volume', '52 Week High') 128 ... .where(Column('close') >= 350) 129 ... .get_scanner_data()) 130 (159, 131 ticker close volume price_52_week_high 132 0 AMEX:SPY 410.68 107367671 459.44 133 1 NASDAQ:NVDA 405.00 41677185 502.66 134 2 NYSE:BRK.A 503375.05 7910 566569.97 135 3 AMEX:IVV 412.55 5604525 461.88 136 4 AMEX:VOO 377.32 5638752 422.15 137 .. ... ... ... ... 138 45 NASDAQ:EQIX 710.39 338549 821.63 139 46 NYSE:MCK 448.03 527406 465.90 140 47 NYSE:MTD 976.25 241733 1615.97 141 48 NASDAQ:CTAS 496.41 464631 525.37 142 49 NASDAQ:ROP 475.57 450141 508.90 143 [50 rows x 4 columns]) 144 145 You can even use other columns in these kind of operations 146 >>> (Query() 147 ... .select('close', 'VWAP') 148 ... .where(Column('close') >= Column('VWAP')) 149 ... .get_scanner_data()) 150 (9044, 151 ticker close VWAP 152 0 NASDAQ:AAPL 168.22 168.003333 153 1 NASDAQ:META 296.73 296.336667 154 2 NASDAQ:GOOGL 122.17 121.895233 155 3 NASDAQ:AMD 96.43 96.123333 156 4 NASDAQ:GOOG 123.40 123.100000 157 .. ... ... ... 158 45 NYSE:GD 238.25 238.043333 159 46 NYSE:GOLD 16.33 16.196667 160 47 AMEX:SLV 21.18 21.041667 161 48 AMEX:VXX 27.08 26.553333 162 49 NYSE:SLB 55.83 55.676667 163 [50 rows x 3 columns]) 164 165 Let's find all the stocks that the price is between the EMA 5 and 20, and the type is a stock 166 or fund 167 >>> (Query() 168 ... .select('close', 'volume', 'EMA5', 'EMA20', 'type') 169 ... .where( 170 ... Column('close').between(Column('EMA5'), Column('EMA20')), 171 ... Column('type').isin(['stock', 'fund']) 172 ... ) 173 ... .get_scanner_data()) 174 (1730, 175 ticker close volume EMA5 EMA20 type 176 0 NASDAQ:AMZN 127.74 125309313 125.033517 127.795142 stock 177 1 AMEX:HYG 72.36 35621800 72.340776 72.671058 fund 178 2 AMEX:LQD 99.61 21362859 99.554272 100.346388 fund 179 3 NASDAQ:IEF 90.08 11628236 89.856804 90.391503 fund 180 4 NYSE:SYK 261.91 3783608 261.775130 266.343290 stock 181 .. ... ... ... ... ... ... 182 45 NYSE:EMN 72.58 1562328 71.088034 72.835394 stock 183 46 NYSE:KIM 16.87 6609420 16.858920 17.096582 fund 184 47 NASDAQ:COLM 71.34 1516675 71.073116 71.658864 stock 185 48 NYSE:AOS 67.81 1586796 67.561619 67.903168 stock 186 49 NASDAQ:IGIB 47.81 2073538 47.761338 48.026795 fund 187 [50 rows x 6 columns]) 188 189 There are also the `ORDER BY`, `OFFSET`, and `LIMIT` statements. 190 Let's select all the tickers with a market cap between 1M and 50M, that have a relative volume 191 bigger than 1.2, and that the MACD is positive 192 >>> (Query() 193 ... .select('name', 'close', 'volume', 'relative_volume_10d_calc') 194 ... .where( 195 ... Column('market_cap_basic').between(1_000_000, 50_000_000), 196 ... Column('relative_volume_10d_calc') > 1.2, 197 ... Column('MACD.macd') >= Column('MACD.signal') 198 ... ) 199 ... .order_by('volume', ascending=False) 200 ... .offset(5) 201 ... .limit(15) 202 ... .get_scanner_data()) 203 (393, 204 ticker name close volume relative_volume_10d_calc 205 0 OTC:YCRM YCRM 0.0120 19626514 1.887942 206 1 OTC:PLPL PLPL 0.0002 17959914 3.026059 207 2 NASDAQ:ABVC ABVC 1.3800 16295824 1.967505 208 3 OTC:TLSS TLSS 0.0009 15671157 1.877976 209 4 OTC:GVSI GVSI 0.0128 14609774 2.640792 210 5 OTC:IGEX IGEX 0.0012 14285592 1.274861 211 6 OTC:EEGI EEGI 0.0004 12094000 2.224749 212 7 NASDAQ:GLG GLG 0.0591 9811974 1.990526 213 8 NASDAQ:TCRT TCRT 0.0890 8262894 2.630553 214 9 OTC:INKW INKW 0.0027 7196404 1.497134) 215 216 To avoid rewriting the same query again and again, you can save the query to a variable and 217 just call `get_scanner_data()` again and again to get the latest data: 218 >>> top_50_bullish = (Query() 219 ... .select('name', 'close', 'volume', 'relative_volume_10d_calc') 220 ... .where( 221 ... Column('market_cap_basic').between(1_000_000, 50_000_000), 222 ... Column('relative_volume_10d_calc') > 1.2, 223 ... Column('MACD.macd') >= Column('MACD.signal') 224 ... ) 225 ... .order_by('volume', ascending=False) 226 ... .limit(50)) 227 >>> top_50_bullish.get_scanner_data() 228 (393, 229 ticker name close volume relative_volume_10d_calc 230 0 OTC:BEGI BEGI 0.001050 127874055 3.349924 231 1 OTC:HCMC HCMC 0.000100 126992562 1.206231 232 2 OTC:HEMP HEMP 0.000150 101382713 1.775458 233 3 OTC:SONG SONG 0.000800 92640779 1.805721 234 4 OTC:APRU APRU 0.001575 38104499 29.028958 235 .. ... ... ... ... ... 236 45 OTC:BSHPF BSHPF 0.001000 525000 1.280899 237 46 OTC:GRHI GRHI 0.033000 507266 1.845738 238 47 OTC:OMGGF OMGGF 0.035300 505000 4.290059 239 48 NASDAQ:GBNH GBNH 0.273000 500412 9.076764 240 49 OTC:CLRMF CLRMF 0.032500 496049 17.560935 241 [50 rows x 5 columns]) 242 """ 243 244 def __init__(self) -> None: 245 # noinspection PyTypeChecker 246 self.query: QueryDict = { 247 'markets': ['america'], 248 'symbols': {'query': {'types': []}, 'tickers': []}, 249 'options': {'lang': 'en'}, 250 'columns': ['name', 'close', 'volume', 'market_cap_basic'], 251 # 'filter': ..., 252 'sort': {'sortBy': 'Value.Traded', 'sortOrder': 'desc'}, 253 'range': DEFAULT_RANGE.copy(), 254 } 255 self.url = 'https://scanner.tradingview.com/america/scan' 256 257 def select(self, *columns: Column | str) -> Self: 258 self.query['columns'] = [ 259 col.name if isinstance(col, Column) else Column(col).name for col in columns 260 ] 261 return self 262 263 def where(self, *expressions: FilterOperationDict) -> Self: 264 """ 265 Filter screener (expressions are joined with the AND operator) 266 """ 267 self.query['filter'] = list(expressions) # convert tuple[dict] -> list[dict] 268 return self 269 270 def where2(self, operation: OperationDict) -> Self: 271 """ 272 Filter screener using AND/OR operators (nested expressions also allowed) 273 274 Rules: 275 1. The argument passed to `where2()` **must** be wrapped in `And()` or `Or()`. 276 2. `And()` and `Or()` can accept one or more conditions as arguments. 277 3. Conditions can be simple (e.g., `Column('field') == 'value'`) or complex, allowing nesting of `And()` and `Or()` to create intricate logical filters. 278 4. Unlike the `where()` method, which only supports chaining conditions with the `AND` operator, `where2()` allows mixing and nesting of `AND` and `OR` operators. 279 280 Examples: 281 282 1. **Combining conditions with `OR` and nested `AND`:** 283 ```python 284 Query() 285 .select('type', 'typespecs') 286 .where2( 287 Or( 288 And(Column('type') == 'stock', Column('typespecs').has(['common', 'preferred'])), 289 And(Column('type') == 'fund', Column('typespecs').has_none_of(['etf'])), 290 Column('type') == 'dr', 291 ) 292 ) 293 ``` 294 295 This query filters entities where: 296 - The `type` is `'stock'` and `typespecs` contains `'common'` or `'preferred'`, **OR** 297 - The `type` is `'fund'` and `typespecs` does not contain `'etf'`, **OR** 298 - The `type` is `'dr'`. 299 300 2. **Mixing conditions with `OR`:** 301 ```python 302 Query().where2( 303 Or( 304 And(col('type') == 'stock', col('typespecs').has(['common'])), 305 col('type') == 'fund', 306 ) 307 ) 308 ``` 309 This query filters entities where: 310 - The `type` is `'stock'` and `typespecs` contains `'common'`, **OR** 311 - The `type` is `'fund'`. 312 313 3. **Combining conditions with `AND`:** 314 ```python 315 Query() 316 .set_markets('crypto') 317 .where2( 318 And( 319 col('exchange').isin(['UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff']), 320 col('currency_id') == 'USD', 321 ) 322 ) 323 ``` 324 This query filters entities in the `'crypto'` market where: 325 - The `exchange` is one of `'UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff'`, **AND** 326 - The `currency_id` is `'USD'`. 327 """ 328 self.query['filter2'] = operation['operation'] 329 return self 330 331 def order_by( 332 self, column: Column | str, ascending: bool = True, nulls_first: bool = False 333 ) -> Self: 334 """ 335 Applies sorting to the query results based on the specified column. 336 337 Examples: 338 339 >>> Query().order_by('volume', ascending=False) # sort descending 340 >>> Query().order_by('close', ascending=True) 341 >>> Query().order_by('dividends_yield_current', ascending=False, nulls_first=False) 342 343 :param column: Either a `Column` object or a string with the column name. 344 :param ascending: Set to True for ascending order (default), or False for descending. 345 :param nulls_first: If True, places `None` values at the beginning of the results. Defaults 346 to False. 347 :return: The updated query object. 348 """ 349 dct: SortByDict = { 350 'sortBy': column.name if isinstance(column, Column) else column, 351 'sortOrder': 'asc' if ascending else 'desc', 352 'nullsFirst': nulls_first, 353 } 354 self.query['sort'] = dct 355 return self 356 357 def limit(self, limit: int) -> Self: 358 self.query.setdefault('range', DEFAULT_RANGE.copy())[1] = limit 359 return self 360 361 def offset(self, offset: int) -> Self: 362 self.query.setdefault('range', DEFAULT_RANGE.copy())[0] = offset 363 return self 364 365 def set_markets(self, *markets: str) -> Self: 366 """ 367 This method allows you to select the market/s which you want to query. 368 369 By default, the screener will only scan US equities, but you can change it to scan any 370 market or country, that includes a list of 67 countries, and also the following 371 asset classes: `bonds`, `cfd`, `coin`, `crypto`, `euronext`, `forex`, 372 `futures`, `options`. 373 374 You may choose any value from `tradingview_screener.constants.MARKETS`. 375 376 If you select multiple countries, you might want to 377 378 Examples: 379 380 By default, the screener will show results from the `america` market, but you can 381 change it (note the difference between `market` and `country`) 382 >>> columns = ['close', 'market', 'country', 'currency'] 383 >>> (Query() 384 ... .select(*columns) 385 ... .set_markets('italy') 386 ... .get_scanner_data()) 387 (2346, 388 ticker close market country currency 389 0 MIL:UCG 23.9150 italy Italy EUR 390 1 MIL:ISP 2.4910 italy Italy EUR 391 2 MIL:STLAM 17.9420 italy Netherlands EUR 392 3 MIL:ENEL 6.0330 italy Italy EUR 393 4 MIL:ENI 15.4800 italy Italy EUR 394 .. ... ... ... ... ... 395 45 MIL:UNI 5.1440 italy Italy EUR 396 46 MIL:3OIS 0.4311 italy Ireland EUR 397 47 MIL:3SIL 35.2300 italy Ireland EUR 398 48 MIL:IWDE 69.1300 italy Ireland EUR 399 49 MIL:QQQS 19.2840 italy Ireland EUR 400 [50 rows x 5 columns]) 401 402 You can also select multiple markets 403 >>> (Query() 404 ... .select(*columns) 405 ... .set_markets('america', 'israel', 'hongkong', 'switzerland') 406 ... .get_scanner_data()) 407 (23964, 408 ticker close market country currency 409 0 AMEX:SPY 420.1617 america United States USD 410 1 NASDAQ:TSLA 201.2000 america United States USD 411 2 NASDAQ:NVDA 416.7825 america United States USD 412 3 NASDAQ:AMD 106.6600 america United States USD 413 4 NASDAQ:QQQ 353.7985 america United States USD 414 .. ... ... ... ... ... 415 45 NASDAQ:GOOGL 124.9200 america United States USD 416 46 HKEX:1211 233.2000 hongkong China HKD 417 47 TASE:ALHE 1995.0000 israel Israel ILA 418 48 AMEX:BIL 91.4398 america United States USD 419 49 NASDAQ:GOOG 126.1500 america United States USD 420 [50 rows x 5 columns]) 421 422 You may also select different financial instruments 423 >>> (Query() 424 ... .select('close', 'market') 425 ... .set_markets('cfd', 'crypto', 'forex', 'futures') 426 ... .get_scanner_data()) 427 (118076, 428 ticker ... market 429 0 UNISWAP3ETH:JUSTICEUSDT ... crypto 430 1 UNISWAP3ETH:UAHGUSDT ... crypto 431 2 UNISWAP3ETH:KENDUWETH ... crypto 432 3 UNISWAP3ETH:MATICSTMATIC ... crypto 433 4 UNISWAP3ETH:WETHETHM ... crypto 434 .. ... ... ... 435 45 UNISWAP:MUSICAIWETH_1F5304.USD ... crypto 436 46 CRYPTOCAP:FIL ... cfd 437 47 CRYPTOCAP:SUI ... cfd 438 48 CRYPTOCAP:ARBI ... cfd 439 49 CRYPTOCAP:OP ... cfd 440 [50 rows x 3 columns]) 441 442 :param markets: one or more markets from `tradingview_screener.constants.MARKETS` 443 :return: Self 444 """ 445 if len(markets) == 1: 446 market = markets[0] 447 self.url = URL.format(market=market) 448 self.query['markets'] = [market] 449 else: # len(markets) == 0 or len(markets) > 1 450 self.url = URL.format(market='global') 451 self.query['markets'] = list(markets) 452 453 return self 454 455 def set_tickers(self, *tickers: str) -> Self: 456 """ 457 Set the tickers you wish to receive information on. 458 459 Note that this resets the markets and sets the URL market to `global`. 460 461 Examples: 462 463 >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd') 464 >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data() 465 (1, 466 ticker name market close volume VWAP MACD.macd 467 0 NASDAQ:TSLA TSLA america 186 3519931 185.53 2.371601) 468 469 To set tickers from multiple markets we need to update the markets that include them: 470 >>> (Query() 471 ... .set_markets('america', 'italy', 'vietnam') 472 ... .set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX') 473 ... .get_scanner_data()) 474 (4, 475 ticker name close volume market_cap_basic 476 0 HOSE:VIX VIX 16700.00 33192500 4.568961e+08 477 1 AMEX:SPY SPY 544.35 1883562 NaN 478 2 NYSE:GME GME 23.80 3116758 1.014398e+10 479 3 MIL:RACE RACE 393.30 122878 1.006221e+11) 480 481 :param tickers: One or more tickers, syntax: `exchange:symbol` 482 :return: Self 483 """ 484 self.query.setdefault('symbols', {})['tickers'] = list(tickers) 485 self.set_markets() 486 return self 487 488 def set_index(self, *indexes: str) -> Self: 489 """ 490 Scan only the equities that are in in the given index (or indexes). 491 492 Note that this resets the markets and sets the URL market to `global`. 493 494 Examples: 495 496 >>> Query().set_index('SYML:SP;SPX').get_scanner_data() 497 (503, 498 ticker name close volume market_cap_basic 499 0 NASDAQ:NVDA NVDA 1208.88 41238122 2.973644e+12 500 1 NASDAQ:AAPL AAPL 196.89 53103705 3.019127e+12 501 2 NASDAQ:TSLA TSLA 177.48 56244929 5.660185e+11 502 3 NASDAQ:AMD AMD 167.87 44795240 2.713306e+11 503 4 NASDAQ:MSFT MSFT 423.85 13621485 3.150183e+12 504 5 NASDAQ:AMZN AMZN 184.30 28021473 1.917941e+12 505 6 NASDAQ:META META 492.96 9379199 1.250410e+12 506 7 NASDAQ:GOOGL GOOGL 174.46 19660698 2.164346e+12 507 8 NASDAQ:SMCI SMCI 769.11 3444852 4.503641e+10 508 9 NASDAQ:GOOG GOOG 175.95 14716134 2.164346e+12 509 10 NASDAQ:AVGO AVGO 1406.64 1785876 6.518669e+11) 510 511 You can set multiple indices as well, like the NIFTY 50 and UK 100 Index. 512 >>> Query().set_index('SYML:NSE;NIFTY', 'SYML:TVC;UKX').get_scanner_data() 513 (150, 514 ticker name close volume market_cap_basic 515 0 NSE:INFY INFY 1533.600000 24075302 7.623654e+10 516 1 LSE:AZN AZN 12556.000000 2903032 2.489770e+11 517 2 NSE:HDFCBANK HDFCBANK 1573.350000 18356108 1.432600e+11 518 3 NSE:RELIANCE RELIANCE 2939.899900 9279348 2.381518e+11 519 4 LSE:LSEG LSEG 9432.000000 2321053 6.395329e+10 520 5 NSE:BAJFINANCE BAJFINANCE 7191.399900 2984052 5.329685e+10 521 6 LSE:BARC BARC 217.250000 96238723 4.133010e+10 522 7 NSE:SBIN SBIN 829.950010 25061284 8.869327e+10 523 8 NSE:LT LT 3532.500000 5879660 5.816100e+10 524 9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11) 525 526 You can find the full list of indices in [`constants.INDICES`](constants.html#INDICES), 527 just note that the syntax is 528 `SYML:{source};{symbol}`. 529 530 :param indexes: One or more strings representing the financial indexes to filter by 531 :return: An instance of the `Query` class with the filter applied 532 """ 533 self.query.setdefault('preset', 'index_components_market_pages') 534 self.query.setdefault('symbols', {})['symbolset'] = list(indexes) 535 # reset markets list and URL to `/global` 536 self.set_markets() 537 return self 538 539 # def set_currency(self, currency: Literal['symbol', 'market'] | str) -> Self: 540 # """ 541 # Change the currency of the screener. 542 # 543 # Note that this changes *only* the currency of the columns of type `fundamental_price`, 544 # for example: `market_cap_basic`, `net_income`, `total_debt`. Other columns like `close` and 545 # `Value.Traded` won't change, because they are of a different type. 546 # 547 # This can be particularly useful if you are querying tickers across different markets. 548 # 549 # Examples: 550 # 551 # >>> Query().set_currency('symbol') # convert to symbol currency 552 # >>> Query().set_currency('market') # convert to market currency 553 # >>> Query().set_currency('usd') # or another currency 554 # >>> Query().set_currency('jpy') 555 # >>> Query().set_currency('eur') 556 # """ 557 # # symbol currency -> self.query['price_conversion'] = {'to_symbol': True} 558 # # market currency -> self.query['price_conversion'] = {'to_symbol': False} 559 # # USD or other currency -> self.query['price_conversion'] = {'to_currency': 'usd'} 560 # if currency == 'symbol': 561 # self.query['price_conversion'] = {'to_symbol': True} 562 # elif currency == 'market': 563 # self.query['price_conversion'] = {'to_symbol': False} 564 # else: 565 # self.query['price_conversion'] = {'to_currency': currency} 566 # return self 567 568 def set_property(self, key: str, value: Any) -> Self: 569 self.query[key] = value 570 return self 571 572 def get_scanner_data_raw(self, **kwargs) -> ScreenerDict: 573 """ 574 Perform a POST web-request and return the data from the API (dictionary). 575 576 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 577 this can be very useful if you want to pass your own headers/cookies. 578 579 >>> Query().select('close', 'volume').limit(5).get_scanner_data_raw() 580 { 581 'totalCount': 17559, 582 'data': [ 583 {'s': 'NASDAQ:NVDA', 'd': [116.14, 312636630]}, 584 {'s': 'AMEX:SPY', 'd': [542.04, 52331224]}, 585 {'s': 'NASDAQ:QQQ', 'd': [462.58, 40084156]}, 586 {'s': 'NASDAQ:TSLA', 'd': [207.83, 76247251]}, 587 {'s': 'NASDAQ:SBUX', 'd': [95.9, 157211696]}, 588 ], 589 } 590 """ 591 self.query.setdefault('range', DEFAULT_RANGE.copy()) 592 593 kwargs.setdefault('headers', HEADERS) 594 kwargs.setdefault('timeout', 20) 595 r = requests.post(self.url, json=self.query, **kwargs) 596 597 if not r.ok: 598 # add the body to the error message for debugging purposes 599 r.reason += f'\n Body: {r.text}\n' 600 r.raise_for_status() 601 602 return r.json() 603 604 def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]: 605 """ 606 Perform a POST web-request and return the data from the API as a DataFrame (along with 607 the number of rows/tickers that matched your query). 608 609 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 610 this can be very useful if you want to pass your own headers/cookies. 611 612 ### Live/Delayed data 613 614 Note that to get live-data you have to authenticate, which is done by passing your cookies. 615 Have a look in the README at the "Real-Time Data Access" sections. 616 617 :param kwargs: kwargs to pass to `requests.post()` 618 :return: a tuple consisting of: (total_count, dataframe) 619 """ 620 import pandas as pd 621 622 json_obj = self.get_scanner_data_raw(**kwargs) 623 rows_count = json_obj['totalCount'] 624 data = json_obj['data'] 625 626 df = pd.DataFrame( 627 data=([row['s'], *row['d']] for row in data), 628 columns=['ticker', *self.query.get('columns', ())], # pyright: ignore [reportArgumentType] 629 ) 630 return rows_count, df 631 632 def copy(self) -> Query: 633 new = Query() 634 new.query = self.query.copy() 635 return new 636 637 def __repr__(self) -> str: 638 return f'< {pprint.pformat(self.query)}\n url={self.url!r} >' 639 640 def __eq__(self, other) -> bool: 641 return isinstance(other, Query) and self.query == other.query and self.url == other.url
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])
263 def where(self, *expressions: FilterOperationDict) -> Self: 264 """ 265 Filter screener (expressions are joined with the AND operator) 266 """ 267 self.query['filter'] = list(expressions) # convert tuple[dict] -> list[dict] 268 return self
Filter screener (expressions are joined with the AND operator)
270 def where2(self, operation: OperationDict) -> Self: 271 """ 272 Filter screener using AND/OR operators (nested expressions also allowed) 273 274 Rules: 275 1. The argument passed to `where2()` **must** be wrapped in `And()` or `Or()`. 276 2. `And()` and `Or()` can accept one or more conditions as arguments. 277 3. Conditions can be simple (e.g., `Column('field') == 'value'`) or complex, allowing nesting of `And()` and `Or()` to create intricate logical filters. 278 4. Unlike the `where()` method, which only supports chaining conditions with the `AND` operator, `where2()` allows mixing and nesting of `AND` and `OR` operators. 279 280 Examples: 281 282 1. **Combining conditions with `OR` and nested `AND`:** 283 ```python 284 Query() 285 .select('type', 'typespecs') 286 .where2( 287 Or( 288 And(Column('type') == 'stock', Column('typespecs').has(['common', 'preferred'])), 289 And(Column('type') == 'fund', Column('typespecs').has_none_of(['etf'])), 290 Column('type') == 'dr', 291 ) 292 ) 293 ``` 294 295 This query filters entities where: 296 - The `type` is `'stock'` and `typespecs` contains `'common'` or `'preferred'`, **OR** 297 - The `type` is `'fund'` and `typespecs` does not contain `'etf'`, **OR** 298 - The `type` is `'dr'`. 299 300 2. **Mixing conditions with `OR`:** 301 ```python 302 Query().where2( 303 Or( 304 And(col('type') == 'stock', col('typespecs').has(['common'])), 305 col('type') == 'fund', 306 ) 307 ) 308 ``` 309 This query filters entities where: 310 - The `type` is `'stock'` and `typespecs` contains `'common'`, **OR** 311 - The `type` is `'fund'`. 312 313 3. **Combining conditions with `AND`:** 314 ```python 315 Query() 316 .set_markets('crypto') 317 .where2( 318 And( 319 col('exchange').isin(['UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff']), 320 col('currency_id') == 'USD', 321 ) 322 ) 323 ``` 324 This query filters entities in the `'crypto'` market where: 325 - The `exchange` is one of `'UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff'`, **AND** 326 - The `currency_id` is `'USD'`. 327 """ 328 self.query['filter2'] = operation['operation'] 329 return self
Filter screener using AND/OR operators (nested expressions also allowed)
Rules:
- The argument passed to
where2()
must be wrapped inAnd()
orOr()
. And()
andOr()
can accept one or more conditions as arguments.- Conditions can be simple (e.g.,
Column('field') == 'value'
) or complex, allowing nesting ofAnd()
andOr()
to create intricate logical filters. - Unlike the
where()
method, which only supports chaining conditions with theAND
operator,where2()
allows mixing and nesting ofAND
andOR
operators.
Examples:
Combining conditions with
OR
and nestedAND
:Query() .select('type', 'typespecs') .where2( Or( And(Column('type') == 'stock', Column('typespecs').has(['common', 'preferred'])), And(Column('type') == 'fund', Column('typespecs').has_none_of(['etf'])), Column('type') == 'dr', ) )
This query filters entities where:
- The
type
is'stock'
andtypespecs
contains'common'
or'preferred'
, OR - The
type
is'fund'
andtypespecs
does not contain'etf'
, OR - The
type
is'dr'
.
- The
Mixing conditions with
OR
:Query().where2( Or( And(col('type') == 'stock', col('typespecs').has(['common'])), col('type') == 'fund', ) )
This query filters entities where:
- The
type
is'stock'
andtypespecs
contains'common'
, OR - The
type
is'fund'
.
- The
Combining conditions with
AND
:Query() .set_markets('crypto') .where2( And( col('exchange').isin(['UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff']), col('currency_id') == 'USD', ) )
This query filters entities in the
'crypto'
market where:- The
exchange
is one of'UNISWAP3POLYGON', 'VERSEETH', 'a', 'fffffffff'
, AND - The
currency_id
is'USD'
.
- The
331 def order_by( 332 self, column: Column | str, ascending: bool = True, nulls_first: bool = False 333 ) -> Self: 334 """ 335 Applies sorting to the query results based on the specified column. 336 337 Examples: 338 339 >>> Query().order_by('volume', ascending=False) # sort descending 340 >>> Query().order_by('close', ascending=True) 341 >>> Query().order_by('dividends_yield_current', ascending=False, nulls_first=False) 342 343 :param column: Either a `Column` object or a string with the column name. 344 :param ascending: Set to True for ascending order (default), or False for descending. 345 :param nulls_first: If True, places `None` values at the beginning of the results. Defaults 346 to False. 347 :return: The updated query object. 348 """ 349 dct: SortByDict = { 350 'sortBy': column.name if isinstance(column, Column) else column, 351 'sortOrder': 'asc' if ascending else 'desc', 352 'nullsFirst': nulls_first, 353 } 354 self.query['sort'] = dct 355 return self
Applies sorting to the query results based on the specified column.
Examples:
>>> Query().order_by('volume', ascending=False) # sort descending
>>> Query().order_by('close', ascending=True)
>>> Query().order_by('dividends_yield_current', ascending=False, nulls_first=False)
Parameters
- column: Either a
Column
object or a string with the column name. - ascending: Set to True for ascending order (default), or False for descending.
- nulls_first: If True, places
None
values at the beginning of the results. Defaults to False.
Returns
The updated query object.
365 def set_markets(self, *markets: str) -> Self: 366 """ 367 This method allows you to select the market/s which you want to query. 368 369 By default, the screener will only scan US equities, but you can change it to scan any 370 market or country, that includes a list of 67 countries, and also the following 371 asset classes: `bonds`, `cfd`, `coin`, `crypto`, `euronext`, `forex`, 372 `futures`, `options`. 373 374 You may choose any value from `tradingview_screener.constants.MARKETS`. 375 376 If you select multiple countries, you might want to 377 378 Examples: 379 380 By default, the screener will show results from the `america` market, but you can 381 change it (note the difference between `market` and `country`) 382 >>> columns = ['close', 'market', 'country', 'currency'] 383 >>> (Query() 384 ... .select(*columns) 385 ... .set_markets('italy') 386 ... .get_scanner_data()) 387 (2346, 388 ticker close market country currency 389 0 MIL:UCG 23.9150 italy Italy EUR 390 1 MIL:ISP 2.4910 italy Italy EUR 391 2 MIL:STLAM 17.9420 italy Netherlands EUR 392 3 MIL:ENEL 6.0330 italy Italy EUR 393 4 MIL:ENI 15.4800 italy Italy EUR 394 .. ... ... ... ... ... 395 45 MIL:UNI 5.1440 italy Italy EUR 396 46 MIL:3OIS 0.4311 italy Ireland EUR 397 47 MIL:3SIL 35.2300 italy Ireland EUR 398 48 MIL:IWDE 69.1300 italy Ireland EUR 399 49 MIL:QQQS 19.2840 italy Ireland EUR 400 [50 rows x 5 columns]) 401 402 You can also select multiple markets 403 >>> (Query() 404 ... .select(*columns) 405 ... .set_markets('america', 'israel', 'hongkong', 'switzerland') 406 ... .get_scanner_data()) 407 (23964, 408 ticker close market country currency 409 0 AMEX:SPY 420.1617 america United States USD 410 1 NASDAQ:TSLA 201.2000 america United States USD 411 2 NASDAQ:NVDA 416.7825 america United States USD 412 3 NASDAQ:AMD 106.6600 america United States USD 413 4 NASDAQ:QQQ 353.7985 america United States USD 414 .. ... ... ... ... ... 415 45 NASDAQ:GOOGL 124.9200 america United States USD 416 46 HKEX:1211 233.2000 hongkong China HKD 417 47 TASE:ALHE 1995.0000 israel Israel ILA 418 48 AMEX:BIL 91.4398 america United States USD 419 49 NASDAQ:GOOG 126.1500 america United States USD 420 [50 rows x 5 columns]) 421 422 You may also select different financial instruments 423 >>> (Query() 424 ... .select('close', 'market') 425 ... .set_markets('cfd', 'crypto', 'forex', 'futures') 426 ... .get_scanner_data()) 427 (118076, 428 ticker ... market 429 0 UNISWAP3ETH:JUSTICEUSDT ... crypto 430 1 UNISWAP3ETH:UAHGUSDT ... crypto 431 2 UNISWAP3ETH:KENDUWETH ... crypto 432 3 UNISWAP3ETH:MATICSTMATIC ... crypto 433 4 UNISWAP3ETH:WETHETHM ... crypto 434 .. ... ... ... 435 45 UNISWAP:MUSICAIWETH_1F5304.USD ... crypto 436 46 CRYPTOCAP:FIL ... cfd 437 47 CRYPTOCAP:SUI ... cfd 438 48 CRYPTOCAP:ARBI ... cfd 439 49 CRYPTOCAP:OP ... cfd 440 [50 rows x 3 columns]) 441 442 :param markets: one or more markets from `tradingview_screener.constants.MARKETS` 443 :return: Self 444 """ 445 if len(markets) == 1: 446 market = markets[0] 447 self.url = URL.format(market=market) 448 self.query['markets'] = [market] 449 else: # len(markets) == 0 or len(markets) > 1 450 self.url = URL.format(market='global') 451 self.query['markets'] = list(markets) 452 453 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
market or country, that includes a list of 67 countries, and also the following
asset classes: bonds
, cfd
, coin
, crypto
, euronext
, forex
,
futures
, options
.
You may choose any value from tradingview_screener.constants.MARKETS
.
If you select multiple countries, you might want to
Examples:
By default, the screener will show results from the america
market, but you can
change it (note the difference between market
and country
)
>>> columns = ['close', 'market', 'country', 'currency']
>>> (Query()
... .select(*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(*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', 'forex', 'futures')
... .get_scanner_data())
(118076,
ticker ... market
0 UNISWAP3ETH:JUSTICEUSDT ... crypto
1 UNISWAP3ETH:UAHGUSDT ... crypto
2 UNISWAP3ETH:KENDUWETH ... crypto
3 UNISWAP3ETH:MATICSTMATIC ... crypto
4 UNISWAP3ETH:WETHETHM ... crypto
.. ... ... ...
45 UNISWAP:MUSICAIWETH_1F5304.USD ... crypto
46 CRYPTOCAP:FIL ... cfd
47 CRYPTOCAP:SUI ... cfd
48 CRYPTOCAP:ARBI ... cfd
49 CRYPTOCAP:OP ... cfd
[50 rows x 3 columns])
Parameters
- markets: one or more markets from
tradingview_screener.constants.MARKETS
Returns
Self
455 def set_tickers(self, *tickers: str) -> Self: 456 """ 457 Set the tickers you wish to receive information on. 458 459 Note that this resets the markets and sets the URL market to `global`. 460 461 Examples: 462 463 >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd') 464 >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data() 465 (1, 466 ticker name market close volume VWAP MACD.macd 467 0 NASDAQ:TSLA TSLA america 186 3519931 185.53 2.371601) 468 469 To set tickers from multiple markets we need to update the markets that include them: 470 >>> (Query() 471 ... .set_markets('america', 'italy', 'vietnam') 472 ... .set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX') 473 ... .get_scanner_data()) 474 (4, 475 ticker name close volume market_cap_basic 476 0 HOSE:VIX VIX 16700.00 33192500 4.568961e+08 477 1 AMEX:SPY SPY 544.35 1883562 NaN 478 2 NYSE:GME GME 23.80 3116758 1.014398e+10 479 3 MIL:RACE RACE 393.30 122878 1.006221e+11) 480 481 :param tickers: One or more tickers, syntax: `exchange:symbol` 482 :return: Self 483 """ 484 self.query.setdefault('symbols', {})['tickers'] = list(tickers) 485 self.set_markets() 486 return self
Set the tickers you wish to receive information on.
Note that this resets the markets and sets the URL market to global
.
Examples:
>>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd')
>>> q.set_tickers('NASDAQ:TSLA').get_scanner_data()
(1,
ticker name market close volume VWAP MACD.macd
0 NASDAQ:TSLA TSLA america 186 3519931 185.53 2.371601)
To set tickers from multiple markets we need to update the markets that include them:
>>> (Query()
... .set_markets('america', 'italy', 'vietnam')
... .set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX')
... .get_scanner_data())
(4,
ticker name close volume market_cap_basic
0 HOSE:VIX VIX 16700.00 33192500 4.568961e+08
1 AMEX:SPY SPY 544.35 1883562 NaN
2 NYSE:GME GME 23.80 3116758 1.014398e+10
3 MIL:RACE RACE 393.30 122878 1.006221e+11)
Parameters
- **tickers: One or more tickers, syntax:
exchange**: symbol
Returns
Self
488 def set_index(self, *indexes: str) -> Self: 489 """ 490 Scan only the equities that are in in the given index (or indexes). 491 492 Note that this resets the markets and sets the URL market to `global`. 493 494 Examples: 495 496 >>> Query().set_index('SYML:SP;SPX').get_scanner_data() 497 (503, 498 ticker name close volume market_cap_basic 499 0 NASDAQ:NVDA NVDA 1208.88 41238122 2.973644e+12 500 1 NASDAQ:AAPL AAPL 196.89 53103705 3.019127e+12 501 2 NASDAQ:TSLA TSLA 177.48 56244929 5.660185e+11 502 3 NASDAQ:AMD AMD 167.87 44795240 2.713306e+11 503 4 NASDAQ:MSFT MSFT 423.85 13621485 3.150183e+12 504 5 NASDAQ:AMZN AMZN 184.30 28021473 1.917941e+12 505 6 NASDAQ:META META 492.96 9379199 1.250410e+12 506 7 NASDAQ:GOOGL GOOGL 174.46 19660698 2.164346e+12 507 8 NASDAQ:SMCI SMCI 769.11 3444852 4.503641e+10 508 9 NASDAQ:GOOG GOOG 175.95 14716134 2.164346e+12 509 10 NASDAQ:AVGO AVGO 1406.64 1785876 6.518669e+11) 510 511 You can set multiple indices as well, like the NIFTY 50 and UK 100 Index. 512 >>> Query().set_index('SYML:NSE;NIFTY', 'SYML:TVC;UKX').get_scanner_data() 513 (150, 514 ticker name close volume market_cap_basic 515 0 NSE:INFY INFY 1533.600000 24075302 7.623654e+10 516 1 LSE:AZN AZN 12556.000000 2903032 2.489770e+11 517 2 NSE:HDFCBANK HDFCBANK 1573.350000 18356108 1.432600e+11 518 3 NSE:RELIANCE RELIANCE 2939.899900 9279348 2.381518e+11 519 4 LSE:LSEG LSEG 9432.000000 2321053 6.395329e+10 520 5 NSE:BAJFINANCE BAJFINANCE 7191.399900 2984052 5.329685e+10 521 6 LSE:BARC BARC 217.250000 96238723 4.133010e+10 522 7 NSE:SBIN SBIN 829.950010 25061284 8.869327e+10 523 8 NSE:LT LT 3532.500000 5879660 5.816100e+10 524 9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11) 525 526 You can find the full list of indices in [`constants.INDICES`](constants.html#INDICES), 527 just note that the syntax is 528 `SYML:{source};{symbol}`. 529 530 :param indexes: One or more strings representing the financial indexes to filter by 531 :return: An instance of the `Query` class with the filter applied 532 """ 533 self.query.setdefault('preset', 'index_components_market_pages') 534 self.query.setdefault('symbols', {})['symbolset'] = list(indexes) 535 # reset markets list and URL to `/global` 536 self.set_markets() 537 return self
Scan only the equities that are in in the given index (or indexes).
Note that this resets the markets and sets the URL market to global
.
Examples:
>>> Query().set_index('SYML:SP;SPX').get_scanner_data()
(503,
ticker name close volume market_cap_basic
0 NASDAQ:NVDA NVDA 1208.88 41238122 2.973644e+12
1 NASDAQ:AAPL AAPL 196.89 53103705 3.019127e+12
2 NASDAQ:TSLA TSLA 177.48 56244929 5.660185e+11
3 NASDAQ:AMD AMD 167.87 44795240 2.713306e+11
4 NASDAQ:MSFT MSFT 423.85 13621485 3.150183e+12
5 NASDAQ:AMZN AMZN 184.30 28021473 1.917941e+12
6 NASDAQ:META META 492.96 9379199 1.250410e+12
7 NASDAQ:GOOGL GOOGL 174.46 19660698 2.164346e+12
8 NASDAQ:SMCI SMCI 769.11 3444852 4.503641e+10
9 NASDAQ:GOOG GOOG 175.95 14716134 2.164346e+12
10 NASDAQ:AVGO AVGO 1406.64 1785876 6.518669e+11)
You can set multiple indices as well, like the NIFTY 50 and UK 100 Index.
>>> Query().set_index('SYML:NSE;NIFTY', 'SYML:TVC;UKX').get_scanner_data()
(150,
ticker name close volume market_cap_basic
0 NSE:INFY INFY 1533.600000 24075302 7.623654e+10
1 LSE:AZN AZN 12556.000000 2903032 2.489770e+11
2 NSE:HDFCBANK HDFCBANK 1573.350000 18356108 1.432600e+11
3 NSE:RELIANCE RELIANCE 2939.899900 9279348 2.381518e+11
4 LSE:LSEG LSEG 9432.000000 2321053 6.395329e+10
5 NSE:BAJFINANCE BAJFINANCE 7191.399900 2984052 5.329685e+10
6 LSE:BARC BARC 217.250000 96238723 4.133010e+10
7 NSE:SBIN SBIN 829.950010 25061284 8.869327e+10
8 NSE:LT LT 3532.500000 5879660 5.816100e+10
9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11)
You can find the full list of indices in constants.INDICES
,
just note that the syntax is
SYML:{source};{symbol}
.
Parameters
- indexes: One or more strings representing the financial indexes to filter by
Returns
An instance of the
Query
class with the filter applied
572 def get_scanner_data_raw(self, **kwargs) -> ScreenerDict: 573 """ 574 Perform a POST web-request and return the data from the API (dictionary). 575 576 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 577 this can be very useful if you want to pass your own headers/cookies. 578 579 >>> Query().select('close', 'volume').limit(5).get_scanner_data_raw() 580 { 581 'totalCount': 17559, 582 'data': [ 583 {'s': 'NASDAQ:NVDA', 'd': [116.14, 312636630]}, 584 {'s': 'AMEX:SPY', 'd': [542.04, 52331224]}, 585 {'s': 'NASDAQ:QQQ', 'd': [462.58, 40084156]}, 586 {'s': 'NASDAQ:TSLA', 'd': [207.83, 76247251]}, 587 {'s': 'NASDAQ:SBUX', 'd': [95.9, 157211696]}, 588 ], 589 } 590 """ 591 self.query.setdefault('range', DEFAULT_RANGE.copy()) 592 593 kwargs.setdefault('headers', HEADERS) 594 kwargs.setdefault('timeout', 20) 595 r = requests.post(self.url, json=self.query, **kwargs) 596 597 if not r.ok: 598 # add the body to the error message for debugging purposes 599 r.reason += f'\n Body: {r.text}\n' 600 r.raise_for_status() 601 602 return r.json()
Perform a POST web-request and return the data from the API (dictionary).
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.
>>> Query().select('close', 'volume').limit(5).get_scanner_data_raw()
{
'totalCount': 17559,
'data': [
{'s': 'NASDAQ:NVDA', 'd': [116.14, 312636630]},
{'s': 'AMEX:SPY', 'd': [542.04, 52331224]},
{'s': 'NASDAQ:QQQ', 'd': [462.58, 40084156]},
{'s': 'NASDAQ:TSLA', 'd': [207.83, 76247251]},
{'s': 'NASDAQ:SBUX', 'd': [95.9, 157211696]},
],
}
604 def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]: 605 """ 606 Perform a POST web-request and return the data from the API as a DataFrame (along with 607 the number of rows/tickers that matched your query). 608 609 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 610 this can be very useful if you want to pass your own headers/cookies. 611 612 ### Live/Delayed data 613 614 Note that to get live-data you have to authenticate, which is done by passing your cookies. 615 Have a look in the README at the "Real-Time Data Access" sections. 616 617 :param kwargs: kwargs to pass to `requests.post()` 618 :return: a tuple consisting of: (total_count, dataframe) 619 """ 620 import pandas as pd 621 622 json_obj = self.get_scanner_data_raw(**kwargs) 623 rows_count = json_obj['totalCount'] 624 data = json_obj['data'] 625 626 df = pd.DataFrame( 627 data=([row['s'], *row['d']] for row in data), 628 columns=['ticker', *self.query.get('columns', ())], # pyright: ignore [reportArgumentType] 629 ) 630 return rows_count, df
Perform a POST web-request and return the data from the API as a DataFrame (along with the number of rows/tickers that matched your query).
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.
Live/Delayed data
Note that to get live-data you have to authenticate, which is done by passing your cookies. Have a look in the README at the "Real-Time Data Access" sections.
Parameters
- kwargs: kwargs to pass to
requests.post()
Returns
a tuple consisting of: (total_count, dataframe)