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), where subscription is any object carrying the right attributes. The example defines a small class; the reference client's own ScannerSubscription works too.
  • Reading scanner_data rows, each a rank and a ContractDetails, until scanner_data_end.
  • cancel_scanner_subscription(req_id) before disconnecting.

What the subscription object is read for

The scan itself:

attributeabsent means
instrumentempty
locationCodeempty
scanCodeempty
numberOfRowsthe 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()