Example #1
0
    def _get_closest_valid_contract(self,
                                    theoretical_strike,
                                    expiration,
                                    ib,
                                    right='P'):
        """Return valid contract for expiration closest to theoretical_strike"""
        exchange = self.option_asset.chain.exchange
        symbol = self.option_asset.underlying_qc.symbol
        strikes_sorted = sorted(list(self.option_asset.chain.strikes),
                                key=lambda x: abs(x - theoretical_strike))
        ii = 0
        contract = Option(symbol,
                          expiration,
                          strikes_sorted[ii],
                          right,
                          exchange,
                          tradingClass=self.option_asset.trading_class)
        qualified_contract = ib.qualifyContracts(contract)
        while len(qualified_contract) == 0 or ii > 1000:
            ii = ii + 1
            contract = Option(symbol, expiration, strikes_sorted[ii], right,
                              exchange)
            qualified_contract = ib.qualifyContracts(contract)

        # Assertion to break when infinite loop exits after after ii > 1000
        assert len(qualified_contract) > 0, "No valid contracts found"
        return qualified_contract
Example #2
0
    def find_eligible_contracts(self, symbol, right):
        EXCHANGE = 'SMART'
        MAX_STRIKE_OFFSET = 5

        stock = Stock(symbol, EXCHANGE, currency="USD")
        self.ib.qualifyContracts(stock)
        [ticker] = self.ib.reqTickers(stock)
        ticker_value = ticker.marketPrice()
        chains = self.ib.reqSecDefOptParams(stock.symbol, "", stock.secType, stock.conId)
        chain = next(c for c in chains if c.exchange == EXCHANGE)

        def valid_strike(strike):
            if strike % 1 == 0:
                if right == 'C':
                    max_ntm_call_strike = ticker_value + MAX_STRIKE_OFFSET
                    return ticker_value <= strike <= max_ntm_call_strike
                elif right == 'P':
                    min_ntm_put_strike = ticker_value - MAX_STRIKE_OFFSET
                    return min_ntm_put_strike <= strike <= ticker_value
            return False

        strikes = [strike for strike in chain.strikes if valid_strike(strike)]

        # TODO: Remove slicing once contract selection algorithm implemented
        exp_offset = self.config["nope"]["expiry_offset"]
        expirations = sorted(exp for exp in chain.expirations)[exp_offset:exp_offset + 1]

        contracts = [Option(self.SYMBOL, expiration, strike, right, EXCHANGE, tradingClass=self.SYMBOL)
                     for expiration in expirations
                     for strike in strikes]

        return contracts
Example #3
0
def _createOption(underlying, strike, right, expiration):
    option = FuturesOption() if _isFuture(underlying) else Option()
    option.symbol = underlying.symbol
    option.lastTradeDateOrContractMonth = toTWSDateFromDate(expiration)
    option.strike = strike
    option.right = right
    option.exchange = underlying.exchange
    return option
Example #4
0
    def get_closest_valid_twin_contract(qualified_contracts, ib):
        """ Returns call for put (and vice versa) qualified contract
        Will return an error if contract not found"""
        key = lambda x: 'C' if x == 'P' else 'P'
        contracts = [Option(list_elem[0], list_elem[1], list_elem[2], list_elem[3], list_elem[4]) for list_elem \
                     in [[contract.symbol, contract.lastTradeDateOrContractMonth, contract.strike, key(contract.right),
                          contract.exchange] for contract in qualified_contracts]]
        qualified_contract_twins = ib.qualifyContracts(*contracts)

        return qualified_contract_twins
Example #5
0
 def fetch_possible_contract(self, right='C'):
     print('fetch_possible_contract------ 1')
     print(right, ' ', self.symbol)
     o = Option(symbol=self.symbol, right=right, exchange=self.exchange)
     # print('fetch_possible_contract 1: ', self.symbol, ' ', right)
     o_cd = self.ib_.reqContractDetails(o)
     # print('fetch_possible_contract 2: ', self.symbol, ' ', right)
     cs = [j.contract for j in o_cd]
     print('fetch_possible_contract------ 2')
     print('Done: ', right, ' ', self.symbol)
     print('fetch_possible_contract------ 3')
     return cs
