from ib_insync import *
util.startLoop()
ib = IB()
ib.connect('127.0.0.1', 7497, clientId=17)
<IB connected to 127.0.0.1:7497 clientId=17>
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:
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])
50 results, first one: ScanData(rank=0, contractDetails=ContractDetails(contract=Contract(secType='STK', conId=326089336, symbol='HCCHR', exchange='SMART', currency='USD', localSymbol='HCCHR', tradingClass='SCM'), marketName='SCM', minTick=0.0, orderTypes='', validExchanges='', priceMagnifier=0, underConId=0, longName='', contractMonth='', industry='', category='', subcategory='', timeZoneId='', tradingHours='', liquidHours='', evRule='', evMultiplier=0, mdSizeMultiplier=0, aggGroup=0, underSymbol='', underSecType='', marketRuleIds='', secIdList=[], realExpirationDate='', lastTradeTime='', stockType='', cusip='', ratings='', descAppend='', bondType='', couponType='', callable=False, putable=False, coupon=0, convertible=False, maturity='', issueDate='', nextOptionDate='', nextOptionType='', nextOptionPartial=False, notes=''), distance='', benchmark='', projection='', legsStr='')
Error 162, reqId 3: Historical Market Data Service error message:API scanner subscription cancelled: 3
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).
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:
sub.abovePrice = 200
scanData = ib.reqScannerData(sub)
symbols = [sd.contractDetails.contract.symbol for sd in scanData]
print(symbols)
['MKTX', 'TPL', 'BIO B', 'TSLA', 'COKE', 'SHOP', 'RNG', 'TYL', 'BIO', 'ALX', 'MCO', 'ANSS', 'AMT', 'BH A', 'SEB', 'GHC', 'MSG', 'ICUI']
Error 162, reqId 4: Historical Market Data Service error message:API scanner subscription cancelled: 4
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
:
xml = ib.reqScannerParameters()
print(len(xml), 'bytes')
1708609 bytes
To view the XML in a web browser:
path = 'scanner_parameters.xml'
with open(path, 'w') as f:
f.write(xml)
import webbrowser
webbrowser.open(path)
True
# 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])
1144 tags, showing first 100: ['priceAbove', 'priceBelow', 'usdPriceAbove', 'usdPriceBelow', 'volumeAbove', 'usdVolumeAbove', 'usdVolumeBelow', 'avgVolumeAbove', 'avgVolumeBelow', 'avgUsdVolumeAbove', 'avgUsdVolumeBelow', 'ihNumSharesInsiderAbove', 'ihNumSharesInsiderBelow', 'ihInsiderOfFloatPercAbove', 'ihInsiderOfFloatPercBelow', 'iiNumSharesInstitutionalAbove', 'iiNumSharesInstitutionalBelow', 'iiInstitutionalOfFloatPercAbove', 'iiInstitutionalOfFloatPercBelow', 'marketCapAbove1e6', 'marketCapBelow1e6', 'moodyRatingAbove', 'moodyRatingBelow', 'spRatingAbove', 'spRatingBelow', 'ratingsRelation', 'bondCreditRating', 'maturityDateAbove', 'maturityDateBelow', 'currencyLike', 'excludeConvertible', 'couponRateAbove', 'couponRateBelow', 'optVolumeAbove', 'optVolumeBelow', 'avgOptVolumeAbove', 'optVolumePCRatioAbove', 'optVolumePCRatioBelow', 'impVolatAbove', 'impVolatBelow', 'impVolatOverHistAbove', 'impVolatOverHistBelow', 'imbalanceAbove', 'imbalanceBelow', 'displayImbalanceAdvRatioAbove', 'displayImbalanceAdvRatioBelow', 'regulatoryImbalanceAbove', 'regulatoryImbalanceBelow', 'displayRegulatoryImbAdvRatioAbove', 'displayRegulatoryImbAdvRatioBelow', 'avgRatingAbove', 'avgRatingBelow', 'numRatingsAbove', 'numRatingsBelow', 'avgPriceTargetAbove', 'avgPriceTargetBelow', 'numPriceTargetsAbove', 'numPriceTargetsBelow', 'avgAnalystTarget2PriceRatioAbove', 'avgAnalystTarget2PriceRatioBelow', 'stkTypes', 'hasOptionsIs', 'leadFutOnly', 'dividendFrdAbove', 'dividendFrdBelow', 'dividendYieldFrdAbove', 'dividendYieldFrdBelow', 'dividendNextDateAbove', 'dividendNextDateBelow', 'dividendNextAmountAbove', 'dividendNextAmountBelow', 'histDividendFrdAbove', 'histDividendFrdBelow', 'histDividendFrdYieldAbove', 'histDividendFrdYieldBelow', 'minGrowthRate', 'maxGrowthRate', 'minPeRatio', 'maxPeRatio', 'minQuickRatio', 'maxQuickRatio', 'minRetnOnEq', 'maxRetnOnEq', 'minPrice2Bk', 'maxPrice2Bk', 'minPrice2TanBk', 'maxPrice2TanBk', 'firstTradeDateAbove', 'firstTradeDateBelow', 'changePercAbove', 'changePercBelow', 'afterHoursChangePercAbove', 'afterHoursChangePercBelow', 'changeOpenPercAbove', 'changeOpenPercBelow', 'openGapPercAbove', 'openGapPercBelow', 'priceRangeAbove', 'priceRangeBelow', 'tradeCountAbove']
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:
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)
['BSGM', 'ARNC', 'FTAI', 'ALIN PRA', 'MDLX', 'CNX', 'IFRX']
Error 162, reqId 5: Historical Market Data Service error message:API scanner subscription cancelled: 5
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:
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')])
531 scan codes, showing the ones starting with "TOP": ['TOP_PERC_GAIN', 'TOP_PERC_LOSE', 'TOP_TRADE_COUNT', 'TOP_TRADE_RATE', 'TOP_PRICE_RANGE', 'TOP_VOLUME_RATE', 'TOP_OPEN_PERC_GAIN', 'TOP_OPEN_PERC_LOSE', 'TOP_AFTER_HOURS_PERC_GAIN', 'TOP_AFTER_HOURS_PERC_LOSE', 'TOP_OPT_IMP_VOLAT_GAIN', 'TOP_OPT_IMP_VOLAT_LOSE', 'TOP_STOCK_BUY_IMBALANCE_ADV_RATIO', 'TOP_STOCK_SELL_IMBALANCE_ADV_RATIO', 'TOP_STOCK_BUY_REG_IMBALANCE_ADV_RATIO', 'TOP_STOCK_SELL_REG_IMBALANCE_ADV_RATIO', 'TOP_PERC_GAIN', 'TOP_PERC_LOSE', 'TOP_TRADE_RATE', 'TOP_PERC_GAIN']
Queries are not limited to stocks. To get a list of all supported instruments:
instrumentTypes = set(e.text for e in tree.findall('.//Instrument/type'))
print(instrumentTypes)
{'FUT.HK', 'SSF.NA', 'IND.EU', 'PORTSTK', 'ETF.EQ.US', 'STOCK.ME', 'BOND.GOVT.NON-US', 'FUT.EU', 'FUT.NA', 'BOND', 'IND.HK', 'IND.US', 'WAR.EU', 'FUT.US', 'STOCK.NA', 'BOND.AGNCY', 'SSF.HK', 'BOND.GOVT', 'STK', 'SSF.EU', 'BOND.CD', 'BOND.MUNI', 'SSF.US', 'NATCOMB', 'Global', 'SLB.US', 'STOCK.EU', 'STOCK.HK', 'ETF.FI.US'}
To find all location codes:
locationCodes = [e.text for e in tree.findall('.//locationCode')]
print(locationCodes)
['STK.US', 'STK.US.MAJOR', 'STK.NYSE', 'STK.AMEX', 'STK.ARCA', 'STK.NASDAQ', 'STK.NASDAQ.NMS', 'STK.NASDAQ.SCM', 'STK.BATS', 'STK.US.MINOR', 'STK.PINK', 'STK.OTCBB', 'ETF.EQ.US', 'ETF.EQ.US.MAJOR', 'ETF.EQ.ARCA', 'ETF.EQ.NASDAQ.NMS', 'ETF.EQ.BATS', 'ETF.FI.US', 'ETF.FI.US.MAJOR', 'ETF.FI.ARCA', 'ETF.FI.NASDAQ.NMS', 'ETF.FI.BATS', 'FUT.US', 'FUT.GLOBEX', 'FUT.ECBOT', 'FUT.IPE', 'FUT.NYBOT', 'FUT.NYMEX', 'FUT.NYSELIFFE', 'FUT.CFE', 'FUT.CFECRYPTO', 'FUT.CMECRYPTO', 'FUT.ICECRYPTO', 'IND.US', 'SSF.US', 'SSF.ONE', 'BOND.WW', 'BOND.US', 'BOND.EU.EURONEXT', 'BOND.CD.US', 'BOND.AGNCY.US', 'BOND.GOVT.US', 'BOND.MUNI.US', 'BOND.GOVT.NON-US', 'BOND.GOVT.US.NON-US', 'BOND.GOVT.EU.EURONEXT', 'BOND.GOVT.HK.SEHK', 'SLB.PREBORROW', 'STK.NA', 'STK.NA.CANADA', 'STK.NA.TSE', 'STK.NA.VENTURE', 'STK.NA.MEXI', 'FUT.NA', 'FUT.NA.CDE', 'FUT.NA.MEXDER', 'SSF.NA', 'SSF.NA.MEXDER', 'STK.EU', 'STK.EU.VSE', 'STK.EU.SBF', 'STK.EU.IBIS', 'STK.EU.IBIS-XETRA', 'STK.EU.IBIS-EUSTARS', 'STK.EU.IBIS-USSTARS', 'STK.EU.IBIS-ETF', 'STK.EU.IBIS-NEWX', 'STK.EU.BVME', 'STK.EU.AEB', 'STK.EU.BM', 'STK.EU.SFB', 'STK.EU.SWISS', 'STK.EU.VIRTX', 'STK.EU.EBS', 'STK.EU.LSE', 'STK.EU.OTHER', 'STK.EU.PRA', 'STK.EU.CPH', 'STK.EU.HEX', 'STK.EU.OSE', 'STK.EU.BVL', 'STK.EU.MOEX', 'FUT.EU', 'FUT.EU.BELFOX', 'FUT.EU.DTB', 'FUT.EU.FTA', 'FUT.EU.EDXNO', 'FUT.EU.IDEM', 'FUT.EU.UK', 'FUT.EU.ICEEU', 'FUT.EU.LMEOTC', 'FUT.EU.MEFFRV', 'FUT.EU.MONEP', 'FUT.EU.OMS', 'FUT.EU.SOFFEX', 'IND.EU', 'IND.EU.BELFOX', 'IND.EU.DTB', 'IND.EU.FTA', 'IND.EU.ICEEU', 'IND.EU.MONEP', 'SSF.EU', 'SSF.EU.DTB', 'SSF.EU.EDXNO', 'SSF.EU.IDEM', 'SSF.EU.ICEEU', 'SSF.EU.MEFFRV', 'SSF.EU.OMS', 'SSF.EU.SOFFEX', 'WAR.EU.ALL', 'STK.ME', 'STK.ME.TASE', 'STK.HK', 'STK.HK.TSE_JPN', 'STK.HK.SEHK', 'STK.HK.SEHKNTL', 'STK.HK.SEHKSZSE', 'STK.HK.ASX', 'STK.HK.NSE', 'STK.HK.SGX', 'FUT.HK', 'FUT.HK.HKFE', 'FUT.HK.KSE', 'FUT.HK.NSE', 'FUT.HK.OSE_JPN', 'FUT.HK.SGX', 'FUT.HK.SNFE', 'IND.HK', 'IND.HK.HKFE', 'IND.HK.KSE', 'IND.HK.NSE', 'IND.HK.OSE_JPN', 'IND.HK.SGX', 'IND.HK.SNFE', 'SSF.HK', 'SSF.HK.HKFE', 'SSF.HK.KSE', 'SSF.HK.NSE', 'SSF.HK.SGX', 'NATCOMB', 'NATCOMB.OPT.US', 'NATCOMB.OPT.AMEX', 'NATCOMB.OPT.CBOE', 'NATCOMB.OPT.ISE', 'NATCOMB.OPT.PHLX', 'NATCOMB.OPT.PSE', 'NATCOMB.GLOBEX', 'BACKTEST']
ib.disconnect()