def limitSell(self, market: str = '', base_quantity: float = 0, future_price: float = 0) -> pd.DataFrame: if not self._isMarketValid(market): raise ValueError('Coinbase Pro market is invalid.') if not isinstance(base_quantity, int) and not isinstance( base_quantity, float): raise TypeError('The crypto amount is not numeric.') if not isinstance(future_price, int) and not isinstance( future_price, float): raise TypeError('The future crypto price is not numeric.') order = { 'product_id': market, 'type': 'limit', 'side': 'sell', 'size': self.marketBaseIncrement(market, base_quantity), 'price': future_price } Logger.debug(order) model = AuthAPI(self._api_key, self._api_secret, self._api_passphrase, self._api_url) return model.authAPI('POST', 'orders', order)
def limitSell(self, market: str = "", base_quantity: float = 0, future_price: float = 0) -> pd.DataFrame: """Initiates a limit sell order""" if not self._isMarketValid(market): raise ValueError("Coinbase Pro market is invalid.") if not isinstance(base_quantity, int) and not isinstance( base_quantity, float): raise TypeError("The crypto amount is not numeric.") if not isinstance(future_price, int) and not isinstance( future_price, float): raise TypeError("The future crypto price is not numeric.") try: order = { "product_id": market, "type": "limit", "side": "sell", "size": self.marketBaseIncrement(market, base_quantity), "price": future_price, } Logger.debug(order) model = AuthAPI(self._api_key, self._api_secret, self._api_passphrase, self._api_url) return model.authAPI("POST", "orders", order) except: return pd.DataFrame()
def marketBuy(self, market: str = "", quote_quantity: float = 0, test: bool = False) -> list: """Executes a market buy providing a funding amount""" # validates the market is syntactically correct if not self._isMarketValid(market): raise ValueError("Binance market is invalid.") # validates quote_quantity is either an integer or float if not isinstance(quote_quantity, int) and not isinstance( quote_quantity, float): raise TypeError("The funding amount is not numeric.") try: current_price = self.getTicker(market)[1] base_quantity = np.divide(quote_quantity, current_price) df_filters = self.getMarketInfoFilters(market) step_size = float(df_filters.loc[df_filters["filterType"] == "LOT_SIZE"]["stepSize"]) precision = int(round(-math.log(step_size, 10), 0)) # remove fees base_quantity = base_quantity - (base_quantity * self.getTradeFee(market)) # execute market buy stepper = 10.0**precision truncated = math.trunc(stepper * base_quantity) / stepper order = { "symbol": market, "side": "BUY", "type": "MARKET", "quantity": truncated, "recvWindow": self.recv_window, } Logger.debug(order) # POST /api/v3/order/test if test is True: resp = self.authAPI("POST", "/api/v3/order/test", order) else: resp = self.authAPI("POST", "/api/v3/order", order) return resp except Exception as err: ts = datetime.now().strftime("%d-%m-%Y %H:%M:%S") Logger.error(f"{ts} Binance marketBuy {str(err)}") return []
def attempt_orders(): resp = self.client.get_all_orders(symbol=market) if len(resp) > 0: self.order_attempts = 0 return pd.DataFrame(resp) else: if self.order_attempts < 3: self.order_attempts += 1 Logger.debug(f'No previous orders found. Retrying: attempt {self.order_attempts}/3') return attempt_orders() else: Logger.debug(f'Exhausted attempts to retrieve orders') return pd.DataFrame()
def handle_api_error(self, err: str, reason: str) -> dict: if self.debug: if self.die_on_api_error: raise SystemExit(err) else: Logger.debug(err) return {} else: if self.die_on_api_error: raise SystemExit(f"{reason}: {self._api_url}") else: Logger.info(f"{reason}: {self._api_url}") return {}
def handle_api_error(self, err: str, reason: str) -> pd.DataFrame: """Handle API errors""" if self.debug: if self.die_on_api_error: raise SystemExit(err) else: Logger.debug(err) return pd.DataFrame() else: if self.die_on_api_error: raise SystemExit(f"{reason}: {self._api_url}") else: Logger.error(f"{reason}: {self._api_url}") return pd.DataFrame()
def marketBuy(self, market: str = "", quote_quantity: float = 0) -> pd.DataFrame: """Executes a market buy providing a funding amount""" # validates the market is syntactically correct if not self._isMarketValid(market): raise ValueError("Coinbase Pro market is invalid.") # validates quote_quantity is either an integer or float if not isinstance(quote_quantity, int) and not isinstance( quote_quantity, float): Logger.critical("Please report this to Michael Whittle: " + str(quote_quantity) + " " + str(type(quote_quantity))) raise TypeError("The funding amount is not numeric.") # funding amount needs to be greater than 10 if quote_quantity < MINIMUM_TRADE_AMOUNT: Logger.warning( f"Trade amount is too small (>= {MINIMUM_TRADE_AMOUNT}).") return pd.DataFrame() # raise ValueError(f"Trade amount is too small (>= {MINIMUM_TRADE_AMOUNT}).") try: order = { "product_id": market, "type": "market", "side": "buy", "funds": self.marketQuoteIncrement(market, quote_quantity), } Logger.debug(order) # connect to authenticated coinbase pro api model = AuthAPI(self._api_key, self._api_secret, self._api_passphrase, self._api_url) # place order and return result return model.authAPI("POST", "orders", order) except: return pd.DataFrame()
def is1hSMA50200Bull(self, iso8601end: str = ''): try: if self.isSimulation() and isinstance(self.sma50200_1h_cache, pd.DataFrame): df_data = self.sma50200_1h_cache[( self.sma50200_1h_cache['date'] <= iso8601end)] elif self.exchange == 'coinbasepro': api = CBPublicAPI() df_data = api.getHistoricalData(self.market, 3600) self.sma50200_1h_cache = df_data elif self.exchange == 'binance': api = BPublicAPI() df_data = api.getHistoricalData(self.market, '1h') self.sma50200_1h_cache = df_data else: return False ta = TechnicalAnalysis(df_data) if 'sma50' not in df_data: ta.addSMA(50) if 'sma200' not in df_data: ta.addSMA(200) df_last = ta.getDataFrame().copy().iloc[-1, :] Logger.debug("---- SMA50200 1H Check----") if self.isSimulation(): Logger.debug("simdate: " + str(df_last['date'])) Logger.debug("sma50 1h: " + str(df_last['sma50'])) Logger.debug("sma200 1h: " + str(df_last['sma200'])) Logger.debug("bull 1h: " + str(df_last['sma50'] > df_last['sma200'])) df_last['bull'] = df_last['sma50'] > df_last['sma200'] return bool(df_last['bull']) except Exception: return False
def isSellSignal(self) -> bool: # required technical indicators or candle sticks for buy signal strategy required_indicators = ["ema12ltema26co", "macdltsignal"] for indicator in required_indicators: if indicator not in self._df_last: raise AttributeError(f"'{indicator}' not in Pandas dataframe") # criteria for a sell signal 1 if ( bool(self._df_last["ema12ltema26co"].values[0]) is True and ( bool(self._df_last["macdltsignal"].values[0]) is True or self.app.disableBuyMACD() ) and self.state.last_action not in ["", "SELL"] ): Logger.debug("*** Sell Signal ***") for indicator in required_indicators: Logger.debug(f"{indicator}: {self._df_last[indicator].values[0]}") Logger.debug(f"last_action: {self.state.last_action}") return True return False
def isSellSignal(self) -> bool: # required technical indicators or candle sticks for buy signal strategy required_indicators = [ 'ema12ltema26co', 'macdltsignal', 'macdltsignalco' ] for indicator in required_indicators: if indicator not in self._df_last: raise AttributeError(f"'{indicator}' not in Pandas dataframe") # if EMA, MACD are disabled, do not sell if self.app.disableBuyEMA() and self.app.disableBuyMACD(): return False # criteria for a sell signal 1 if (bool(self._df_last['ema12ltema26co'].values[0]) is True or self.app.disableBuyEMA()) \ and (bool(self._df_last['macdltsignalco'].values[0]) is True or self.app.disableBuyMACD()) \ and self.state.last_action not in ['', 'SELL']: Logger.debug('*** Sell Signal ***') for indicator in required_indicators: Logger.debug( f'{indicator}: {self._df_last[indicator].values[0]}') Logger.debug(f'last_action: {self.state.last_action}') return True return False
def is6hEMA1226Bull(self, iso8601end: str = ''): try: if self.isSimulation() and isinstance(self.ema1226_6h_cache, pd.DataFrame): df_data = self.ema1226_6h_cache[(self.ema1226_6h_cache['date'] <= iso8601end)] elif self.exchange == 'coinbasepro': api = CBPublicAPI() df_data = api.getHistoricalData(self.market, 21600) self.ema1226_6h_cache = df_data elif self.exchange == 'binance': api = BPublicAPI() df_data = api.getHistoricalData(self.market, '6h') self.ema1226_6h_cache = df_data else: return False ta = TechnicalAnalysis(df_data) if 'ema12' not in df_data: ta.addEMA(12) if 'ema26' not in df_data: ta.addEMA(26) df_last = ta.getDataFrame().copy().iloc[-1, :] df_last['bull'] = df_last['ema12'] > df_last['ema26'] Logger.debug("---- EMA1226 6H Check----") if self.isSimulation(): Logger.debug("simdate: " + str(df_last['date'])) Logger.debug("ema12 6h: " + str(df_last['ema12'])) Logger.debug("ema26 6h: " + str(df_last['ema26'])) Logger.debug("bull 6h: " + str(df_last['ema12'] > df_last['ema26'])) return bool(df_last['bull']) except Exception: return False
def marketBuy(self, market: str = '', quote_quantity: float = 0) -> pd.DataFrame: """Executes a market buy providing a funding amount""" # validates the market is syntactically correct if not self._isMarketValid(market): raise ValueError('Coinbase Pro market is invalid.') # validates quote_quantity is either an integer or float if not isinstance(quote_quantity, int) and not isinstance( quote_quantity, float): Logger.critical('Please report this to Michael Whittle: ' + str(quote_quantity) + ' ' + str(type(quote_quantity))) raise TypeError('The funding amount is not numeric.') # funding amount needs to be greater than 10 if quote_quantity < MINIMUM_TRADE_AMOUNT: raise ValueError( f"Trade amount is too small (>= {MINIMUM_TRADE_AMOUNT}).") order = { 'product_id': market, 'type': 'market', 'side': 'buy', 'funds': self.marketQuoteIncrement(market, quote_quantity) } Logger.debug(order) # connect to authenticated coinbase pro api model = AuthAPI(self._api_key, self._api_secret, self._api_passphrase, self._api_url) # place order and return result return model.authAPI('POST', 'orders', order)
def calculate_margin(buy_size: float = 0.0, buy_filled: int = 0.0, buy_price: int = 0.0, buy_fee: float = 0.0, sell_percent: float = 100, sell_price: float = 0.0, sell_fee: float = 0.0, sell_taker_fee: float = 0.0) -> float: precision = 8 Logger.debug( f'buy_size: {buy_size}') # buy_size is quote currency (before fees) Logger.debug(f'buy_filled: {buy_filled}' ) # buy_filled is base currency (after fees) Logger.debug(f'buy_price: {buy_price}') # buy_price is quote currency Logger.debug(f'buy_fee: {buy_fee}') # buy_fee is quote currency # sell_size is quote currency (before fees) - price * buy_filled sell_size = round((sell_percent / 100) * (sell_price * buy_filled), precision) # calculate sell_fee in quote currency, sell_fee is actual fee, sell_taker_fee is the rate if sell_fee == 0.0 and sell_taker_fee > 0.0: sell_fee = round((sell_size * sell_taker_fee), precision) # calculate sell_filled after fees in quote currency sell_filled = round(sell_size - sell_fee, precision) # profit is the difference between buy_size without fees and sell_filled with fees profit = round(sell_filled - buy_size, precision) # calculate margin margin = round((profit / buy_size) * 100, precision) Logger.debug( f'sell_size: {sell_size}') # sell_size is quote currency (before fees) Logger.debug(f'sell_filled: {sell_filled}' ) # sell_filled is quote currency (after fees) Logger.debug(f'sell_price: {sell_price}') # sell_price is quote currency Logger.debug(f'sell_fee: {sell_fee}') # sell_fee is quote currency Logger.debug(f'profit: {profit}') Logger.debug(f'margin: {margin}') return margin, profit, sell_fee
def isBuySignal(self, now: datetime = datetime.today().strftime( '%Y-%m-%d %H:%M:%S'), price: float = 0.0) -> bool: # required technical indicators or candle sticks for buy signal strategy required_indicators = [ 'ema12gtema26co', 'macdgtsignal', 'goldencross', 'obv_pc', 'eri_buy' ] for indicator in required_indicators: if indicator not in self._df_last: raise AttributeError(f"'{indicator}' not in Pandas dataframe") # buy signal exclusion (if disabled, do not buy within 3% of the dataframe close high) if self.state.last_action == 'SELL' and self.app.disableBuyNearHigh( ) is True and (price > (self._df['close'].max() * 0.97)): log_text = str(now) + ' | ' + self.app.getMarket( ) + ' | ' + self.app.printGranularity( ) + ' | Ignoring Buy Signal (price ' + str( price) + ' within 3% of high ' + str( self._df['close'].max()) + ')' Logger.warning(log_text) return False # criteria for a buy signal 1 if bool(self._df_last['ema12gtema26co'].values[0]) is True \ and (bool(self._df_last['macdgtsignal'].values[0]) is True or self.app.disableBuyMACD()) \ and (bool(self._df_last['goldencross'].values[0]) is True or self.app.disableBullOnly()) \ and (float(self._df_last['obv_pc'].values[0]) > -5 or self.app.disableBuyOBV()) \ and (bool(self._df_last['eri_buy'].values[0]) is True or self.app.disableBuyElderRay()) \ and self.state.last_action != 'BUY': # required for all strategies Logger.debug('*** Buy Signal ***') for indicator in required_indicators: Logger.debug( f'{indicator}: {self._df_last[indicator].values[0]}') Logger.debug(f'last_action: {self.state.last_action}') return True # criteria for buy signal 2 (optionally add additional buy singals) elif bool(self._df_last['ema12gtema26co'].values[0]) is True \ and bool(self._df_last['macdgtsignalco'].values[0]) is True \ and (bool(self._df_last['goldencross'].values[0]) is True or self.app.disableBullOnly()) \ and (float(self._df_last['obv_pc'].values[0]) > -5 or self.app.disableBuyOBV()) \ and (bool(self._df_last['eri_buy'].values[0]) is True or self.app.disableBuyElderRay()) \ and self.state.last_action != 'BUY': # required for all strategies Logger.debug('*** Buy Signal ***') for indicator in required_indicators: Logger.debug( f'{indicator}: {self._df_last[indicator].values[0]}') Logger.debug(f'last_action: {self.state.last_action}') return True return False
def executeJob(sc=None, app: PyCryptoBot = None, state: AppState = None, trading_data=pd.DataFrame()): """Trading bot job which runs at a scheduled interval""" global technical_analysis # connectivity check (only when running live) if app.isLive() and app.getTime() is None: Logger.warning( 'Your connection to the exchange has gone down, will retry in 1 minute!' ) # poll every 5 minute list(map(s.cancel, s.queue)) s.enter(300, 1, executeJob, (sc, app, state)) return # increment state.iterations state.iterations = state.iterations + 1 if not app.isSimulation(): # retrieve the app.getMarket() data trading_data = app.getHistoricalData(app.getMarket(), app.getGranularity()) else: if len(trading_data) == 0: return None # analyse the market data if app.isSimulation() and len(trading_data.columns) > 8: df = trading_data # if smartswitch the get the market data using new granularity if app.sim_smartswitch: df_last = app.getInterval(df, state.iterations) if len(df_last.index.format()) > 0: current_df_index = str(df_last.index.format()[0]) current_sim_date = f'{current_df_index} 00:00:00' if len( current_df_index) == 10 else current_df_index dt = current_sim_date.split(' ') date = dt[0].split('-') time = dt[1].split(':') startDate = datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2])) trading_data = app.getHistoricalData( app.getMarket(), app.getGranularity(), startDate.isoformat(timespec='milliseconds'), datetime.now().isoformat(timespec='milliseconds')) trading_dataCopy = trading_data.copy() technical_analysis = TechnicalAnalysis(trading_dataCopy) technical_analysis.addAll() df = technical_analysis.getDataFrame() state.iterations = 1 app.sim_smartswitch = False else: trading_dataCopy = trading_data.copy() technical_analysis = TechnicalAnalysis(trading_dataCopy) technical_analysis.addAll() df = technical_analysis.getDataFrame() if app.isSimulation(): df_last = app.getInterval(df, state.iterations) else: df_last = app.getInterval(df) if len(df_last.index.format()) > 0: current_df_index = str(df_last.index.format()[0]) else: current_df_index = state.last_df_index formatted_current_df_index = f'{current_df_index} 00:00:00' if len( current_df_index) == 10 else current_df_index current_sim_date = formatted_current_df_index # use actual sim mode date to check smartchswitch if app.getSmartSwitch() == 1 and app.getGranularity( ) == 3600 and app.is1hEMA1226Bull( current_sim_date) is True and app.is6hEMA1226Bull( current_sim_date) is True: Logger.info( '*** smart switch from granularity 3600 (1 hour) to 900 (15 min) ***' ) if app.isSimulation(): app.sim_smartswitch = True app.notifyTelegram( app.getMarket() + " smart switch from granularity 3600 (1 hour) to 900 (15 min)") app.setGranularity(900) list(map(s.cancel, s.queue)) s.enter(5, 1, executeJob, (sc, app, state)) # use actual sim mode date to check smartchswitch if app.getSmartSwitch() == 1 and app.getGranularity( ) == 900 and app.is1hEMA1226Bull( current_sim_date) is False and app.is6hEMA1226Bull( current_sim_date) is False: Logger.info( "*** smart switch from granularity 900 (15 min) to 3600 (1 hour) ***" ) if app.isSimulation(): app.sim_smartswitch = True app.notifyTelegram( app.getMarket() + " smart switch from granularity 900 (15 min) to 3600 (1 hour)") app.setGranularity(3600) list(map(s.cancel, s.queue)) s.enter(5, 1, executeJob, (sc, app, state)) if app.getExchange() == 'binance' and app.getGranularity() == 86400: if len(df) < 250: # data frame should have 250 rows, if not retry Logger.error('error: data frame length is < 250 (' + str(len(df)) + ')') list(map(s.cancel, s.queue)) s.enter(300, 1, executeJob, (sc, app, state)) else: if len(df) < 300: if not app.isSimulation(): # data frame should have 300 rows, if not retry Logger.error('error: data frame length is < 300 (' + str(len(df)) + ')') list(map(s.cancel, s.queue)) s.enter(300, 1, executeJob, (sc, app, state)) if len(df_last) > 0: now = datetime.today().strftime('%Y-%m-%d %H:%M:%S') # last_action polling if live if app.isLive(): last_action_current = state.last_action state.pollLastAction() if last_action_current != state.last_action: Logger.info( f'last_action change detected from {last_action_current} to {state.last_action}' ) app.notifyTelegram( f"{app.getMarket} last_action change detected from {last_action_current} to {state.last_action}" ) if not app.isSimulation(): ticker = app.getTicker(app.getMarket()) now = ticker[0] price = ticker[1] if price < df_last['low'].values[0] or price == 0: price = float(df_last['close'].values[0]) else: price = float(df_last['close'].values[0]) if price < 0.0001: raise Exception( app.getMarket() + ' is unsuitable for trading, quote price is less than 0.0001!') # technical indicators ema12gtema26 = bool(df_last['ema12gtema26'].values[0]) ema12gtema26co = bool(df_last['ema12gtema26co'].values[0]) goldencross = bool(df_last['goldencross'].values[0]) macdgtsignal = bool(df_last['macdgtsignal'].values[0]) macdgtsignalco = bool(df_last['macdgtsignalco'].values[0]) ema12ltema26 = bool(df_last['ema12ltema26'].values[0]) ema12ltema26co = bool(df_last['ema12ltema26co'].values[0]) macdltsignal = bool(df_last['macdltsignal'].values[0]) macdltsignalco = bool(df_last['macdltsignalco'].values[0]) obv = float(df_last['obv'].values[0]) obv_pc = float(df_last['obv_pc'].values[0]) elder_ray_buy = bool(df_last['eri_buy'].values[0]) elder_ray_sell = bool(df_last['eri_sell'].values[0]) # if simulation, set goldencross based on actual sim date if app.isSimulation(): goldencross = app.is1hSMA50200Bull(current_sim_date) # if simulation interations < 200 set goldencross to true #if app.isSimulation() and state.iterations < 200: # goldencross = True # candlestick detection hammer = bool(df_last['hammer'].values[0]) inverted_hammer = bool(df_last['inverted_hammer'].values[0]) hanging_man = bool(df_last['hanging_man'].values[0]) shooting_star = bool(df_last['shooting_star'].values[0]) three_white_soldiers = bool(df_last['three_white_soldiers'].values[0]) three_black_crows = bool(df_last['three_black_crows'].values[0]) morning_star = bool(df_last['morning_star'].values[0]) evening_star = bool(df_last['evening_star'].values[0]) three_line_strike = bool(df_last['three_line_strike'].values[0]) abandoned_baby = bool(df_last['abandoned_baby'].values[0]) morning_doji_star = bool(df_last['morning_doji_star'].values[0]) evening_doji_star = bool(df_last['evening_doji_star'].values[0]) two_black_gapping = bool(df_last['two_black_gapping'].values[0]) strategy = Strategy(app, state, df, state.iterations) state.action = strategy.getAction() immediate_action = False margin, profit, sell_fee = 0, 0, 0 if state.last_buy_size > 0 and state.last_buy_price > 0 and price > 0 and state.last_action == 'BUY': # update last buy high if price > state.last_buy_high: state.last_buy_high = price if state.last_buy_high > 0: change_pcnt_high = ((price / state.last_buy_high) - 1) * 100 else: change_pcnt_high = 0 # buy and sell calculations state.last_buy_fee = round(state.last_buy_size * app.getTakerFee(), 8) state.last_buy_filled = round( ((state.last_buy_size - state.last_buy_fee) / state.last_buy_price), 8) # if not a simulation, sync with exchange orders if not app.isSimulation(): exchange_last_buy = app.getLastBuy() if exchange_last_buy is not None: if state.last_buy_size != exchange_last_buy['size']: state.last_buy_size = exchange_last_buy['size'] if state.last_buy_filled != exchange_last_buy['filled']: state.last_buy_filled = exchange_last_buy['filled'] if state.last_buy_price != exchange_last_buy['price']: state.last_buy_price = exchange_last_buy['price'] if app.getExchange() == 'coinbasepro': if state.last_buy_fee != exchange_last_buy['fee']: state.last_buy_fee = exchange_last_buy['fee'] margin, profit, sell_fee = calculate_margin( buy_size=state.last_buy_size, buy_filled=state.last_buy_filled, buy_price=state.last_buy_price, buy_fee=state.last_buy_fee, sell_percent=app.getSellPercent(), sell_price=price, sell_taker_fee=app.getTakerFee()) # handle immedate sell actions if strategy.isSellTrigger(price, technical_analysis.getTradeExit(price), margin, change_pcnt_high, obv_pc, macdltsignal): state.action = 'SELL' state.last_action = 'BUY' immediate_action = True # handle overriding wait actions (do not sell if sell at loss disabled!) if strategy.isWaitTrigger(margin): state.action = 'WAIT' state.last_action = 'BUY' immediate_action = False bullbeartext = '' if app.disableBullOnly() is True or (df_last['sma50'].values[0] == df_last['sma200'].values[0]): bullbeartext = '' elif goldencross is True: bullbeartext = ' (BULL)' elif goldencross is False: bullbeartext = ' (BEAR)' # polling is every 5 minutes (even for hourly intervals), but only process once per interval if (immediate_action is True or state.last_df_index != current_df_index): precision = 4 if (price < 0.01): precision = 8 # Since precision does not change after this point, it is safe to prepare a tailored `truncate()` that would # work with this precision. It should save a couple of `precision` uses, one for each `truncate()` call. truncate = functools.partial(_truncate, n=precision) price_text = 'Close: ' + truncate(price) ema_text = '' if app.disableBuyEMA() is False: ema_text = app.compare(df_last['ema12'].values[0], df_last['ema26'].values[0], 'EMA12/26', precision) macd_text = '' if app.disableBuyMACD() is False: macd_text = app.compare(df_last['macd'].values[0], df_last['signal'].values[0], 'MACD', precision) obv_text = '' if app.disableBuyOBV() is False: obv_text = 'OBV: ' + truncate( df_last['obv'].values[0]) + ' (' + str( truncate(df_last['obv_pc'].values[0])) + '%)' state.eri_text = '' if app.disableBuyElderRay() is False: if elder_ray_buy is True: state.eri_text = 'ERI: buy | ' elif elder_ray_sell is True: state.eri_text = 'ERI: sell | ' else: state.eri_text = 'ERI: | ' if hammer is True: log_text = '* Candlestick Detected: Hammer ("Weak - Reversal - Bullish Signal - Up")' Logger.info(log_text) if shooting_star is True: log_text = '* Candlestick Detected: Shooting Star ("Weak - Reversal - Bearish Pattern - Down")' Logger.info(log_text) if hanging_man is True: log_text = '* Candlestick Detected: Hanging Man ("Weak - Continuation - Bearish Pattern - Down")' Logger.info(log_text) if inverted_hammer is True: log_text = '* Candlestick Detected: Inverted Hammer ("Weak - Continuation - Bullish Pattern - Up")' Logger.info(log_text) if three_white_soldiers is True: log_text = '*** Candlestick Detected: Three White Soldiers ("Strong - Reversal - Bullish Pattern - Up")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if three_black_crows is True: log_text = '* Candlestick Detected: Three Black Crows ("Strong - Reversal - Bearish Pattern - Down")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if morning_star is True: log_text = '*** Candlestick Detected: Morning Star ("Strong - Reversal - Bullish Pattern - Up")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if evening_star is True: log_text = '*** Candlestick Detected: Evening Star ("Strong - Reversal - Bearish Pattern - Down")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if three_line_strike is True: log_text = '** Candlestick Detected: Three Line Strike ("Reliable - Reversal - Bullish Pattern - Up")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if abandoned_baby is True: log_text = '** Candlestick Detected: Abandoned Baby ("Reliable - Reversal - Bullish Pattern - Up")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if morning_doji_star is True: log_text = '** Candlestick Detected: Morning Doji Star ("Reliable - Reversal - Bullish Pattern - Up")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if evening_doji_star is True: log_text = '** Candlestick Detected: Evening Doji Star ("Reliable - Reversal - Bearish Pattern - Down")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) if two_black_gapping is True: log_text = '*** Candlestick Detected: Two Black Gapping ("Reliable - Reversal - Bearish Pattern - Down")' Logger.info(log_text) app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') ' + log_text) ema_co_prefix = '' ema_co_suffix = '' if app.disableBuyEMA() is False: if ema12gtema26co is True: ema_co_prefix = '*^ ' ema_co_suffix = ' ^*' elif ema12ltema26co is True: ema_co_prefix = '*v ' ema_co_suffix = ' v*' elif ema12gtema26 is True: ema_co_prefix = '^ ' ema_co_suffix = ' ^' elif ema12ltema26 is True: ema_co_prefix = 'v ' ema_co_suffix = ' v' macd_co_prefix = '' macd_co_suffix = '' if app.disableBuyMACD() is False: if macdgtsignalco is True: macd_co_prefix = '*^ ' macd_co_suffix = ' ^*' elif macdltsignalco is True: macd_co_prefix = '*v ' macd_co_suffix = ' v*' elif macdgtsignal is True: macd_co_prefix = '^ ' macd_co_suffix = ' ^' elif macdltsignal is True: macd_co_prefix = 'v ' macd_co_suffix = ' v' obv_prefix = '' obv_suffix = '' if app.disableBuyOBV() is False: if float(obv_pc) > 0: obv_prefix = '^ ' obv_suffix = ' ^ | ' elif float(obv_pc) < 0: obv_prefix = 'v ' obv_suffix = ' v | ' if not app.isVerbose(): if state.last_action != '': output_text = formatted_current_df_index + ' | ' + app.getMarket() + bullbeartext + ' | ' + \ app.printGranularity() + ' | ' + price_text + ' | ' + ema_co_prefix + \ ema_text + ema_co_suffix + ' | ' + macd_co_prefix + macd_text + macd_co_suffix + \ obv_prefix + obv_text + obv_suffix + state.eri_text + ' | ' + state.action + \ ' | Last Action: ' + state.last_action else: output_text = formatted_current_df_index + ' | ' + app.getMarket() + bullbeartext + ' | ' + \ app.printGranularity() + ' | ' + price_text + ' | ' + ema_co_prefix + \ ema_text + ema_co_suffix + ' | ' + macd_co_prefix + macd_text + macd_co_suffix + \ obv_prefix + obv_text + obv_suffix + state.eri_text + ' | ' + state.action + ' ' if state.last_action == 'BUY': if state.last_buy_size > 0: margin_text = truncate(margin) + '%' else: margin_text = '0%' output_text += ' | ' + margin_text + ' (delta: ' + str( round(price - state.last_buy_price, precision)) + ')' Logger.info(output_text) # Seasonal Autoregressive Integrated Moving Average (ARIMA) model (ML prediction for 3 intervals from now) if not app.isSimulation(): try: prediction = technical_analysis.seasonalARIMAModelPrediction( int(app.getGranularity() / 60) * 3) # 3 intervals from now Logger.info( f'Seasonal ARIMA model predicts the closing price will be {str(round(prediction[1], 2))} at {prediction[0]} (delta: {round(prediction[1] - price, 2)})' ) except: pass if state.last_action == 'BUY': # display support, resistance and fibonacci levels Logger.info( technical_analysis. printSupportResistanceFibonacciLevels(price)) else: Logger.debug('-- Iteration: ' + str(state.iterations) + ' --' + bullbeartext) if state.last_action == 'BUY': if state.last_buy_size > 0: margin_text = truncate(margin) + '%' else: margin_text = '0%' Logger.debug('-- Margin: ' + margin_text + ' --') Logger.debug('price: ' + truncate(price)) Logger.debug('ema12: ' + truncate(float(df_last['ema12'].values[0]))) Logger.debug('ema26: ' + truncate(float(df_last['ema26'].values[0]))) Logger.debug('ema12gtema26co: ' + str(ema12gtema26co)) Logger.debug('ema12gtema26: ' + str(ema12gtema26)) Logger.debug('ema12ltema26co: ' + str(ema12ltema26co)) Logger.debug('ema12ltema26: ' + str(ema12ltema26)) Logger.debug('sma50: ' + truncate(float(df_last['sma50'].values[0]))) Logger.debug('sma200: ' + truncate(float(df_last['sma200'].values[0]))) Logger.debug('macd: ' + truncate(float(df_last['macd'].values[0]))) Logger.debug('signal: ' + truncate(float(df_last['signal'].values[0]))) Logger.debug('macdgtsignal: ' + str(macdgtsignal)) Logger.debug('macdltsignal: ' + str(macdltsignal)) Logger.debug('obv: ' + str(obv)) Logger.debug('obv_pc: ' + str(obv_pc)) Logger.debug('action: ' + state.action) # informational output on the most recent entry Logger.info('') Logger.info( '================================================================================' ) txt = ' Iteration : ' + str( state.iterations) + bullbeartext Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Timestamp : ' + str(df_last.index.format()[0]) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') Logger.info( '--------------------------------------------------------------------------------' ) txt = ' Close : ' + truncate(price) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' EMA12 : ' + truncate( float(df_last['ema12'].values[0])) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' EMA26 : ' + truncate( float(df_last['ema26'].values[0])) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Crossing Above : ' + str(ema12gtema26co) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Currently Above : ' + str(ema12gtema26) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Crossing Below : ' + str(ema12ltema26co) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Currently Below : ' + str(ema12ltema26) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') if (ema12gtema26 is True and ema12gtema26co is True): txt = ' Condition : EMA12 is currently crossing above EMA26' elif (ema12gtema26 is True and ema12gtema26co is False): txt = ' Condition : EMA12 is currently above EMA26 and has crossed over' elif (ema12ltema26 is True and ema12ltema26co is True): txt = ' Condition : EMA12 is currently crossing below EMA26' elif (ema12ltema26 is True and ema12ltema26co is False): txt = ' Condition : EMA12 is currently below EMA26 and has crossed over' else: txt = ' Condition : -' Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' SMA20 : ' + truncate( float(df_last['sma20'].values[0])) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' SMA200 : ' + truncate( float(df_last['sma200'].values[0])) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') Logger.info( '--------------------------------------------------------------------------------' ) txt = ' MACD : ' + truncate( float(df_last['macd'].values[0])) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Signal : ' + truncate( float(df_last['signal'].values[0])) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Currently Above : ' + str(macdgtsignal) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') txt = ' Currently Below : ' + str(macdltsignal) Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') if (macdgtsignal is True and macdgtsignalco is True): txt = ' Condition : MACD is currently crossing above Signal' elif (macdgtsignal is True and macdgtsignalco is False): txt = ' Condition : MACD is currently above Signal and has crossed over' elif (macdltsignal is True and macdltsignalco is True): txt = ' Condition : MACD is currently crossing below Signal' elif (macdltsignal is True and macdltsignalco is False): txt = ' Condition : MACD is currently below Signal and has crossed over' else: txt = ' Condition : -' Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') Logger.info( '--------------------------------------------------------------------------------' ) txt = ' Action : ' + state.action Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') Logger.info( '================================================================================' ) if state.last_action == 'BUY': txt = ' Margin : ' + margin_text Logger.info(' | ' + txt + (' ' * (75 - len(txt))) + ' | ') Logger.info( '================================================================================' ) # if a buy signal if state.action == 'BUY': state.last_buy_price = price state.last_buy_high = state.last_buy_price # if live if app.isLive(): app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') BUY at ' + price_text) if not app.isVerbose(): Logger.info(formatted_current_df_index + ' | ' + app.getMarket() + ' | ' + app.printGranularity() + ' | ' + price_text + ' | BUY') else: Logger.info( '--------------------------------------------------------------------------------' ) Logger.info( '| *** Executing LIVE Buy Order *** |' ) Logger.info( '--------------------------------------------------------------------------------' ) # display balances Logger.info(app.getBaseCurrency() + ' balance before order: ' + str(account.getBalance(app.getBaseCurrency()))) Logger.info( app.getQuoteCurrency() + ' balance before order: ' + str(account.getBalance(app.getQuoteCurrency()))) # execute a live market buy state.last_buy_size = float( account.getBalance(app.getQuoteCurrency())) if app.getBuyMaxSize( ) and state.last_buy_size > app.getBuyMaxSize(): state.last_buy_size = app.getBuyMaxSize() resp = app.marketBuy(app.getMarket(), state.last_buy_size, app.getBuyPercent()) Logger.debug(resp) # display balances Logger.info(app.getBaseCurrency() + ' balance after order: ' + str(account.getBalance(app.getBaseCurrency()))) Logger.info( app.getQuoteCurrency() + ' balance after order: ' + str(account.getBalance(app.getQuoteCurrency()))) # if not live else: app.notifyTelegram(app.getMarket() + ' (' + app.printGranularity() + ') TEST BUY at ' + price_text) # TODO: Improve simulator calculations by including calculations for buy and sell limit configurations. if state.last_buy_size == 0 and state.last_buy_filled == 0: state.last_buy_size = 1000 state.first_buy_size = 1000 state.buy_count = state.buy_count + 1 state.buy_sum = state.buy_sum + state.last_buy_size if not app.isVerbose(): Logger.info(formatted_current_df_index + ' | ' + app.getMarket() + ' | ' + app.printGranularity() + ' | ' + price_text + ' | BUY') bands = technical_analysis.getFibonacciRetracementLevels( float(price)) Logger.info(' Fibonacci Retracement Levels:' + str(bands)) technical_analysis.printSupportResistanceLevel( float(price)) if len(bands) >= 1 and len(bands) <= 2: if len(bands) == 1: first_key = list(bands.keys())[0] if first_key == 'ratio1': state.fib_low = 0 state.fib_high = bands[first_key] if first_key == 'ratio1_618': state.fib_low = bands[first_key] state.fib_high = bands[first_key] * 2 else: state.fib_low = bands[first_key] elif len(bands) == 2: first_key = list(bands.keys())[0] second_key = list(bands.keys())[1] state.fib_low = bands[first_key] state.fib_high = bands[second_key] else: Logger.info( '--------------------------------------------------------------------------------' ) Logger.info( '| *** Executing TEST Buy Order *** |' ) Logger.info( '--------------------------------------------------------------------------------' ) if app.shouldSaveGraphs(): tradinggraphs = TradingGraphs(technical_analysis) ts = datetime.now().timestamp() filename = app.getMarket() + '_' + app.printGranularity( ) + '_buy_' + str(ts) + '.png' tradinggraphs.renderEMAandMACD(len(trading_data), 'graphs/' + filename, True) # if a sell signal elif state.action == 'SELL': # if live if app.isLive(): app.notifyTelegram( app.getMarket() + ' (' + app.printGranularity() + ') SELL at ' + price_text + ' (margin: ' + margin_text + ', (delta: ' + str(round(price - state.last_buy_price, precision)) + ')') if not app.isVerbose(): Logger.info(formatted_current_df_index + ' | ' + app.getMarket() + ' | ' + app.printGranularity() + ' | ' + price_text + ' | SELL') bands = technical_analysis.getFibonacciRetracementLevels( float(price)) Logger.info(' Fibonacci Retracement Levels:' + str(bands)) if len(bands) >= 1 and len(bands) <= 2: if len(bands) == 1: first_key = list(bands.keys())[0] if first_key == 'ratio1': state.fib_low = 0 state.fib_high = bands[first_key] if first_key == 'ratio1_618': state.fib_low = bands[first_key] state.fib_high = bands[first_key] * 2 else: state.fib_low = bands[first_key] elif len(bands) == 2: first_key = list(bands.keys())[0] second_key = list(bands.keys())[1] state.fib_low = bands[first_key] state.fib_high = bands[second_key] else: Logger.info( '--------------------------------------------------------------------------------' ) Logger.info( '| *** Executing LIVE Sell Order *** |' ) Logger.info( '--------------------------------------------------------------------------------' ) # display balances Logger.info(app.getBaseCurrency() + ' balance before order: ' + str(account.getBalance(app.getBaseCurrency()))) Logger.info( app.getQuoteCurrency() + ' balance before order: ' + str(account.getBalance(app.getQuoteCurrency()))) # execute a live market sell resp = app.marketSell( app.getMarket(), float(account.getBalance(app.getBaseCurrency())), app.getSellPercent()) Logger.debug(resp) # display balances Logger.info(app.getBaseCurrency() + ' balance after order: ' + str(account.getBalance(app.getBaseCurrency()))) Logger.info( app.getQuoteCurrency() + ' balance after order: ' + str(account.getBalance(app.getQuoteCurrency()))) # if not live else: margin, profit, sell_fee = calculate_margin( buy_size=state.last_buy_size, buy_filled=state.last_buy_filled, buy_price=state.last_buy_price, buy_fee=state.last_buy_fee, sell_percent=app.getSellPercent(), sell_price=price, sell_taker_fee=app.getTakerFee()) if state.last_buy_size > 0: margin_text = truncate(margin) + '%' else: margin_text = '0%' app.notifyTelegram( app.getMarket() + ' (' + app.printGranularity() + ') TEST SELL at ' + price_text + ' (margin: ' + margin_text + ', (delta: ' + str(round(price - state.last_buy_price, precision)) + ')') # Preserve next buy values for simulator state.sell_count = state.sell_count + 1 buy_size = ((app.getSellPercent() / 100) * ((price / state.last_buy_price) * (state.last_buy_size - state.last_buy_fee))) state.last_buy_size = buy_size - sell_fee state.sell_sum = state.sell_sum + state.last_buy_size if not app.isVerbose(): if price > 0: margin_text = truncate(margin) + '%' else: margin_text = '0%' Logger.info(formatted_current_df_index + ' | ' + app.getMarket() + ' | ' + app.printGranularity() + ' | SELL | ' + str(price) + ' | BUY | ' + str(state.last_buy_price) + ' | DIFF | ' + str(price - state.last_buy_price) + ' | DIFF | ' + str(profit) + ' | MARGIN NO FEES | ' + margin_text + ' | MARGIN FEES | ' + str(round(sell_fee, precision))) else: Logger.info( '--------------------------------------------------------------------------------' ) Logger.info( '| *** Executing TEST Sell Order *** |' ) Logger.info( '--------------------------------------------------------------------------------' ) if app.shouldSaveGraphs(): tradinggraphs = TradingGraphs(technical_analysis) ts = datetime.now().timestamp() filename = app.getMarket() + '_' + app.printGranularity( ) + '_sell_' + str(ts) + '.png' tradinggraphs.renderEMAandMACD(len(trading_data), 'graphs/' + filename, True) # last significant action if state.action in ['BUY', 'SELL']: state.last_action = state.action state.last_df_index = str(df_last.index.format()[0]) if not app.isLive() and state.iterations == len(df): Logger.info("\nSimulation Summary: ") if state.buy_count > state.sell_count and app.allowSellAtLoss( ): # Calculate last sell size state.last_buy_size = ((app.getSellPercent() / 100) * ( (price / state.last_buy_price) * (state.last_buy_size - state.last_buy_fee))) # Reduce sell fee from last sell size state.last_buy_size = state.last_buy_size - state.last_buy_price * app.getTakerFee( ) state.sell_sum = state.sell_sum + state.last_buy_size state.sell_count = state.sell_count + 1 elif state.buy_count > state.sell_count and not app.allowSellAtLoss( ): Logger.info("\n") Logger.info( ' Note : "sell at loss" is disabled and you have an open trade, if the margin' ) Logger.info( ' result below is negative it will assume you sold at the end of the' ) Logger.info( ' simulation which may not be ideal. Try setting --sellatloss 1' ) Logger.info("\n") Logger.info(' Buy Count : ' + str(state.buy_count)) Logger.info(' Sell Count : ' + str(state.sell_count)) Logger.info(' First Buy : ' + str(state.first_buy_size)) Logger.info(' Last Sell : ' + str(state.last_buy_size)) app.notifyTelegram( f"Simulation Summary\n Buy Count: {state.buy_count}\n Sell Count: {state.sell_count}\n First Buy: {state.first_buy_size}\n Last Sell: {state.last_buy_size}\n" ) if state.sell_count > 0: Logger.info("\n") Logger.info(' Margin : ' + _truncate(( ((state.last_buy_size - state.first_buy_size) / state.first_buy_size) * 100), 4) + '%') Logger.info("\n") Logger.info( ' ** non-live simulation, assuming highest fees') app.notifyTelegram( f" Margin: {_truncate((((state.last_buy_size - state.first_buy_size) / state.first_buy_size) * 100), 4)}%\n ** non-live simulation, assuming highest fees\n" ) else: if state.last_buy_size > 0 and state.last_buy_price > 0 and price > 0 and state.last_action == 'BUY': # show profit and margin if already bought Logger.info(now + ' | ' + app.getMarket() + bullbeartext + ' | ' + app.printGranularity() + ' | Current Price: ' + str(price) + ' | Margin: ' + str(margin) + ' | Profit: ' + str(profit)) else: Logger.info(now + ' | ' + app.getMarket() + bullbeartext + ' | ' + app.printGranularity() + ' | Current Price: ' + str(price)) # decrement ignored iteration state.iterations = state.iterations - 1 # if live if not app.disableTracker() and app.isLive(): # update order tracker csv if app.getExchange() == 'binance': account.saveTrackerCSV(app.getMarket()) elif app.getExchange() == 'coinbasepro': account.saveTrackerCSV() if app.isSimulation(): if state.iterations < 300: if app.simuluationSpeed() in ['fast', 'fast-sample']: # fast processing list(map(s.cancel, s.queue)) s.enter(0, 1, executeJob, (sc, app, state, df)) else: # slow processing list(map(s.cancel, s.queue)) s.enter(1, 1, executeJob, (sc, app, state, df)) else: # poll every 1 minute list(map(s.cancel, s.queue)) s.enter(60, 1, executeJob, (sc, app, state))
def isBuySignal( self, app, price, now: datetime = datetime.today().strftime("%Y-%m-%d %H:%M:%S") ) -> bool: # required technical indicators or candle sticks for buy signal strategy required_indicators = [ "ema12gtema26co", "macdgtsignal", "goldencross", "obv_pc", "eri_buy", ] for indicator in required_indicators: if indicator not in self._df_last: raise AttributeError(f"'{indicator}' not in Pandas dataframe") # buy signal exclusion (if disabled, do not buy within 3% of the dataframe close high) if ( self.state.last_action == "SELL" and self.app.disableBuyNearHigh() is True and ( price > (self._df["close"].max() * (1 - self.app.noBuyNearHighPcnt() / 100)) ) ): if not app.isSimulation() or ( app.isSimulation() and not app.simResultOnly() ): log_text = ( str(now) + " | " + self.app.getMarket() + " | " + self.app.printGranularity() + " | Ignoring Buy Signal (price " + str(price) + " within " + str(self.app.noBuyNearHighPcnt()) + "% of high " + str(self._df["close"].max()) + ")" ) Logger.warning(log_text) return False ## if last_action was set to "WAIT" due to an API problem, do not buy if self.state.last_action == "WAIT": log_text = f"{str(now)} | {self.app.getMarket()} | {self.app.printGranularity()} | last_action is WAIT, do not buy yet" Logger.warning(log_text) return False # if EMA, MACD are disabled, do not buy if self.app.disableBuyEMA() and self.app.disableBuyMACD(): log_text = f"{str(now)} | {self.app.getMarket()} | {self.app.printGranularity()} | EMA, MACD indicators are disabled" Logger.warning(log_text) return False # criteria for a buy signal 1 if ( ( bool(self._df_last["ema12gtema26co"].values[0]) is True or self.app.disableBuyEMA() ) and ( bool(self._df_last["macdgtsignal"].values[0]) is True or self.app.disableBuyMACD() ) and ( bool(self._df_last["goldencross"].values[0]) is True or self.app.disableBullOnly() ) and ( float(self._df_last["obv_pc"].values[0]) > -5 or self.app.disableBuyOBV() ) and ( bool(self._df_last["eri_buy"].values[0]) is True or self.app.disableBuyElderRay() ) and self.state.last_action != "BUY" ): # required for all strategies Logger.debug("*** Buy Signal ***") for indicator in required_indicators: Logger.debug(f"{indicator}: {self._df_last[indicator].values[0]}") Logger.debug(f"last_action: {self.state.last_action}") return True # criteria for buy signal 2 (optionally add additional buy signals) elif ( ( bool(self._df_last["ema12gtema26co"].values[0]) is True or self.app.disableBuyEMA() ) and bool(self._df_last["macdgtsignalco"].values[0]) is True and ( bool(self._df_last["goldencross"].values[0]) is True or self.app.disableBullOnly() ) and ( float(self._df_last["obv_pc"].values[0]) > -5 or self.app.disableBuyOBV() ) and ( bool(self._df_last["eri_buy"].values[0]) is True or self.app.disableBuyElderRay() ) and self.state.last_action != "BUY" ): # required for all strategies Logger.debug("*** Buy Signal ***") for indicator in required_indicators: Logger.debug(f"{indicator}: {self._df_last[indicator].values[0]}") Logger.debug(f"last_action: {self.state.last_action}") return True return False
def isSellTrigger( self, app, state, price: float = 0.0, price_exit: float = 0.0, margin: float = 0.0, change_pcnt_high: float = 0.0, obv_pc: float = 0.0, macdltsignal: bool = False, ) -> bool: # set to true for verbose debugging debug = False # preventloss - attempt selling before margin drops below 0% if self.app.preventLoss(): if state.prevent_loss == 0 and margin > self.app.preventLossTrigger( ): state.prevent_loss = 1 Logger.warning( f"{self.app.getMarket()} - reached prevent loss trigger of {self.app.preventLossTrigger()}%. Watch margin ({self.app.preventLossMargin()}%) to prevent loss." ) elif ( state.prevent_loss == 1 and margin <= self.app.preventLossMargin() ) or ( # trigger of 0 disables trigger check and only checks margin set point self.app.preventLossTrigger() == 0 and margin <= self.app.preventLossMargin()): Logger.warning( f"{self.app.getMarket()} - time to sell before losing funds! Prevent Loss Activated!" ) self.app.notifyTelegram( f"{self.app.getMarket()} - time to sell before losing funds! Prevent Loss Activated!" ) return True # check sellatloss and nosell bounds before continuing if not self.app.allowSellAtLoss() and margin <= 0: return False elif ((self.app.nosellminpcnt is not None) and (margin >= self.app.nosellminpcnt)) and ( (self.app.nosellmaxpcnt is not None) and (margin <= self.app.nosellmaxpcnt)): return False # loss failsafe sell at trailing_stop_loss if (self.app.trailingStopLoss() != None and change_pcnt_high < self.app.trailingStopLoss() and margin > self.app.trailingStopLossTrigger()): log_text = f"! Trailing Stop Loss Triggered (< {str(self.app.trailingStopLoss())}%)" if not app.isSimulation() or (app.isSimulation() and not app.simResultOnly()): Logger.warning(log_text) self.app.notifyTelegram( f"{self.app.getMarket()} ({self.app.printGranularity()}) {log_text}" ) return True if debug: Logger.debug("-- loss failsafe sell at sell_lower_pcnt --") Logger.debug( f"self.app.disableFailsafeLowerPcnt() is False (actual: {self.app.disableFailsafeLowerPcnt()})" ) Logger.debug( f"and self.app.allowSellAtLoss() is True (actual: {self.app.allowSellAtLoss()})" ) Logger.debug( f"and self.app.sellLowerPcnt() != None (actual: {self.app.sellLowerPcnt()})" ) Logger.debug( f"and margin ({margin}) < self.app.sellLowerPcnt() ({self.app.sellLowerPcnt()})" ) Logger.debug( f"(self.app.allowSellAtLoss() is True (actual: {self.app.allowSellAtLoss()}) or margin ({margin}) > 0)" ) Logger.debug("\n") # loss failsafe sell at sell_lower_pcnt if (self.app.disableFailsafeLowerPcnt() is False and self.app.allowSellAtLoss() and self.app.sellLowerPcnt() != None and margin < self.app.sellLowerPcnt()): log_text = ("! Loss Failsafe Triggered (< " + str(self.app.sellLowerPcnt()) + "%)") Logger.warning(log_text) self.app.notifyTelegram( f"{self.app.getMarket()} ({self.app.printGranularity()}) {log_text}" ) return True if debug: Logger.debug("\n*** isSellTrigger ***\n") Logger.debug("-- ignoring sell signal --") Logger.debug( f"self.app.nosellminpcnt is None (nosellminpcnt: {self.app.nosellminpcnt})" ) Logger.debug( f"margin >= self.app.nosellminpcnt (margin: {margin})") Logger.debug( f"margin <= self.app.nosellmaxpcnt (nosellmaxpcnt: {self.app.nosellmaxpcnt})" ) Logger.debug("\n") if debug: Logger.debug("\n*** isSellTrigger ***\n") Logger.debug("-- loss failsafe sell at fibonacci band --") Logger.debug( f"self.app.disableFailsafeFibonacciLow() is False (actual: {self.app.disableFailsafeFibonacciLow()})" ) Logger.debug( f"self.app.allowSellAtLoss() is True (actual: {self.app.allowSellAtLoss()})" ) Logger.debug( f"self.app.sellLowerPcnt() is None (actual: {self.app.sellLowerPcnt()})" ) Logger.debug(f"self.state.fib_low {self.state.fib_low} > 0") Logger.debug( f"self.state.fib_low {self.state.fib_low} >= {float(price)}") Logger.debug( f"(self.app.allowSellAtLoss() is True (actual: {self.app.allowSellAtLoss()}) or margin ({margin}) > 0)" ) Logger.debug("\n") # loss failsafe sell at fibonacci band if (self.app.disableFailsafeFibonacciLow() is False and self.app.allowSellAtLoss() and self.app.sellLowerPcnt() is None and self.state.fib_low > 0 and self.state.fib_low >= float(price)): log_text = ( f"! Loss Failsafe Triggered (Fibonacci Band: {str(self.state.fib_low)})" ) Logger.warning(log_text) self.app.notifyTelegram( f"{self.app.getMarket()} ({self.app.printGranularity()}) {log_text}" ) return True if debug: Logger.debug("-- loss failsafe sell at trailing_stop_loss --") Logger.debug( f"self.app.trailingStopLoss() != None (actual: {self.app.trailingStopLoss()})" ) Logger.debug( f"change_pcnt_high ({change_pcnt_high}) < self.app.trailingStopLoss() ({self.app.trailingStopLoss()})" ) Logger.debug( f"margin ({margin}) > self.app.trailingStopLossTrigger() ({self.app.trailingStopLossTrigger()})" ) Logger.debug( f"(self.app.allowSellAtLoss() is True (actual: {self.app.allowSellAtLoss()}) or margin ({margin}) > 0)" ) Logger.debug("\n") if debug: Logger.debug("-- profit bank at sell_upper_pcnt --") Logger.debug( f"self.app.disableProfitbankUpperPcnt() is False (actual: {self.app.disableProfitbankUpperPcnt()})" ) Logger.debug( f"and self.app.sellUpperPcnt() != None (actual: {self.app.sellUpperPcnt()})" ) Logger.debug( f"and margin ({margin}) > self.app.sellUpperPcnt() ({self.app.sellUpperPcnt()})" ) Logger.debug( f"(self.app.allowSellAtLoss() is True (actual: {self.app.allowSellAtLoss()}) or margin ({margin}) > 0)" ) Logger.debug("\n") # profit bank at sell_upper_pcnt if (self.app.disableProfitbankUpperPcnt() is False and self.app.sellUpperPcnt() != None and margin > self.app.sellUpperPcnt()): log_text = f"! Profit Bank Triggered (> {str(self.app.sellUpperPcnt())}%)" if not app.isSimulation() or (app.isSimulation() and not app.simResultOnly()): Logger.warning(log_text) self.app.notifyTelegram( f"{self.app.getMarket()} ({self.app.printGranularity()}) {log_text}" ) return True if debug: Logger.debug("-- profit bank when strong reversal detected --") Logger.debug( f"self.app.sellAtResistance() is True (actual {self.app.sellAtResistance()})" ) Logger.debug(f"and price ({price}) > 0") Logger.debug(f"and price ({price}) >= price_exit ({price_exit})") Logger.debug( f"(self.app.allowSellAtLoss() is True (actual: {self.app.allowSellAtLoss()}) or margin ({margin}) > 0)" ) Logger.debug("\n") # profit bank when strong reversal detected if (self.app.sellAtResistance() is True and margin >= 2 and price > 0 and price >= price_exit and (self.app.allowSellAtLoss() or margin > 0)): log_text = "! Profit Bank Triggered (Selling At Resistance)" if not app.isSimulation() or (app.isSimulation() and not app.simResultOnly()): Logger.warning(log_text) if not (not self.app.allowSellAtLoss() and margin <= 0): self.app.notifyTelegram( f"{self.app.getMarket()} ({self.app.printGranularity()}) {log_text}" ) return True return False
def isWaitTrigger(self, app, margin: float = 0.0, goldencross: bool = False): # set to true for verbose debugging debug = False if debug and self.state.action != "WAIT": Logger.debug("\n*** isWaitTrigger ***\n") if debug and self.state.action == "BUY": Logger.debug( "-- if bear market and bull only return true to abort buy --") Logger.debug( f"self.state.action == 'BUY' (actual: {self.state.action})") Logger.debug( f"and self.app.disableBullOnly() is True (actual: {self.app.disableBullOnly()})" ) Logger.debug(f"and goldencross is False (actual: {goldencross})") Logger.debug("\n") # if bear market and bull only return true to abort buy if (self.state.action == "BUY" and not self.app.disableBullOnly() and not goldencross): log_text = "! Ignore Buy Signal (Bear Buy In Bull Only)" Logger.warning(log_text) return True if debug and self.state.action == "SELL": Logger.debug("-- configuration specifies to not sell at a loss --") Logger.debug( f"self.state.action == 'SELL' (actual: {self.state.action})") Logger.debug( f"and self.app.allowSellAtLoss() is False (actual: {self.app.allowSellAtLoss()})" ) Logger.debug(f"and margin ({margin}) <= 0") Logger.debug("\n") # configuration specifies to not sell at a loss if (self.state.action == "SELL" and not self.app.allowSellAtLoss() and margin <= 0): if not app.isSimulation() or (app.isSimulation() and not app.simResultOnly()): log_text = "! Ignore Sell Signal (No Sell At Loss)" Logger.warning(log_text) return True if debug and self.state.action == "SELL": Logger.debug( "-- configuration specifies not to sell within min and max margin percent bounds --" ) Logger.debug( f"self.state.action == 'SELL' (actual: {self.state.action})") Logger.debug( f"(self.app.nosellminpcnt is not None (actual: {self.app.nosellminpcnt})) and (margin ({margin}) >= self.app.nosellminpcnt ({self.app.nosellminpcnt}))" ) Logger.debug( f"(self.app.nosellmaxpcnt is not None (actual: {self.app.nosellmaxpcnt})) and (margin ({margin}) <= self.app.nosellmaxpcnt ({self.app.nosellmaxpcnt}))" ) Logger.debug("\n") # configuration specifies not to sell within min and max margin percent bounds if (self.state.action == "SELL" and ((self.app.nosellminpcnt is not None) and (margin >= self.app.nosellminpcnt)) and ((self.app.nosellmaxpcnt is not None) and (margin <= self.app.nosellmaxpcnt))): if not app.isSimulation() or (app.isSimulation() and not app.simResultOnly()): Logger.warning("! Ignore Sell Signal (Within No-Sell Bounds)") return True return False
def data_display(self): # get % gains and delta for pair in self.order_pairs: try: # return 0 if unexpected exception pair['delta'] = float(pair['sell']['size']) - float( pair['buy']['size']) except: Logger.warning( 'unexpected error calculating delta, returning 0') Logger.debug(pair) pair['delta'] = 0 try: # return 0 if unexpected exception pair['gain'] = (float(pair['delta']) / float(pair['buy']['size'])) * 100 except: Logger.warning( 'unexpected error calculating gain, returning 0') Logger.debug(pair) pair['gain'] = 0 # get day/week/month/all time totals totals = {'today': [], 'week': [], 'month': [], 'all_time': []} today = datetime.today().date() lastweek = today - timedelta(days=7) lastmonth = today - timedelta(days=30) if self.app.statstartdate: try: start = datetime.strptime(self.app.statstartdate, '%Y-%m-%d').date() except: raise ValueError( "format of --statstartdate must be yyyy-mm-dd") else: start = None # popular currencies symbol = self.app.getQuoteCurrency() if symbol in ['USD', 'AUD', 'CAD', 'SGD', 'NZD']: symbol = '$' if symbol == 'EUR': symbol = '€' if symbol == 'GBP': symbol = '£' if self.app.statdetail: headers = [ "| Num ", "| Market ", "| Date of Sell ", "| Price bought ", "| Price sold ", "| Delta ", "| Gain/Loss |" ] border = "+" for header in headers: border += "-" * (len(header) - 1) + '+' border = border[:-2] + '+' Logger.info(border + "\n" + "".join([x for x in headers]) + "\n" + border) for i, pair in enumerate(self.order_pairs): if start: if pair['sell']['time'].date() < start: continue d_num = '| ' + str(i + 1) d_num = d_num + ' ' * (len(headers[0]) - len(d_num)) d_date = '| ' + str(pair['sell']['time'].date()) d_market = '| ' + pair['market'] d_market = d_market + ' ' * (len(headers[1]) - len(d_market)) d_date = d_date + ' ' * (len(headers[2]) - len(d_date)) d_buy_size = '| ' + symbol + ' ' + '{:.2f}'.format( pair['buy']['size']) d_buy_size = d_buy_size + ' ' * (len(headers[3]) - len(d_buy_size)) d_sell_size = '| ' + symbol + ' ' + '{:.2f}'.format( pair['sell']['size']) d_sell_size = d_sell_size + ' ' * (len(headers[4]) - len(d_sell_size)) if pair['delta'] > 0: d_delta = '| ' + symbol + ' {:.2f}'.format(pair['delta']) else: d_delta = '| ' + symbol + '{:.2f}'.format(pair['delta']) d_delta = d_delta + ' ' * (len(headers[5]) - len(d_delta)) if pair['gain'] > 0: d_gain = '| ' + '{:.2f}'.format(pair['gain']) + ' %' else: d_gain = '| ' + '{:.2f}'.format(pair['gain']) + ' %' d_gain = d_gain + ' ' * (len(headers[6]) - len(d_gain) - 1) + '|' Logger.info( f'{d_num}{d_market}{d_date}{d_buy_size}{d_sell_size}{d_delta}{d_gain}' ) Logger.info(border) sys.exit() for pair in self.order_pairs: if start: if pair['sell']['time'].date() < start: continue totals['all_time'].append(pair) if pair['sell']['time'].date() == today: totals['today'].append(pair) if pair['sell']['time'].date() > lastweek: totals['week'].append(pair) if pair['sell']['time'].date() > lastmonth: totals['month'].append(pair) # prepare data for output today_per = [x['gain'] for x in totals['today']] week_per = [x['gain'] for x in totals['week']] month_per = [x['gain'] for x in totals['month']] all_time_per = [x['gain'] for x in totals['all_time']] today_gain = [x['delta'] for x in totals['today']] week_gain = [x['delta'] for x in totals['week']] month_gain = [x['delta'] for x in totals['month']] all_time_gain = [x['delta'] for x in totals['all_time']] if len(today_per) > 0: today_delta = [ (x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['today'] ] today_delta = timedelta( seconds=int(sum(today_delta) / len(today_delta))) else: today_delta = '0:0:0' if len(week_per) > 0: week_delta = [ (x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['week'] ] week_delta = timedelta( seconds=int(sum(week_delta) / len(week_delta))) else: week_delta = '0:0:0' if len(month_per) > 0: month_delta = [ (x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['month'] ] month_delta = timedelta( seconds=int(sum(month_delta) / len(month_delta))) else: month_delta = '0:0:0' if len(all_time_per) > 0: all_time_delta = [ (x['sell']['time'] - x['buy']['time']).total_seconds() for x in totals['all_time'] ] all_time_delta = timedelta( seconds=int(sum(all_time_delta) / len(all_time_delta))) else: all_time_delta = '0:0:0' today_sum = symbol + ' {:.2f}'.format(round( sum(today_gain), 2)) if len(today_gain) > 0 else symbol + ' 0.00' week_sum = symbol + ' {:.2f}'.format(round( sum(week_gain), 2)) if len(week_gain) > 0 else symbol + ' 0.00' month_sum = symbol + ' {:.2f}'.format(round( sum(month_gain), 2)) if len(month_gain) > 0 else symbol + ' 0.00' all_time_sum = symbol + ' {:.2f}'.format(round(sum( all_time_gain), 2)) if len(all_time_gain) > 0 else symbol + ' 0.00' today_percent = str(round( sum(today_per), 4)) + '%' if len(today_per) > 0 else '0.0000%' week_percent = str(round(sum(week_per), 4)) + '%' if len(week_per) > 0 else '0.0000%' month_percent = str(round( sum(month_per), 4)) + '%' if len(month_per) > 0 else '0.0000%' all_time_percent = str(round(sum(all_time_per), 4)) + '%' if len( all_time_per) > 0 else '0.0000%' trades = 'Number of Completed Trades:' gains = 'Percentage Gains:' aver = 'Average Time Held (H:M:S):' success = 'Total Profit/Loss:' width = 30 if self.app.statgroup: header = 'MERGE' else: header = self.app.getMarket() Logger.info(f'------------- TODAY : {header} --------------') Logger.info(trades + ' ' * (width - len(trades)) + str(len(today_per))) Logger.info(gains + ' ' * (width - len(gains)) + today_percent) Logger.info(aver + ' ' * (width - len(aver)) + str(today_delta)) Logger.info(success + ' ' * (width - len(success)) + today_sum) Logger.info(f'\n-------------- WEEK : {header} --------------') Logger.info(trades + ' ' * (width - len(trades)) + str(len(week_per))) Logger.info(gains + ' ' * (width - len(gains)) + week_percent) Logger.info(aver + ' ' * (width - len(aver)) + str(week_delta)) Logger.info(success + ' ' * (width - len(success)) + week_sum) Logger.info(f'\n------------- MONTH : {header} --------------') Logger.info(trades + ' ' * (width - len(trades)) + str(len(month_per))) Logger.info(gains + ' ' * (width - len(gains)) + month_percent) Logger.info(aver + ' ' * (width - len(aver)) + str(month_delta)) Logger.info(success + ' ' * (width - len(success)) + month_sum) Logger.info(f'\n------------ ALL TIME : {header} ------------') Logger.info(trades + ' ' * (width - len(trades)) + str(len(all_time_per))) Logger.info(gains + ' ' * (width - len(gains)) + all_time_percent) Logger.info(aver + ' ' * (width - len(aver)) + str(all_time_delta)) Logger.info(success + ' ' * (width - len(success)) + all_time_sum) sys.exit()