def __initclist(self, timeframe, end, pair): ''' Private function to initialize the CandleList object that goes from trade.start to self.settings.getint('counter', 'period') Parameters ---------- timeframe : Timeframe end : Datetime object that will be the end of this CandleList pair : pair Returns ------- Candlelist or None if Oanda API query was not successful ''' delta_period = periodToDelta(self.settings.getint('trade_bot', 'period_range'), timeframe) delta_1 = periodToDelta(1, timeframe) start = end - delta_period # get the start datetime for this CandleList period end = end + delta_1 # increase end by one candle to include end tb_logger.debug("Fetching candlelist for period: {0}-{1}".format(start, end)) oanda = OandaAPI(url=self.settings.get('oanda_api', 'url'), instrument=pair, granularity=timeframe, settingf=self.settingf, settings=self.settings) if self.ser_data_obj is None: tb_logger.info("Fetching data from API") resp = oanda.run(start=start.isoformat(), end=end.isoformat()) else: tb_logger.info("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=start, end=end) resp = 200 if resp == 200: candle_list = oanda.fetch_candleset() cl = CandleList(candle_list, settingf=self.settingf, settings=self.settings, instrument=pair, granularity=timeframe, ser_data_obj=self.ser_data_obj, id="test", type="short") cl.calc_rsi() return cl else: t_logger.warn("API query was not OK. No CandleList created ") return None
def __initclist(self): ''' Private function to initialize the CandleList object that goes from self.trade.start to self.settings.getint('counter', 'period') This will set the self.clist_period class attribute Returns ------- None if Oanda API query was not successful ''' delta_period = periodToDelta(self.settings.getint('counter', 'period'), self.trade.timeframe) delta_1 = periodToDelta(1, self.trade.timeframe) start = self.trade.start - delta_period # get the start datetime for this CandleList period end = self.trade.start + delta_1 # increase self.start by one candle to include self.start c_logger.debug("Fetching candlelist for period: {0}-{1}".format( start, end)) oanda = OandaAPI(url=self.settings.get('oanda_api', 'url'), instrument=self.trade.pair, granularity=self.trade.timeframe, settingf=self.settingf, settings=self.settings) resp = None if self.ser_data_obj is None: c_logger.debug("Fetching data from API") resp = oanda.run(start=start.isoformat(), end=end.isoformat()) else: c_logger.debug("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=start, end=end) resp = 200 if resp == 200: candle_list = oanda.fetch_candleset() cl = CandleList(candle_list, settingf=self.settingf, settings=self.settings, instrument=self.trade.pair, granularity=self.trade.timeframe, id=self.trade.id, ser_data_obj=self.ser_data_obj, type=self.trade.type) self.clist_period = cl else: c_logger.warn( "API query was not OK. 'self.clist_period' will be None ") self.clist_period = None
def get_max_min(self, adateObj): ''' Function to get the price range for identifying S/R by checking the max and min price for CandleList starting in 'adateObj'- self.settings.getint('trade_bot', 'period_range') and ending in 'adateObj' Parameters ---------- datetime object used for identifying S/R areas Returns ------- max, min floats ''' oanda = OandaAPI(instrument=self.pair, granularity=self.timeframe, settingf=self.settingf, settings=self.settings) delta_period = periodToDelta(self.settings.getint('trade_bot', 'period_range'), self.timeframe) delta_1 = periodToDelta(1, self.timeframe) start = adateObj - delta_period # get the start datetime for this CandleList period end = adateObj + delta_1 # increase self.start by one candle to include self.start if self.ser_data_obj is None: tb_logger.info("Fetching data from API") oanda.run(start=start.isoformat(), end=end.isoformat()) else: tb_logger.info("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=start, end=end) candle_list = oanda.fetch_candleset() cl = CandleList(candle_list, instrument=self.pair, id='test', granularity=self.timeframe, settingf=self.settingf, settings=self.settings) max = cl.get_highest() min = cl.get_lowest() # add a number of pips to max,min to be sure that we # also detect the extreme pivots max = add_pips2price(self.pair, max, self.settings.getint('trade_bot', 'add_pips')) min = substract_pips2price(self.pair, min, self.settings.getint('trade_bot', 'add_pips')) return max, min
def calc_pips_c_trend(self): ''' Function to calculate the pips_c_trend Returns ------- Float with number of pips for the trend_i ''' oanda = OandaAPI(url=self.settings.get('oanda_api', 'url'), instrument=self.trade.pair, granularity=self.trade.timeframe, settingf=self.settingf, settings=self.settings) resp = None if self.ser_data_obj is None: c_logger.debug("Fetching data from API") resp = oanda.run(start=self.trend_i.isoformat(), end=self.trade.start.isoformat()) else: c_logger.debug("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=self.trend_i, end=self.trade.start) resp = 200 if resp == 200: candle_list = oanda.fetch_candleset() cl = CandleList(candle_list, settingf=self.settingf, settings=self.settings, instrument=self.trade.pair, granularity=self.trade.timeframe, id=self.trade.id, type=self.trade.type) pips_c_trend = cl.get_length_pips() / cl.get_length_candles() return round(pips_c_trend, 1) else: c_logger.warn( "API query was not OK. 'pips_c_trend' could not be calculated")
def fetch_candlelist(self): ''' This function returns a CandleList object for this Trade Returns ------- A CandleList object ''' oanda = OandaAPI(instrument=self.pair, granularity=self.timeframe, settingf=self.settingf, settings=self.settings) if isinstance(self.start, datetime) is True: astart = self.start else: astart = try_parsing_date(self.start) if isinstance(self.end, datetime) is True: anend = self.end else: anend = try_parsing_date(self.end) if self.ser_data_obj is None: t_logger.debug("Fetching data from API") oanda.run(start=astart.isoformat(), end=anend.isoformat()) else: t_logger.debug("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=astart, end=anend) candle_list = oanda.fetch_candleset() cl = CandleList(candle_list, type=self.type, settingf=self.settingf, settings=self.settings) return cl
def calc_rsi(self): ''' Calculate the RSI for a certain candle list ''' cl_logger.debug("Running calc_rsi") start_time = self.clist[0].time end_time = self.clist[-1].time delta_period = None if self.granularity == "D": delta_period = timedelta(hours=24 * self.settings.getint('candlelist', 'period')) else: fgran = self.granularity.replace('H', '') delta_period = timedelta(hours=int(fgran) * self.settings.getint('candlelist', 'period')) start_calc_time = start_time - delta_period #fetch candle set from start_calc_time oanda = OandaAPI(instrument=self.instrument, granularity=self.granularity, settingf=self.settingf, settings=self.settings) ''' Get candlelist from start_calc_time to (start_time-1) This 2-step API call is necessary in order to avoid maximum number of candles errors ''' if self.ser_data_obj is None: cl_logger.debug("Fetching data from API") oanda.run(start=start_calc_time.isoformat(), end=start_time.isoformat()) else: cl_logger.debug("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=start_calc_time, end=start_time) cl1 = oanda.fetch_candleset() '''Get candlelist from start_time to end_time''' if self.ser_data_obj is None: cl_logger.debug("Fetching data from API") oanda.run(start=start_time.isoformat(), end=end_time.isoformat()) else: cl_logger.debug("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=start_time, end=end_time) cl2 = oanda.fetch_candleset() if cl1[-1].time == cl2[0].time: del cl1[-1] candle_list = cl1 + cl2 series = [c.closeAsk for c in candle_list] df = pd.DataFrame({'close': series}) chg = df['close'].diff(1) gain = chg.mask(chg < 0, 0) loss = chg.mask(chg > 0, 0) rsi_period = self.settings.getint('candlelist', 'rsi_period') avg_gain = gain.ewm(com=rsi_period - 1, min_periods=rsi_period).mean() avg_loss = loss.ewm(com=rsi_period - 1, min_periods=rsi_period).mean() rs = abs(avg_gain / avg_loss) rsi = 100 - (100 / (1 + rs)) rsi4cl = rsi[-len(self.clist):] # set rsi attribute in each candle of the CandleList ix = 0 for c, v in zip(self.clist, rsi4cl): self.clist[ix].rsi = round(v, 2) ix += 1 cl_logger.debug("Done calc_rsi")
def run_trade(self, expires=None): ''' Run the trade until conclusion from a start date ''' t_logger.info("Run run_trade with id: {0}".format(self.id)) entry = HArea(price=self.entry, instrument=self.pair, pips=self.settings.getint('trade', 'hr_pips'), granularity=self.timeframe, ser_data_obj=None, settings=self.settings) SL = HArea(price=self.SL, instrument=self.pair, pips=self.settings.getint('trade', 'hr_pips'), granularity=self.timeframe, ser_data_obj=None, settings=self.settings) TP = HArea(price=self.TP, instrument=self.pair, pips=self.settings.getint('trade', 'hr_pips'), granularity=self.timeframe, ser_data_obj=None, settings=self.settings) period = None if self.timeframe == "D": period = 24 else: period = int(self.timeframe.replace('H', '')) # generate a range of dates starting at self.start and ending numperiods later in order to assess the outcome # of trade and also the entry time self.start = datetime.strptime(str(self.start), '%Y-%m-%d %H:%M:%S') numperiods = self.settings.getint('trade', 'numperiods') # date_list will contain a list with datetimes that will be used for running self date_list = [ datetime.strptime(str(self.start.isoformat()), '%Y-%m-%dT%H:%M:%S') + timedelta(hours=x * period) for x in range(0, numperiods) ] count = 0 self.entered = False for d in date_list: count += 1 if expires is not None: if count > expires and self.entered is False: self.outcome = 'n.a.' break oanda = OandaAPI(instrument=self.pair, granularity=self.timeframe, settingf=self.settingf, settings=self.settings) if self.ser_data_obj is None: t_logger.debug("Fetching data from API") oanda.run(start=d.isoformat(), count=1) else: t_logger.debug("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=d, count=1) cl = oanda.fetch_candleset()[0] if self.entered is False: entry_time = entry.get_cross_time( candle=cl, granularity=self.settings.get('trade', 'granularity')) if entry_time != 'n.a.': t_logger.info("Trade entered") # modify self.start to the datetime # that Trade has actually entered self.start = d self.entry_time = entry_time.isoformat() self.entered = True if self.entered is True: # will be n.a. is cl does not cross SL failure_time = SL.get_cross_time(candle=cl, granularity=self.settings.get( 'trade', 'granularity')) # sometimes there is a jump in the price and SL is not crossed is_gap = False if (self.type == "short" and cl.lowAsk > SL.price) or ( self.type == "long" and cl.highAsk < SL.price): is_gap = True failure_time = d if (failure_time is not None and failure_time != 'n.a.') or is_gap is True: self.outcome = 'failure' self.end = failure_time self.pips = float( calculate_pips(self.pair, abs(self.SL - self.entry))) * -1 t_logger.info("S/L was hit") break # will be n.a. if cl does not cross TP success_time = TP.get_cross_time(candle=cl, granularity=self.settings.get( 'trade', 'granularity')) # sometimes there is a jump in the price and TP is not crossed is_gap = False if (self.type == "short" and cl.highAsk < TP.price) or ( self.type == "long" and cl.lowAsk > TP.price): is_gap = True success_time = d if (success_time is not None and success_time != 'n.a.') or is_gap is True: self.outcome = 'success' t_logger.info("T/P was hit") self.end = success_time self.pips = float( calculate_pips(self.pair, abs(self.TP - self.entry))) break try: assert getattr(self, 'outcome') except: t_logger.warning("No outcome could be calculated") self.outcome = "n.a." self.pips = 0 t_logger.info("Done run_trade")
def get_cross_time(self, candle, granularity='M30'): ''' This function is used get the time that the candle crosses (go through) HArea Parameters ---------- candle : Candle object that crosses the HArea granularity : To what granularity we should descend Returns ------ datetime object with crossing time. n.a. if crossing time could not retrieved. This can happens when there is an artefactual jump in Oanda's data ''' if candle.lowAsk <= self.price <= candle.highAsk: delta = None if self.granularity == "D": delta = timedelta(hours=24) else: fgran = self.granularity.replace('H', '') delta = timedelta(hours=int(fgran)) cstart = candle.time cend = cstart + delta oanda = None if self.settingf is None and self.settings is None: raise Exception( "No 'settings' nor 'settingf' defined for this object") elif self.settingf is None and self.settings is not None: oanda = OandaAPI( instrument=self.instrument, granularity=granularity, # 'M30' is the default settings=self.settings) elif self.settingf is not None and self.settings is None: oanda = OandaAPI( instrument=self.instrument, granularity=granularity, # 'M30' is the default settingf=self.settingf) elif self.settingf is not None and self.settings is not None: oanda = OandaAPI( instrument=self.instrument, granularity=granularity, # 'M30' is the default settingf=self.settingf) if self.ser_data_obj is None: h_logger.debug("Fetching data from API") oanda.run(start=cstart.isoformat(), end=cend.isoformat()) else: h_logger.debug("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=cstart, end=cend) candle_list = oanda.fetch_candleset() seen = False for c in candle_list: if c.lowAsk <= self.price <= c.highAsk: seen = True return c.time if seen is False: return 'n.a.' else: return 'n.a.'
def __get_trade_type(self, ic, delta): ''' Function to guess what is the trade type (short or long) for this possible trade. It will also adjust the SL price to the most recent highest high/lowest low Parameters ---------- ic : Candle object Indecision candle for this trade delta : Timedelta object corresponding to the time that needs to be increased Returns ------- str: Trade type (long or short) float: adjusted SL ''' oanda = OandaAPI(instrument=self.pair, granularity=self.timeframe, settingf=self.settingf, settings=self.settings) # n x delta controls how many candles to go back in time # to check start = ic.time - 20*delta end = ic.time if self.ser_data_obj is None: tb_logger.info("Fetching data from API") oanda.run(start=start.isoformat(), end=end.isoformat()) else: tb_logger.info("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=start, end=end) candle_list = oanda.fetch_candleset() clObj = CandleList(candle_list, instrument=self.pair, id='fit_reg', granularity=self.timeframe, settingf=self.settingf, settings=self.settings) # fit a regression line in order to check its slope # and guess the trade type (fitted_model, regression_model_mse) = clObj.fit_reg_line() slope = fitted_model.coef_[0][0] if slope < 0: type = 'long' elif slope > 0: type = 'short' # adjust SL if type == 'short': part = 'high{0}'.format(self.settings.get('general', 'bit')) elif type == 'long': part = 'low{0}'.format(self.settings.get('general', 'bit')) SL = None for c in reversed(candle_list): price = getattr(c, part) if SL is None: SL = price continue if type == 'short': if price > SL: SL = price if type == 'long': if price < SL: SL = price return type, SL
def run(self, discard_sat=True, pickled_file=None): ''' This function will run the Bot from start to end one candle at a time Parameter --------- discard_sat : Bool If this is set to True, then the Trade wil not be taken if IC falls on a Saturday. Default: True pickled_file : str Path used to dump the pickled representation of the dict of HAreaList generated by store_SRlist (if self.settings. getboolean('trade_bot', 'store_SRlist' is True Or path used to load the pickled representation (if self.settings.getboolean('trade_bot', 'load_SRlist' is True) Returns ------- TradeList object with Trades taken. None if no trades were taken ''' tb_logger.info("Running...") oanda = OandaAPI(instrument=self.pair, granularity=self.timeframe, settingf=self.settingf, settings=self.settings) delta = None nhours = None if self.timeframe == "D": nhours = 24 delta = datetime.timedelta(hours=24) else: p1 = re.compile('^H') m1 = p1.match(self.timeframe) if m1: nhours = int(self.timeframe.replace('H', '')) delta = datetime.timedelta(hours=int(nhours)) startO = pd.datetime.strptime(self.start, '%Y-%m-%d %H:%M:%S') endO = pd.datetime.strptime(self.end, '%Y-%m-%d %H:%M:%S') dict_SRlist = {} if self.settings.getboolean('trade_bot', 'load_SRlist') is True: tb_logger.info("Loading dict of HAreaLists from pickled " "file: {0}".format(pickled_file)) pickled_fh = open(pickled_file, 'rb') dict_SRlist = pickle.load(pickled_fh) SRlst = None loop = 0 tlist = [] tend = None while startO <= endO: if tend is not None: # this means that there is currently an active trade if startO <= tend: startO = startO + delta loop += 1 continue else: tend = None tb_logger.info("Trade bot - analyzing candle: {0}".format(startO.isoformat())) if loop == 0: # no iteration has occurred yet, so invoke .calc_SR for the first time if self.settings.getboolean('trade_bot', 'load_SRlist') is True: SRlst= dict_SRlist[startO] else: SRlst = self.calc_SR(adateObj=startO) res = SRlst.print() dict_SRlist[startO] = SRlst tb_logger.info("Identified HAreaList for time {0}:".format(startO.isoformat())) tb_logger.info("{0}".format(res)) elif loop >= self.settings.getint('trade_bot', 'period'): # An entire cycle has occurred. Invoke .calc_SR if self.settings.getboolean('trade_bot', 'load_SRlist') is True: SRlst = dict_SRlist[startO] else: SRlst = self.calc_SR(adateObj=startO) res = SRlst.print() dict_SRlist[startO] = SRlst tb_logger.info("Identified HAreaList for time {0}:".format(startO.isoformat())) tb_logger.info("{0}".format(res)) loop = 0 # fetch candle for current datetime if self.ser_data_obj is None: tb_logger.info("Fetching data from API") oanda.run(start=startO.isoformat(), count=1) else: tb_logger.info("Fetching data from File") oanda.data = self.ser_data_obj.slice(start=startO, count=1) candle_list = oanda.fetch_candleset() c_candle = candle_list[0] # this is the current candle that # is being checked # c_candle.time is not equal to startO # when startO is non-working day, for example delta1hr = datetime.timedelta(hours=1) if (c_candle.time != startO) and (abs(c_candle.time-startO) > delta1hr): loop += 1 tb_logger.info("Analysed dt {0} is not the same than APIs returned dt {1}." " Skipping...".format(startO, c_candle.time)) startO = startO + delta continue #check if there is any HArea overlapping with c_candle HAreaSel, sel_ix = SRlst.onArea(candle=candle_list[0]) if HAreaSel is not None: c_candle.set_candle_features() # guess the if trade is 'long' or 'short' type, SL = self.__get_trade_type(ic=c_candle, delta=delta) prepare_trade = False if c_candle.indecision_c(ic_perc=self.settings.getint('general', 'ic_perc')) is True: prepare_trade = True elif type == 'short' and c_candle.colour == 'red': prepare_trade = True elif type == 'long' and c_candle.colour == 'green': prepare_trade = True # discard if IC falls on a Saturday if c_candle.time.weekday() == 5 and discard_sat is True: tb_logger.info("Possible trade at {0} falls on Sat. Skipping...".format(c_candle.time)) prepare_trade = False if prepare_trade is True: t = self.prepare_trade( type=type, SL=SL, ic=c_candle, harea_sel=HAreaSel, delta=delta) t.strat = 'counter' t.tot_SR = len(SRlst.halist) t.rank_selSR = sel_ix # calculate t.entry-t.SL in number of pips # and discard if it is over threshold diff = abs(t.entry-t.SL) number_pips = float(calculate_pips(self.pair, diff)) if number_pips > self.settings.getint('trade_bot', 'SL_width_pips'): loop += 1 startO = startO + delta continue if self.settings.getboolean('trade_bot', 'run_trades') is True: t.run_trade(expires=2) if t.entered is True: if not hasattr(t, 'end'): tb_logger.info("Trade.end will be n.a. Check if this trade hit the SL/TP in" "the analysed timeframe. Skipping...") tend = None else: tlist.append(t) tend = t.end else: tlist.append(t) startO = startO+delta loop += 1 tb_logger.info("Run done") if self.settings.getboolean('trade_bot', 'store_SRlist') is True: tb_logger.info("Dumping dict of HAreaLists to pickled " "file: {0}".format(pickled_file)) pickled_lst_f = open(pickled_file, 'wb') pickle.dump(dict_SRlist, pickled_lst_f) pickled_lst_f.close() if len(tlist) == 0: return None else: tl = TradeList(tlist=tlist, settingf=self.settingf, settings=self.settings, ser_data_obj=self.ser_data_obj ) if self.settings.getboolean('trade_bot', 'run_trades') is True: # analyse trades tl.analyze() return tl