def marketSell(self, market: str='', base_quantity: float=0) -> list: """Executes a market sell providing a crypto amount""" # validates the market is syntactically correct if not self._isMarketValid(market): raise ValueError('Binance market is invalid.') if not isinstance(base_quantity, int) and not isinstance(base_quantity, float): raise TypeError('The crypto amount is not numeric.') try: 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 sell stepper = 10.0 ** precision truncated = math.trunc(stepper * base_quantity) / stepper Logger.info('Order quantity after rounding and fees: ' + str(truncated)) return self.client.order_market_sell(symbol=market, quantity=truncated) except Exception as err: ts = datetime.now().strftime("%d-%m-%Y %H:%M:%S") Logger.error(ts + ' Binance ' + ' marketSell ' + str(err)) return []
def __init__(self, api_key: str='', api_secret: str='', api_url: str='https://api.binance.com') -> None: """Binance API object model Parameters ---------- api_key : str Your Binance account portfolio API key api_secret : str Your Binance account portfolio API secret """ # options self.debug = False self.die_on_api_error = False valid_urls = [ 'https://api.binance.com/', 'https://api.binance.us/', 'https://testnet.binance.vision/api/' ] if len(api_url) > 1 and api_url[-1] != '/': api_url = api_url + '/' # validate Binance API if api_url not in valid_urls: raise ValueError('Binance API URL is invalid') # validates the api key is syntactically correct p = re.compile(r"^[A-z0-9]{64,64}$") if not p.match(api_key): self.handle_init_error('Binance API key is invalid') # validates the api secret is syntactically correct p = re.compile(r"^[A-z0-9]{64,64}$") if not p.match(api_secret): self.handle_init_error('Binance API secret is invalid') self.mode = 'live' # TODO: check if this needs to be set here self.api_url = api_url self.api_key = api_key self.api_secret = api_secret self.order_attempts = 0 for i in range(10): try: sys.tracebacklimit = 0 if 'api.binance.us' in api_url: self.client = Client(self.api_key, self.api_secret, { 'verify': False, 'timeout': 20 }, tld='us') else: self.client = Client(self.api_key, self.api_secret, { 'verify': False, 'timeout': 20 }) break except Exception as e: if i == 9: raise SystemExit("Can not create instance of AuthAPI client.") Logger.error('Exception: ' + str(e)) Logger.error('Error on creating instance of AuthAPI Client. Trying again... Attempt: ' + str(i)) sleep(0.1) sys.tracebacklimit = 1
def authAPI(self, method: str, uri: str, payload: str = '') -> pd.DataFrame: if not isinstance(method, str): raise TypeError('Method is not a string.') if not method in ['DELETE', 'GET', 'POST']: raise TypeError('Method not DELETE, GET or POST.') if not isinstance(uri, str): raise TypeError('URI is not a string.') try: if method == 'DELETE': resp = requests.delete(self._api_url + uri, auth=self) elif method == 'GET': resp = requests.get(self._api_url + uri, auth=self) elif method == 'POST': resp = requests.post(self._api_url + uri, json=payload, auth=self) if resp.status_code != 200: if self.die_on_api_error or resp.status_code == 401: # disable traceback sys.tracebacklimit = 0 raise Exception(method.upper() + ' (' + '{}'.format(resp.status_code) + ') ' + self._api_url + uri + ' - ' + '{}'.format(resp.json()['message'])) else: Logger.error('error: ' + method.upper() + ' (' + '{}'.format(resp.status_code) + ') ' + self._api_url + uri + ' - ' + '{}'.format(resp.json()['message'])) return pd.DataFrame() resp.raise_for_status() if isinstance(resp.json(), list): df = pd.DataFrame.from_dict(resp.json()) return df else: df = pd.DataFrame(resp.json(), index=[0]) return df except requests.ConnectionError as err: return self.handle_api_error(err, 'ConnectionError') except requests.exceptions.HTTPError as err: return self.handle_api_error(err, 'HTTPError') except requests.Timeout as err: return self.handle_api_error(err, 'Timeout') except json.decoder.JSONDecodeError as err: return self.handle_api_error(err, 'JSONDecodeError')
def handle_init_error(self, err: str) -> None: """Handle initialisation error""" if self.debug: raise TypeError(err) elif self.die_on_api_error: raise SystemExit(err) else: Logger.error(f"Initialization Error: {err}")
def authAPI(self, method: str, uri: str, payload: str = "") -> dict: """Initiates a REST API call""" if not isinstance(method, str): raise TypeError("Method is not a string.") if not method in ["GET", "POST"]: raise TypeError("Method not GET or POST.") if not isinstance(uri, str): raise TypeError("URI is not a string.") try: if method == "GET": resp = requests.get(self._api_url + uri) elif method == "POST": resp = requests.post(self._api_url + uri, json=payload) trycnt, maxretry = (1, 5) while trycnt <= maxretry: if resp.status_code != 200: resp_message = resp.json()["message"] message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}" if self.die_on_api_error: raise Exception(message) else: Logger.error( f"Coinbase Pro API request error - retry attempt {trycnt}: {message}" ) time.sleep(15) trycnt += 1 else: break if method == "GET": resp = requests.get(self._api_url + uri) elif method == "POST": resp = requests.post(self._api_url + uri, json=payload) resp.raise_for_status() return resp.json() except requests.ConnectionError as err: Logger.error("requests.ConnectionError") # remove this later return self.handle_api_error(err, "ConnectionError") except requests.exceptions.HTTPError as err: Logger.error("requests.exceptions.HTTPError") # remove this later return self.handle_api_error(err, "HTTPError") except requests.Timeout as err: Logger.error("requests.Timeout") # remove this later return self.handle_api_error(err, "Timeout") except json.decoder.JSONDecodeError as err: Logger.error("json.decoder.JSONDecodeError") # remove this later return self.handle_api_error(err, "JSONDecodeError")
def getTime(self) -> datetime: """Retrieves the exchange time""" try: # GET /api/v3/time resp = self.authAPI("GET", "/api/v3/time") return self.convert_time(int(resp["serverTime"])) - timedelta(hours=1) except Exception as e: Logger.error(f"Error: {e}") return None
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 getTakerFee(self, market: str='') -> float: if market == '': return DEFAULT_TAKER_FEE_RATE else: fees = self.getFees(market) if len(fees) == 0 or 'taker_fee_rate' not in fees: Logger.error(f"error: 'taker_fee_rate' not in fees (using {DEFAULT_TAKER_FEE_RATE} as a fallback)") return DEFAULT_TAKER_FEE_RATE return float(fees['taker_fee_rate'].to_string(index=False).strip())
def __init__(self) -> None: for i in range(10): try: self.client = Client() break except Exception as e: if i == 9: raise SystemExit("Can not create instance of AuthAPI client.") Logger.error('Exception: ' + str(e)) Logger.error('Error on creating instance of AuthAPI Client. Trying again... Attempt: ' + str(i)) sleep(0.1)
def on_error(self, e, data=None): Logger.error(e) Logger.error("{} - data: {}".format(e, data)) self.stop = True try: self.ws = None self.tickers = None self.candles = None self.start_time = None self.time_elapsed = 0 except: pass
def getTime(self) -> datetime: """Retrieves the exchange time""" try: resp = self.authAPI("GET", "time") if "epoch" in resp: epoch = int(resp["epoch"]) return datetime.fromtimestamp(epoch) else: Logger.error(resp) return None except Exception as e: Logger.error(f"Error: {e}") return None
def handle_api_error(self, err: str, reason: str) -> dict: """Handler for API errors""" if self.debug: if self.die_on_api_error: raise SystemExit(err) else: Logger.error(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 getTakerFee(self, market: str = "") -> float: """Retrieves the taker fee""" if len(market) != None: fees = self.getFees(market) else: fees = self.getFees() if len(fees) == 0 or "taker_fee_rate" not in fees: Logger.error( f"error: 'taker_fee_rate' not in fees (using {DEFAULT_TAKER_FEE_RATE} as a fallback)" ) return DEFAULT_TAKER_FEE_RATE return float(fees["taker_fee_rate"].to_string(index=False).strip())
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.error(err) return pd.DataFrame() else: if self.die_on_api_error: raise SystemExit(f"{reason}: {self._api_url}") else: Logger.info(f"{reason}: {self._api_url}") return pd.DataFrame()
def authAPI(self, method: str, uri: str, payload: str = '') -> dict: if not isinstance(method, str): raise TypeError('Method is not a string.') if not method in ['GET', 'POST']: raise TypeError('Method not GET or POST.') if not isinstance(uri, str): raise TypeError('URI is not a string.') try: if method == 'GET': resp = requests.get(self._api_url + uri) elif method == 'POST': resp = requests.post(self._api_url + uri, json=payload) if resp.status_code != 200: resp_message = resp.json()['message'] message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}" if self.die_on_api_error: raise Exception(message) else: Logger.error(f"Error: {message}") return {} resp.raise_for_status() return resp.json() except requests.ConnectionError as err: return self.handle_api_error(err, "ConnectionError") except requests.exceptions.HTTPError as err: return self.handle_api_error(err, "HTTPError") except requests.Timeout as err: return self.handle_api_error(err, "Timeout") except json.decoder.JSONDecodeError as err: return self.handle_api_error(err, "JSONDecodeError")
def authAPI(self, method: str, uri: str, payload: str = {}) -> dict: """Initiates a REST API call to exchange""" if not isinstance(method, str): raise TypeError("Method is not a string.") if not method in ["GET", "POST"]: raise TypeError("Method not GET or POST.") if not isinstance(uri, str): raise TypeError("URI is not a string.") try: resp = requests.get(f"{self._api_url}{uri}", params=payload) if resp.status_code != 200: resp_message = resp.json()["msg"] message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}" if self.die_on_api_error: raise Exception(message) else: Logger.error(f"Error: {message}") return {} resp.raise_for_status() return resp.json() except requests.ConnectionError as err: return self.handle_api_error(err, "ConnectionError") except requests.exceptions.HTTPError as err: return self.handle_api_error(err, "HTTPError") except requests.Timeout as err: return self.handle_api_error(err, "Timeout") except json.decoder.JSONDecodeError as err: return self.handle_api_error(err, "JSONDecodeError")
def getTime(self) -> datetime: """Retrieves the exchange time""" try: resp = self.authAPI("GET", "time") if "epoch" in resp: epoch = int(resp["epoch"]) return datetime.fromtimestamp(epoch) else: Logger.error( "resp does not contain the epoch key for some reason!" ) # remove this later Logger.error(resp) return None except Exception as e: Logger.error(f"Error: {e}") return None
def authAPI(self, method: str, uri: str, payload: str = "") -> pd.DataFrame: """Initiates a REST API call""" if not isinstance(method, str): raise TypeError("Method is not a string.") if not method in ["DELETE", "GET", "POST"]: raise TypeError("Method not DELETE, GET or POST.") if not isinstance(uri, str): raise TypeError("URI is not a string.") try: if method == "DELETE": resp = requests.delete(self._api_url + uri, auth=self) elif method == "GET": resp = requests.get(self._api_url + uri, auth=self) elif method == "POST": resp = requests.post(self._api_url + uri, json=payload, auth=self) if "msg" in resp.json(): resp_message = resp.json()["msg"] elif "message" in resp.json(): resp_message = resp.json()["message"] else: resp_message = "" if resp.status_code == 401 and (resp_message == "request timestamp expired"): message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message} (hint: check your system time is using NTP)" Logger.error(f"Error: {message}") return {} elif resp.status_code != 200: if self.die_on_api_error or resp.status_code == 401: # disable traceback sys.tracebacklimit = 0 raise Exception( f"{method.upper()} ({resp.status_code}) {self._api_url}{uri} - {resp_message}" ) else: Logger.error( f"error: {method.upper()} ({resp.status_code}) {self._api_url}{uri} - {resp_message}" ) return pd.DataFrame() resp.raise_for_status() if isinstance(resp.json(), list): df = pd.DataFrame.from_dict(resp.json()) return df else: df = pd.DataFrame(resp.json(), index=[0]) return df except requests.ConnectionError as err: return self.handle_api_error(err, "ConnectionError") except requests.exceptions.HTTPError as err: return self.handle_api_error(err, "HTTPError") except requests.Timeout as err: return self.handle_api_error(err, "Timeout") except json.decoder.JSONDecodeError as err: return self.handle_api_error(err, "JSONDecodeError")
def authAPI(self, method: str, uri: str, payload: str = {}) -> dict: """Initiates a REST API call to the exchange""" if not isinstance(method, str): raise TypeError("Method is not a string.") if not method in ["GET", "POST"]: raise TypeError("Method not GET or POST.") if not isinstance(uri, str): raise TypeError("URI is not a string.") signed_uri = [ "/api/v3/account", "/api/v3/allOrders", "/api/v3/order", "/api/v3/order/test", "/sapi/v1/asset/tradeFee", ] query_string = urlencode(payload, True) if uri in signed_uri and query_string: query_string = "{}×tamp={}".format(query_string, self.getTimestamp()) elif uri in signed_uri: query_string = "timestamp={}".format(self.getTimestamp()) if uri in signed_uri: url = ( self._api_url + uri + "?" + query_string + "&signature=" + self.createHash(query_string) ) else: url = self._api_url + uri + "?" + query_string params = {"url": url, "params": {}} try: resp = self._dispatch_request(method)(**params) if "msg" in resp.json(): resp_message = resp.json()["msg"] elif "message" in resp.json(): resp_message = resp.json()["message"] else: resp_message = "" if resp.status_code == 400 and ( resp_message == "Timestamp for this request is outside of the recvWindow." ): message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message} (hint: increase recvWindow with --recvWindow <5000-60000>)" Logger.error(f"Error: {message}") return {} elif resp.status_code == 429 and ( resp_message.startswith("Too much request weight used") ): message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message} (sleeping for 5 seconds to prevent being banned)" Logger.error(f"Error: {message}") time.sleep(5) return {} elif resp.status_code != 200: message = f"{method} ({resp.status_code}) {self._api_url}{uri} - {resp_message}" if self.die_on_api_error: raise Exception(message) else: Logger.error(f"Error: {message}") return {} resp.raise_for_status() return resp.json() except requests.ConnectionError as err: return self.handle_api_error(err, "ConnectionError") except requests.exceptions.HTTPError as err: return self.handle_api_error(err, "HTTPError") except requests.Timeout as err: return self.handle_api_error(err, "Timeout") except json.decoder.JSONDecodeError as err: return self.handle_api_error(err, "JSONDecodeError")
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 authAPI(self, method: str, uri: str, payload: str = "") -> pd.DataFrame: """Initiates a REST API call""" if not isinstance(method, str): raise TypeError("Method is not a string.") if not method in ["DELETE", "GET", "POST"]: raise TypeError("Method not DELETE, GET or POST.") if not isinstance(uri, str): raise TypeError("URI is not a string.") try: if method == "DELETE": resp = requests.delete(self._api_url + uri, auth=self) elif method == "GET": # resp = requests.request('GET', self._api_url + uri, headers=headers) resp = requests.get(self._api_url + uri, auth=self) elif method == "POST": resp = requests.post(self._api_url + uri, json=payload, auth=self) # Logger.debug(resp.json()) if resp.status_code != 200: if self.die_on_api_error or resp.status_code == 401: # disable traceback sys.tracebacklimit = 0 raise Exception(method.upper() + " (" + "{}".format(resp.status_code) + ") " + self._api_url + uri + " - " + "{}".format(resp.json()["msg"])) else: Logger.error("error: " + method.upper() + " (" + "{}".format(resp.status_code) + ") " + self._api_url + uri + " - " + "{}".format(resp.json()["msg"])) return pd.DataFrame() resp.raise_for_status() mjson = resp.json() if isinstance(mjson, list): df = pd.DataFrame.from_dict(mjson) if "data" in mjson: mjson = mjson["data"] if "items" in mjson: if isinstance(mjson["items"], list): df = pd.DataFrame.from_dict(mjson["items"]) else: df = pd.DataFrame(mjson["items"], index=[0]) elif "data" in mjson: if isinstance(mjson, list): df = pd.DataFrame.from_dict(mjson) else: df = pd.DataFrame(mjson, index=[0]) else: if isinstance(mjson, list): df = pd.DataFrame.from_dict(mjson) else: df = pd.DataFrame(mjson, index=[0]) return df except requests.ConnectionError as err: return self.handle_api_error(err, "ConnectionError") except requests.exceptions.HTTPError as err: return self.handle_api_error(err, "HTTPError") except requests.Timeout as err: return self.handle_api_error(err, "Timeout") except json.decoder.JSONDecodeError as err: return self.handle_api_error(err, "JSONDecodeError") except Exception as err: return self.handle_api_error(err, "Exception")