Example #6
0
    def find_eligible_contracts(self, symbol, right):
        is_auto_select = self.config["nope"]["contract_auto_select"]
        EXCHANGE = "SMART"
        MAX_STRIKE_OFFSET = 6 if is_auto_select else 11

        stock = Stock(symbol, EXCHANGE, currency="USD")
        self.ib.qualifyContracts(stock)
        [ticker] = self.ib.reqTickers(stock)
        ticker_value = ticker.marketPrice()
        chains = self.ib.reqSecDefOptParams(stock.symbol, "", stock.secType,
                                            stock.conId)
        chain = next(c for c in chains if c.exchange == EXCHANGE)

        def valid_strike(strike):
            if strike % 1 == 0:
                if right == "C":
                    max_ntm_call_strike = ticker_value + MAX_STRIKE_OFFSET
                    min_itm_call_strike = (ticker_value - MAX_STRIKE_OFFSET
                                           if is_auto_select else ticker_value)
                    return min_itm_call_strike <= strike <= max_ntm_call_strike
                elif right == "P":
                    min_ntm_put_strike = ticker_value - MAX_STRIKE_OFFSET
                    max_itm_put_strike = (ticker_value + MAX_STRIKE_OFFSET
                                          if is_auto_select else ticker_value)
                    return min_ntm_put_strike <= strike <= max_itm_put_strike
            return False

        strikes = [strike for strike in chain.strikes if valid_strike(strike)]

        if is_auto_select:
            min_dte = self.config["nope"]["auto_min_dte"]
            expirations = sorted(
                exp for exp in chain.expirations)[min_dte:min_dte + 5]
        else:
            exp_offset = self.config["nope"]["expiry_offset"]
            expirations = sorted(
                exp for exp in chain.expirations)[exp_offset:exp_offset + 1]

        contracts = [
            Option(
                self.SYMBOL,
                expiration,
                strike,
                right,
                EXCHANGE,
                tradingClass=self.SYMBOL,
            ) for expiration in expirations for strike in strikes
        ]

        return contracts
Example #7
0
def get_opt(ib, df):
    '''returns the valid options and pickles them 
    Args:
        (ib) as the active ib object
        (df) datframe with columns und_contract, expiry, strike
    Returns: options dataframe'''

    df = df.reset_index(drop=True)  # reset the index

    und_contract = df.iloc[0].und_contract

    # get the underlying
    df_und = snp_und(ib, und_contract)
    divrate = df_und.divrate.item()  # extract the dividend rate

    # get the ohlc
    df_ohlc = get_ohlc(ib, und_contract, fspath)

    # symbol
    symbol = und_contract.symbol

    undPrice = df_und.undPrice[0]

    # build the puts and calls
    df['right'] = np.where(df.strike < undPrice, 'P', 'C')

    df['dte'] = [get_dte(e) for e in df.expiry]

    df_tgt = filter_kxdte(df, df_ohlc)

    # make the und_contracts
    und_contracts = [
        Option(symbol, expiry, strike, right,
               exchange) for symbol, expiry, strike, right in zip(
                   df_tgt.symbol, df_tgt.expiry, df_tgt.strike, df_tgt.right)
    ]

    qc = [
        ib.qualifyContracts(*und_contracts[i:i + blks])
        for i in range(0, len(und_contracts), blks)
    ]

    qc1 = [q for q1 in qc for q in q1]
    df_qc = util.df(qc1).iloc[:, [2, 3, 4, 5]]
    df_qc.columns = ['symbol', 'expiry', 'strike', 'right']

    df_opt = df_qc.merge(df_tgt, on=list(df_qc), how='inner')
    df_opt['option'] = qc1

    df_und1 = df_und[['symbol', 'undPrice', 'lot', 'margin']].set_index(
        'symbol')  # get respective columns from df_und

    df_opt = df_opt.set_index('symbol').join(
        df_und1)  # join for lot and margin

    # get the standard deviation based on days to expiry
    df_opt = df_opt.assign(stdev=[df_ohlc.iloc[i].stdev for i in df_opt.dte])

    # get the volatality based on days to expiry
    df_opt = df_opt.assign(
        volatility=[df_ohlc.iloc[i].volatility for i in df_opt.dte])

    # high52 and low52 for the underlying
    df_opt = df_opt.assign(hi52=df_ohlc[:252].high.max())
    df_opt = df_opt.assign(lo52=df_ohlc[:252].low.min())
    df_opt.loc[df_opt.right == 'P', 'hi52'] = np.nan
    df_opt.loc[df_opt.right == 'C', 'lo52'] = np.nan

    df_opt.loc[
        df_opt.dte <= 1,
        'dte'] = 2  # Make the dte as 2 for 1 day-to-expiry to prevent bsm divide-by-zero error

    # get the black scholes delta, call and put prices
    bsms = [
        get_bsm(undPrice, strike, dte, rate, volatility, divrate)
        for undPrice, strike, dte, rate, volatility, divrate in zip(
            repeat(undPrice), df_opt.strike, df_opt.dte, repeat(rate),
            df_opt.volatility, repeat(divrate))
    ]

    df_bsm = pd.DataFrame(bsms)

    df_opt = df_opt.reset_index().join(df_bsm)  # join with black-scholes

    df_opt['bsmPrice'] = np.where(df_opt.right == 'P', df_opt.bsmPut,
                                  df_opt.bsmCall)
    df_opt['pop'] = np.where(df_opt.right == 'C', 1 - df_opt.bsmDelta,
                             df_opt.bsmDelta)
    df_opt = df_opt.drop(['bsmCall', 'bsmPut', 'bsmDelta'], axis=1)

    # get the option prices
    cs = list(df_opt.option)

    tickers = [ib.reqTickers(*cs[i:i + 100]) for i in range(0, len(cs), 100)]

    df_opt = df_opt.assign(
        price=[t.marketPrice() for ts in tickers for t in ts])

    df_opt = df_opt.assign(rom=df_opt.price / df_opt.margin * tradingdays /
                           df_opt.dte * df_opt.lot)

    df_opt.to_pickle(fspath + und_contract.symbol + '_opt.pkl')

    return None
