def valuesField(ibc, account, field, usd=None): aVal = values(ibc, account) for i in range(0, len(aVal)): if aVal[i][1] == field and (not usd or aVal[i][3] == 'USD'): return aVal[i][2] fatal.errorAndExit('problem: did not find {} in account values: {}'.format( field, aVal))
def summaryField(ibc, account, field, usd=None): aSum = summary(ibc, account) for i in range(0, len(aSum)): if aSum[i][1] == field and (not usd or aSum[i][3] == 'USD'): return aSum[i][2] fatal.errorAndExit( 'problem: did not find {} in account summary: {}'.format(field, aSum))
def ibContract(self): c = None if self.symbol == 'TQQQ' or self.symbol == 'AAPL' or self.symbol == 'AMZN' or self.symbol == 'FB' or self.symbol == 'GOOG': c = Stock(symbol=self.symbol, exchange='SMART', currency='USD', primaryExchange='NASDAQ') elif self.symbol == 'SQQQ': c = Stock(symbol=self.symbol, exchange='SMART', currency='USD', primaryExchange='NASDAQ') elif self.symbol == 'AAP2' or self.symbol == 'AMZ2' or self.symbol == 'CRM2' or self.symbol == 'FB2' or self.symbol == 'GOO2' or self.symbol == 'GS2' or self.symbol == 'MSF2' or self.symbol == 'NFL2' or self.symbol == 'NVD2' or self.symbol == 'VIS2': c = Stock(symbol=self.symbol, exchange='SMART', currency='USD', primaryExchange='LSE') elif (self.symbol == 'ES' or self.symbol == 'NQ') and self.localSymbol != None: c = Contract(secType='FUT', symbol=self.symbol, localSymbol=self.localSymbol, exchange='GLOBEX', currency='USD') else: fatal.errorAndExit('no security specified') self.contract = c
def connect(conf=None, debug=None): util.logToConsole(logging.WARN) if debug: util.logToConsole(logging.DEBUG) ibc = IB() connected = False n = 0 while not connected and n < 3: n += 1 try: if conf.prod: if conf.tradingMode != 'live': fatal.fatal(conf, 'prod set but trading mode is not live') ibc.connect(host="localhost", port=getPort(conf.prod), clientId=rand.Int(), timeout=3, readonly=False, account=conf.account) else: ibc.connect(host="localhost", port=getPort(conf.prod), clientId=rand.Int(), account=conf.account) ibc.sleep(0.25) connected = ibc.isConnected() except: pass if not connected: fatal.errorAndExit('could not connect') return ibc
def _marketNextCloseTime(r): dt = nowInUtc() if len(r) < 2: fatal.errorAndExit('seem like this might not be a range') for r_ in r: if dt in r_: return r_.end_datetime fatal.errorAndExit('cannot find next close time {} {}'.format(dt, r))
def _marketOpenedAt(r): dt = nowInUtc() if len(r) < 2: fatal.errorAndExit('seem like this might not be a range') for r_ in r: if dt in r_: return r_.start_datetime fatal.errorAndExit('cannot find market open time {} {}'.format(dt, r))
def qualify(self): r = self.ibClient.qualifyContracts(self.contract) if len(r) != 1 or r[0].symbol != self.symbol: fatal.errorAndExit('could not validate response: %s', r[0]) if self.localSymbol == None: # sometimes the local symbol isn't passed in (like with stocks) if self.contract.localSymbol == None: fatal.errorAndExit('problem with looking up contract') else: self.localSymbol = self.contract.localSymbol
def _marketOpenedLessThan(r, td): dt = nowInUtc() if len(r) < 2: fatal.errorAndExit('seems like this might not be a range') for r_ in r: if dt not in r_: continue elif dt - td not in r_: return True return False
def anotate(self): # used for three-bar pattern detection self.barSize = abs(self.open - self.close) self.lineSize = abs(self.high - self.low) if self.open < self.close: self.color = 'G' elif self.close < self.open: self.color = 'R' elif self.close == self.open and self.high != self.low: self.color = 'G' elif math.isnan(self.close) or math.isnan(self.open): fatal.errorAndExit('got a self with NaN: {}'.format(self))
def marketRule(self): if not isinstance(self.details.marketRuleIds, str): fatal.errorAndExit('wrong format {}'.format(self.details)) mrStr = self.details.marketRuleIds mrs = mrStr.split(',') if len(mrs) < 1: fatal.errorAndExit('wrong format {}'.format(self.details)) r0 = mrs[0] for r in mrs: if r != r0: fatal.errorAndExit( 'multiple market rules for a single contract {}'.format( self.details)) mr = self.ibClient.reqMarketRule(r0) self.marketRule = mr penny = False if len(self.marketRule) > 1: for r in self.marketRule: if r.increment == 0.01: penny = True if not penny: fatal.errorAndExit('multiple price incmrenets {} {}'.format( self.details, self.marketRule)) logging.warn( 'default to a penny for the increment, multiple price increments found {} {}' .format(self.marketRule, self.symbol)) self.priceIncrement = 0.01 else: self.priceIncrement = self.marketRule[0].increment
def analyze(self): entryPrice = None if self.first.color == 'X' or self.second.color == 'X' or self.third.color == 'X': logging.debug('got a partial bar') #FIXME: the bar size testing seems to have a large impact on not entering, to the detriment of the return elif not self.first.color == 'G' and not self.second.color == 'R' and not self.third.color == 'G' and not self.second.barSize < 0.3 * self.first.barSize and not self.second.barSize < 0.5 * self.third.barSize and not self.third.barSize > self.second.barSize: entryPrice = None else: entryPrice = self.third.close if math.isnan(entryPrice): fatal.errorAndExit( 'got floating point which is NaN {} {}'.format( entryPrice, self.third)) logging.info('found a potential entry point: %d', entryPrice) return entryPrice
def __init__(self, barSizeStr, wContract, shortInterval=None, longInterval=None, watchCount=None): if shortInterval is not None: self.shortInterval = shortInterval if longInterval is not None: self.longInterval = longInterval if watchCount is not None: self.watchCount = watchCount dur = data.barSizeToDuration[barSizeStr] self.wContract = wContract if dur['unit'] != 'S' or not dur['value'] or not isinstance( dur['value'], int): fatal.errorAndExit('re-factor') self.barSize = dur['value']
def initIndicators(self, dataStream): short = 0 long_ = 0 logging.info('datastream is {}'.format(len(dataStream))) for interval in [self.shortInterval, self.longInterval]: if self.backTest: # in backtest, we can just start from 0 instead of later sma = 0 startIndex = 0 if self.byPeriod: startIndex = len(dataStream) - 1 - int( self.byPeriod * 60 * 60 / self.barSize) logging.info( 'doing by period, using index/period(hours): {}/{}'. format(startIndex, self.byPeriod)) sma = data.calcSMA(interval, dataStream, startIndex) # FIXME: might be a bug here, because interval calculations ema = data.calcEMA(dataStream[startIndex + interval].close, sma, interval) self.curEmaIndex = startIndex + interval else: # first we calculate the SMA over the interval (going backwards) one interval back in the dataStream smaStartIndex = len(dataStream) - 1 - interval * 2 if interval == self.longInterval and smaStartIndex < 0: fatal.errorAndExit('wrong interval calc: {} {} {}'.format( smaStartIndex, len(dataStream), interval)) sma = data.calcSMA(interval, dataStream, smaStartIndex) logging.info( 'calculated sma of {} for {} starting at {}'.format( sma, interval, smaStartIndex)) prevEMA = sma ema = 0 index = len(dataStream) - 1 - interval for point in range(index, len(dataStream)): midpoint = dataStream[index].close ema = data.calcEMA(midpoint, prevEMA, interval) prevEMA = ema index += 1 logging.info('calculated ema for {} as {}'.format(interval, ema)) if interval == self.shortInterval: short = ema elif interval == self.longInterval: long_ = ema self.updateIndicators(short, long_)
def getHistData(wc, barSizeStr, longInterval, e='', d=None, t='MIDPOINT', r=False, f=2, k=False): duration = barSizeToDuration[barSizeStr] if not duration['unit'] or duration['unit'] != 'S' or not duration[ 'value'] or not isinstance(duration['value'], int): fatal.errorAndExit('using seconds is supported') durationStr = '' if d is not None: # doing a backtest, so add the long interval to build the SMA d = d * 60 * 60 + longInterval * duration[ 'value'] # force to seconds, then back up to bar size if d > 86400: # d is in minutes because barSizeToDuration supports minutes atm d = int(d / 60 / 60) if int(d / 60 / 60) > 0 else 1 durationStr = str(d) + ' D' else: durationStr = str(d) + ' S' else: # not doing a backtest # add one because when market closed, latest bar is not yet ready (crazy but true) # happens especially when there are two closes (like with the futures maintenace # window) d = 2 * longInterval + 1 durationStr = str(d * duration['value']) + ' ' + duration['unit'] logging.info( 'getting historical data for c:{}/{}, e:{}, d:{}, b:{}, w:{}, u:{}, f:{}, k:{}' .format(wc.symbol, wc.localSymbol, e, durationStr, barSizeStr, t, r, f, k)) histData = wc.ibClient.reqHistoricalData(contract=wc.contract, endDateTime=e, durationStr=durationStr, barSizeSetting=barSizeStr, whatToShow=t, useRTH=r, formatDate=f, keepUpToDate=k) return histData
def adequateFunds(orderDetails, orders): qty = calculateQty(orderDetails) availableFunds = account.availableFunds(orderDetails.wContract.ibClient, orderDetails.config.account) buyingPower = account.buyingPower(orderDetails.wContract.ibClient, orderDetails.config.account) lhs = orderDetails.entryPrice * qty af_rhs = availableFunds - orderDetails.config.bufferAmt bp_rhs = buyingPower - orderDetails.config.bufferAmt os = None if orderDetails.wContract.contract.secType == 'FUT': wio = whatIfOrder(orders.entryOrder) os = orderDetails.wContract.ibClient.whatIfOrder(orderDetails.wContract.contract, wio) if not os.initMarginAfter or not isinstance(os.initMarginAfter, str): fatal.errorAndExit('got back invalid format: {} {} {}'.format(os, orderDetails, order)) ima = float( os.initMarginAfter ) lhs += ima if lhs < af_rhs and lhs < bp_rhs: logging.warn('detected adequate funds {} {} {}'.format(lhs, af_rhs, bp_rhs)) return True logging.error('not enough funds: {} {} {}'.format(os, orderDetails, orders)) return False
def parseIbHours(ibHours, tz): if not isinstance(ibHours, str): fatal.errorAndExit('trading hours is a string') openHours = [] # '20200427:0930-20200427:1600;20200428:0930-20200428:1600' ranges = ibHours.split(';') m = re.compile('.*:CLOSED') for range_ in ranges: if range_ == '': continue if m.match(range_): # skip closed days continue ts = range_.split('-') if len(ts) != 2: fatal.errorAndExit( 'only two timestamps per range: {} {}'.format(ts, ibHours)) start = tz.localize(datetime.strptime( ts[0], '%Y%m%d:%H%M')).astimezone(pytz.utc) end = tz.localize(datetime.strptime(ts[1], '%Y%m%d:%H%M')).astimezone( pytz.utc) r = DateTimeRange(start, end) if not r.is_valid_timerange(): fatal.errorAndExit('should get a valid timerange') openHours.append(r) logging.debug('openHours: %s', openHours) return openHours
def updatePnl(self, account): pnlR = self.ibClient.pnlSingle(account=account, conId=self.contract.conId) if len(pnlR) != 1: fatal.errorAndExit( 'should get back one pnl for security: {} {}'.format( pnlR, self.contract)) pnl = pnlR[0] if pnl.account != account: fatal.errorAndExit('got back mismatched accounts: {} {} {}'.format( pnl, account, self.contract)) elif pnl.conId != self.contract.conId: fatal.errorAndExit( 'got back mismatched contract IDs: {} {} {}'.format( pnl, account, self.contract)) self.pnl = pnl
def parseTimezone(timeZoneId): if not isinstance(timeZoneId, str): fatal.errorAndExit('timeZoneId should be a string') return pytz.timezone(timeZoneId)
def ibDetails(self): r = self.ibClient.reqContractDetails(self.contract) if len(r) != 1 or r[0].contract != self.contract: fatal.errorAndExit('problem getting contract details: %s', r) self.details = r[0] self.handleDaylightSavings()
def validate(aSum, account): for i in range(0, len(aSum)): if aSum[i][0] != account: fatal.errorAndExit( 'problem: did not get back expected {} from account summary {}' .format(aSum[i], aSum))
def validatePriceIncrement(self): if self.details.minTick != self.priceIncrement and len( self.marketRule) < 2: fatal.errorAndExit('ticks dont match: {} {}'.format( self.details.minTick, self.priceIncrement))
def setupData(wc, conf, backtestArgs=None): dataStore = None dataStream = None if conf.detector == 'threeBarPattern': dataStore = barSet = bars.BarSet() if backtestArgs is not None: logging.fatal('WARNING: DOING A BACKTEST, NOT USING LIVE DATA') if conf.detector == 'Crossover': dataStore = Crossover(conf.barSizeStr, wc, backtestArgs['shortInterval'], backtestArgs['longInterval'], backtestArgs['watchCount']) dataStore.backTest = True dataStream = data.getHistData(wc, barSizeStr=conf.barSizeStr, longInterval=dataStore.longInterval, e=backtestArgs['e'], d=backtestArgs['d'], t=backtestArgs['t'], r=backtestArgs['r'], f=backtestArgs['f'], k=backtestArgs['k']) dataStore.initIndicators(dataStream) else: dataStream = data.getHistData( wc, barSizeStr=conf.barSizeStr, longInterval=backtestArgs['longInterval'], e=backtestArgs['e'], d=backtestArgs['d'], t=backtestArgs['t'], r=backtestArgs['r'], f=backtestArgs['f'], k=backtestArgs['k']) elif conf.detector == 'threeBarPattern': dataStream = wc.getTicker() elif conf.detector == 'Crossover': dataStore = Crossover(conf.barSizeStr, wc, conf.shortEMA, conf.longEMA, conf.watchCount) # disable wrapper logging to hide the API error for canceling the data every hour logging.getLogger('ib_insync.wrapper').setLevel(logging.CRITICAL) logging.warn('ignoring hdms broken errors') wc.ibClient.errorEvent += data.dataStreamErrorHandler logging.warn('installing auto restart handler.') wc.ibClient.errorEvent += connectivityError useRth = False if conf.enterOutsideRth else True histData = data.getHistData(wc, barSizeStr=conf.barSizeStr, longInterval=dataStore.longInterval, r=useRth) if len(histData) < dataStore.longInterval * 2: fatal.fatal( conf, 'did not get back the right amount of data from historical data call, perhaps broken?' ) dataStore.initIndicators(histData) wc.realtimeBars() else: fatal.errorAndExit('do not know what to do!') return dataStore, dataStream