#!/usr/bin/env python # coding: utf-8 # # Scanners # In[1]: from ib_insync import * util.startLoop() ib = IB() ib.connect('127.0.0.1', 7497, clientId=17) # ## Basic Scanner # # To create a scanner, create a `ScannerSubscription` to submit to the `reqScannerData` method. For any scanner to work, at least these three fields must be filled: `instrument` (the what), `locationCode` (the where), and `scanCode` (the ranking). # # For example, to find the top ranked US stock percentage gainers: # In[2]: sub = ScannerSubscription( instrument='STK', locationCode='STK.US.MAJOR', scanCode='TOP_PERC_GAIN') scanData = ib.reqScannerData(sub) print(f'{len(scanData)} results, first one:') print(scanData[0]) # *The displayed error 162 can be ignored* # # The scanner returns a list of contract details, without current market data (this can be obtained via seperate market data requests). # # ## Filtering scanner results, the old way # # The `ScannerSubscription` object has addional parameters that can be set to filter the results, such as `abovePrice`, `aboveVolume`, `marketCapBelow` or `spRatingAbove`. # # For example, to reuse the previous `sub` and query only for stocks with a price above 200 dollar: # In[3]: sub.abovePrice = 200 scanData = ib.reqScannerData(sub) symbols = [sd.contractDetails.contract.symbol for sd in scanData] print(symbols) # ## Filtering, the new way # # In the new way there is a truly vast number of parameters available to use for filtering. # These new scanner parameters map directly to the options available through the TWS "Advanced Market Scanner." The parameters # are dynamically available from a huge XML document that is returned by ``reqScannerParameters``: # In[4]: xml = ib.reqScannerParameters() print(len(xml), 'bytes') # To view the XML in a web browser: # In[5]: path = 'scanner_parameters.xml' with open(path, 'w') as f: f.write(xml) import webbrowser webbrowser.open(path) # In[6]: # parse XML document import xml.etree.ElementTree as ET tree = ET.fromstring(xml) # find all tags that are available for filtering tags = [elem.text for elem in tree.findall('.//AbstractField/code')] print(len(tags), 'tags, showing first 100:') print(tags[:100]) # Notice how ``abovePrice`` is now called ``priceAbove``... # # Using three of these filter tags, let's perform a query to find all US stocks that went up 20% and have a current price between 5 and 50 dollar, sorted by percentage gain: # In[7]: sub = ScannerSubscription( instrument='STK', locationCode='STK.US.MAJOR', scanCode='TOP_PERC_GAIN') tagValues = [ TagValue("changePercAbove", "20"), TagValue('priceAbove', 5), TagValue('priceBelow', 50)] # the tagValues are given as 3rd argument; the 2nd argument must always be an empty list # (IB has not documented the 2nd argument and it's not clear what it does) scanData = ib.reqScannerData(sub, [], tagValues) symbols = [sd.contractDetails.contract.symbol for sd in scanData] print(symbols) # Any scanner query that TWS can do can alse be done through the API. The `scanCode` parameter maps directly to the "Parameter" window in the TWS "Advanced Market Scanner." We can verify this by printing out the `scanCode` values available: # In[8]: scanCodes = [e.text for e in tree.findall('.//scanCode')] print(len(scanCodes), 'scan codes, showing the ones starting with "TOP":') print([sc for sc in scanCodes if sc.startswith('TOP')]) # Queries are not limited to stocks. To get a list of all supported instruments: # In[9]: instrumentTypes = set(e.text for e in tree.findall('.//Instrument/type')) print(instrumentTypes) # To find all location codes: # In[10]: locationCodes = [e.text for e in tree.findall('.//locationCode')] print(locationCodes) # In[11]: ib.disconnect()