Example #8
0
    # ..compute 1 stdev and mask chains within fence
    df_ch = df_ch.assign(sd1=df_ch.undPrice * df_ch.iv *
                         (df_ch.dte / 365).apply(math.sqrt))

    fence_mask = (df_ch.strike > df_ch.undPrice + df_ch.sd1 * CALLSTD) | \
                 (df_ch.strike < df_ch.undPrice - df_ch.sd1 * PUTSTD)

    df_ch = df_ch[fence_mask].reset_index(drop=True)

    # ..identify puts and calls
    df_ch.insert(3, 'right', np.where(df_ch.strike < df_ch.undPrice, 'P', 'C'))

    # .. make the opts raw contract list
    opts = [
        Option(s, e, k, r, x) for s, e, k, r, x in
        zip(df_ch.symbol, df_ch.expiry, df_ch.strike, df_ch.right,
            ['NSE' if MARKET.upper() == 'NSE' else 'SMART'] * len(df_ch))
    ]

    # Qualify the options

    BLK = 100

    optblks = [opts[i:i + BLK] for i in range(0, len(opts), BLK)]

    blkdict = dict()
    todo = set()
    result = set()

    for optblk in optblks:
def get_option_chain(
    ib: IB,
    qualified_contract: Contract,
    expirations: str,
    use_delayed_data=False,
    strike_min=None,
    strike_max=None,
    strike_modulus=None,
    rights=["P", "C"],
) -> pd.DataFrame:
    """
    TODO: Write documentation
    """

    if use_delayed_data:
        ib.reqMarketDataType(3)
    [ticker] = ib.reqTickers(qualified_contract)
    current_price = ticker.marketPrice()
    strike_min = strike_min or current_price * 0.90
    strike_max = strike_max or current_price * 1.10

    chains = ib.reqSecDefOptParams(qualified_contract.symbol, '',
                                   qualified_contract.secType,
                                   qualified_contract.conId)
    chain = next(c for c in chains
                 if c.tradingClass == qualified_contract.symbol
                 and c.exchange == qualified_contract.exchange)
    if strike_modulus:
        strikes = [
            strike for strike in chain.strikes
            if strike_min < strike < strike_max and strike %
            strike_modulus == 0
        ]
    else:
        strikes = [
            strike for strike in chain.strikes
            if strike_min < strike < strike_max
        ]
    contracts = [
        Option(qualified_contract.symbol,
               expiration,
               strike,
               right,
               qualified_contract.exchange,
               tradingClass=qualified_contract.symbol) for right in rights
        for expiration in expirations for strike in strikes
    ]

    if use_delayed_data:
        ib.reqMarketDataType(3)
    ib.qualifyContracts(*contracts)
    contracts = [contract for contract in contracts if contract.multiplier]

    if use_delayed_data:
        ib.reqMarketDataType(3)
    tickers = ib.reqTickers(*contracts)

    d = {
        "Expiration": [
            str(ticker.contract.lastTradeDateOrContractMonth)
            for ticker in tickers
        ],
        "Strike": [ticker.contract.strike for ticker in tickers],
        "Right": [str(ticker.contract.right) for ticker in tickers],
        "Ask": [ticker.ask for ticker in tickers],
        "Multiplier": [int(ticker.contract.multiplier) for ticker in tickers],
    }
    return pd.DataFrame(data=d)
