from ib_insync import *
util.startLoop()  

ib = IB()
ib.connect('127.0.0.1', 7497, clientId=17)

"""
Explanation: Scanners
End of explanation
"""


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])

"""
Explanation: 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:
End of explanation
"""


sub.abovePrice = 200
scanData = ib.reqScannerData(sub)

symbols = [sd.contractDetails.contract.symbol for sd in scanData]
print(symbols)

"""
Explanation: 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:
End of explanation
"""


xml = ib.reqScannerParameters()

print(len(xml), 'bytes')

"""
Explanation: 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:
End of explanation
"""


path = 'scanner_parameters.xml'
with open(path, 'w') as f:
    f.write(xml)

import webbrowser
webbrowser.open(path)

# 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:')
print(tags)

"""
Explanation: To view the XML in a web browser:
End of explanation
"""


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)

"""
Explanation: 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:
End of explanation
"""


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')])

"""
Explanation: 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:
End of explanation
"""


instrumentTypes = set(e.text for e in tree.findall('.//Instrument/type'))
print(instrumentTypes)

"""
Explanation: Queries are not limited to stocks. To get a list of all supported instruments:
End of explanation
"""


locationCodes = [e.text for e in tree.findall('.//locationCode')]
print(locationCodes)

ib.disconnect()

"""
Explanation: To find all location codes:
End of explanation
"""


