import json import time import requests import credentials import sqlite3 from copy import deepcopy class broker: def __init__(self,exchange,read_config,config_filename): self.config_filename = config_filename self.read_config = read_config self.exchange = exchange self.last_price = 0 self.wait_time = 1 #Default wait time for API breathing room self.cooldown_multiplier = 2 #Default cooldown multiplier value if "cooldown_multiplier" in self.read_config: self.cooldown_multiplier = self.read_config["cooldown_multiplier"] self.wait_before_new_safety_order = 1 if "wait_before_new_safety_order" in self.read_config: self.wait_before_new_safety_order = self.read_config["wait_before_new_safety_order"] self.empty_order = {"id": "", "status": "", "filled": 0, "remaining": 0, "price": 0, "cost": 0, "fees": [], "symbol": ""} self.retries = read_config["retries"] if "retries" in self.read_config else 10 self.slippage_default_threshold = self.read_config["slippage_default_threshold"] if "slippage_default_threshold" in read_config else .03 self.logger = logger(self.read_config) self.write_order_history = True #This should be a toggle in config_file self.profits_database_filename = "profits/profits_database.db" self.database_connection = sqlite3.connect(self.profits_database_filename) self.database_cursor = self.database_connection.cursor() self.database_cursor.execute(''' CREATE TABLE IF NOT EXISTS profits_table ( timestamp REAL PRIMARY KEY, pair TEXT, amount REAL, exchange_name TEXT, order_id TEXT, order_history TEXT ) ''') self.database_connection.commit() self.database_connection.close() self.exchange.load_markets() #Populates deals cache self.deals_cache_length = 10 self.deals_list = self.preload_deals(amount_to_preload=self.deals_cache_length) def preload_deals(self,amount_to_preload=10): ''' Reads the last n deals from the database and returns them in a list ''' connection = sqlite3.connect(self.profits_database_filename) cursor = connection.cursor() cursor.execute(f"SELECT * FROM profits_table WHERE exchange_name = ? ORDER BY timestamp DESC LIMIT ?", (self.get_exchange_name(), amount_to_preload)) result = cursor.fetchall() connection.close() return [(row[0],row[1],row[2],row[3],row[4],"") for row in result] def get_deals_cache(self): return self.deals_list def all_markets(self,no_retries=False): retries = self.retries while retries>0: try: return self.exchange.load_markets() except Exception as e: self.logger.log_this(f"Exception in reload_markets: {e}") if no_retries: break retries-=1 time.sleep(self.wait_time) return {} def reload_markets(self): try: self.exchange.load_markets(reload=True) return 0 except Exception as e: self.logger.log_this(f"Exception in reload_markets: {e}") return 1 def get_trades_timestamps(self,pair,timespan,no_retries=False): ''' Returns the timestamps of the last trades from the database for the boosting algorithm ''' retries = self.retries while retries>0: try: database_connection = sqlite3.connect(self.profits_database_filename) database_cursor = database_connection.cursor() database_cursor.execute(f"SELECT * FROM profits_table WHERE timestamp >= {time.time()-timespan} ORDER BY timestamp") rows = database_cursor.fetchall() return [item[0] for item in rows if item[1]==pair] except Exception as e: self.logger.log_this(f"Exception in preload_timestamps: {e}") if no_retries: break retries-=1 time.sleep(self.wait_time) return [] def write_profit_to_cache(self,dataset): ''' dataset format: (timestamp,pair,amount,exchange_name,order_id,order_history) ''' self.deals_list.insert(0,(dataset[0],dataset[1],dataset[2],dataset[3],dataset[4],"")) self.deals_list = self.deals_list[:self.deals_cache_length] return 0 def write_profit_to_db(self,dataset,no_retries=False): ''' dataset format: (timestamp,pair,amount,exchange_name,order_id,order_history) ''' retries = self.retries while retries>0: try: database_connection = sqlite3.connect(self.profits_database_filename) database_cursor = database_connection.cursor() database_cursor.execute('INSERT INTO profits_table VALUES(?, ?, ?, ?, ?, ?)', dataset) database_connection.commit() database_connection.close() except Exception as e: self.logger.log_this(f"Exception in write_profit_to_db: {e}") if no_retries: break retries-=1 time.sleep(self.wait_time) return 0 return 1 def check_for_duplicate_profit_in_db(self,order,no_retries=False): ''' SQLite implementation of check_for_duplicate_profit(): Compares the id of the last profit order with the one in the database. ''' retries = self.retries while retries>0: try: database_connection = sqlite3.connect(self.profits_database_filename) database_cursor = database_connection.cursor() database_cursor.execute(f"SELECT * FROM profits_table WHERE pair = '{order['symbol']}' ORDER BY timestamp DESC LIMIT 1;") rows = database_cursor.fetchall() database_connection.close() if rows==[]: return False return order["id"]==rows[0][4] except Exception as e: self.logger.log_this(f"Exception in check_for_duplicate_profit_in_db: {e}",1) if no_retries: break retries-=1 time.sleep(self.wait_time) return False def get_write_order_history(self): return self.write_order_history def get_cooldown_multiplier(self): return self.cooldown_multiplier def set_cooldown_multiplier(self, value:int): self.cooldown_multiplier = value return 0 def get_wait_before_new_safety_order(self): return self.wait_before_new_safety_order def set_wait_before_new_safety_order(self,value:float): self.wait_before_new_safety_order = value return 0 def get_default_order_size(self): return self.read_config["default_order_size"] def set_default_order_size(self,size): try: self.read_config["default_order_size"] = float(size) except Exception as e: self.logger.log_this(f"Exception in set_default_order_size: {e}",1) return 1 return 0 def get_slippage_default_threshold(self): return self.slippage_default_threshold def set_slippage_default_threshold(self,threshold): try: self.slippage_default_threshold = float(threshold) return 0 except Exception as e: self.logger.log_this(f"Exception in set_slippage_default_threshold: {e}") return 1 def get_retries(self): return self.retries def set_retries(self,amount): try: self.retries = int(amount) return 0 except Exception as e: self.logger.log_this(f"Exception in set_retries: {e}") return 1 def get_empty_order(self): return self.empty_order def get_exchange_name(self): return self.read_config["exchange"] def set_wait_time(self,sec): ''' Sets the default wait time between some API calls ''' try: new_time = float(sec) except Exception as e: self.logger.log_this(f"Exception in set_wait_time: {e}") return 1 self.wait_time = new_time return 0 def get_wait_time(self): ''' Returns the default wait time between some API calls ''' return self.wait_time def get_config(self): return deepcopy(self.read_config) def set_config(self,new_config): self.read_config = deepcopy(new_config) return 0 def reload_config_file(self): try: with open(self.config_filename) as f: self.read_config = json.load(f) except Exception as e: self.logger.log_this(f"Exception while reading the config file: {e}",1) def add_pair_to_config(self,pair): if pair not in self.read_config["pairs"]: self.read_config["pairs"].append(pair) return 0 return 1 def remove_pair_from_config(self,pair): try: if pair in self.read_config["pairs"]: self.read_config["pairs"].remove(pair) return 0 self.logger.log_this("Pair does not exist - Can't remove from read_config",1,pair) return 2 except Exception as e: self.logger.log_this(f"Problems removing pair: {e}",1,pair) return 1 def get_pairs(self): return self.read_config["pairs"] def clear_pairs(self): self.read_config["pairs"].clear() return 0 def get_lap_time(self): return self.read_config["lap_time"] def set_lap_time(self,new_lap_time): try: self.read_config["lap_time"]=float(new_lap_time) return 0 except Exception as e: self.logger.log_this(f"Can't set new lap time. {new_lap_time} is an invalid entry. Exception: {e}",1) return 1 def rewrite_config_file(self): try: with open(f"{self.config_filename}","w") as f: f.write(json.dumps(self.read_config, indent=4)) return 0 except Exception as e: self.logger.log_this(f"Problems writing the config file. Exception: {e}",1) return 1 def get_order_book(self,symbol,no_retries=False): ''' Returns the complete orderbook ''' retries = self.retries while retries>0: try: return self.exchange.fetch_order_book(symbol) except Exception as e: self.logger.log_this(f"Exception in get_order_book: {e}",1) if no_retries: break time.sleep(self.wait_time) retries-=1 return {} def find_minimum_viable_price(self,order_book,amount,side): suma = 0 data = order_book["bids"] if side=="sell" else order_book["asks"] for x in data: suma += x[1] if suma>=amount: return x[0] def get_prices(self,pair_list=None,no_retries=False): ''' Returns the closing prices of all the pairs in the pair_list list :param pair_list: list of pairs to get prices for :param no_retries: if True, the function will not retry if it fails :return: dictionary {pair: price} ''' retries = self.retries while retries>0: try: if self.get_exchange_name()=="binance": a = self.exchange.fetch_last_prices(pair_list) return {x: a[x]["price"] for x in a.keys()} else: #a = self.exchange.fetch_tickers(pair_list) a = self.exchange.fetch_tickers() #return {x.upper(): a[x]["close"] for x in a.keys() if x.upper() in pair_list} if pair_list is None: return {x: a[x]["close"] for x in a.keys()} return {x: a[x]["close"] for x in a.keys() if x in pair_list} except Exception as e: self.logger.log_this(f"Exception in get_prices: {e}",1) if no_retries: break time.sleep(self.wait_time) retries-=1 return {} def get_ticker_price(self,symbol,no_retries=False): ''' Returns the closing price of a trading pair :param symbol: trading pair symbol :param no_retries: if True, will not retry if exception occurs :return: closing price of trading pair ''' retries = self.retries while retries>0: try: pair = symbol a = self.exchange.fetch_ticker(pair) self.last_price = a["close"] return self.last_price except Exception as e: self.logger.log_this(f"Exception in get_ticker_price: {e}",1) if no_retries: break time.sleep(self.wait_time) retries-=1 return self.last_price def get_top_ask_price(self,symbol): ''' Returns the top ask price for the given symbol Parameters: symbol: the symbol to get the top ask price for Returns: the top ask price for the given symbol ''' orderbook = self.get_order_book(symbol) if orderbook=={}: self.logger.log_this("Can't fetch orderbook (from get_top_ask_price)",1,symbol) return self.get_ticker_price(symbol) try: return orderbook["asks"][0][0] except Exception as e: self.logger.log_this(f"Exception getting top ask price: {e}",1,symbol) return self.get_ticker_price(symbol) def get_top_bid_price(self,symbol): ''' Returns the top bid price for the given symbol Parameters: symbol: the symbol to get the top bid price for Returns: the top bid price for the given symbol ''' orderbook = self.get_order_book(symbol) if orderbook=={}: self.logger.log_this("Can't fetch orderbook (from get_top_bid_price)",1,symbol) return self.get_ticker_price(symbol) try: return orderbook["bids"][0][0] except Exception as e: self.logger.log_this(f"Exception getting top mid price: {e}",1,symbol) return self.get_ticker_price(symbol) def get_mid_price(self,symbol): ''' Retrieves the orderbook and returns the average price [(top bid + top ask)/2] :param symbol: the symbol to get the mid price for :return: the mid price ''' orderbook = self.get_order_book(symbol) if orderbook=={}: self.logger.log_this("Can't fetch orderbook (from get_mid_price)",1,symbol) return self.get_ticker_price(symbol) try: mid_price = (orderbook["asks"][0][0]+orderbook["bids"][0][0])/2 except Exception as e: self.logger.log_this(f"Exception getting mid_price: {e}",1,symbol) return self.get_ticker_price(symbol) return self.price_to_precision(symbol,mid_price) def get_coins_balance(self,no_retries=False): ''' Retrieves the balance of all coins on the exchange :param no_retries: if True, it will not retry on failure :return: list of all coins and their balance on the exchange ''' retries = self.retries while retries>0: try: return self.exchange.fetch_balance() except Exception as e: self.logger.log_this(f"Exception in get_coins_balance: {e}",1) if no_retries: break time.sleep(self.wait_time) retries-=1 return [] def fetch_full_orders(self,pairs=None) -> list: ''' Returns a list of all orders on the exchange :param pairs: list of pairs to get orders for :return: list of orders ''' if pairs is None: pairs = [] try: orders = [] if self.get_exchange_name()=="binance": orders = self.get_opened_orders_binance(pairs) else: orders = self.get_opened_orders() return [] if orders is None else orders except Exception as e: self.logger.log_this(f"Exception in fetch_full_orders: {e}",2) return [] def fetch_open_orders(self,pairs=None) -> list: ''' Returns a list of IDs of all open orders on the exchange :param pairs: list of pairs to get opened orders :return: list of IDs of all open orders ''' if pairs is None: pairs = [] try: #id_list = [] if self.get_exchange_name()=="binance": return self.get_opened_orders_binance(pairs) return self.get_opened_orders() #else: # orders = self.get_opened_orders() #if orders!=[]: # id_list.extend(x["id"] for x in orders) #return id_list except Exception as e: self.logger.log_this(f"Exception in fetch_open_orders: {e}",2) return [] def get_opened_orders(self,no_retries=False): #It should return a list of all opened orders ''' Returns a list of all the orders on the exchange :param pairs: list of pairs :return: list of all the open orders on the exchange ''' retries = self.retries while retries>0: try: return self.exchange.fetch_open_orders() except Exception as e: self.logger.log_this(f"Exception in get_opened_orders: {e}",1) if no_retries: break time.sleep(self.wait_time) retries-=1 return [] def get_opened_orders_binance(self,pairs): ''' Returns a list of all the open orders on the exchange :param pairs: list of pairs :return: list of all the open orders on the exchange ''' try: if "unified_order_query" in self.read_config and self.read_config["unified_order_query"] is True: return self.exchange.fetch_open_orders() result = [] for pair in pairs: a = self.exchange.fetch_open_orders(pair) result.extend(iter(a)) return result except Exception as e: self.logger.log_this(f"Exception in get_opened_orders_binance: {e}",1) return [] def cancel_order(self,id,symbol,no_retries=False): ''' Receives an order id and cancels the corresponding order :param id: order id :param symbol: pair :param no_retries: if True, the function will not retry to cancel the order :return: 0 if order was succesfully canceled, 1 if not ''' pair = symbol tries = self.retries//2 while tries>0: try: while self.get_order(id,pair)["status"]=="open": self.exchange.cancel_order(id,symbol=pair) time.sleep(self.wait_time) return 0 except Exception as e: if self.get_order(id,pair)["status"]=="canceled": return 0 self.logger.log_this(f"Exception in cancel_order: id {id} - exception: {e}",1) if no_retries: break time.sleep(self.wait_time) tries-=1 return 1 def amount_to_precision(self,pair,amount): try: return float(self.exchange.amount_to_precision(pair,amount)) except Exception as e: self.logger.log_this(f"Can't convert amount {amount} to precision. Exception: {e}",1,pair) return amount def price_to_precision(self,pair,price): try: return float(self.exchange.price_to_precision(pair,price)) except Exception as e: self.logger.log_this(f"Can't convert price {price} to precision. Exception: {e}",1,pair) return price def cost_to_precision(self,pair,amount): try: return float(self.exchange.cost_to_precision(pair,amount)) except Exception as e: self.logger.log_this(f"Can't convert cost {amount} to precision. Exception: {e}",1,pair) return amount def new_simulated_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False): ''' TODO: Emulating Market Orders With Limit Orders It is also possible to emulate a market order with a limit order. WARNING this method can be risky due to high volatility, use it at your own risk and only use it when you know really well what you're doing! Most of the time a market sell can be emulated with a limit sell at a very low price – the exchange will automatically make it a taker order for market price (the price that is currently in your best interest from the ones that are available in the order book). When the exchange detects that you're selling for a very low price it will automatically offer you the best buyer price available from the order book. That is effectively the same as placing a market sell order. Thus market orders can be emulated with limit orders (where missing). The opposite is also true – a market buy can be emulated with a limit buy for a very high price. Most exchanges will again close your order for best available price, that is, the market price. However, you should never rely on that entirely, ALWAYS test it with a small amount first! You can try that in their web interface first to verify the logic. You can sell the minimal amount at a specified limit price (an affordable amount to lose, just in case) and then check the actual filling price in trade history. :param symbol: The symbol of the asset you want to place a market order for. :param size: The size of the order you want to place. :param side: The side of the order you want to place (buy or sell) :param amount_in_base: Signals is the size parameter is nominated in base or quote currency :param no_retries: If True, the function will not try to fetch the order again if it fails ''' retries = self.retries//2 pair = symbol while retries>0: try: if self.get_exchange_name()=="gateio" and side=="buy" and not amount_in_base: new_order = self.exchange.create_market_buy_order_with_cost(pair, size) else: order_book = self.get_order_book(symbol) if order_book=={}: self.logger.log_this(f"new_simulated_market_order. Order book returned an empty dictionary",1,symbol) return self.empty_order if amount_in_base or side!="buy": base_amount = self.amount_to_precision(pair,size) else: avg_price = self.average_price_depth(order_book,size,"sell") base_amount = size/avg_price if avg_price is not None else size/self.get_ticker_price(symbol) price = self.find_minimum_viable_price(order_book,base_amount,side) #Maybe check for slippage here instead of within the trader itself? idk new_order = self.exchange.create_order(pair,"limit",side,base_amount,price) time.sleep(self.wait_time) return self.get_order(new_order["id"],pair) except Exception as e: self.logger.log_this(f"new_simulated_market_order exception: {e}",1,symbol) if no_retries: break time.sleep(self.wait_time) retries -= 1 return self.empty_order def weighted_average(self,prices,weights): ''' Given a list of prices and a list of weights, returns the weighted average of those prices. :param prices: list of prices :param weights: list of weights ''' return sum(prices[i]*weights[i] for i in range(len(prices)))/sum(weights) def average_price_depth(self,order_book,size,side): ''' Given the size of the order in quote, it returns the average price that a market BUY order of that amount would get in the current orderbook. :param order_book: the orderbook :param size: the size of the order in quote :param side: the side of the order ''' quote = 0 prices = [] weights = [] dataset = order_book["asks"] if side=="buy" else order_book["bids"] for x in dataset: prices.append(x[0]) weights.append(x[1]) quote+=x[1]*x[0] if quote>=size: #Now we calculate the weighted average return self.weighted_average(prices,weights) return None def new_market_order(self,symbol,size,side,amount_in_base=False,no_retries=False): #It should send a new market order to the exchange ''' Sends a new market order to the exchange. :param symbol: The symbol of the asset. :param size: The size of the order. :param side: The side of the order. :param amount_in_base: Whether the amount is nominated in base or quote currency. :param no_retries: If True, the function will not try to fetch the order again if it fails ''' if self.read_config["simulate_market_orders"]: return self.new_simulated_market_order(symbol,size,side,amount_in_base=amount_in_base) retries = self.retries pair = symbol while retries>0: try: if side=="buy": to_buy = float(size) if not amount_in_base: to_buy = float(size)/self.get_top_ask_price(pair) amount = self.amount_to_precision(pair,to_buy) else: amount = self.amount_to_precision(pair,size) #Market sell orders are always nominated in base currency order_to_send = self.exchange.create_order(pair,"market",side,amount) time.sleep(self.wait_time) return self.get_order(order_to_send["id"],pair) except Exception as e: self.logger.log_this(f"Exception in new_market_order: {e}",1,pair) if no_retries: break time.sleep(self.wait_time) retries-=1 return None def not_enough_balance_error(self, error_object): ''' Checks if the error is a balance error. Receives an error object. Returns True if the error is a balance error, False otherwise. :param error_object: The error object. :return: Boolean value. ''' error_text = str(error_object) return "insufficient" in error_text.lower() or "BALANCE_NOT_ENOUGH" in error_text or "Low available balance" in error_text def price_too_high_error(self, error_object): ''' Checks if the error is a price too high error. Receives an error object. Returns True if the error is a price too high error, False otherwise. :param error_object: The error object. :return: Boolean value ''' return "the highest price limit for buy orders is" in str(error_object).lower() def price_too_low_error(self, error_object): ''' Checks if the error is a price too low error. Receives an error object. Returns True if the error is a price too low error, False otherwise. :param error_object: The error object. :return: Boolean value ''' return "the lowest price limit for sell orders is" in str(error_object).lower() def new_limit_order(self,symbol,size,side,price,no_retries=False): ''' Sends a new limit order. :param symbol: The symbol of the order. :param size: The size of the order. :param side: The side of the order. :param price: The price of the order. :param no_retries: If True, the function will not retry to send the order if there is an error. ''' tries = self.retries pair = symbol while tries>=0: try: order_to_send = self.exchange.create_order(pair,"limit",side,self.amount_to_precision(pair,size),price) time.sleep(self.wait_time) return self.get_order(order_to_send["id"],pair) #if order_to_send["amount"] is not None: # Because Kucoin etc etc # return self.get_order(order_to_send["id"],pair) # #self.logger.log_this(f"Error sending order: Null order returned",2,pair) # #self.cancel_order(order_to_send["id"],symbol,no_retries=True) # #retries-=1 except Exception as e: self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(pair,size)} - Exception: {e}",1,symbol) if self.not_enough_balance_error(e): if tries<=self.retries//2: #Halves the amount of retries if there is a balance error. return 1 if self.get_exchange_name()=="binance": #If the exchange is Binance, it doesn't try that hard to resend the order: Since CCXT doesn't handle binance fees at all, #instead of guesstimating the fees, it's easier and more precise to query the exchange for the remaining base currency #and send the order with that amount. tries-=1 elif self.price_too_high_error(e): #In high volatility moments, OKX sometimes just needs a bit of extra thinking time. time.sleep(self.wait_time) elif self.price_too_low_error(e): time.sleep(self.wait_time) if no_retries: break tries-=1 time.sleep(self.wait_time) return None def get_order(self,id,symbol,no_retries=False): ''' Gets an order from the exchange. :param id: The id of the order. :param symbol: The symbol of the order. :param no_retries: If True, the function will not try to fetch the order again if it fails. :return: The order. ''' if id=="": return self.empty_order tries = self.retries pair = symbol while tries>0: try: return self.exchange.fetch_order(id,symbol=pair) except Exception as e: self.logger.log_this(f"Exception in get_order: {e}",1,symbol) if no_retries: break time.sleep(self.wait_time) tries -=1 return self.empty_order def fetch_market(self,symbol,no_retries=False): ''' Returns the market. :param symbol: The symbol of the market. :param no_retries: If True, the function will not retry if an exception occurs. :return: The market information. ''' tries = self.retries pair = symbol while tries>0: try: return self.exchange.market(pair) except Exception as e: self.logger.log_this(f"Exception in fetch_market: {e}",1,symbol) if no_retries: break time.sleep(self.wait_time) tries-=1 return None def get_ticker(self,symbol,no_retries=False): ''' Returns the ticker information. :param symbol: The trading pair. :param no_retries: If True, the function will not retry if an exception occurs. :return: The ticker information. ''' tries = self.retries pair = symbol while tries>0: try: return self.exchange.fetch_ticker(pair) except Exception as e: self.logger.log_this(f"Exception in get_ticker: {e}") if no_retries: break tries-=1 time.sleep(self.wait_time) return None def get_min_base_size(self,pair): ''' Returns the minimum order base size that the exchange supports. :param pair: pair :return: minimum order base size ''' market = self.fetch_market(pair) if market is None: return None if self.get_exchange_name() in ["okex","kucoin"]: return float(market["limits"]["amount"]["min"]) elif self.get_exchange_name() in ["gateio"]: return (float(market["limits"]["cost"]["min"])+1)/self.get_ticker_price(pair) elif self.get_exchange_name()=="binance": for line in market["info"]["filters"]: if line["filterType"] == "NOTIONAL": return (float(line["minNotional"])+1)/self.get_ticker_price(pair) return None def get_min_quote_size(self,pair): ''' Returns the minimum order size in quote currency. :param pair: pair :return: minimum order quote size ''' market = self.fetch_market(pair) if market is None: return None if self.get_exchange_name()=="binance": for line in market["info"]["filters"]: if line["filterType"] == "NOTIONAL": #return self.broker.amount_to_precision(pair,(float(line["minNotional"]))) return float(line["minNotional"]) elif self.get_exchange_name()=="gateio": #return self.cost_to_precision(pair,float(market["info"]["min_base_amount"])*self.broker.get_mid_price(pair)) return float(market["limits"]["cost"]["min"]) elif self.get_exchange_name() in ["okex","kucoin"]: return self.cost_to_precision(pair,float(market["limits"]["amount"]["min"])*self.get_ticker_price(pair)) return None def get_step_size(self,pair): ''' Returns the step size of the market :param pair: pair :return: step size ''' market = self.fetch_market(pair) if market is None: return None try: if self.get_exchange_name()=="binance": for filter in market["info"]["filters"]: if filter["filterType"]=="LOT_SIZE": return float(filter["stepSize"]) elif self.get_exchange_name()=="kucoin": return float(market["info"]["baseIncrement"]) elif self.get_exchange_name() in ["gateio","okex"]: return float(market["precision"]["amount"]) except Exception as e: self.logger.log_this(f"Exception in get_step_size: {e}",1,pair) return None class logger: def __init__(self,broker_config): self.broker_config = broker_config self.exchange_name = self.broker_config["exchange"] self.tg_credentials = credentials.get_credentials("telegram") self.log_list_max_length = 10 self.log_list = self.preload_logs() def preload_logs(self): try: with open(f"logs/{self.exchange_name}.log","r") as f: self.log_list = f.readlines() return self.log_list[-self.log_list_max_length:] except Exception as e: print(e) return [] def set_log_list_max_length(self, amount): self.log_list_max_length = amount return self.log_list_max_length def get_log_list(self): return self.log_list def set_telegram_notifications(self, toggle): try: self.broker_config["telegram"] = bool(toggle) except Exception as e: self.log_this(f"Error in set_telegram_notifications",1) return 1 return 0 def send_tg_message(self,message,ignore_config=False): ''' Sends a Telegram message ''' tg_credentials = credentials.get_credentials("telegram") send_text = f"https://api.telegram.org/bot{tg_credentials['token']}/sendMessage?chat_id={tg_credentials['chatid']}&parse_mode=Markdown&text={message}" output = None if self.broker_config["telegram"] or ignore_config: output = requests.get(send_text,timeout=5).json() #5 seconds timeout. This could also be a tunable. if not output["ok"]: self.log_this(f"Error in send_tg_message: {output}") return 1 return 0 def log_this(self,message,level=2,pair=None): ''' Level 0: Screen, log file and Telegram Level 1: Screen and log file Level 2: Screen only ''' pair_data = "" if pair is None else f"{pair} | " text = time.strftime(f"[%Y/%m/%d %H:%M:%S] | {pair_data}{message}") print(text) if level<2: try: #Write to log file with open(f"logs/{self.exchange_name}.log","a") as log_file: log_file.write(text+"\n") log_file.close() #Append to log list self.log_list.append(text) #Trim log list self.log_list = self.log_list[-self.log_list_max_length:] except Exception as e: print("Can't write log file") print(e) if level<1: self.send_tg_message(f"{self.broker_config['exchange'].capitalize()} | {pair_data}{message}",ignore_config=level==-1) return 0