Example #10
0
def test_position_pnl():
    qqq_put = PortfolioItem(
        contract=Option(
            conId=397556522,
            symbol="QQQ",
            lastTradeDateOrContractMonth="20201218",
            strike=300.0,
            right="P",
            multiplier="100",
            primaryExchange="AMEX",
            currency="USD",
            localSymbol="QQQ   201218P00300000",
            tradingClass="QQQ",
        ),
        position=-1.0,
        marketPrice=4.1194396,
        marketValue=-411.94,
        averageCost=222.4293,
        unrealizedPNL=-189.51,
        realizedPNL=0.0,
        account="DU2962946",
    )
    assert round(position_pnl(qqq_put), 2) == -0.85

    spy = PortfolioItem(
        contract=Stock(
            conId=756733,
            symbol="SPY",
            right="0",
            primaryExchange="ARCA",
            currency="USD",
            localSymbol="SPY",
            tradingClass="SPY",
        ),
        position=100.0,
        marketPrice=365.4960022,
        marketValue=36549.6,
        averageCost=368.42,
        unrealizedPNL=-292.4,
        realizedPNL=0.0,
        account="DU2962946",
    )
    assert round(position_pnl(spy), 4) == -0.0079

    spy_call = PortfolioItem(
        contract=Option(
            conId=454208258,
            symbol="SPY",
            lastTradeDateOrContractMonth="20201214",
            strike=373.0,
            right="C",
            multiplier="100",
            primaryExchange="AMEX",
            currency="USD",
            localSymbol="SPY   201214C00373000",
            tradingClass="SPY",
        ),
        position=-1.0,
        marketPrice=0.08,
        marketValue=-8.0,
        averageCost=96.422,
        unrealizedPNL=88.42,
        realizedPNL=0.0,
        account="DU2962946",
    )
    assert round(position_pnl(spy_call), 2) == 0.92

    spy_put = PortfolioItem(
        contract=Option(
            conId=458705534,
            symbol="SPY",
            lastTradeDateOrContractMonth="20210122",
            strike=352.5,
            right="P",
            multiplier="100",
            primaryExchange="AMEX",
            currency="USD",
            localSymbol="SPY   210122P00352500",
            tradingClass="SPY",
        ),
        position=-1.0,
        marketPrice=5.96710015,
        marketValue=-596.71,
        averageCost=528.9025,
        unrealizedPNL=-67.81,
        realizedPNL=0.0,
        account="DU2962946",
    )
    assert round(position_pnl(spy_put), 2) == -0.13
Example #11
0
                    ['symbol', 'dte', 'strike'], ascending=[True, True, True])
                df_calls = df_calls.groupby(['symbol', 'dte']).head(nBand)
            else:
                df_calls = pd.DataFrame([])

            if 'P' in [k for k in gb.indices]:
                df_puts = gb.get_group('P').reset_index(drop=True).sort_values(
                    ['symbol', 'dte', 'strike'], ascending=[True, True, False])
                df_puts = df_puts.groupby(['symbol', 'dte']).head(nBand)
            else:
                df_puts = pd.DataFrame([])

            dfc = pd.concat([df_puts, df_calls]).reset_index(drop=True)

            # qualify the options
            opts = [Option(i.symbol, i.expiry, i.strike, i.right, data[market]['exchange'])
                    for i in dfc[['symbol', 'expiry', 'strike', 'right']].itertuples()]
            qual_opts = [ib.run(qual_contract(ib, opt)) for opt in opts]
            qual_opts = [q for q in qual_opts if q]
            qual_opts = [o for q in qual_opts for o in q]

            # get the qualified option prices
            optPrice = {q.conId: ib.run(
                get_tick(ib, q))[0].marketPrice() for q in qual_opts}

            # get the margins
            mgn_ord = Order(action='SELL', orderType='MKT', totalQuantity=1,
                            whatIf=True)
            optwim = {q.conId: ib.run(get_margin(
                ib, q, mgn_ord)) for q in qual_opts}
