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 """ 272 self.query['filter2'] = operation['operation'] 273 return self 274 275 def order_by( 276 self, column: Column | str, ascending: bool = True, nulls_first: bool = False 277 ) -> Self: 278 """ 279 Applies sorting to the query results based on the specified column. 280 281 Examples: 282 283 >>> Query().order_by('volume', ascending=False) # sort descending 284 >>> Query().order_by('close', ascending=True) 285 >>> Query().order_by('dividends_yield_current', ascending=False, nulls_first=False) 286 287 :param column: Either a `Column` object or a string with the column name. 288 :param ascending: Set to True for ascending order (default), or False for descending. 289 :param nulls_first: If True, places `None` values at the beginning of the results. Defaults 290 to False. 291 :return: The updated query object. 292 """ 293 dct: SortByDict = { 294 'sortBy': column.name if isinstance(column, Column) else column, 295 'sortOrder': 'asc' if ascending else 'desc', 296 'nullsFirst': nulls_first, 297 } 298 self.query['sort'] = dct 299 return self 300 301 def limit(self, limit: int) -> Self: 302 self.query.setdefault('range', DEFAULT_RANGE.copy())[1] = limit 303 return self 304 305 def offset(self, offset: int) -> Self: 306 self.query.setdefault('range', DEFAULT_RANGE.copy())[0] = offset 307 return self 308 309 def set_markets(self, *markets: str) -> Self: 310 """ 311 This method allows you to select the market/s which you want to query. 312 313 By default, the screener will only scan US equities, but you can change it to scan any 314 market or country, that includes a list of 67 countries, and also the following 315 asset classes: `bonds`, `cfd`, `coin`, `crypto`, `euronext`, `forex`, 316 `futures`, `options`. 317 318 You may choose any value from `tradingview_screener.constants.MARKETS`. 319 320 If you select multiple countries, you might want to 321 322 Examples: 323 324 By default, the screener will show results from the `america` market, but you can 325 change it (note the difference between `market` and `country`) 326 >>> columns = ['close', 'market', 'country', 'currency'] 327 >>> (Query() 328 ... .select(*columns) 329 ... .set_markets('italy') 330 ... .get_scanner_data()) 331 (2346, 332 ticker close market country currency 333 0 MIL:UCG 23.9150 italy Italy EUR 334 1 MIL:ISP 2.4910 italy Italy EUR 335 2 MIL:STLAM 17.9420 italy Netherlands EUR 336 3 MIL:ENEL 6.0330 italy Italy EUR 337 4 MIL:ENI 15.4800 italy Italy EUR 338 .. ... ... ... ... ... 339 45 MIL:UNI 5.1440 italy Italy EUR 340 46 MIL:3OIS 0.4311 italy Ireland EUR 341 47 MIL:3SIL 35.2300 italy Ireland EUR 342 48 MIL:IWDE 69.1300 italy Ireland EUR 343 49 MIL:QQQS 19.2840 italy Ireland EUR 344 [50 rows x 5 columns]) 345 346 You can also select multiple markets 347 >>> (Query() 348 ... .select(*columns) 349 ... .set_markets('america', 'israel', 'hongkong', 'switzerland') 350 ... .get_scanner_data()) 351 (23964, 352 ticker close market country currency 353 0 AMEX:SPY 420.1617 america United States USD 354 1 NASDAQ:TSLA 201.2000 america United States USD 355 2 NASDAQ:NVDA 416.7825 america United States USD 356 3 NASDAQ:AMD 106.6600 america United States USD 357 4 NASDAQ:QQQ 353.7985 america United States USD 358 .. ... ... ... ... ... 359 45 NASDAQ:GOOGL 124.9200 america United States USD 360 46 HKEX:1211 233.2000 hongkong China HKD 361 47 TASE:ALHE 1995.0000 israel Israel ILA 362 48 AMEX:BIL 91.4398 america United States USD 363 49 NASDAQ:GOOG 126.1500 america United States USD 364 [50 rows x 5 columns]) 365 366 You may also select different financial instruments 367 >>> (Query() 368 ... .select('close', 'market') 369 ... .set_markets('cfd', 'crypto', 'forex', 'futures') 370 ... .get_scanner_data()) 371 (118076, 372 ticker ... market 373 0 UNISWAP3ETH:JUSTICEUSDT ... crypto 374 1 UNISWAP3ETH:UAHGUSDT ... crypto 375 2 UNISWAP3ETH:KENDUWETH ... crypto 376 3 UNISWAP3ETH:MATICSTMATIC ... crypto 377 4 UNISWAP3ETH:WETHETHM ... crypto 378 .. ... ... ... 379 45 UNISWAP:MUSICAIWETH_1F5304.USD ... crypto 380 46 CRYPTOCAP:FIL ... cfd 381 47 CRYPTOCAP:SUI ... cfd 382 48 CRYPTOCAP:ARBI ... cfd 383 49 CRYPTOCAP:OP ... cfd 384 [50 rows x 3 columns]) 385 386 :param markets: one or more markets from `tradingview_screener.constants.MARKETS` 387 :return: Self 388 """ 389 if len(markets) == 1: 390 market = markets[0] 391 self.url = URL.format(market=market) 392 self.query['markets'] = [market] 393 else: # len(markets) == 0 or len(markets) > 1 394 self.url = URL.format(market='global') 395 self.query['markets'] = list(markets) 396 397 return self 398 399 def set_tickers(self, *tickers: str) -> Self: 400 """ 401 Set the tickers you wish to receive information on. 402 403 Note that this resets the markets and sets the URL market to `global`. 404 405 Examples: 406 407 >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd') 408 >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data() 409 (1, 410 ticker name market close volume VWAP MACD.macd 411 0 NASDAQ:TSLA TSLA america 186 3519931 185.53 2.371601) 412 413 To set tickers from multiple markets we need to update the markets that include them: 414 >>> (Query() 415 ... .set_markets('america', 'italy', 'vietnam') 416 ... .set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX') 417 ... .get_scanner_data()) 418 (4, 419 ticker name close volume market_cap_basic 420 0 HOSE:VIX VIX 16700.00 33192500 4.568961e+08 421 1 AMEX:SPY SPY 544.35 1883562 NaN 422 2 NYSE:GME GME 23.80 3116758 1.014398e+10 423 3 MIL:RACE RACE 393.30 122878 1.006221e+11) 424 425 :param tickers: One or more tickers, syntax: `exchange:symbol` 426 :return: Self 427 """ 428 self.query.setdefault('symbols', {})['tickers'] = list(tickers) 429 self.set_markets() 430 return self 431 432 def set_index(self, *indexes: str) -> Self: 433 """ 434 Scan only the equities that are in in the given index (or indexes). 435 436 Note that this resets the markets and sets the URL market to `global`. 437 438 Examples: 439 440 >>> Query().set_index('SYML:SP;SPX').get_scanner_data() 441 (503, 442 ticker name close volume market_cap_basic 443 0 NASDAQ:NVDA NVDA 1208.88 41238122 2.973644e+12 444 1 NASDAQ:AAPL AAPL 196.89 53103705 3.019127e+12 445 2 NASDAQ:TSLA TSLA 177.48 56244929 5.660185e+11 446 3 NASDAQ:AMD AMD 167.87 44795240 2.713306e+11 447 4 NASDAQ:MSFT MSFT 423.85 13621485 3.150183e+12 448 5 NASDAQ:AMZN AMZN 184.30 28021473 1.917941e+12 449 6 NASDAQ:META META 492.96 9379199 1.250410e+12 450 7 NASDAQ:GOOGL GOOGL 174.46 19660698 2.164346e+12 451 8 NASDAQ:SMCI SMCI 769.11 3444852 4.503641e+10 452 9 NASDAQ:GOOG GOOG 175.95 14716134 2.164346e+12 453 10 NASDAQ:AVGO AVGO 1406.64 1785876 6.518669e+11) 454 455 You can set multiple indices as well, like the NIFTY 50 and UK 100 Index. 456 >>> Query().set_index('SYML:NSE;NIFTY', 'SYML:TVC;UKX').get_scanner_data() 457 (150, 458 ticker name close volume market_cap_basic 459 0 NSE:INFY INFY 1533.600000 24075302 7.623654e+10 460 1 LSE:AZN AZN 12556.000000 2903032 2.489770e+11 461 2 NSE:HDFCBANK HDFCBANK 1573.350000 18356108 1.432600e+11 462 3 NSE:RELIANCE RELIANCE 2939.899900 9279348 2.381518e+11 463 4 LSE:LSEG LSEG 9432.000000 2321053 6.395329e+10 464 5 NSE:BAJFINANCE BAJFINANCE 7191.399900 2984052 5.329685e+10 465 6 LSE:BARC BARC 217.250000 96238723 4.133010e+10 466 7 NSE:SBIN SBIN 829.950010 25061284 8.869327e+10 467 8 NSE:LT LT 3532.500000 5879660 5.816100e+10 468 9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11) 469 470 You can find the full list of indices in [`constants.INDICES`](constants.html#INDICES), 471 just note that the syntax is 472 `SYML:{source};{symbol}`. 473 474 :param indexes: One or more strings representing the financial indexes to filter by 475 :return: An instance of the `Query` class with the filter applied 476 """ 477 self.query.setdefault('preset', 'index_components_market_pages') 478 self.query.setdefault('symbols', {})['symbolset'] = list(indexes) 479 # reset markets list and URL to `/global` 480 self.set_markets() 481 return self 482 483 # def set_currency(self, currency: Literal['symbol', 'market'] | str) -> Self: 484 # """ 485 # Change the currency of the screener. 486 # 487 # Note that this changes *only* the currency of the columns of type `fundamental_price`, 488 # for example: `market_cap_basic`, `net_income`, `total_debt`. Other columns like `close` and 489 # `Value.Traded` won't change, because they are of a different type. 490 # 491 # This can be particularly useful if you are querying tickers across different markets. 492 # 493 # Examples: 494 # 495 # >>> Query().set_currency('symbol') # convert to symbol currency 496 # >>> Query().set_currency('market') # convert to market currency 497 # >>> Query().set_currency('usd') # or another currency 498 # >>> Query().set_currency('jpy') 499 # >>> Query().set_currency('eur') 500 # """ 501 # # symbol currency -> self.query['price_conversion'] = {'to_symbol': True} 502 # # market currency -> self.query['price_conversion'] = {'to_symbol': False} 503 # # USD or other currency -> self.query['price_conversion'] = {'to_currency': 'usd'} 504 # if currency == 'symbol': 505 # self.query['price_conversion'] = {'to_symbol': True} 506 # elif currency == 'market': 507 # self.query['price_conversion'] = {'to_symbol': False} 508 # else: 509 # self.query['price_conversion'] = {'to_currency': currency} 510 # return self 511 512 def set_property(self, key: str, value: Any) -> Self: 513 self.query[key] = value 514 return self 515 516 def get_scanner_data_raw(self, **kwargs) -> ScreenerDict: 517 """ 518 Perform a POST web-request and return the data from the API (dictionary). 519 520 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 521 this can be very useful if you want to pass your own headers/cookies. 522 523 >>> Query().select('close', 'volume').limit(5).get_scanner_data_raw() 524 { 525 'totalCount': 17559, 526 'data': [ 527 {'s': 'NASDAQ:NVDA', 'd': [116.14, 312636630]}, 528 {'s': 'AMEX:SPY', 'd': [542.04, 52331224]}, 529 {'s': 'NASDAQ:QQQ', 'd': [462.58, 40084156]}, 530 {'s': 'NASDAQ:TSLA', 'd': [207.83, 76247251]}, 531 {'s': 'NASDAQ:SBUX', 'd': [95.9, 157211696]}, 532 ], 533 } 534 """ 535 self.query.setdefault('range', DEFAULT_RANGE.copy()) 536 537 kwargs.setdefault('headers', HEADERS) 538 kwargs.setdefault('timeout', 20) 539 r = requests.post(self.url, json=self.query, **kwargs) 540 541 if not r.ok: 542 # add the body to the error message for debugging purposes 543 r.reason += f'\n Body: {r.text}\n' 544 r.raise_for_status() 545 546 return r.json() 547 548 def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]: 549 """ 550 Perform a POST web-request and return the data from the API as a DataFrame (along with 551 the number of rows/tickers that matched your query). 552 553 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 554 this can be very useful if you want to pass your own headers/cookies. 555 556 ### Live/Delayed data 557 558 Note that to get live-data you have to authenticate, which is done by passing your cookies. 559 Have a look in the README at the "Real-Time Data Access" sections. 560 561 :param kwargs: kwargs to pass to `requests.post()` 562 :return: a tuple consisting of: (total_count, dataframe) 563 """ 564 import pandas as pd 565 566 json_obj = self.get_scanner_data_raw(**kwargs) 567 rows_count = json_obj['totalCount'] 568 data = json_obj['data'] 569 570 df = pd.DataFrame( 571 data=([row['s'], *row['d']] for row in data), 572 columns=['ticker', *self.query.get('columns', ())], # pyright: ignore [reportArgumentType] 573 ) 574 return rows_count, df 575 576 def copy(self) -> Query: 577 new = Query() 578 new.query = self.query.copy() 579 return new 580 581 def __repr__(self) -> str: 582 return f'< {pprint.pformat(self.query)}\n url={self.url!r} >' 583 584 def __eq__(self, other) -> bool: 585 return isinstance(other, Query) and self.query == other.query and self.url == other.url 586 587 588# TODO: Query should have no defaults (except limit), and a separate module should have all the 589# default screeners 590# 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 """ 273 self.query['filter2'] = operation['operation'] 274 return self 275 276 def order_by( 277 self, column: Column | str, ascending: bool = True, nulls_first: bool = False 278 ) -> Self: 279 """ 280 Applies sorting to the query results based on the specified column. 281 282 Examples: 283 284 >>> Query().order_by('volume', ascending=False) # sort descending 285 >>> Query().order_by('close', ascending=True) 286 >>> Query().order_by('dividends_yield_current', ascending=False, nulls_first=False) 287 288 :param column: Either a `Column` object or a string with the column name. 289 :param ascending: Set to True for ascending order (default), or False for descending. 290 :param nulls_first: If True, places `None` values at the beginning of the results. Defaults 291 to False. 292 :return: The updated query object. 293 """ 294 dct: SortByDict = { 295 'sortBy': column.name if isinstance(column, Column) else column, 296 'sortOrder': 'asc' if ascending else 'desc', 297 'nullsFirst': nulls_first, 298 } 299 self.query['sort'] = dct 300 return self 301 302 def limit(self, limit: int) -> Self: 303 self.query.setdefault('range', DEFAULT_RANGE.copy())[1] = limit 304 return self 305 306 def offset(self, offset: int) -> Self: 307 self.query.setdefault('range', DEFAULT_RANGE.copy())[0] = offset 308 return self 309 310 def set_markets(self, *markets: str) -> Self: 311 """ 312 This method allows you to select the market/s which you want to query. 313 314 By default, the screener will only scan US equities, but you can change it to scan any 315 market or country, that includes a list of 67 countries, and also the following 316 asset classes: `bonds`, `cfd`, `coin`, `crypto`, `euronext`, `forex`, 317 `futures`, `options`. 318 319 You may choose any value from `tradingview_screener.constants.MARKETS`. 320 321 If you select multiple countries, you might want to 322 323 Examples: 324 325 By default, the screener will show results from the `america` market, but you can 326 change it (note the difference between `market` and `country`) 327 >>> columns = ['close', 'market', 'country', 'currency'] 328 >>> (Query() 329 ... .select(*columns) 330 ... .set_markets('italy') 331 ... .get_scanner_data()) 332 (2346, 333 ticker close market country currency 334 0 MIL:UCG 23.9150 italy Italy EUR 335 1 MIL:ISP 2.4910 italy Italy EUR 336 2 MIL:STLAM 17.9420 italy Netherlands EUR 337 3 MIL:ENEL 6.0330 italy Italy EUR 338 4 MIL:ENI 15.4800 italy Italy EUR 339 .. ... ... ... ... ... 340 45 MIL:UNI 5.1440 italy Italy EUR 341 46 MIL:3OIS 0.4311 italy Ireland EUR 342 47 MIL:3SIL 35.2300 italy Ireland EUR 343 48 MIL:IWDE 69.1300 italy Ireland EUR 344 49 MIL:QQQS 19.2840 italy Ireland EUR 345 [50 rows x 5 columns]) 346 347 You can also select multiple markets 348 >>> (Query() 349 ... .select(*columns) 350 ... .set_markets('america', 'israel', 'hongkong', 'switzerland') 351 ... .get_scanner_data()) 352 (23964, 353 ticker close market country currency 354 0 AMEX:SPY 420.1617 america United States USD 355 1 NASDAQ:TSLA 201.2000 america United States USD 356 2 NASDAQ:NVDA 416.7825 america United States USD 357 3 NASDAQ:AMD 106.6600 america United States USD 358 4 NASDAQ:QQQ 353.7985 america United States USD 359 .. ... ... ... ... ... 360 45 NASDAQ:GOOGL 124.9200 america United States USD 361 46 HKEX:1211 233.2000 hongkong China HKD 362 47 TASE:ALHE 1995.0000 israel Israel ILA 363 48 AMEX:BIL 91.4398 america United States USD 364 49 NASDAQ:GOOG 126.1500 america United States USD 365 [50 rows x 5 columns]) 366 367 You may also select different financial instruments 368 >>> (Query() 369 ... .select('close', 'market') 370 ... .set_markets('cfd', 'crypto', 'forex', 'futures') 371 ... .get_scanner_data()) 372 (118076, 373 ticker ... market 374 0 UNISWAP3ETH:JUSTICEUSDT ... crypto 375 1 UNISWAP3ETH:UAHGUSDT ... crypto 376 2 UNISWAP3ETH:KENDUWETH ... crypto 377 3 UNISWAP3ETH:MATICSTMATIC ... crypto 378 4 UNISWAP3ETH:WETHETHM ... crypto 379 .. ... ... ... 380 45 UNISWAP:MUSICAIWETH_1F5304.USD ... crypto 381 46 CRYPTOCAP:FIL ... cfd 382 47 CRYPTOCAP:SUI ... cfd 383 48 CRYPTOCAP:ARBI ... cfd 384 49 CRYPTOCAP:OP ... cfd 385 [50 rows x 3 columns]) 386 387 :param markets: one or more markets from `tradingview_screener.constants.MARKETS` 388 :return: Self 389 """ 390 if len(markets) == 1: 391 market = markets[0] 392 self.url = URL.format(market=market) 393 self.query['markets'] = [market] 394 else: # len(markets) == 0 or len(markets) > 1 395 self.url = URL.format(market='global') 396 self.query['markets'] = list(markets) 397 398 return self 399 400 def set_tickers(self, *tickers: str) -> Self: 401 """ 402 Set the tickers you wish to receive information on. 403 404 Note that this resets the markets and sets the URL market to `global`. 405 406 Examples: 407 408 >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd') 409 >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data() 410 (1, 411 ticker name market close volume VWAP MACD.macd 412 0 NASDAQ:TSLA TSLA america 186 3519931 185.53 2.371601) 413 414 To set tickers from multiple markets we need to update the markets that include them: 415 >>> (Query() 416 ... .set_markets('america', 'italy', 'vietnam') 417 ... .set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX') 418 ... .get_scanner_data()) 419 (4, 420 ticker name close volume market_cap_basic 421 0 HOSE:VIX VIX 16700.00 33192500 4.568961e+08 422 1 AMEX:SPY SPY 544.35 1883562 NaN 423 2 NYSE:GME GME 23.80 3116758 1.014398e+10 424 3 MIL:RACE RACE 393.30 122878 1.006221e+11) 425 426 :param tickers: One or more tickers, syntax: `exchange:symbol` 427 :return: Self 428 """ 429 self.query.setdefault('symbols', {})['tickers'] = list(tickers) 430 self.set_markets() 431 return self 432 433 def set_index(self, *indexes: str) -> Self: 434 """ 435 Scan only the equities that are in in the given index (or indexes). 436 437 Note that this resets the markets and sets the URL market to `global`. 438 439 Examples: 440 441 >>> Query().set_index('SYML:SP;SPX').get_scanner_data() 442 (503, 443 ticker name close volume market_cap_basic 444 0 NASDAQ:NVDA NVDA 1208.88 41238122 2.973644e+12 445 1 NASDAQ:AAPL AAPL 196.89 53103705 3.019127e+12 446 2 NASDAQ:TSLA TSLA 177.48 56244929 5.660185e+11 447 3 NASDAQ:AMD AMD 167.87 44795240 2.713306e+11 448 4 NASDAQ:MSFT MSFT 423.85 13621485 3.150183e+12 449 5 NASDAQ:AMZN AMZN 184.30 28021473 1.917941e+12 450 6 NASDAQ:META META 492.96 9379199 1.250410e+12 451 7 NASDAQ:GOOGL GOOGL 174.46 19660698 2.164346e+12 452 8 NASDAQ:SMCI SMCI 769.11 3444852 4.503641e+10 453 9 NASDAQ:GOOG GOOG 175.95 14716134 2.164346e+12 454 10 NASDAQ:AVGO AVGO 1406.64 1785876 6.518669e+11) 455 456 You can set multiple indices as well, like the NIFTY 50 and UK 100 Index. 457 >>> Query().set_index('SYML:NSE;NIFTY', 'SYML:TVC;UKX').get_scanner_data() 458 (150, 459 ticker name close volume market_cap_basic 460 0 NSE:INFY INFY 1533.600000 24075302 7.623654e+10 461 1 LSE:AZN AZN 12556.000000 2903032 2.489770e+11 462 2 NSE:HDFCBANK HDFCBANK 1573.350000 18356108 1.432600e+11 463 3 NSE:RELIANCE RELIANCE 2939.899900 9279348 2.381518e+11 464 4 LSE:LSEG LSEG 9432.000000 2321053 6.395329e+10 465 5 NSE:BAJFINANCE BAJFINANCE 7191.399900 2984052 5.329685e+10 466 6 LSE:BARC BARC 217.250000 96238723 4.133010e+10 467 7 NSE:SBIN SBIN 829.950010 25061284 8.869327e+10 468 8 NSE:LT LT 3532.500000 5879660 5.816100e+10 469 9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11) 470 471 You can find the full list of indices in [`constants.INDICES`](constants.html#INDICES), 472 just note that the syntax is 473 `SYML:{source};{symbol}`. 474 475 :param indexes: One or more strings representing the financial indexes to filter by 476 :return: An instance of the `Query` class with the filter applied 477 """ 478 self.query.setdefault('preset', 'index_components_market_pages') 479 self.query.setdefault('symbols', {})['symbolset'] = list(indexes) 480 # reset markets list and URL to `/global` 481 self.set_markets() 482 return self 483 484 # def set_currency(self, currency: Literal['symbol', 'market'] | str) -> Self: 485 # """ 486 # Change the currency of the screener. 487 # 488 # Note that this changes *only* the currency of the columns of type `fundamental_price`, 489 # for example: `market_cap_basic`, `net_income`, `total_debt`. Other columns like `close` and 490 # `Value.Traded` won't change, because they are of a different type. 491 # 492 # This can be particularly useful if you are querying tickers across different markets. 493 # 494 # Examples: 495 # 496 # >>> Query().set_currency('symbol') # convert to symbol currency 497 # >>> Query().set_currency('market') # convert to market currency 498 # >>> Query().set_currency('usd') # or another currency 499 # >>> Query().set_currency('jpy') 500 # >>> Query().set_currency('eur') 501 # """ 502 # # symbol currency -> self.query['price_conversion'] = {'to_symbol': True} 503 # # market currency -> self.query['price_conversion'] = {'to_symbol': False} 504 # # USD or other currency -> self.query['price_conversion'] = {'to_currency': 'usd'} 505 # if currency == 'symbol': 506 # self.query['price_conversion'] = {'to_symbol': True} 507 # elif currency == 'market': 508 # self.query['price_conversion'] = {'to_symbol': False} 509 # else: 510 # self.query['price_conversion'] = {'to_currency': currency} 511 # return self 512 513 def set_property(self, key: str, value: Any) -> Self: 514 self.query[key] = value 515 return self 516 517 def get_scanner_data_raw(self, **kwargs) -> ScreenerDict: 518 """ 519 Perform a POST web-request and return the data from the API (dictionary). 520 521 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 522 this can be very useful if you want to pass your own headers/cookies. 523 524 >>> Query().select('close', 'volume').limit(5).get_scanner_data_raw() 525 { 526 'totalCount': 17559, 527 'data': [ 528 {'s': 'NASDAQ:NVDA', 'd': [116.14, 312636630]}, 529 {'s': 'AMEX:SPY', 'd': [542.04, 52331224]}, 530 {'s': 'NASDAQ:QQQ', 'd': [462.58, 40084156]}, 531 {'s': 'NASDAQ:TSLA', 'd': [207.83, 76247251]}, 532 {'s': 'NASDAQ:SBUX', 'd': [95.9, 157211696]}, 533 ], 534 } 535 """ 536 self.query.setdefault('range', DEFAULT_RANGE.copy()) 537 538 kwargs.setdefault('headers', HEADERS) 539 kwargs.setdefault('timeout', 20) 540 r = requests.post(self.url, json=self.query, **kwargs) 541 542 if not r.ok: 543 # add the body to the error message for debugging purposes 544 r.reason += f'\n Body: {r.text}\n' 545 r.raise_for_status() 546 547 return r.json() 548 549 def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]: 550 """ 551 Perform a POST web-request and return the data from the API as a DataFrame (along with 552 the number of rows/tickers that matched your query). 553 554 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 555 this can be very useful if you want to pass your own headers/cookies. 556 557 ### Live/Delayed data 558 559 Note that to get live-data you have to authenticate, which is done by passing your cookies. 560 Have a look in the README at the "Real-Time Data Access" sections. 561 562 :param kwargs: kwargs to pass to `requests.post()` 563 :return: a tuple consisting of: (total_count, dataframe) 564 """ 565 import pandas as pd 566 567 json_obj = self.get_scanner_data_raw(**kwargs) 568 rows_count = json_obj['totalCount'] 569 data = json_obj['data'] 570 571 df = pd.DataFrame( 572 data=([row['s'], *row['d']] for row in data), 573 columns=['ticker', *self.query.get('columns', ())], # pyright: ignore [reportArgumentType] 574 ) 575 return rows_count, df 576 577 def copy(self) -> Query: 578 new = Query() 579 new.query = self.query.copy() 580 return new 581 582 def __repr__(self) -> str: 583 return f'< {pprint.pformat(self.query)}\n url={self.url!r} >' 584 585 def __eq__(self, other) -> bool: 586 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)
276 def order_by( 277 self, column: Column | str, ascending: bool = True, nulls_first: bool = False 278 ) -> Self: 279 """ 280 Applies sorting to the query results based on the specified column. 281 282 Examples: 283 284 >>> Query().order_by('volume', ascending=False) # sort descending 285 >>> Query().order_by('close', ascending=True) 286 >>> Query().order_by('dividends_yield_current', ascending=False, nulls_first=False) 287 288 :param column: Either a `Column` object or a string with the column name. 289 :param ascending: Set to True for ascending order (default), or False for descending. 290 :param nulls_first: If True, places `None` values at the beginning of the results. Defaults 291 to False. 292 :return: The updated query object. 293 """ 294 dct: SortByDict = { 295 'sortBy': column.name if isinstance(column, Column) else column, 296 'sortOrder': 'asc' if ascending else 'desc', 297 'nullsFirst': nulls_first, 298 } 299 self.query['sort'] = dct 300 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.
310 def set_markets(self, *markets: str) -> Self: 311 """ 312 This method allows you to select the market/s which you want to query. 313 314 By default, the screener will only scan US equities, but you can change it to scan any 315 market or country, that includes a list of 67 countries, and also the following 316 asset classes: `bonds`, `cfd`, `coin`, `crypto`, `euronext`, `forex`, 317 `futures`, `options`. 318 319 You may choose any value from `tradingview_screener.constants.MARKETS`. 320 321 If you select multiple countries, you might want to 322 323 Examples: 324 325 By default, the screener will show results from the `america` market, but you can 326 change it (note the difference between `market` and `country`) 327 >>> columns = ['close', 'market', 'country', 'currency'] 328 >>> (Query() 329 ... .select(*columns) 330 ... .set_markets('italy') 331 ... .get_scanner_data()) 332 (2346, 333 ticker close market country currency 334 0 MIL:UCG 23.9150 italy Italy EUR 335 1 MIL:ISP 2.4910 italy Italy EUR 336 2 MIL:STLAM 17.9420 italy Netherlands EUR 337 3 MIL:ENEL 6.0330 italy Italy EUR 338 4 MIL:ENI 15.4800 italy Italy EUR 339 .. ... ... ... ... ... 340 45 MIL:UNI 5.1440 italy Italy EUR 341 46 MIL:3OIS 0.4311 italy Ireland EUR 342 47 MIL:3SIL 35.2300 italy Ireland EUR 343 48 MIL:IWDE 69.1300 italy Ireland EUR 344 49 MIL:QQQS 19.2840 italy Ireland EUR 345 [50 rows x 5 columns]) 346 347 You can also select multiple markets 348 >>> (Query() 349 ... .select(*columns) 350 ... .set_markets('america', 'israel', 'hongkong', 'switzerland') 351 ... .get_scanner_data()) 352 (23964, 353 ticker close market country currency 354 0 AMEX:SPY 420.1617 america United States USD 355 1 NASDAQ:TSLA 201.2000 america United States USD 356 2 NASDAQ:NVDA 416.7825 america United States USD 357 3 NASDAQ:AMD 106.6600 america United States USD 358 4 NASDAQ:QQQ 353.7985 america United States USD 359 .. ... ... ... ... ... 360 45 NASDAQ:GOOGL 124.9200 america United States USD 361 46 HKEX:1211 233.2000 hongkong China HKD 362 47 TASE:ALHE 1995.0000 israel Israel ILA 363 48 AMEX:BIL 91.4398 america United States USD 364 49 NASDAQ:GOOG 126.1500 america United States USD 365 [50 rows x 5 columns]) 366 367 You may also select different financial instruments 368 >>> (Query() 369 ... .select('close', 'market') 370 ... .set_markets('cfd', 'crypto', 'forex', 'futures') 371 ... .get_scanner_data()) 372 (118076, 373 ticker ... market 374 0 UNISWAP3ETH:JUSTICEUSDT ... crypto 375 1 UNISWAP3ETH:UAHGUSDT ... crypto 376 2 UNISWAP3ETH:KENDUWETH ... crypto 377 3 UNISWAP3ETH:MATICSTMATIC ... crypto 378 4 UNISWAP3ETH:WETHETHM ... crypto 379 .. ... ... ... 380 45 UNISWAP:MUSICAIWETH_1F5304.USD ... crypto 381 46 CRYPTOCAP:FIL ... cfd 382 47 CRYPTOCAP:SUI ... cfd 383 48 CRYPTOCAP:ARBI ... cfd 384 49 CRYPTOCAP:OP ... cfd 385 [50 rows x 3 columns]) 386 387 :param markets: one or more markets from `tradingview_screener.constants.MARKETS` 388 :return: Self 389 """ 390 if len(markets) == 1: 391 market = markets[0] 392 self.url = URL.format(market=market) 393 self.query['markets'] = [market] 394 else: # len(markets) == 0 or len(markets) > 1 395 self.url = URL.format(market='global') 396 self.query['markets'] = list(markets) 397 398 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
400 def set_tickers(self, *tickers: str) -> Self: 401 """ 402 Set the tickers you wish to receive information on. 403 404 Note that this resets the markets and sets the URL market to `global`. 405 406 Examples: 407 408 >>> q = Query().select('name', 'market', 'close', 'volume', 'VWAP', 'MACD.macd') 409 >>> q.set_tickers('NASDAQ:TSLA').get_scanner_data() 410 (1, 411 ticker name market close volume VWAP MACD.macd 412 0 NASDAQ:TSLA TSLA america 186 3519931 185.53 2.371601) 413 414 To set tickers from multiple markets we need to update the markets that include them: 415 >>> (Query() 416 ... .set_markets('america', 'italy', 'vietnam') 417 ... .set_tickers('NYSE:GME', 'AMEX:SPY', 'MIL:RACE', 'HOSE:VIX') 418 ... .get_scanner_data()) 419 (4, 420 ticker name close volume market_cap_basic 421 0 HOSE:VIX VIX 16700.00 33192500 4.568961e+08 422 1 AMEX:SPY SPY 544.35 1883562 NaN 423 2 NYSE:GME GME 23.80 3116758 1.014398e+10 424 3 MIL:RACE RACE 393.30 122878 1.006221e+11) 425 426 :param tickers: One or more tickers, syntax: `exchange:symbol` 427 :return: Self 428 """ 429 self.query.setdefault('symbols', {})['tickers'] = list(tickers) 430 self.set_markets() 431 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
433 def set_index(self, *indexes: str) -> Self: 434 """ 435 Scan only the equities that are in in the given index (or indexes). 436 437 Note that this resets the markets and sets the URL market to `global`. 438 439 Examples: 440 441 >>> Query().set_index('SYML:SP;SPX').get_scanner_data() 442 (503, 443 ticker name close volume market_cap_basic 444 0 NASDAQ:NVDA NVDA 1208.88 41238122 2.973644e+12 445 1 NASDAQ:AAPL AAPL 196.89 53103705 3.019127e+12 446 2 NASDAQ:TSLA TSLA 177.48 56244929 5.660185e+11 447 3 NASDAQ:AMD AMD 167.87 44795240 2.713306e+11 448 4 NASDAQ:MSFT MSFT 423.85 13621485 3.150183e+12 449 5 NASDAQ:AMZN AMZN 184.30 28021473 1.917941e+12 450 6 NASDAQ:META META 492.96 9379199 1.250410e+12 451 7 NASDAQ:GOOGL GOOGL 174.46 19660698 2.164346e+12 452 8 NASDAQ:SMCI SMCI 769.11 3444852 4.503641e+10 453 9 NASDAQ:GOOG GOOG 175.95 14716134 2.164346e+12 454 10 NASDAQ:AVGO AVGO 1406.64 1785876 6.518669e+11) 455 456 You can set multiple indices as well, like the NIFTY 50 and UK 100 Index. 457 >>> Query().set_index('SYML:NSE;NIFTY', 'SYML:TVC;UKX').get_scanner_data() 458 (150, 459 ticker name close volume market_cap_basic 460 0 NSE:INFY INFY 1533.600000 24075302 7.623654e+10 461 1 LSE:AZN AZN 12556.000000 2903032 2.489770e+11 462 2 NSE:HDFCBANK HDFCBANK 1573.350000 18356108 1.432600e+11 463 3 NSE:RELIANCE RELIANCE 2939.899900 9279348 2.381518e+11 464 4 LSE:LSEG LSEG 9432.000000 2321053 6.395329e+10 465 5 NSE:BAJFINANCE BAJFINANCE 7191.399900 2984052 5.329685e+10 466 6 LSE:BARC BARC 217.250000 96238723 4.133010e+10 467 7 NSE:SBIN SBIN 829.950010 25061284 8.869327e+10 468 8 NSE:LT LT 3532.500000 5879660 5.816100e+10 469 9 LSE:SHEL SHEL 2732.500000 7448315 2.210064e+11) 470 471 You can find the full list of indices in [`constants.INDICES`](constants.html#INDICES), 472 just note that the syntax is 473 `SYML:{source};{symbol}`. 474 475 :param indexes: One or more strings representing the financial indexes to filter by 476 :return: An instance of the `Query` class with the filter applied 477 """ 478 self.query.setdefault('preset', 'index_components_market_pages') 479 self.query.setdefault('symbols', {})['symbolset'] = list(indexes) 480 # reset markets list and URL to `/global` 481 self.set_markets() 482 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
517 def get_scanner_data_raw(self, **kwargs) -> ScreenerDict: 518 """ 519 Perform a POST web-request and return the data from the API (dictionary). 520 521 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 522 this can be very useful if you want to pass your own headers/cookies. 523 524 >>> Query().select('close', 'volume').limit(5).get_scanner_data_raw() 525 { 526 'totalCount': 17559, 527 'data': [ 528 {'s': 'NASDAQ:NVDA', 'd': [116.14, 312636630]}, 529 {'s': 'AMEX:SPY', 'd': [542.04, 52331224]}, 530 {'s': 'NASDAQ:QQQ', 'd': [462.58, 40084156]}, 531 {'s': 'NASDAQ:TSLA', 'd': [207.83, 76247251]}, 532 {'s': 'NASDAQ:SBUX', 'd': [95.9, 157211696]}, 533 ], 534 } 535 """ 536 self.query.setdefault('range', DEFAULT_RANGE.copy()) 537 538 kwargs.setdefault('headers', HEADERS) 539 kwargs.setdefault('timeout', 20) 540 r = requests.post(self.url, json=self.query, **kwargs) 541 542 if not r.ok: 543 # add the body to the error message for debugging purposes 544 r.reason += f'\n Body: {r.text}\n' 545 r.raise_for_status() 546 547 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]},
],
}
549 def get_scanner_data(self, **kwargs) -> tuple[int, pd.DataFrame]: 550 """ 551 Perform a POST web-request and return the data from the API as a DataFrame (along with 552 the number of rows/tickers that matched your query). 553 554 Note that you can pass extra keyword-arguments that will be forwarded to `requests.post()`, 555 this can be very useful if you want to pass your own headers/cookies. 556 557 ### Live/Delayed data 558 559 Note that to get live-data you have to authenticate, which is done by passing your cookies. 560 Have a look in the README at the "Real-Time Data Access" sections. 561 562 :param kwargs: kwargs to pass to `requests.post()` 563 :return: a tuple consisting of: (total_count, dataframe) 564 """ 565 import pandas as pd 566 567 json_obj = self.get_scanner_data_raw(**kwargs) 568 rows_count = json_obj['totalCount'] 569 data = json_obj['data'] 570 571 df = pd.DataFrame( 572 data=([row['s'], *row['d']] for row in data), 573 columns=['ticker', *self.query.get('columns', ())], # pyright: ignore [reportArgumentType] 574 ) 575 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)