def hasBidAsk(self) -> bool: """ See if this ticker has a valid bid and ask. """ return ( self.bid != -1 and not isNan(self.bid) and self.bidSize > 0 and self.ask != -1 and not isNan(self.ask) and self.askSize > 0)
def exit_positions(self): portfolio = self.get_portfolio() trades = self.get_trades() curr_date, curr_dt = get_datetime_for_logging() if self._nope_value > self.config["nope"]["long_exit"]: held_calls = self.get_held_contracts(portfolio, 'C') existing_call_order_ids = self.get_existing_order_ids(trades, 'C', 'SELL') remaining_calls = list(filter(lambda c: c['contract'].conId not in existing_call_order_ids, held_calls)) if len(remaining_calls) > 0: remaining_calls.sort(key=lambda c: c['contract'].conId) remaining_call_contracts = [c['contract'] for c in remaining_calls] qualified_contracts = self.ib.qualifyContracts(*remaining_call_contracts) tickers = self.ib.reqTickers(*qualified_contracts) tickers.sort(key=lambda t: t.contract.conId) for idx, ticker in enumerate(tickers): price = midpoint_or_market_price(ticker) if not util.isNan(price): quantity = remaining_calls[idx]['position'] order = LimitOrder("SELL", quantity, price, algoStrategy="Adaptive", algoParams=[TagValue(tag='adaptivePriority', value='Normal')], tif="DAY") call_contract = ticker.contract self.wait_for_trade_submitted(self.ib.placeOrder(call_contract, order)) with open(f"logs/{curr_date}-trade.txt", "a") as f: f.write(f'Sold {quantity} {call_contract.strike}C{call_contract.lastTradeDateOrContractMonth} ({remaining_calls[idx]["avg"]} average) for {price * 100} each, {self._nope_value} | {self._underlying_price} | {curr_dt}\n') else: with open("logs/errors.txt", "a") as f: f.write(f'Error selling call at {self._nope_value} | {self._underlying_price} | {curr_dt}\n') if self._nope_value < self.config["nope"]["short_exit"]: held_puts = self.get_held_contracts(portfolio, 'P') existing_put_order_ids = self.get_existing_order_ids(trades, 'P', 'SELL') remaining_puts = list(filter(lambda c: c['contract'].conId not in existing_put_order_ids, held_puts)) if len(remaining_puts) > 0: remaining_puts.sort(key=lambda c: c['contract'].conId) remaining_put_contracts = [c['contract'] for c in remaining_puts] qualified_contracts = self.ib.qualifyContracts(*remaining_put_contracts) tickers = self.ib.reqTickers(*qualified_contracts) tickers.sort(key=lambda t: t.contract.conId) for idx, ticker in enumerate(tickers): price = midpoint_or_market_price(ticker) if not util.isNan(price): quantity = remaining_puts[idx]['position'] order = LimitOrder("SELL", quantity, price, algoStrategy="Adaptive", algoParams=[TagValue(tag='adaptivePriority', value='Normal')], tif="DAY") put_contract = ticker.contract self.wait_for_trade_submitted(self.ib.placeOrder(put_contract, order)) with open(f"logs/{curr_date}-trade.txt", "a") as f: f.write(f'Sold {quantity} {put_contract.strike}P{put_contract.lastTradeDateOrContractMonth} ({remaining_puts[idx]["avg"]} average) for {price * 100} each, {self._nope_value} | {self._underlying_price} | {curr_dt}\n') else: with open("logs/errors.txt", "a") as f: f.write(f'Error selling put at {self._nope_value} | {self._underlying_price} | {curr_dt}\n')
def enter_positions(self): portfolio = self.get_portfolio() trades = self.get_trades() curr_date, curr_dt = get_datetime_for_logging() if self._nope_value < self.config["nope"]["long_enter"]: held_calls = self.get_total_position(portfolio, 'C') existing_order_quantity = self.get_total_buys(trades, 'C') total_buys = held_calls + existing_order_quantity if total_buys < self.config["nope"]["call_limit"]: contracts = self.find_eligible_contracts(self.SYMBOL, 'C') # TODO: Implement contract selection from eligible candidiates contract_to_buy = contracts[self.config["nope"]["call_strike_offset"]] qualified_contracts = self.ib.qualifyContracts(contract_to_buy) tickers = self.ib.reqTickers(*qualified_contracts) if len(tickers) > 0: price = midpoint_or_market_price(tickers[0]) call_contract = qualified_contracts[0] if not util.isNan(price): quantity = self.config["nope"]["call_quantity"] order = LimitOrder('BUY', quantity, price, algoStrategy="Adaptive", algoParams=[TagValue(tag='adaptivePriority', value='Normal')], tif="DAY") self.wait_for_trade_submitted(self.ib.placeOrder(call_contract, order)) with open(f"logs/{curr_date}-trade.txt", "a") as f: f.write(f'Bought {quantity} {call_contract.strike}C{call_contract.lastTradeDateOrContractMonth} for {price * 100} each, {self._nope_value} | {self._underlying_price} | {curr_dt}\n') else: with open("logs/errors.txt", "a") as f: f.write(f'Error buying call at {self._nope_value} | {self._underlying_price} | {curr_dt}\n') elif self._nope_value > self.config["nope"]["short_enter"]: held_puts = self.get_total_position(portfolio, 'P') existing_order_quantity = self.get_total_buys(trades, 'P') total_buys = held_puts + existing_order_quantity if total_buys < self.config["nope"]["put_limit"]: contracts = self.find_eligible_contracts(self.SYMBOL, 'P') # TODO: Implement contract selection from eligible candidates contract_to_buy = contracts[-self.config["nope"]["put_strike_offset"] - 1] qualified_contracts = self.ib.qualifyContracts(contract_to_buy) tickers = self.ib.reqTickers(*qualified_contracts) if len(tickers) > 0: price = midpoint_or_market_price(tickers[0]) put_contract = qualified_contracts[0] if not util.isNan(price): quantity = self.config["nope"]["put_quantity"] order = LimitOrder('BUY', quantity, price, algoStrategy="Adaptive", algoParams=[TagValue(tag='adaptivePriority', value='Normal')], tif="DAY") self.wait_for_trade_submitted(self.ib.placeOrder(put_contract, order)) with open(f"logs/{curr_date}-trade.txt", "a") as f: f.write(f'Bought {quantity} {put_contract.strike}P{put_contract.lastTradeDateOrContractMonth} for {price * 100} each, {self._nope_value} | {self._underlying_price} | {curr_dt}\n') else: with open("logs/errors.txt", "a") as f: f.write(f'Error buying put at {self._nope_value} | {self._underlying_price} | {curr_dt}\n')
def midpoint_or_market_price(ticker): # As per the ib_insync docs, marketPrice returns the last price first, but # we often prefer the midpoint over the last price. This function pulls the # midpoint first, then falls back to marketPrice() if midpoint is nan. if util.isNan(ticker.midpoint()): if util.isNan(ticker.marketPrice()): # Fallback to the model price return ticker.modelGreeks.optPrice else: return ticker.marketPrice() return ticker.midpoint()
def marketPrice(self): """ Return the first available one of * last price * average of bid and ask * close price """ price = self.last if isNan(price): price = (self.bid + self.ask) / 2 if isNan(price): price = self.close return price
def buy_contracts(self, right): action = "BUY" contracts = self.find_eligible_contracts(self.SYMBOL, right) ticker = self.select_contract(contracts, right) if ticker is not None: price = midpoint_or_market_price(ticker) quantity = (self.config["nope"]["call_quantity"] if right == "C" else self.config["nope"]["put_quantity"]) if not util.isNan(price) and self.check_acc_balance( price, quantity): contract = ticker.contract order = LimitOrder( action, quantity, price, algoStrategy="Adaptive", algoParams=[ TagValue(tag="adaptivePriority", value="Normal") ], tif="DAY", ) self.cancel_order_type("SELL", "STP") trade = self.ib.placeOrder(contract, order) trade.filledEvent += log_fill trade.filledEvent += self.on_buy_fill self.log_order(contract, quantity, price, action) else: with open("logs/errors.txt", "a") as f: f.write( f"Error buying {right} at {self._nope_value} | {self._underlying_price}\n" )
def marketPrice(self): """ Return the first available one of * last price if within current bid/ask; * average of bid and ask (midpoint); * close price. """ midpoint = (self.bid + self.ask) / 2 price = self.last if (isNan(midpoint) or self.bid <= self.last <= self.ask) else nan if isNan(price): price = midpoint if isNan(price) or price == -1: price = self.close return price
def buy_contracts(self, right): action = "BUY" contracts = self.find_eligible_contracts(self.SYMBOL, right) # TODO: Improve contract selection https://github.com/ajhpark/ib_nope/issues/21 offset = (self.config["nope"]["call_strike_offset"] if right == "C" else -self.config["nope"]["put_strike_offset"] - 1) contract_to_buy = contracts[offset] qualified_contracts = self.ib.qualifyContracts(contract_to_buy) tickers = self.ib.reqTickers(*qualified_contracts) if len(tickers) > 0: price = midpoint_or_market_price(tickers[0]) if not util.isNan(price): contract = qualified_contracts[0] quantity = (self.config["nope"]["call_quantity"] if right == "C" else self.config["nope"]["put_quantity"]) order = LimitOrder( action, quantity, price, algoStrategy="Adaptive", algoParams=[ TagValue(tag="adaptivePriority", value="Normal") ], tif="DAY", ) trade = self.ib.placeOrder(contract, order) trade.filledEvent += log_fill self.log_order(contract, quantity, price, action) else: with open("logs/errors.txt", "a") as f: f.write( f"Error buying {right} at {self._nope_value} | {self._underlying_price}\n" )
def open_interest_is_valid(ticker): ticker = self.ib.reqMktData(ticker.contract, genericTickList="101") while util.isNan(ticker.putOpenInterest) or util.isNan( ticker.callOpenInterest): self.ib.waitOnUpdate(timeout=2) self.ib.cancelMktData(ticker.contract) # The open interest value is never present when using historical # data, so just ignore it when the value is None if right.startswith("P"): return (ticker.putOpenInterest >= self.config["target"]["minimum_open_interest"]) if right.startswith("C"): return (ticker.callOpenInterest >= self.config["target"]["minimum_open_interest"])
def delta_is_valid(ticker): return ( ticker.modelGreeks and not util.isNan(ticker.modelGreeks.delta) and ticker.modelGreeks.delta is not None and abs(ticker.modelGreeks.delta) <= get_target_delta(self.config, symbol, right) )
def _on_timer(self, time): if self.bars: bar = self.bars[-1] if isNan(bar.close) and len(self.bars) > 1: bar.open = bar.high = bar.low = bar.close = \ self.bars[-2].close self.bars.updateEvent.emit(self.bars, True) self.emit(bar) self.bars.append(Bar(time))
def wait_for_market_price(self, ticker): try: while_n_times( lambda: util.isNan(ticker.marketPrice()), lambda: self.ib.waitOnUpdate(timeout=5), 25, ) except: return False return True
def wait_for_market_price(self, ticker): try: wait_n_seconds( lambda: util.isNan(ticker.marketPrice()), lambda: self.ib.waitOnUpdate(timeout=15), 60, ) except: return False return True
def wait_for_midpoint_price(self, ticker): try: wait_n_seconds( lambda: util.isNan(ticker.midpoint()), lambda: self.ib.waitOnUpdate(timeout=15), 60, ) except RuntimeError: return False return True
def tickSize(self, reqId: int, tickType: int, size: float): ticker = self.reqId2Ticker.get(reqId) if not ticker: self._logger.error(f'tickSize: Unknown reqId: {reqId}') return price = -1.0 # https://interactivebrokers.github.io/tws-api/tick_types.html if tickType in (0, 69): if size == ticker.bidSize: return price = ticker.bid ticker.prevBidSize = ticker.bidSize ticker.bidSize = size elif tickType in (3, 70): if size == ticker.askSize: return price = ticker.ask ticker.prevAskSize = ticker.askSize ticker.askSize = size elif tickType in (5, 71): price = ticker.last if isNan(price): return if size != ticker.lastSize: ticker.prevLastSize = ticker.lastSize ticker.lastSize = size elif tickType in (8, 74): ticker.volume = size elif tickType == 21: ticker.avVolume = size elif tickType == 27: ticker.callOpenInterest = size elif tickType == 28: ticker.putOpenInterest = size elif tickType == 29: ticker.callVolume = size elif tickType == 30: ticker.putVolume = size elif tickType == 34: ticker.auctionVolume = size elif tickType == 36: ticker.auctionImbalance = size elif tickType == 61: ticker.regulatoryImbalance = size elif tickType == 86: ticker.futuresOpenInterest = size elif tickType == 87: ticker.avOptionVolume = size elif tickType == 89: ticker.shortableShares = size if price or size: tick = TickData(self.lastTime, tickType, price, size) ticker.ticks.append(tick) ticker.marketDataType = self.reqId2MarketDataType.get(reqId, 0) self.pendingTickers.add(ticker)
def on_source(self, time, price, size): if not self.bars: return bar = self.bars[-1] if isNan(bar.open): bar.open = bar.high = bar.low = price bar.high = max(bar.high, price) bar.low = min(bar.low, price) bar.close = price bar.volume += size bar.count += 1 self.bars.updateEvent.emit(self.bars, False)
def __repr__(self): attrs = {} for k, d in self.__class__.defaults.items(): v = getattr(self, k) if v != d and not isNan(v): attrs[k] = v if self.ticks: # ticks can grow too large to display attrs.pop('ticks') clsName = self.__class__.__name__ kwargs = ', '.join(f'{k}={v!r}' for k, v in attrs.items()) return f'{clsName}({kwargs})'
def get_ticker_list_for(self, contracts): ticker_list = self.ib.reqTickers(*contracts) try: wait_n_seconds( lambda: any([util.isNan(t.midpoint()) for t in ticker_list]), lambda: self.ib.waitOnUpdate(timeout=15), API_RESPONSE_WAIT_TIME, ) except RuntimeError: pass return ticker_list
def marketPrice(self) -> float: """ Return the first available one of * last price if within current bid/ask; * average of bid and ask (midpoint); * close price. """ price = self.last if ( self.hasBidAsk() and self.bid <= self.last <= self.ask) else \ self.midpoint() if isNan(price): price = self.close return price
def sell_held_contracts(self, right): portfolio = self.get_portfolio() trades = self.get_trades() action = "SELL" held_contracts_info = self.get_held_contracts_info(portfolio, right) existing_contract_order_ids = self.get_existing_order_ids( trades, right, action) remaining_contracts_info = list( filter( lambda c: c["contract"].conId not in existing_contract_order_ids, held_contracts_info, )) if len(remaining_contracts_info) > 0: remaining_contracts_info.sort(key=lambda c: c["contract"].conId) remaining_contracts = [ c["contract"] for c in remaining_contracts_info ] qualified_contracts = self.ib.qualifyContracts( *remaining_contracts) tickers = self.ib.reqTickers(*qualified_contracts) tickers.sort(key=lambda t: t.contract.conId) for idx, ticker in enumerate(tickers): price = midpoint_or_market_price(ticker) avg = remaining_contracts_info[idx]["avg"] if not util.isNan(price): quantity = remaining_contracts_info[idx]["position"] order = LimitOrder( action, quantity, price, algoStrategy="Adaptive", algoParams=[ TagValue(tag="adaptivePriority", value="Normal") ], tif="DAY", ) contract = ticker.contract trade = self.ib.placeOrder(contract, order) trade.filledEvent += log_fill self.log_order(contract, quantity, price, action, avg) else: with open("logs/errors.txt", "a") as f: f.write( f"Error selling {right} at {self._nope_value} | {self._underlying_price}\n" )
def tickSize(self, reqId, tickType, size): ticker = self.reqId2Ticker.get(reqId) if not ticker: self._logger.error(f'tickSize: Unknown reqId: {reqId}') return ticker.time = self.lastTime price = -1.0 # https://interactivebrokers.github.io/tws-api/tick_types.html if tickType in (0, 69): price = ticker.bid if size != ticker.bidSize: ticker.prevBidSize = ticker.bidSize ticker.bidSize = size elif tickType in (3, 70): price = ticker.ask if size != ticker.askSize: ticker.prevAskSize = ticker.askSize ticker.askSize = size elif tickType in (5, 71): price = ticker.last if util.isNan(price): return if size != ticker.lastSize: ticker.prevLastSize = ticker.lastSize ticker.lastSize = size elif tickType in (8, 74): ticker.volume = size elif tickType == 21: ticker.avVolume = size elif tickType == 27: ticker.callOpenInterest = size elif tickType == 28: ticker.putOpenInterest = size elif tickType == 29: ticker.callVolume = size elif tickType == 30: ticker.putVolume = size elif tickType == 86: ticker.futuresOpenInterest = size if price or size: tick = TickData(self.lastTime, tickType, price, size) ticker.ticks.append(tick) self.pendingTickers.add(ticker)
def _get_market_prices(ib, contracts): # tickers = ib.reqTickers(*contracts) # Alternative to get live tickers for contract in contracts: ib.reqMktData(contract, '', False, False) # print('Waiting for tickers') ib.sleep(1) tickers = [ib.ticker(contract) for contract in contracts] # print(tickers) mkt_prices = [ ticker.last if ticker.marketPrice() == ticker.close else ticker.marketPrice() for ticker in tickers ] if any([True for item in mkt_prices if isNan(item)]): mkt_prices = [ticker.marketPrice() for ticker in tickers] return mkt_prices
def find_eligible_contracts( self, symbol, primary_exchange, right, strike_limit, exclude_expirations_before=None, exclude_first_exp_strike=None, ): click.echo() click.secho( f"Searching option chain for symbol={symbol} " f"right={right}, this can take a while...", fg="green", ) click.echo() stock = Stock(symbol, "SMART", currency="USD", primaryExchange=primary_exchange) self.ib.qualifyContracts(stock) ticker = self.get_ticker_for(stock) tickerValue = ticker.marketPrice() chains = self.get_chains_for_stock(stock) chain = next(c for c in chains if c.exchange == "SMART") def valid_strike(strike): if right.startswith("P") and strike_limit: return strike <= tickerValue and strike <= strike_limit elif right.startswith("P"): return strike <= tickerValue elif right.startswith("C") and strike_limit: return strike >= tickerValue and strike >= strike_limit elif right.startswith("C"): return strike >= tickerValue return False chain_expirations = self.config["option_chains"]["expirations"] min_dte = (option_dte(exclude_expirations_before) if exclude_expirations_before else 0) strikes = sorted(strike for strike in chain.strikes if valid_strike(strike)) expirations = sorted( exp for exp in chain.expirations if option_dte(exp) >= self.config["target"]["dte"] and option_dte(exp) >= min_dte)[:chain_expirations] rights = [right] def nearest_strikes(strikes): chain_strikes = self.config["option_chains"]["strikes"] if right.startswith("P"): return strikes[-chain_strikes:] if right.startswith("C"): return strikes[:chain_strikes] contracts = [ Option( symbol, expiration, strike, right, "SMART", # tradingClass=chain.tradingClass, ) for right in rights for expiration in expirations for strike in nearest_strikes(strikes) ] contracts = self.ib.qualifyContracts(*contracts) # exclude strike, but only for the first exp if exclude_first_exp_strike: contracts = [ c for c in contracts if (c.lastTradeDateOrContractMonth == expirations[0] and c.strike != exclude_first_exp_strike) or c.lastTradeDateOrContractMonth != expirations[0] ] tickers = self.get_ticker_list_for(tuple(contracts)) # Filter out contracts which don't have a midpoint price tickers = [t for t in tickers if not util.isNan(t.midpoint())] def open_interest_is_valid(ticker): def open_interest_is_not_ready(): if right.startswith("P"): return util.isNan(ticker.putOpenInterest) if right.startswith("C"): return util.isNan(ticker.callOpenInterest) try: wait_n_seconds( open_interest_is_not_ready, lambda: self.ib.waitOnUpdate(timeout=15), API_RESPONSE_WAIT_TIME, ) except RuntimeError: click.secho( f"Timeout waiting on market data for " f"{ticker.contract}. Continuing...", fg="yellow", ) return False finally: self.ib.cancelMktData(ticker.contract) # The open interest value is never present when using historical # data, so just ignore it when the value is None if right.startswith("P"): return (ticker.putOpenInterest >= self.config["target"]["minimum_open_interest"]) if right.startswith("C"): return (ticker.callOpenInterest >= self.config["target"]["minimum_open_interest"]) def delta_is_valid(ticker): return (ticker.modelGreeks and not util.isNan(ticker.modelGreeks.delta) and ticker.modelGreeks.delta is not None and abs(ticker.modelGreeks.delta) <= get_target_delta( self.config, symbol, right)) def price_is_valid(ticker): return not util.isNan(ticker.midpoint()) or not util.isNan( ticker.marketPrice()) # Filter out tickers without prices tickers = [ticker for ticker in tickers if price_is_valid(ticker)] # Filter by delta and open interest tickers = [ticker for ticker in tickers if delta_is_valid(ticker)] # Fetch market data tickers = [ self.ib.reqMktData(ticker.contract, genericTickList="101") for ticker in tickers ] # Fetch open interest tickers = [ ticker for ticker in tickers if open_interest_is_valid(ticker) ] # Sort by delta first, then expiry date tickers = sorted( reversed(sorted(tickers, key=lambda t: abs(t.modelGreeks.delta))), key=lambda t: option_dte(t.contract.lastTradeDateOrContractMonth), ) if len(tickers) == 0: raise RuntimeError( f"No valid contracts found for {symbol}. Aborting.") # Return the first result return tickers[0]
def find_eligible_contracts(self, symbol, right, min_strike=0, excluded_expirations=[]): click.echo() click.secho( f"Searching option chain for symbol={symbol} right={right}, this can take a while...", fg="green", ) click.echo() stock = Stock(symbol, "SMART", currency="USD") contracts = self.ib.qualifyContracts(stock) [ticker] = self.ib.reqTickers(stock) tickerValue = ticker.marketPrice() chains = self.ib.reqSecDefOptParams(stock.symbol, "", stock.secType, stock.conId) chain = next(c for c in chains if c.exchange == "SMART") def valid_strike(strike): if right.startswith("P"): return strike <= tickerValue if right.startswith("C"): return strike >= tickerValue and strike >= min_strike return False chain_expirations = self.config["option_chains"]["expirations"] strikes = sorted(strike for strike in chain.strikes if valid_strike(strike)) expirations = sorted( exp for exp in chain.expirations if option_dte(exp) >= self.config["target"]["dte"] and exp not in excluded_expirations)[:chain_expirations] rights = [right] def nearest_strikes(strikes): chain_strikes = self.config["option_chains"]["strikes"] if right.startswith("P"): return strikes[-chain_strikes:] if right.startswith("C"): return strikes[:chain_strikes] contracts = [ Option( symbol, expiration, strike, right, "SMART", tradingClass=chain.tradingClass, ) for right in rights for expiration in expirations for strike in nearest_strikes(strikes) ] contracts = self.ib.qualifyContracts(*contracts) tickers = self.ib.reqTickers(*contracts) # Filter out tickers which don't have a midpoint price tickers = [t for t in tickers if not util.isNan(t.midpoint())] def open_interest_is_valid(ticker): ticker = self.ib.reqMktData(ticker.contract, genericTickList="101") def open_interest_is_not_ready(): if right.startswith("P"): return util.isNan(ticker.putOpenInterest) if right.startswith("C"): return util.isNan(ticker.callOpenInterest) try: while_n_times( open_interest_is_not_ready, lambda: self.ib.waitOnUpdate(timeout=5), 25, ) except: return False self.ib.cancelMktData(ticker.contract) # The open interest value is never present when using historical # data, so just ignore it when the value is None if right.startswith("P"): return (ticker.putOpenInterest >= self.config["target"]["minimum_open_interest"]) if right.startswith("C"): return (ticker.callOpenInterest >= self.config["target"]["minimum_open_interest"]) def delta_is_valid(ticker): return (ticker.modelGreeks and not util.isNan(ticker.modelGreeks.delta) and abs(ticker.modelGreeks.delta) <= get_target_delta( self.config, symbol, right)) # Filter by delta and open interest tickers = [ticker for ticker in tickers if delta_is_valid(ticker)] tickers = [ ticker for ticker in tickers if open_interest_is_valid(ticker) ] tickers = sorted( reversed(sorted(tickers, key=lambda t: abs(t.modelGreeks.delta))), key=lambda t: option_dte(t.contract.lastTradeDateOrContractMonth), ) if len(tickers) == 0: raise RuntimeError( f"No valid contracts found for {symbol}. Aborting.") # Return the first result return tickers[0]
def open_interest_is_not_ready(): if right.startswith("P"): return util.isNan(ticker.putOpenInterest) if right.startswith("C"): return util.isNan(ticker.callOpenInterest)
def midpoint_or_market_price(ticker): if util.isNan(ticker.midpoint()): return round(ticker.marketPrice(), 2) return round(ticker.midpoint(), 2)
def price_is_valid(ticker): return not util.isNan(ticker.midpoint()) or not util.isNan( ticker.marketPrice())
def wait_for_midpoint_price(self, ticker): while_n_times( lambda: util.isNan(ticker.midpoint()), lambda: self.ib.waitOnUpdate(timeout=3), 10, )
def wait_for_market_price(self, ticker): while_n_times( lambda: util.isNan(ticker.marketPrice()), lambda: self.ib.waitOnUpdate(timeout=2), 10, )
def delta_is_valid(ticker): return (ticker.modelGreeks and not util.isNan(ticker.modelGreeks.delta) and abs(ticker.modelGreeks.delta) <= self.config["target"]["delta"])