Example #12
0
# get SPY option chain
symbol = "SPY"
stock = Stock(symbol, "SMART", currency="USD")
contracts = ib.qualifyContracts(stock)
[ticker] = ib.reqTickers(stock)
tickerValue = ticker.marketPrice()
print(tickerValue)
chains = ib.reqSecDefOptParams(stock.symbol, "", stock.secType, stock.conId)
chain = next(c for c in chains if c.exchange == "SMART")
print(chain)

# get call options for all expirations and strikes within range
strikes = [
    strike for strike in chain.strikes
    if strike % 5 == 0 and tickerValue - 20 < strike < tickerValue + 20
]
contracts = [
    Option(symbol,
           expiration,
           strike,
           "C",
           "SMART",
           tradingClass=chain.tradingClass) for expiration in chain.expirations
    for strike in strikes
]

contracts = ib.qualifyContracts(*contracts)
tickers = ib.reqTickers(*contracts)
print(tickers[0])
Example #13
0
def main(symbol):
    # util.logToConsole(logging.DEBUG)
    util.logToFile('log.txt')

    s = symbol.upper()
    click.echo("Options for {} Loading: ".format(s), nl=False)

    ib = IB()
    ib.connect('127.0.0.1', 7497, clientId=3, readonly=True)

    contract = Stock(s, 'SMART', 'USD')
    ib.qualifyContracts(contract)

    click.echo('Chains ', nl=False)
    chains = ib.reqSecDefOptParams(contract.symbol, '', contract.secType,
                                   contract.conId)
    chain = next(c for c in chains if c.exchange == 'SMART')

    click.echo('Price '.format(s), nl=False)
    ib.reqMarketDataType(1)
    [ticker] = ib.reqTickers(contract)
    value = ticker.marketPrice()

    strikes = [
        strike for strike in chain.strikes
        if value * 0.90 < strike < value * 1.0
    ]
    expirations = sorted(exp for exp in chain.expirations)[:2]
    rights = ['P', 'C']

    click.echo("Option Contracts {}@{} ".format(s, value), nl=False)
    contracts = [
        Option(s, expiration, strike, right, 'SMART', tradingClass=s)
        for right in rights for expiration in expirations for strike in strikes
    ]
    click.echo('Validate ', nl=False)
    contracts = ib.qualifyContracts(*contracts)
    click.echo(len(contracts), nl=False)

    ib.reqMarketDataType(4)
    click.echo(' Ticker')
    tickers = ib.reqTickers(*contracts)
    options = []
    for t in tickers:
        # click.echo(t)
        # calc = ib.calculateOptionPrice(
        #       t.contract, volatility=0.14, underPrice=value)
        # print(calc)
        options.append(OptionData(t))

    df = util.df(options, [
        'symbol', 'lastTradeDateOrContractMonth', 'strike', 'right',
        'marketPrice', 'optionYield', 'timeToExpiration', 'spread', 'bid',
        'ask', 'impliedVol', 'delta', 'gamma', 'vega'
    ])
    click.echo(df)

    currentWeekPut = df[(df['right'] == 'P') &
                        (df['lastTradeDateOrContractMonth'] == expirations[0])]

    click.echo(currentWeekPut.loc[(abs(abs(currentWeekPut.delta) -
                                       0.2)).sort_values().index].head(2))

    ib.disconnect()
Example #14
0
)
chain = next(c for c in chains if c.tradingClass == "SPY" and c.exchange == "SMART")

[ticker] = ib.reqTickers(x)
xValue = ticker.marketPrice()

strikes = [
    strike
    for strike in chain.strikes
    if strike % 5 == 0 and xValue - 2 < strike < xValue + 2
]
expirations = sorted(exp for exp in chain.expirations)[:3]
rights = ["P", "C"]

contracts = [
    Option("SPY", expiration, strike, right, "SMART", tradingClass="SPY")
    for right in rights
    for expiration in expirations
    for strike in strikes
]

