Market Scanner
Run the TOP_PERC_GAIN scan over US major stocks, filtered to names above $5,
and print the top ten.
What this shows
req_scanner_subscription(req_id, subscription), wheresubscriptionis any object carrying the right attributes. The example defines a small class; the reference client's ownScannerSubscriptionworks too.- Reading
scanner_datarows, each a rank and aContractDetails, untilscanner_data_end. cancel_scanner_subscription(req_id)before disconnecting.
What the subscription object is read for
The scan itself:
| attribute | absent means |
|---|---|
instrument | empty |
locationCode | empty |
scanCode | empty |
numberOfRows | the venue picks |
Then the filters, each read by attribute and sent under the venue's own tag
name: abovePrice, belowPrice, aboveVolume, marketCapAbove,
marketCapBelow, moodyRatingAbove, moodyRatingBelow, spRatingAbove,
spRatingBelow, maturityDateAbove, maturityDateBelow, couponRateAbove,
couponRateBelow, averageOptionVolumeAbove, plus excludeConvertible and
stockTypeFilter.
An attribute you leave off takes its default. One that is present and cannot be read is refused rather than run as a different scan under your request id.
What comes back
One scanner_data callback per row, then scanner_data_end for the request id.
Rows are ranked; the example sorts by rank because it collects them into a list
first.
A scanner subscription keeps answering until it is cancelled. The example cancels; if you leave one running it goes on delivering into a session nobody is reading.
Limits
Which scan codes and locations are valid is the venue's list.
req_scanner_parameters() fetches it, and an unknown code comes back as a
stated refusal on the error callback. When the historical connection drops, a
running scan is told 165 as it drops, at each attempt to bring it back that
fails and once it is back, and is then subscribed again and goes on under its
number, as a gateway does; a 165 ends nothing.
Run it
IB_USERNAME=... IB_PASSWORD=... python examples/hello_scanner.py
Source
"""Recipe: subscribe to TOP_PERC_GAIN scanner, print first 10 results.
Usage:
IB_USERNAME=... IB_PASSWORD=... python examples/hello_scanner.py
"""
import os
import threading
from ib_dx import EClient, EWrapper
class ScannerSubscription:
def __init__(self):
self.instrument = "STK"
self.locationCode = "STK.US.MAJOR"
self.scanCode = "TOP_PERC_GAIN"
self.numberOfRows = 25
self.abovePrice = 5.0
class ScannerWrapper(EWrapper):
def __init__(self):
self.rows = []
self.done = threading.Event()
def scanner_data(self, req_id, rank, contract_details,
distance, benchmark, projection, legs_str):
self.rows.append((rank, contract_details))
def scanner_data_end(self, req_id):
self.done.set()
def error(self, req_id, error_time, code, msg, advanced=""):
if code not in (2104, 2106, 2158):
print(f"[error] {code}: {msg}")
w = ScannerWrapper()
c = EClient(w)
c.connect(
username=os.environ["IB_USERNAME"],
password=os.environ["IB_PASSWORD"],
paper=True,
)
threading.Thread(target=c.run, daemon=True).start()
req_id = 1
print("subscribing TOP_PERC_GAIN, STK.US.MAJOR…")
c.req_scanner_subscription(req_id, ScannerSubscription())
if not w.done.wait(timeout=30):
print(f"(scanner_data_end not received; got {len(w.rows)} rows so far)")
w.rows.sort(key=lambda r: r[0])
print(f"results: {len(w.rows)}")
for rank, d in w.rows[:10]:
print(f" #{rank:<3} {d.contract.symbol:<8} {d.contract.primary_exchange:<6} con_id={d.contract.con_id}")
c.cancel_scanner_subscription(req_id)
c.disconnect()