contracts = ib.qualifyContracts(*contracts)

# 2) ricevi tickers
# 3) calcola hedge basato su greeks
start = datetime.datetime(2020, 4, 14, 13, 30).replace(tzinfo=pytz.utc)
allticks: List = []
ticks = ib.reqHistoricalTicks(
    contracts[0],
    startDateTime=start,
    endDateTime=None,
Example #15
0
class TestIBInstrumentProvider:
    def setup(self):
        self.ib = MagicMock()
        self.loop = asyncio.get_event_loop()
        self.clock = LiveClock()
        self.logger = LiveLogger(
            loop=self.loop,
            clock=self.clock,
            level_stdout=LogLevel.DEBUG,
        )
        self.provider = InteractiveBrokersInstrumentProvider(
            client=self.ib,
            logger=self.logger,
            config=InstrumentProviderConfig())

    @staticmethod
    def async_return_value(value: object) -> asyncio.Future:
        future: asyncio.Future = asyncio.Future()
        future.set_result(value)
        return future

    @pytest.mark.parametrize(
        "filters, expected",
        [
            (
                {
                    "secType": "STK",
                    "symbol": "AMD",
                    "exchange": "SMART",
                    "currency": "USD"
                },
                Stock("AMD", "SMART", "USD"),
            ),
            (
                {
                    "secType": "STK",
                    "symbol": "INTC",
                    "exchange": "SMART",
                    "primaryExchange": "NASDAQ",
                    "currency": "USD",
                },
                Stock("INTC", "SMART", "USD", primaryExchange="NASDAQ"),
            ),
            (
                {
                    "secType": "CASH",
                    "symbol": "EUR",
                    "currency": "USD",
                    "exchange": "IDEALPRO"
                },
                Forex(symbol="EUR", currency="USD"),
            ),  # EUR/USD,
            ({
                "secType": "CFD",
                "symbol": "IBUS30"
            }, CFD("IBUS30")),
            (
                {
                    "secType": "FUT",
                    "symbol": "ES",
                    "exchange": "GLOBEX",
                    "lastTradeDateOrContractMonth": "20180921",
                },
                Future("ES", "20180921", "GLOBEX"),
            ),
            (
                {
                    "secType": "OPT",
                    "symbol": "SPY",
                    "exchange": "SMART",
                    "lastTradeDateOrContractMonth": "20170721",
                    "strike": 240,
                    "right": "C",
                },
                Option("SPY", "20170721", 240, "C", "SMART"),
            ),
            (
                {
                    "secType": "BOND",
                    "secIdType": "ISIN",
                    "secId": "US03076KAA60"
                },
                Bond(secIdType="ISIN", secId="US03076KAA60"),
            ),
            (
                {
                    "secType": "CRYPTO",
                    "symbol": "BTC",
                    "exchange": "PAXOS",
                    "currency": "USD"
                },
                Crypto("BTC", "PAXOS", "USD"),
            ),
        ],
    )
    def test_parse_contract(self, filters, expected):
        result = self.provider._parse_contract(**filters)
        fields = [
            f.name for f in expected.__dataclass_fields__.values()
            if getattr(expected, f.name)
        ]
        for f in fields:
            assert getattr(result, f) == getattr(expected, f)

    @pytest.mark.asyncio
    async def test_load_equity_contract_instrument(self, mocker):
        # Arrange
        instrument_id = InstrumentId.from_str("AAPL.NASDAQ")
        contract = IBTestStubs.contract(symbol="AAPL")
        contract_details = IBTestStubs.contract_details("AAPL")
        mocker.patch.object(
            self.provider._client,
            "reqContractDetailsAsync",
            return_value=self.async_return_value([contract_details]),
        )
        mocker.patch.object(
            self.provider._client,
            "qualifyContractsAsync",
            return_value=self.async_return_value([contract]),
        )

        # Act
        await self.provider.load(secType="STK",
                                 symbol="AAPL",
                                 exchange="NASDAQ")
        equity = self.provider.find(instrument_id)

        # Assert
        assert InstrumentId(symbol=Symbol("AAPL"),
                            venue=Venue("NASDAQ")) == equity.id
        assert equity.asset_class == AssetClass.EQUITY
        assert equity.asset_type == AssetType.SPOT
        assert 100 == equity.multiplier
        assert Price.from_str("0.01") == equity.price_increment
        assert 2, equity.price_precision

    @pytest.mark.asyncio
    async def test_load_futures_contract_instrument(self, mocker):
        # Arrange
        instrument_id = InstrumentId.from_str("CLZ2.NYMEX")
        contract = IBTestStubs.contract(symbol="CLZ2", exchange="NYMEX")
        contract_details = IBTestStubs.contract_details("CLZ2")
        mocker.patch.object(
            self.provider._client,
            "reqContractDetailsAsync",
            return_value=self.async_return_value([contract_details]),
        )
        mocker.patch.object(
            self.provider._client,
            "qualifyContractsAsync",
            return_value=self.async_return_value([contract]),
        )

        # Act
        await self.provider.load(symbol="CLZ2", exchange="NYMEX")
        future = self.provider.find(instrument_id)

        # Assert
        assert future.id == instrument_id
        assert future.asset_class == AssetClass.INDEX
        assert future.multiplier == 1000
        assert future.price_increment == Price.from_str("0.01")
        assert future.price_precision == 2

    @pytest.mark.asyncio
    async def test_load_options_contract_instrument(self, mocker):
        # Arrange
        instrument_id = InstrumentId.from_str("AAPL211217C00160000.SMART")
        contract = IBTestStubs.contract(secType="OPT",
                                        symbol="AAPL211217C00160000",
                                        exchange="NASDAQ")
        contract_details = IBTestStubs.contract_details("AAPL211217C00160000")
        mocker.patch.object(
            self.provider._client,
            "reqContractDetailsAsync",
            return_value=self.async_return_value([contract_details]),
        )
        mocker.patch.object(
            self.provider._client,
            "qualifyContractsAsync",
            return_value=self.async_return_value([contract]),
        )

        # Act
        await self.provider.load(secType="OPT",
                                 symbol="AAPL211217C00160000",
                                 exchange="SMART")
        option = self.provider.find(instrument_id)

        # Assert
        assert option.id == instrument_id
        assert option.asset_class == AssetClass.EQUITY
        assert option.multiplier == 100
        assert option.expiry_date == datetime.date(2021, 12, 17)
        assert option.strike_price == Price.from_str("160.0")
        assert option.kind == OptionKind.CALL
        assert option.price_increment == Price.from_str("0.01")
        assert option.price_precision == 2

    @pytest.mark.asyncio
    async def test_load_forex_contract_instrument(self, mocker):
        # Arrange
        instrument_id = InstrumentId.from_str("EUR/USD.IDEALPRO")
        contract = IBTestStubs.contract(secType="CASH",
                                        symbol="EURUSD",
                                        exchange="IDEALPRO")
        contract_details = IBTestStubs.contract_details("EURUSD")
        mocker.patch.object(
            self.provider._client,
            "reqContractDetailsAsync",
            return_value=self.async_return_value([contract_details]),
        )
        mocker.patch.object(
            self.provider._client,
            "qualifyContractsAsync",
            return_value=self.async_return_value([contract]),
        )

        # Act
        await self.provider.load(secType="CASH",
                                 symbol="EURUSD",
                                 exchange="IDEALPRO")
        fx = self.provider.find(instrument_id)

        # Assert
        assert fx.id == instrument_id
        assert fx.asset_class == AssetClass.FX
        assert fx.multiplier == 1
        assert fx.price_increment == Price.from_str("0.00005")
        assert fx.price_precision == 5

    @pytest.mark.asyncio
    async def test_contract_id_to_instrument_id(self, mocker):
        # Arrange
        contract = IBTestStubs.contract(symbol="CLZ2", exchange="NYMEX")
        contract_details = IBTestStubs.contract_details("CLZ2")
        mocker.patch.object(
            self.provider._client,
            "qualifyContractsAsync",
            return_value=self.async_return_value([contract]),
        )
        mocker.patch.object(
            self.provider._client,
            "reqContractDetailsAsync",
            return_value=self.async_return_value([contract_details]),
        )

        # Act
        await self.provider.load(symbol="CLZ2", exchange="NYMEX")

        # Assert
        expected = {138979238: InstrumentId.from_str("CLZ2.NYMEX")}
        assert self.provider.contract_id_to_instrument_id == expected

    def test_filters(self):
        pass