def get_binance(key, secret): """Get balance from binance exchange API.""" client = BinClient(key, secret) acct = client.get_account() df = pd.DataFrame(acct['balances']) df['source'] = 'binance' df = df[['free', 'asset', 'source']] df.columns = ['balance', 'coin', 'source'] return df
class BinanceAPIManager: def __init__(self, config: Config, db: Database, logger: Logger): self.binance_client = Client( config.BINANCE_API_KEY, config.BINANCE_API_SECRET_KEY, tld=config.BINANCE_TLD, ) self.db = db self.logger = logger self.config = config @cached(cache=TTLCache(maxsize=1, ttl=43200)) def get_trade_fees(self) -> Dict[str, float]: return { ticker["symbol"]: ticker["taker"] for ticker in self.binance_client.get_trade_fee()["tradeFee"] } @cached(cache=TTLCache(maxsize=1, ttl=60)) def get_using_bnb_for_fees(self): return self.binance_client.get_bnb_burn_spot_margin()["spotBNBBurn"] def get_fee(self, origin_coin: Coin, target_coin: Coin, selling: bool): return 0.001 base_fee = self.get_trade_fees()[origin_coin + target_coin] if not self.get_using_bnb_for_fees(): return base_fee # The discount is only applied if we have enough BNB to cover the fee amount_trading = (self._sell_quantity(origin_coin.symbol, target_coin.symbol) if selling else self._buy_quantity( origin_coin.symbol, target_coin.symbol)) fee_amount = amount_trading * base_fee * 0.75 if origin_coin.symbol == "BNB": fee_amount_bnb = fee_amount else: origin_price = self.get_market_ticker_price(origin_coin + Coin("BNB")) if origin_price is None: return base_fee fee_amount_bnb = fee_amount * origin_price bnb_balance = self.get_currency_balance("BNB") if bnb_balance >= fee_amount_bnb: return base_fee * 0.75 return base_fee def get_all_market_tickers(self) -> AllTickers: """ Get ticker price of all coins """ return AllTickers(self.binance_client.get_all_tickers()) def get_market_ticker_price(self, ticker_symbol: str): """ Get ticker price of a specific coin """ for ticker in self.binance_client.get_symbol_ticker(): if ticker["symbol"] == ticker_symbol: return float(ticker["price"]) return None def get_currency_balance(self, currency_symbol: str): """ Get balance of a specific coin """ for currency_balance in self.binance_client.get_account()["balances"]: if currency_balance["asset"] == currency_symbol: return float(currency_balance["free"]) return None def retry(self, func, *args, **kwargs): time.sleep(2) attempts = 0 while attempts < 30: try: return func(*args, **kwargs) except Exception as e: # pylint: disable=broad-except self.logger.info("Failed to Buy/Sell. Trying Again.") if attempts == 0: self.logger.info(e) attempts += 1 return None def get_symbol_filter(self, origin_symbol: str, target_symbol: str, filter_type: str): return next(_filter for _filter in self.binance_client.get_symbol_info( origin_symbol + target_symbol)["filters"] if _filter["filterType"] == filter_type) @cached(cache=TTLCache(maxsize=2000, ttl=43200)) def get_alt_tick(self, origin_symbol: str, target_symbol: str): step_size = self.get_symbol_filter(origin_symbol, target_symbol, "LOT_SIZE")["stepSize"] if step_size.find("1") == 0: return 1 - step_size.find(".") return step_size.find("1") - 1 @cached(cache=TTLCache(maxsize=2000, ttl=43200)) def get_min_notional(self, origin_symbol: str, target_symbol: str): return float( self.get_symbol_filter(origin_symbol, target_symbol, "MIN_NOTIONAL")["minNotional"]) def wait_for_order(self, origin_symbol, target_symbol, order_id): while True: try: order_status = self.binance_client.get_order( symbol=origin_symbol + target_symbol, orderId=order_id) break except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.info(f"Unexpected Error: {e}") time.sleep(1) self.logger.info(order_status) while order_status["status"] != "FILLED": try: order_status = self.binance_client.get_order( symbol=origin_symbol + target_symbol, orderId=order_id) if self._should_cancel_order(order_status): cancel_order = None while cancel_order is None: cancel_order = self.binance_client.cancel_order( symbol=origin_symbol + target_symbol, orderId=order_id) self.logger.info("Order timeout, canceled...") # sell partially if order_status[ "status"] == "PARTIALLY_FILLED" and order_status[ "side"] == "BUY": self.logger.info("Sell partially filled amount") order_quantity = self._sell_quantity( origin_symbol, target_symbol) partially_order = None while partially_order is None: partially_order = self.binance_client.order_market_sell( symbol=origin_symbol + target_symbol, quantity=order_quantity) self.logger.info("Going back to scouting mode...") return None if order_status["status"] == "CANCELED": self.logger.info( "Order is canceled, going back to scouting mode...") return None time.sleep(1) except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.info(f"Unexpected Error: {e}") time.sleep(1) return order_status def _should_cancel_order(self, order_status): minutes = (time.time() - order_status["time"] / 1000) / 60 timeout = 0 if order_status["side"] == "SELL": timeout = float(self.config.SELL_TIMEOUT) else: timeout = float(self.config.BUY_TIMEOUT) if timeout and minutes > timeout and order_status["status"] == "NEW": return True if timeout and minutes > timeout and order_status[ "status"] == "PARTIALLY_FILLED": if order_status["side"] == "SELL": return True if order_status["side"] == "BUY": current_price = self.get_market_ticker_price( order_status["symbol"]) if float(current_price) * (1 - 0.001) > float( order_status["price"]): return True return False def buy_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers: AllTickers): return self.retry(self._buy_alt, origin_coin, target_coin, all_tickers) def _buy_quantity(self, origin_symbol: str, target_symbol: str, target_balance: float = None, from_coin_price: float = None): target_balance = target_balance or self.get_currency_balance( target_symbol) from_coin_price = from_coin_price or self.get_all_market_tickers( ).get_price(origin_symbol + target_symbol) origin_tick = self.get_alt_tick(origin_symbol, target_symbol) return math.floor(target_balance * 10**origin_tick / from_coin_price) / float(10**origin_tick) def _buy_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers): """ Buy altcoin """ #Update balance first trade_log = self.db.start_trade_log(origin_coin, target_coin, False) origin_symbol = origin_coin.symbol target_symbol = target_coin.symbol origin_balance = self.get_currency_balance(origin_symbol) target_balance = self.get_currency_balance(target_symbol) from_coin_price = all_tickers.get_price(origin_symbol + target_symbol) order_quantity = self._buy_quantity(origin_symbol, target_symbol, target_balance, from_coin_price) self.logger.info( f"BUY QTY {order_quantity} of {origin_symbol} for ${target_balance} {self.config.LOG_USER}" ) # prevent low amount buy if target_balance < 100: self.logger.warning( f"Couldn't BUY with {target_balance}$ {self.config.LOG_USER}") time.sleep(2) return None # Try to buy until successful order = None while order is None: try: order = self.binance_client.order_market_buy( symbol=origin_symbol + target_symbol, quoteOrderQty=target_balance) self.logger.info(order) except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.info(f"Unexpected Error: {e}") trade_log.set_ordered(origin_balance, target_balance, order_quantity) stat = self.wait_for_order(origin_symbol, target_symbol, order["orderId"]) if stat is None: return None self.logger.info(f"Bought {origin_symbol} {self.config.LOG_USER}") trade_log.set_complete(stat["cummulativeQuoteQty"]) return order def sell_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers: AllTickers): return self.retry(self._sell_alt, origin_coin, target_coin, all_tickers) def _sell_quantity(self, origin_symbol: str, target_symbol: str, origin_balance: float = None): origin_balance = origin_balance or self.get_currency_balance( origin_symbol) origin_tick = self.get_alt_tick(origin_symbol, target_symbol) return math.floor(origin_balance * 10**origin_tick) / float( 10**origin_tick) def _sell_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers: AllTickers): """ Sell altcoin """ trade_log = self.db.start_trade_log(origin_coin, target_coin, True) origin_symbol = origin_coin.symbol target_symbol = target_coin.symbol origin_balance = self.get_currency_balance(origin_symbol) target_balance = self.get_currency_balance(target_symbol) from_coin_price = all_tickers.get_price(origin_symbol + target_symbol) order_quantity = self._sell_quantity(origin_symbol, target_symbol, origin_balance) self.logger.info( f"Selling {order_quantity} of {origin_symbol} {self.config.LOG_USER}" ) self.logger.info(f"Balance is {origin_balance}") # prevent low amount buy if origin_balance * from_coin_price < 100: self.logger.warning( f"Couldn't SELL with {origin_balance * from_coin_price}$ {self.config.LOG_USER}" ) time.sleep(2) return None order = None while order is None: # Should sell at calculated price to avoid lost coin order = self.binance_client.order_market_sell( symbol=origin_symbol + target_symbol, quantity=order_quantity) self.logger.info(f"SELL order ID {order}") trade_log.set_ordered(origin_balance, target_balance, order_quantity) # Binance server can take some time to save the order self.logger.info("Waiting for Binance") stat = self.wait_for_order(origin_symbol, target_symbol, order["orderId"]) if stat is None: return None new_balance = self.get_currency_balance(origin_symbol) while new_balance >= origin_balance: new_balance = self.get_currency_balance(origin_symbol) self.logger.info(f"Sold {origin_symbol} {self.config.LOG_USER}") trade_log.set_complete(stat["cummulativeQuoteQty"]) return order
f = open("token.txt", "r") contents = f.read() newline = False secretkey = "" apikey = "" for x in contents: if x == "\n": newline = True continue if not newline: apikey += x else: secretkey += x client = Client(apikey, secretkey) f.close() balances = client.get_account()['balances'] count = 0 for thing in balances: if thing['asset'] == "ZIL": print(count) count += 1 print(balances) print(balances[120]) launchString = 'Backtest("' + LaunchDict["pair"] + '", strat=' + LaunchDict[ 'strat'] + ', klineint=' + LaunchDict["klineint"] + ', ss="' + LaunchDict[ 'ss'] + '"' for thing in LaunchDict: if not LaunchDict[thing]: launchString += ", " + thing + "=False"
class BinanceAPIManager: def __init__(self, config: Config, db: Database, logger: Logger): # initializing the client class calls `ping` API endpoint, verifying the connection self.binance_client = Client( config.BINANCE_API_KEY, config.BINANCE_API_SECRET_KEY, tld=config.BINANCE_TLD, ) self.db = db self.logger = logger self.config = config self.cache = BinanceCache() self.stream_manager: Optional[BinanceStreamManager] = None self.setup_websockets() def setup_websockets(self): self.stream_manager = BinanceStreamManager( self.cache, self.config, self.binance_client, self.logger, ) @cached(cache=TTLCache(maxsize=1, ttl=43200)) def get_trade_fees(self) -> Dict[str, float]: return { ticker["symbol"]: ticker["taker"] for ticker in self.binance_client.get_trade_fee()["tradeFee"] } @cached(cache=TTLCache(maxsize=1, ttl=60)) def get_using_bnb_for_fees(self): return self.binance_client.get_bnb_burn_spot_margin()["spotBNBBurn"] def get_fee(self, origin_coin: Coin, target_coin: Coin, selling: bool): fees = self.get_trade_fees() if not fees: base_fee = 0.001 else: base_fee = fees[origin_coin + target_coin] if not self.get_using_bnb_for_fees(): return base_fee # The discount is only applied if we have enough BNB to cover the fee amount_trading = (self._sell_quantity(origin_coin.symbol, target_coin.symbol) if selling else self._buy_quantity( origin_coin.symbol, target_coin.symbol)) fee_amount = amount_trading * base_fee * 0.75 if origin_coin.symbol == "BNB": fee_amount_bnb = fee_amount else: origin_price = self.get_ticker_price(origin_coin + Coin("BNB")) if origin_price is None: return base_fee fee_amount_bnb = fee_amount * origin_price bnb_balance = self.get_currency_balance("BNB") if bnb_balance >= fee_amount_bnb: return base_fee * 0.75 return base_fee def get_account(self): """ Get account information """ return self.binance_client.get_account() def get_ticker_price(self, ticker_symbol: str): """ Get ticker price of a specific coin """ price = self.cache.ticker_values.get(ticker_symbol, None) if price is None and ticker_symbol not in self.cache.non_existent_tickers: self.cache.ticker_values = { ticker["symbol"]: float(ticker["price"]) for ticker in self.binance_client.get_symbol_ticker() } self.logger.debug( f"Fetched all ticker prices: {self.cache.ticker_values}") price = self.cache.ticker_values.get(ticker_symbol, None) if price is None: self.logger.debug( f"Ticker does not exist: {ticker_symbol} - will not be fetched from now on" ) self.cache.non_existent_tickers.add(ticker_symbol) return price def get_currency_balance(self, currency_symbol: str, force=False) -> float: """ Get balance of a specific coin """ with self.cache.open_balances() as cache_balances: balance = cache_balances.get(currency_symbol, None) if force or balance is None: cache_balances.clear() cache_balances.update({ currency_balance["asset"]: float(currency_balance["free"]) for currency_balance in self.binance_client.get_account() ["balances"] }) self.logger.debug(f"Fetched all balances: {cache_balances}") if currency_symbol not in cache_balances: cache_balances[currency_symbol] = 0.0 return 0.0 return cache_balances.get(currency_symbol, 0.0) return balance def retry(self, func, *args, **kwargs): time.sleep(1) attempts = 0 while attempts < 20: try: return func(*args, **kwargs) except Exception: # pylint: disable=broad-except self.logger.warning( f"Failed to Buy/Sell. Trying Again (attempt {attempts}/20)" ) if attempts == 0: self.logger.warning(traceback.format_exc()) attempts += 1 return None def get_symbol_filter(self, origin_symbol: str, target_symbol: str, filter_type: str): return next(_filter for _filter in self.binance_client.get_symbol_info( origin_symbol + target_symbol)["filters"] if _filter["filterType"] == filter_type) @cached(cache=TTLCache(maxsize=2000, ttl=43200)) def get_alt_tick(self, origin_symbol: str, target_symbol: str): step_size = self.get_symbol_filter(origin_symbol, target_symbol, "LOT_SIZE")["stepSize"] if step_size.find("1") == 0: return 1 - step_size.find(".") return step_size.find("1") - 1 @cached(cache=TTLCache(maxsize=2000, ttl=43200)) def get_min_notional(self, origin_symbol: str, target_symbol: str): return float( self.get_symbol_filter(origin_symbol, target_symbol, "MIN_NOTIONAL")["minNotional"]) def _wait_for_order( self, order_id, origin_symbol: str, target_symbol: str ) -> Optional[BinanceOrder]: # pylint: disable=unsubscriptable-object while True: order_status: BinanceOrder = self.cache.orders.get(order_id, None) if order_status is not None: break self.logger.debug(f"Waiting for order {order_id} to be created") time.sleep(1) self.logger.debug(f"Order created: {order_status}") while order_status.status != "FILLED": try: order_status = self.cache.orders.get(order_id, order_status) self.logger.debug(f"Waiting for order {order_id} to be filled") if self._should_cancel_order(order_status): cancel_order = None while cancel_order is None: cancel_order = self.binance_client.cancel_order( symbol=origin_symbol + target_symbol, orderId=order_id) self.logger.info("Order timeout, canceled...") # sell partially if order_status.status == "PARTIALLY_FILLED" and order_status.side == "BUY": self.logger.info("Sell partially filled amount") order_quantity = self._sell_quantity( origin_symbol, target_symbol) partially_order = None while partially_order is None: partially_order = self.binance_client.order_market_sell( symbol=origin_symbol + target_symbol, quantity=order_quantity) self.logger.info("Going back to scouting mode...") return None if order_status.status == "CANCELED": self.logger.info( "Order is canceled, going back to scouting mode...") return None time.sleep(1) except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.info(f"Unexpected order error: {e}") time.sleep(1) self.logger.debug(f"Order filled: {order_status}") return order_status def wait_for_order( self, order_id, origin_symbol: str, target_symbol: str, order_guard: OrderGuard ) -> Optional[BinanceOrder]: # pylint: disable=unsubscriptable-object with order_guard: return self._wait_for_order(order_id, origin_symbol, target_symbol) def _should_cancel_order(self, order_status): minutes = (time.time() - order_status.time / 1000) / 60 timeout = 0 if order_status.side == "SELL": timeout = float(self.config.SELL_TIMEOUT) else: timeout = float(self.config.BUY_TIMEOUT) if timeout and minutes > timeout and order_status.status == "NEW": return True if timeout and minutes > timeout and order_status.status == "PARTIALLY_FILLED": if order_status.side == "SELL": return True if order_status.side == "BUY": current_price = self.get_ticker_price(order_status.symbol) if float(current_price) * (1 - 0.001) > float( order_status.price): return True return False def buy_alt(self, origin_coin: Coin, target_coin: Coin) -> BinanceOrder: return self.retry(self._buy_alt, origin_coin, target_coin) def _buy_quantity(self, origin_symbol: str, target_symbol: str, target_balance: float = None, from_coin_price: float = None): target_balance = target_balance or self.get_currency_balance( target_symbol) from_coin_price = from_coin_price or self.get_ticker_price( origin_symbol + target_symbol) origin_tick = self.get_alt_tick(origin_symbol, target_symbol) return math.floor(target_balance * 10**origin_tick / from_coin_price) / float(10**origin_tick) def _buy_alt(self, origin_coin: Coin, target_coin: Coin): """ Buy altcoin """ trade_log = self.db.start_trade_log(origin_coin, target_coin, False) origin_symbol = origin_coin.symbol target_symbol = target_coin.symbol with self.cache.open_balances() as balances: balances.clear() origin_balance = self.get_currency_balance(origin_symbol) target_balance = self.get_currency_balance(target_symbol) from_coin_price = self.get_ticker_price(origin_symbol + target_symbol) order_quantity = self._buy_quantity(origin_symbol, target_symbol, target_balance, from_coin_price) self.logger.info(f"Buying roughly {order_quantity} {origin_symbol}") # Try to buy until successful order = None order_guard = self.stream_manager.acquire_order_guard() while order is None: try: order = self.binance_client.order_limit_buy( symbol=origin_symbol + target_symbol, quantity=order_quantity, price=from_coin_price, ) self.logger.debug(order) except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.warning(f"Unexpected Error: {e}") orderId = order["orderId"] self.logger.info( f"Placed buy order {orderId}, waiting for it to complete") trade_log.set_ordered(origin_balance, target_balance, order_quantity) order_guard.set_order(origin_symbol, target_symbol, int(orderId)) order = self.wait_for_order(orderId, origin_symbol, target_symbol, order_guard) if order is None: return None newBalance = self.get_currency_balance(origin_symbol) self.logger.info(f"Bought {newBalance} {origin_symbol}") trade_log.set_complete(order.cumulative_quote_qty) return order def sell_alt(self, origin_coin: Coin, target_coin: Coin) -> BinanceOrder: return self.retry(self._sell_alt, origin_coin, target_coin) def _sell_quantity(self, origin_symbol: str, target_symbol: str, origin_balance: float = None): origin_balance = origin_balance or self.get_currency_balance( origin_symbol) origin_tick = self.get_alt_tick(origin_symbol, target_symbol) return math.floor(origin_balance * 10**origin_tick) / float( 10**origin_tick) def _sell_alt(self, origin_coin: Coin, target_coin: Coin): """ Sell altcoin """ trade_log = self.db.start_trade_log(origin_coin, target_coin, True) origin_symbol = origin_coin.symbol target_symbol = target_coin.symbol with self.cache.open_balances() as balances: balances.clear() origin_balance = self.get_currency_balance(origin_symbol) target_balance = self.get_currency_balance(target_symbol) from_coin_price = self.get_ticker_price(origin_symbol + target_symbol) order_quantity = self._sell_quantity(origin_symbol, target_symbol, origin_balance) self.logger.info(f"Selling {order_quantity} {origin_symbol}") self.logger.debug(f"Balance is {origin_balance}") order = None order_guard = self.stream_manager.acquire_order_guard() while order is None: # Should sell at calculated price to avoid lost coin self.logger.debug("Attempting to place order") order = self.binance_client.order_limit_sell( symbol=origin_symbol + target_symbol, quantity=order_quantity, price=from_coin_price) self.logger.debug(f"order: {order}") orderId = order["orderId"] self.logger.info( f"Placed sell order {orderId}, waiting for it to complete") trade_log.set_ordered(origin_balance, target_balance, order_quantity) order_guard.set_order(origin_symbol, target_symbol, int(order["orderId"])) order = self.wait_for_order(order["orderId"], origin_symbol, target_symbol, order_guard) if order is None: return None new_balance = self.get_currency_balance(origin_symbol) while new_balance >= origin_balance: new_balance = self.get_currency_balance(origin_symbol, True) new_target_balance = self.get_currency_balance(target_symbol) self.logger.info( f"Sold {origin_symbol} for {new_target_balance} {target_symbol}") trade_log.set_complete(order.cumulative_quote_qty) return order
class trade_realtime(): def __init__(self, api_key, api_secret, total_list=None): self.coin_list = total_list self.api_key = api_key self.api_secret = api_secret self.client = Client(api_key, api_secret) def get_time(self): server_time = self.client.get_server_time() # { # "serverTime": 1499827319559 # } return int(server_time['serverTime']) def get_exchange_status(self): return self.client.get_system_status() def get_coin_price(self, coin_list): # kwargs = {'data': coin} output_data = [] for coin in coin_list: price_d = ast.literal_eval( json.dumps(self.client.get_symbol_ticker(symbol=coin))) print(price_d) price = float(price_d['price']) output_data.append(price) return output_data def get_trade(self, start, end): output_data = [] for coin in self.coin_list: output_data.append( self.client.get_aggregate_trades(symbol=coin, startTime=start, endTime=end)) return output_data def get_kline(self, start, end): output_data = [] for coin in self.coin_list: output_data.append( self.client.get_klines(symbol=coin, interval=Client.KLINE_INTERVAL_5MINUTE, limit=500, startTime=start, endTime=end)) return output_data def get_historical_klines(self, symbol, interval, start, end): # init our list output_data = [] # setup the max limit limit = 500 timeframe = interval_to_milliseconds(interval) start_ts = start idx = 0 # it can be difficult to know when a symbol was listed on Binance so allow start time to be before list date symbol_existed = False while True: # fetch the klines from start_ts up to max 500 entries or the end_ts if set temp_data = self.client.get_klines(symbol=symbol, interval=interval, limit=limit, startTime=start_ts, endTime=end) # handle the case where our start date is before the symbol pair listed on Binance if not symbol_existed and len(temp_data): symbol_existed = True if symbol_existed: # append this loops data to our output data output_data += temp_data # update our start timestamp using the last value in the array and add the interval timeframe start_ts = temp_data[len(temp_data) - 1][0] + timeframe else: # it wasn't listed yet, increment our start date start_ts += timeframe idx += 1 # check if we received less than the required limit and exit the loop if len(temp_data) < limit: # exit the while loop break # sleep after every 3rd call to be kind to the API if idx % 3 == 0: time.sleep(1) return output_data def get_orderbook_ticker(self): pass def order_limit_buy(self, **params): return self.client.order_limit_buy(**params) def order_limit_sell(self, **params): return self.client.order_limit_sell(**params) def order_market_sell(self, **params): return self.client.order_market_sell(**params) def order_market_buy(self, **params): return self.client.order_market_buy(**params) def get_open_orders(self, **params): return self.client.get_open_orders(**params) def create_test_order(self, **params): self.client.create_test_order() def get_order(self, **params): self.client.get_order(self, **params) def get_all_orders(self, **params): self.client.get_all_orders(self, **params) def cancel_order(self, **params): self.client.cancel_order(self, **params) def get_account(self, **params): return (self.client.get_account(recvWindow=self.get_time())) def get_asset_balance(self, asset, **params): bal = self.client.get_asset_balance(asset=asset, recvWindow=self.get_time()) return ast.literal_eval(json.dumps(bal))['free'] def start_trade(): pass def get_kline_lag_time(self, coin, lookback_in_ms): # lookback = 2*60*1000 #5mins # rt.pred_coin_list=[coin] end_ts = self.get_time() # calendar.timegm(time.gmtime()) -lookback start_ts = end_ts - lookback_in_ms # print("start=",start_ts) # print("end=",end_ts) f = self.get_historical_klines(symbol=coin, interval=Client.KLINE_INTERVAL_30MINUTE, end=end_ts, start=start_ts) f = ast.literal_eval(json.dumps(f)) return f def getState(self, coin_list): features = np.empty((LOOK_BACK, 0), float) coin_f_index = [2, 3, 4, 5, 7] for coin in coin_list: coin_f = np.array(self.get_kline_lag_time(coin, LOOK_BACK_IN_MS), dtype=np.float).reshape(-1, 12) coin_f = coin_f[coin_f.shape[0] - LOOK_BACK:, coin_f_index] if (coin_f.shape[0] < 10): print("something is wrong with binance api,return shape=", coin_f.shape) return #print("coin_f shape ",coin_f.shape) #print("features shape ",features.shape) # COIN_FEATURE = np.concatenate((COIN_FEATURE, tmp), axis=0) features = np.hstack((features, coin_f)) # features = self.create_input(features,LOOK_BACK) # DF_FEATURES #reshape for tensorflow backend features = features.reshape(1, DF_FEATURES, 1, LOOK_BACK) print("create_predictive_input, features shape ", features.shape) # print("kline shape after modify",features.shape) # features = self.create_input(features ,LOOK_BACK) return features
class BinanceService(ServiceInterface): name = 'binance' def __init__(self, **kwargs): api_key = kwargs["api_key"] api_secret = kwargs["api_secret"] if not all([api_key, api_secret]): raise Exception( f"Both api_key and api_secret are required for {name} exchange" ) self.client = Client(api_key, api_secret) self.debug_mode = environ.get("DEBUG", False) def get_account(self): return self.client.get_account() def buy(self, coin_name, quantity=None, pair_base="BTC", amount=None, order_type="market"): try: symbol = f"{coin_name}{pair_base}".upper() precision = self.get_precision(symbol) current_price = self.get_price(symbol, precision) step_size = self.get_step_size(symbol) quantity = self.calculate_buy_qty(price=current_price, amount=amount, step_size=step_size) logger.info( f"step_size>{step_size}>price>{current_price} > Amount > {amount} > precision: {precision} > qty: {quantity}" ) side = Client.SIDE_BUY time_in_force = None if order_type == "limit": order_type = Client.ORDER_TYPE_LIMIT time_in_force = "GTC" else: order_type = Client.ORDER_TYPE_MARKET if order_type == Client.ORDER_TYPE_LIMIT: logger.info( f"{side} order request:{symbol}>type:{order_type}>quantity:{quantity} > {amount}" ) order = self.client.create_order(symbol=symbol, side=side, type=order_type, quantity=quantity, timeInForce=time_in_force, price=amount) else: logger.info( f"{side} order request:{symbol}>type:{order_type}>quantity:{quantity} > {current_price}" ) order = self.client.create_order(symbol=symbol, side=side, type=order_type, quantity=quantity) return order except (BinanceAPIException, BinanceRequestException) as ex: logger.error(ex, exc_info=True) raise UserAdviceException(ex) def sell(self, coin_name, quantity=None, pair_base="BTC", amount=None, order_type="market"): try: balance = self.get_balance(coin_name) if not balance: raise UserAdviceException( f"Balance not enough to execute action in {name} exchange") symbol = f"{coin_name}{pair_base}".upper() precision = self.get_precision(symbol) current_price = self.get_price(symbol, precision) step_size = self.get_step_size(symbol) quantity = self.calculate_sell_qty(price=current_price, amount=amount, step_size=step_size) side = Client.SIDE_SELL time_in_force = None if order_type == "limit": order_type = Client.ORDER_TYPE_LIMIT time_in_force = "GTC" else: order_type = Client.ORDER_TYPE_MARKET logger.info( f"{side} order request:{symbol}>type:{order_type}>quantity:{quantity} >> current_price: {current_price}" ) if order_type == Client.ORDER_TYPE_LIMIT: order = self.client.create_order(symbol=symbol, side=side, type=order_type, quantity=quantity, timeInForce=time_in_force, price=amount) else: order = self.client.create_order(symbol=symbol, side=side, type=order_type, quantity=quantity) return order except (BinanceAPIException, BinanceRequestException) as ex: logger.error(ex, exc_info=True) raise UserAdviceException(ex) def get_precision(self, symbol): try: step = db_client.stepsizes.find_one({"symbol": symbol}) if step: return step["baseAssetPrecision"] return 8 except Exception as ex: logger.error("Error retriving step size") return 8 def get_step_size(self, symbol): try: step = db_client.stepsizes.find_one({"symbol": symbol}) if step: return float(step["lot_size"]["stepSize"]) return float(0.0) except Exception as ex: logger.error("Error retriving step size") return float(0.0) def get_min_notional(self, symbol): try: step = db_client.stepsizes.find_one({"symbol": symbol}) if step: return float(step["min_notional"]["minNotional"]) return float(0.0) except Exception as ex: logger.error("Error retriving step size") return float(0.0) def get_price(self, symbol, precision=8): try: price_info = self.client.get_symbol_ticker(symbol=symbol) return round(Decimal(price_info.get("price")), precision) except (BinanceAPIException, BinanceRequestException) as ex: logger.error(ex, exc_info=True) raise Exception(ex) def calculate_sell_qty(self, price, amount, step_size): quantity = float(amount) / float(price) step_precision_decimal = str(step_size).split(".")[1] step_precision = len( step_precision_decimal) if int(step_precision_decimal) > 0 else 0 abs_quantity = round(quantity, step_precision) print( f"price:{price}: amount:{amount} :quantity::::{quantity}: abs_quantity: {abs_quantity}:::step_precision:{step_precision}" ) return abs_quantity def calculate_buy_qty(self, price, amount, step_size): quantity = float(amount) / float(price) step_precision_decimal = str(step_size).split(".")[1] step_precision = len( step_precision_decimal) if int(step_precision_decimal) > 0 else 0 abs_quantity = round(quantity, step_precision) print( f"price:{price}: amount:{amount} :quantity::::{quantity}: abs_quantity: {abs_quantity}:::step_precision:{step_precision}" ) return abs_quantity def get_balance(self, coin_name): """Query coin balance in exchange account Args: coin_name (string): Name of coin Raises: Exception: Raise exception if coin is not found Returns: float: Coin balance. """ try: balance_info = self.client.get_asset_balance(coin_name) if not balance_info: raise UserAdviceException( f"Coin not found in account in {self.name} exchange") return balance_info.get("free") except (BinanceAPIException, BinanceRequestException) as ex: logger.error(ex, exc_info=True) raise Exception(ex) def track_coin(self, payload): pass def get_prices(self): """Returns a list of symbols and their prices API: https://python-binance.readthedocs.io/en/latest/binance.html#binance.client.Client.get_symbol_ticker Return Example: [ { "symbol": "LTCBTC", "price": "4.00000200" }, { "symbol": "ETHBTC", "price": "0.07946600" } ] """ return self.client.get_symbol_ticker() def get_symbol_info(self, coin_name=None, pair_base="BTC", symbol=None): """ Reference: https://python-binance.readthedocs.io/en/latest/binance.html#binance.client.Client.get_symbol_info """ try: if not symbol: if not coin_name and pair_base: raise Exception( "Both coin name and base is required where symbol not specified" ) symbol = f"{coin_name}{pair_base}".upper() info = self.client.get_symbol_info(symbol=symbol) return info except (BinanceAPIException, BinanceRequestException) as ex: logger.error(ex, exc_info=True) raise Exception(ex) def get_open_orders(self, coin_name, pair_base="BTC"): try: symbol = f"{coin_name}{pair_base}".upper() orders = self.client.get_open_orders(symbol=symbol) logger.debug(f"orders: {orders}") return orders except (BinanceAPIException, BinanceRequestException) as ex: logger.error(ex, exc_info=True) raise Exception(ex) def get_all_orders(self, coin_name, limit=5, pair_base="BTC"): try: symbol = f"{coin_name}{pair_base}".upper() orders = self.client.get_all_orders(symbol=symbol, limit=limit) logger.debug(f"orders: {orders}") return orders except (BinanceAPIException, BinanceRequestException) as ex: logger.error(ex, exc_info=True) raise Exception(ex)
startTime=deposit['insertTime']) depositCoinVal += deposit['amount'] depositValBTC += float(klines[0][4]) * deposit['amount'] nonBTCDeposit[depCoin] = { 'isBuyer': True, 'price': float(klines[0][4]), 'qty': deposit['amount'], 'commission': 0 } print(depCoin + ': ' + str(depositCoinVal)) print('Overall: ' + str(depositValBTC) + ' BTC') acctStatus = binClient.get_account() prices = binClient.get_all_tickers() print('\n') print('COIN GAINS') printLine() symbols = [ 'XVG', 'POE', 'XLM', 'LEND', 'TRX', 'BNB', 'LTC', 'ADA', 'NEO', 'POWR', 'ICX', 'ETH' ] commission = 0.0 for symbol in sorted(symbols): pair = symbol + "BTC"
import config, csv from binance.client import Client client = Client(config.API_KEY, config.API_SECRET, tld='us') info = client.get_account() my_balances = info['balances'] # my_bal = [] # for d in my_balances: # my_bal.append([d['asset'],float(d['free'])]) # for el in my_bal: # print(el[0],el[1]) # ________________________________________________________________________________________________________ # status = client.get_all_orders(symbol='ADAUSD') # print(status) # from binance.enums import * # order = client.create_test_order( # symbol='BNBBTC', # side=SIDE_BUY, # type=ORDER_TYPE_LIMIT, # timeInForce=TIME_IN_FORCE_GTC, # quantity=100, # price='0.01158') # print(order)
class App: def __init__(self): self.client = Client(configData.binanceApiKey, configData.binanceApiSecret, {"timeout": 20}) self.telegramKey = configData.telegramApiKey self.data = DotMap() def klines(self, symbol, timeframe='5m', count=12): """ Return Klines for the given timeframe and count [[Open time,Open,High,Low,Close,Volume, ...]]""" res = re.search(r'(\d+)([mhdw])', timeframe) num = res.group(1) if res else "5" time_format = res.group(2) if res else "m" time_str = 'minutes' if time_format == 'h': time_str = 'hours' elif time_format == 'd': time_str = 'days' elif time_format == 'w': time_str = 'weeks' time_ago_str = f'{count * int(num)} {time_str} ago' log.debug("getting klines: %s %s '%s' %s", symbol, timeframe, time_ago_str, count) klines = self.client.get_historical_klines(symbol, timeframe, time_ago_str, limit=count) return klines def tickers(self): return { x['symbol']: float(x['price']) for x in self.client.get_all_tickers() } @staticmethod def floor(number, digits=2): return int(math.floor(float(number) * (10**digits))) / (10**digits + 0.0) @staticmethod def floor_new(number, digits=2): return str( Decimal(number).quantize(Decimal(1) / Decimal(10**digits), rounding=ROUND_DOWN)) def place_order(self, orderObj, priceDigits=10, qtyDigits=4, test=True): """ Place and order to binance with the given Order and minimum significan number for quantity and price in digits.If error is due to price and quantity notional value then it will be attempted again by reducing the notional digits""" while qtyDigits >= 0 or priceDigits >= 0: if 'stopPrice' in orderObj: orderObj['stopPrice'] = (self.floor_new( orderObj['stopPrice'], priceDigits)) orderObj['price'] = (self.floor_new(orderObj['price'], priceDigits)) orderObj['quantity'] = (self.floor_new(orderObj['quantity'], qtyDigits)) try: if (test): self.client.create_test_order(**orderObj) log.info(orderObj) return orderObj else: self.client.create_order(**orderObj) return orderObj break except binance.exceptions.BinanceAPIException as e: if ('PRICE_FILTER' in e.message or 'Precision' in e.message): priceDigits = priceDigits - 1 elif ('LOT_SIZE' in e.message): qtyDigits = qtyDigits - 1 else: log.error("** cannot place Order **") log.error(e) orderObj['quantity'] = 0 return orderObj break log.debug("retrying order %s , %s", orderObj['price'], orderObj['quantity']) def get_free_balances_non_usdt(self, worth_threshold=10): """ find balances from Binance account which are non USDT and the worth of them in amount meets the threshold """ account = self.client.get_account() tickers = self.tickers() balances = [(x['asset'], float(x['free']), float(x['locked'])) for x in account['balances'] if x['asset'] != 'USDT'] freebalances = [] for x in balances: symbol = x[0] + self.base_token(x[0], tickers) if symbol in tickers: amomunt = x[1] * tickers[symbol] if 'USDT' in symbol else ( x[1] * tickers[symbol]) * tickers['BTCUSDT'] if (amomunt) > worth_threshold: freebalances.append((symbol, x[1], amomunt)) return freebalances def create_stop_loss_orders(self, test=True, stoploss_prc=3.0, quantity_prc=100, symbols=None): """Find all Free Balances and create Stop Loss Orders""" free_balances = self.get_free_balances_non_usdt() tickers = self.tickers() log.debug("free balances: %s", free_balances) log.debug("required orders: %s", symbols) res = [] for x in free_balances: if symbols and x[0] not in symbols: continue log.info('-- creating order for %s--', x[0]) sym = x[0] action = 'SELL' sprice = (100 - stoploss_prc) / 100.0 * tickers[sym] price = (100 - stoploss_prc - 0.1) / 100.0 * tickers[sym] qty = x[1] orderObj = { 'symbol': sym, 'quantity': float(qty) * quantity_prc / 100, 'side': action, 'type': 'STOP_LOSS_LIMIT', 'stopPrice': sprice, 'price': price, 'timeInForce': 'GTC' } curr_res = self.place_order(orderObj, test=test) res.append(curr_res) return res def send_msg(self, msg, chat_id, reply_message_id=None): """ Sends message to Telegram group Emojis: https://apps.timwhitlock.info/emoji/tables/unicode """ log.debug("sending message %s", msg) url = f"https://api.telegram.org/bot{self.telegramKey}/sendMessage?chat_id={chat_id}&parse_mode=Markdown&text=" url += urllib.parse.quote('\n' + msg) if reply_message_id: url += f'&reply_to_message_id={reply_message_id}' with urllib.request.urlopen(url) as response: pass def send_photo(self, filename, chat_id, caption=""): url = f"https://api.telegram.org/bot{self.telegramKey}/sendPhoto?chat_id={chat_id}&caption={urllib.parse.quote(caption)}" response = requests.post(url, files={'photo': open(filename, 'rb')}) def notify_action(self, chat_id): """ Sends message to Telegram group """ url = f"https://api.telegram.org/bot{self.telegramKey}/sendChatAction?chat_id={chat_id}&action=typing" # https://apps.timwhitlock.info/emoji/tables/unicode with urllib.request.urlopen(url) as response: pass def get_messages(self, offset=0, timeout=10): """ Get notifications from Telegram after the provided offset @return: Tuple( offset, author, chat_id, text, date ) """ urlString = f"https://api.telegram.org/bot{self.telegramKey}/getUpdates?offset={offset}&timeout={timeout}" try: with urllib.request.urlopen(urlString) as response: jsonObj = json.loads(response.read().decode('utf-8')) response = [] for x in jsonObj['result']: log.debug("From Telegram: message: %s", x) if 'message' in x: message = DotMap(x['message']) message.update_id = x['update_id'] if message.entities: del message.entities if message['from'].first_name: del message['from'].first_name if message['from'].last_name: del message['from'].last_name if message['chat'].first_name: del message['chat'].first_name if message['chat'].last_name: del message['chat'].last_name log.info("Message from Telegram: %s", message.toDict()) response.append(message) else: response.append(DotMap({'update_id': x['update_id']})) if 'callback_query' in x: log.info(json.dumps(x)) return response except Exception as e: log.error(e) return [] @staticmethod def timestamp_to_str(ts: float, fmt: str = '%Y-%m-%d %H:%M:%S'): return datetime.datetime.fromtimestamp(ts).strftime(fmt) def to_amount(self, symbol, balance, tickers): if symbol == 'USDT': return self.floor(balance, 2) elif symbol == 'BTC': return self.floor(balance * tickers["BTCUSDT"], 2) elif symbol + 'BTC' in tickers: return self.floor( balance * tickers[symbol + 'BTC'] * tickers["BTCUSDT"], 2) else: return 0.0 def get_account_balances(self): """ Tuple( Symbol, Free , Locked, Free+Locked ) """ account = self.client.get_account() balances = ((x['asset'], float(x['free']), float(x['locked']), float(x['free']) + float(x['locked'])) for x in account['balances']) return balances def get_snapshot(self): balances = self.get_account_balances() tickers = self.tickers() snapshot_raw = {x[0]: x[3] for x in balances if (x[3]) > 0} snapshot = { x: self.floor(snapshot_raw[x], 4) for x in snapshot_raw if self.to_amount(x, snapshot_raw[x], tickers) > 1.0 } return snapshot def snapshot_total(self): tickers = self.tickers() snapshot = db.config(Const.SNAPSHOT, {}) amounts = list( (x, self.to_amount(x, snapshot[x], tickers)) for x in snapshot) amounts_str = [f'{x[0]} -> {x[1]}' for x in amounts] total = sum(x[1] for x in amounts) amounts_str.append("." * 15) amounts_str.append(f'Snapshot: {self.floor(total)}') msg = "\n".join(amounts_str) return msg def account_total(self): """ Returns amounts and balances of the Symbols :return: Tuple( List(str, float), List(str, float)) """ tickers = { x['symbol']: float(x['price']) for x in self.client.get_all_tickers() } balances = self.get_account_balances() aggr_balances: Dict( str, float) = {x[0]: x[3] for x in balances if (x[3]) > 0} amounts: List[Tuple[str, float]] = list( filter(lambda x: x[1] > 0, ((x, self.to_amount(x, aggr_balances[x], tickers)) for x in aggr_balances))) return amounts, aggr_balances def price_alert(self, symbol, timeframe, count, threshold): # log.debug("getting price alert for : %s %s x %s", symbol, timeframe, count) # print(f'Symbol: {symbol}, timeframe: {timeframe} , count: {count}, threshold: {threshold}') klines = self.klines(symbol, timeframe, count) # Max and Min price in the klines # time, Open,High,Low,Close max_kline = max([(int(x[0]), float(x[2])) for x in klines], key=lambda y: y[1]) min_kline = min([(int(x[0]), float(x[3])) for x in klines], key=lambda y: y[1]) first_price = second_price = None if max_kline[0] < min_kline[0]: first_price = max_kline[1] second_price = min_kline[1] else: first_price = min_kline[1] second_price = max_kline[1] log.debug("Price Alert: %s, %s -> %s", symbol, first_price, second_price) first_price = self.floor(first_price, 8) second_price = self.floor(second_price, 8) percentage = math.fabs( self.floor((first_price - second_price) * 100 / first_price, 1)) current = self.floor(klines[-1][4], 8) key = f"{symbol}{timeframe}{count}{threshold}" curr_value = f"{first_price}{second_price}" existing_value = self.data[key] if key in self.data else "" if percentage > threshold: self.data[key] = curr_value if curr_value != existing_value: direction = '⬆' if first_price < second_price else '⬇' return f"{direction} {symbol:10} {percentage}% ({first_price}, {second_price}, {current}) " def stop_loss_orders_percentage(self): """Tuple(orderId, Symbol, StopPrice, CurrentPriceUSDT, Quantity, PercentageDiff)""" tickers = self.tickers() slOrders = self.get_open_orders(type='STOP_LOSS_LIMIT') stats = [] for x in slOrders: # log.debug(x) currPrice = tickers[x['symbol']] stopPrice = float(x['price']) stopPriceUSDT = float( x['price']) * tickers['BTCUSDT'] if x['symbol'].endswith( 'BTC') else float(x['price']) qty = float(x['origQty']) prcGap = (currPrice - stopPrice) * 100 / currPrice currPriceUSDT = currPrice * tickers['BTCUSDT'] if 'BTC' in x[ 'symbol'] else currPrice stats.append( (x['orderId'], x['symbol'], stopPriceUSDT, currPriceUSDT, qty, round(prcGap, 2))) return stats def symbol_with_currency(self, symbol): return symbol.upper() + self.base_token(symbol.upper()) def base_token(self, asset, token=None): cached_token = token if token else self.tickers() return 'USDT' if asset + 'USDT' in cached_token else 'BTC' def sell_x_percent(self, symbol='ALL', qty_prc=None, price_prc_over_market=0, test=True): if not qty_prc: raise Exception("X needs to be defined") if price_prc_over_market < 0: raise Exception("NEGATIVE VALUES NOT ALLOWED") tickers = self.tickers() balances = [(x[0], x[1]) for x in self.get_account_balances() if self.to_amount(x[0], x[1], tickers) > 100] sell_orders = [ (x[0] + self.base_token(x[0], tickers), x[1] * qty_prc / 100, tickers[x[0] + self.base_token(x[0], tickers)]) for x in balances if x[0] != 'USDT' and symbol == 'ALL' or x[0] in symbol ] log.debug(sell_orders) response = [] for x in sell_orders: if float(x[2]) < 1 / (10**4): log.warn('Ignoring a very low priced asset sell Order: %s %s', x[0], x[2]) # continue orderObj = { 'symbol': x[0], 'quantity': x[1], 'side': 'SELL', 'type': 'LIMIT', 'price': x[2] * (100 - 0.005 + price_prc_over_market) / 100, 'timeInForce': 'GTC' } currRes = self.place_order(orderObj, test=test) if 'BTC' in x[0]: # Convert to usdt price currRes['price'] = float(currRes['price']) * tickers['BTCUSDT'] response.append(currRes) return response def buy_x_prc(self, symbol, prc_usdt, prc_belowmarket, test=True): if not prc_usdt: raise Exception("Percentage of USDT amount not defined") if prc_belowmarket < 0: raise Exception("Negative values not allowed") if not symbol: raise Exception("Symbol not defined") balances = list( filter(lambda x: x[0] == 'USDT', self.get_account_balances())) log.debug("USDT Balances: %s", balances) free_balance = balances[0][1] if len(balances) > 0 else 0 buyAmount = min(prc_usdt * free_balance / 100, free_balance) tickers = self.tickers() eff_symbol = self.symbol_with_currency(symbol) qty = buyAmount / tickers[eff_symbol] buyPrice = tickers[eff_symbol] * (100 + 0.005 - prc_belowmarket) / 100 orderObj = { 'symbol': eff_symbol, 'quantity': qty, 'side': 'BUY', 'type': 'LIMIT', 'price': buyPrice, 'timeInForce': 'GTC' } currRes = self.place_order(orderObj, test=test) return currRes def get_open_orders(self, type=None, side=None): openOrders = self.client.get_open_orders(recvWindow=5000) # log.debug("open orders: %s", openOrders) if type: return list(filter(lambda x: x['type'] == type, openOrders)) if side: return list(filter(lambda x: x['side'] == side, openOrders)) def cancel_all_sl_orders(self, symbol='ALL'): stats = self.stop_loss_orders_percentage() # Cancel All Orders will high stop loss percentage cancelled = [] for x in stats: if symbol == 'ALL' or symbol in x[1]: log.debug("Cancelling SL order for : %s", x[1]) self.client.cancel_order(symbol=x[1], orderId=str(x[0])) cancelled.append(x) # send the stats for cancelled orders return cancelled def revise_sl(self, symbol='ALL', threshold=4): stats = self.stop_loss_orders_percentage() log.info(f"Trying to revise Stop Loss for {threshold}%") # Cancel All Orders will high stop loss percentage cancelled_sl_symbols = [] for x in filter( lambda x: (x[5] > (threshold + 0.25) and symbol == 'ALL' or symbol in x[1]), stats): self.client.cancel_order(symbol=x[1], orderId=str(x[0])) cancelled_sl_symbols.append(x[1]) if len(cancelled_sl_symbols) > 0: log.info("trying to revise %s", cancelled_sl_symbols) resp = self.create_stop_loss_orders(test=False, stoploss_prc=threshold, symbols=cancelled_sl_symbols) return resp else: return [] @staticmethod def dataframe(klines): klines_2d_array = [ list(map(lambda x: float(x), x[1:6])) for x in klines ] index_data = [app.timestamp_to_str(float(x[0]) / 1000) for x in klines] df = pd.DataFrame(np.array(klines_2d_array), index=index_data, columns=['open', 'high', 'low', 'close', 'volume']) df.index = pd.to_datetime(df.index) return df
class Binance(object): def __init__(self, api_key, secret_key): self.client = Client(api_key, secret_key) self.client.API_URL = 'https://testnet.binance.vision/api' #only for testing # ---- Account Info Commands --- # def get_account(self): ''' Returns account info ''' recvWindow = {'recvWindow': 59999} account_info = self.client.get_account(**recvWindow) return account_info # ---- ---- # # ---- Order Commands ---- # def send_order(self, order_type, message): raw_message = message.text if order_type == "limit": order_params = limit_order_message_filter(raw_message) order_response = self.limit_order(order_params[0], order_params[1], order_params[2], order_params[3], order_params[4], order_params[5]) order_confirmation = order_message( order_response) #Telegram message sent to user elif order_type == "market": order_params = market_order_message_filter(raw_message) order_response = self.market_order(order_params[0], order_params[1], order_params[2], order_params[3]) order_confirmation = order_message( order_response) #Telegram message sent to user elif order_type == "stoploss": order_params = stoploss_order_message_filter(raw_message) order_response = self.stoploss_order( order_params[0], order_params[1], order_params[2], order_params[3], order_params[4], order_params[5], order_params[6]) order_confirmation = stopLoss_message( order_response) #Telegram message sent to user print(order_response) return order_confirmation def market_order(self, symbol, side, order_type, quantity): ''' Executes market orders/Depending on type of order commands ''' try: order_dictionary = { 'symbol': symbol, 'side': side, 'type': order_type, 'quantity': quantity, 'recvWindow': 59999 } order = self.client.create_order(**order_dictionary) except Exception as e: print("something went wrong - {}".format(e)) #bot.error_message(symbol, quantity, str(e)) return False return order def limit_order(self, symbol, side, order_type, timeInForce, quantity, price): ''' Executes limit orders/Depending on type of order commands ''' try: order_dictionary = { 'symbol': symbol, 'side': side, 'type': order_type, 'timeInForce': timeInForce, 'quantity': quantity, 'price': price, 'recvWindow': 59999 } order = self.client.create_order(**order_dictionary) except Exception as e: print("something went wrong - {}".format(e)) #bot.error_message(symbol, quantity, str(e)) return False return order def stoploss_order(self, symbol, side, order_type, timeInForce, quantity, price, stopPrice): try: order_dictionary = { 'symbol': symbol, 'side': side, 'type': order_type, 'timeInForce': timeInForce, 'quantity': quantity, 'price': price, 'stopPrice': stopPrice, 'recvWindow': 59999 } order = self.client.create_order(**order_dictionary) except Exception as e: print("something went wrong - {}".format(e)) #bot.error_message(symbol, quantity, str(e)) return False return order # ---- ---- # # ---- Check Orders Commands --- # def see_all_orders(self, symbol): try: dict = {'symbol': symbol, 'recvWindow': 59999} all_orders = self.client.get_all_orders(**dict) return all_orders except Exception as e: print(str(e)) return 'Whoops' def open_orders(self, symbol): try: dict = {'symbol': symbol, 'recvWindow': 59999} open_orders = self.client.get_open_orders(**dict) print(open_orders) return open_orders except Exception as e: print(str(e)) return 'Whoops' # ---- ---- # # ---- Cancel Orders ---- # def cancel_order(self, message): try: cancel_order_params = cancel_order_message_filter(message) dict = { 'symbol': cancel_order_params[0], 'orderId': cancel_order_params[1], "recvWindow": 59999 } response = self.client.cancel_order(**dict) print(response) return response except Exception as e: print(str(e))
class BinanceOrders: # Initialization with the API key and secret key def __init__(self, key, secret): self.client = Client(api_key=key, api_secret=secret) self.assets = self.get_assets() self.orders = self.get_orders() self.get_data() # Get all the assets with positive balance def get_assets(self): info = self.client.get_account() assets_list = info['balances'] my_assets = [] for asset in assets_list: if float(asset['free']) > 0: my_assets.append(asset['asset']) return my_assets # Get all the orders that were made def get_orders(self): orders = [] for i in self.assets: if i != 'USDT': orders_list = self.client.get_all_orders(symbol=i + 'USDT') for order in orders_list: orders.append(order) return orders # Create a customized DataFrame with required information, only for orders that were filled def get_data(self): DF = pd.DataFrame(columns=[ 'Date', 'Asset', 'Type', 'Price', 'Amount', 'Fee', 'Total' ]) for x in range(len(self.orders)): if self.orders[x]['status'] == 'FILLED': Date = datetime.fromtimestamp( self.orders[x]['time'] / 1000.0).strftime('%d.%m.%Y %H:%M') Asset = str(self.orders[x]['symbol']).replace('USDT', '') Type = self.orders[x]['side'] Price = round( float(self.orders[x]['cummulativeQuoteQty']) / float(self.orders[x]['executedQty']), 2) Amount = round(float(self.orders[x]['executedQty']), 3) Total = round(float(self.orders[x]['cummulativeQuoteQty']), 3) if self.orders[x]['time'] < 1613816037557: Fee = Price * 0.001 else: Fee = Price * 0.00075 Fee = round(Fee, 3) DF = DF.append(pd.Series( [Date, Asset, Type, Price, Amount, Fee, Total], index=DF.columns), ignore_index=True) DF.to_csv('*PATH TO WHERE YOU WANT TO SAVE THE FILE*', index=False)
from binance.client import Client print('Running bot') # Fetching data from config file: config = parser.Parser() api_key = config.api_key secret = config.secret client = Client(api_key, secret) # get market depth depth = client.get_order_book(symbol='BNBBTC') client.verbose = True balance = client.get_account() print(balance) # place a test market buy order, to place an actual order use the create_order function order = client.create_test_order(symbol='BNBBTC', side=Client.SIDE_BUY, type=Client.ORDER_TYPE_MARKET, quantity=100) # get all symbol prices prices = client.get_all_tickers() # withdraw 100 ETH # check docs for assumptions around withdrawals from binance.exceptions import BinanceAPIException, BinanceWithdrawException try:
class OrderClient(Thread): min_qnty = 0.000001 _latest_price = {'symbol': None, 'price': 0, 'timestamp': 0} # def __init__(self, params): Thread.__init__(self) self.name = params['name'] self.uuid = params['uuid'] self.new_side = params['side'] self.SYMBOL = params['symbol'] self.rest_api = Client(params['API_KEY'], params['API_SECRET']) self.keep_running = True def run(self): trade_count = 0 #removed check permission, because it calls same API as get balance, it will be checked during that call while self.keep_running: try: if self.new_side == "BUY": #lets find out is it is BTC or USDT we need to get balance of asset = self.SYMBOL[3:] elif self.new_side == "SELL": asset = self.SYMBOL[:3] else: raise FatalError('side not set, either BUY or SELL') asset_balance = self.get_account_balance(asset) logger.debug(f"{self.name} fetched balance, {asset_balance}") #removed call to min quantity, call might be necessary later when multiple pairs are added. min_qnty = self.min_qnty if self.new_side == "BUY": min_qnty = self.min_qnty * self.latest_price if asset_balance * 0.999 < min_qnty: #this is naturally where we end our trade, report that trade is complete. logger.info( f"{self.name} trade has completed successfully") break if self.new_side == "BUY": amount = (asset_balance / self.latest_price) * 0.995**(trade_count + 1) logger.debug(f"[+]{self.name} Buying {amount}") self.do_buy(amount) elif self.new_side == "SELL": amount = asset_balance * (0.998**(trade_count + 1)) logger.debug(f"[+]{self.name} Selling {amount}") self.do_sell(amount) except FatalError as e: logger.error(f"{self.name} fatal, {e.message}") break except ZeroDepositError as e: logger.error(f"{self.name}, {e.message}") break except BinanceAPIException as e: if int(e.code) in [-2010, -1010, -2011]: logger.info(f"{self.name}, {e.message}") elif int(e.code) == -1013: #balance below minimum allowable, should get here if we checking balances, end trade logger.info( f"{self.name} cannot {self.new_side} less than minimum, {e.message}" ) break elif int(e.code) == -2015: logger.error(f"{self.name} {e.message}, exiting") break elif int(e.status_code) == 429: logger.warning( f"{self.name} hit a rate limit, backing dowm for 1 minute" ) time.sleep(60) elif int(e.status_code) == 418: logger.error(f"{self.name} Ooops, IP has been auto banned") break else: logger.error( f"{self.name} uncaught API exception, {e.message}, {e.code}, {e.status_code}" ) except Exception as e: #default, for all uncaught exceptions. logger.error(f"{self.name} Exceptiion, {e}") break if trade_count > 9: logger.info( f"{self.name} Exhausted the number of loops, which is {trade_count}, exiting" ) break trade_count += 1 def get_account_balance(self, asset): try: account_info = self.rest_api.get_account() except Exception as e: raise e if not account_info['canTrade']: logger.error(f"{self.name} cannot trade") raise PermissionsError() balance_list = [ x for x in account_info['balances'] if x['asset'] == asset ] if not balance_list: raise ZeroDepositError() balance = balance_list[0] return float(balance['free']) @property def latest_price(self): if self._latest_price['symbol'] == self.SYMBOL and self._latest_price[ 'timestamp'] - time.time() < 5: if self._latest_price['price']: return self._latest_price['price'] price_dict = self.rest_api.get_symbol_ticker(symbol=self.SYMBOL) self._latest_price = { 'symbol': self.SYMBOL, 'timestamp': time.time(), 'price': float(price_dict['price']) } return self._latest_price['price'] def do_buy(self, amount): logger.info(f"[*]{self.name} Buying {amount}") order = self.rest_api.order_market_buy( symbol=self.SYMBOL, quantity="{0:8f}".format(amount)) logger.info("[+]{} Order successful, ID: {}".format( self.name, order['orderId'])) return { 'status': True, 'exception': None, 'message': 'Order successful', 'orderID': order['orderId'] } def do_sell(self, amount): logger.info(f"[*]{self.name} Selling {amount}") order = self.rest_api.order_market_sell( symbol=self.SYMBOL, quantity="{0:8f}".format(amount)) logger.info("[+]{} Order successful, ID: {}".format( self.name, order['orderId'])) return { 'status': True, 'exception': None, 'message': 'Order successful', 'orderID': order['orderId'] }
class BinanceExchage(Exchange): def __init__(self, apiKey, apiSecret, pairs): super().__init__(apiKey, apiSecret, pairs) self.exchange_name = "Binance" self.connection = Client(self.api['key'], self.api['secret']) self.update_balance() self.socket = BinanceSocketManager(self.connection) self.socket.start_user_socket(self.on_balance_update) self.socket.start() def update_balance(self): account_information = self.connection.get_account() self.set_balance(account_information['balances']) def set_balance(self, balances): symbols = self.get_trading_symbols() actual_balance = list( filter(lambda elem: str(elem['asset']) in symbols, balances)) self.balance = actual_balance def on_balance_update(self, upd_balance_ev): if upd_balance_ev['e'] == 'outboundAccountInfo': balance = [] for ev in upd_balance_ev['B']: balance.append({ 'asset': ev['a'], 'free': ev['f'], 'locked': ev['l'] }) self.set_balance(balance) def get_balance(self): return self.balance def get_open_orders(self): return self.connection.get_open_orders() def cancel_order(self, symbol, orderId): self.connection.cancel_order(symbol=symbol, orderId=orderId) print('order canceled') def stop(self): self.socket.close() def _cancel_order_detector(self, event): # detect order id which need to be canceled slave_open_orders = self.connection.get_open_orders() for ordr_open in slave_open_orders: if ordr_open['price'] == event['p']: return ordr_open['orderId'] async def on_order_handler(self, event): # shortcut mean https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#order-update if event['x'] == 'CANCELED': slave_order_id = self._cancel_order_detector(event) self.cancel_order(event['s'], slave_order_id) else: self.create_order(event['s'], event['S'], event['o'], event['p'], event['q'], event['f'], event['P']) def create_order(self, symbol, side, type, price, quantityPart, timeInForce, stopPrice=0): """ :param symbol: :param side: :param type: LIMIT, MARKET, STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT, LIMIT_MAKER :param price: required if limit order :param quantityPart: the part that becomes an order from the entire balance :param timeInForce: required if limit order :param stopPrice: required if type == STOP_LOSS or TAKE_PROFIT """ # # if order[side] == sell don't need calculate quantity # if side == 'BUY': # quantity = self.calc_quatity_from_part(symbol, quantityPart, price) # else: # quantity = quantityPart quantity = self.calc_quantity_from_part(symbol, quantityPart, price, side) print('Slave ' + str(self.get_balance_market_by_symbol(symbol)) + ' ' + str(self.get_balance_coin_by_symbol(symbol)) + ', Create Order:' + ' amount: ' + str(quantity) + ', price: ' + str(price)) try: if (type == 'STOP_LOSS_LIMIT' or type == "TAKE_PROFIT_LIMIT"): self.connection.create_order(symbol=symbol, side=side, type=type, price=price, quantity=quantity, timeInForce=timeInForce, stopPrice=stopPrice) if (type == 'MARKET'): self.connection.create_order(symbol=symbol, side=side, type=type, quantity=quantity) else: self.connection.create_order(symbol=symbol, side=side, type=type, quantity=quantity, price=price, timeInForce=timeInForce) print("order created") except Exception as e: print(str(e))
class ArbitrageStratety: # 手续费率 huobi_fee_rate = 0.0012 binance_fee_rate = 0.0005 bnb_price = 10.8159 # 盈利率 huobi_profit_rate = 0.0003 binance_profit_rate = 0.0003 # btc每次最大交易量 btc_exchange_min = 0.001 usdt_exchange_min = 10 # 程序里有3处需要同时更改 btc_exchange_max = 0.065 # HUOBI API最大连续超时次数 huobi_max_timeout = 3 # 深度参数阈值 STD_THD = 5 SUM_THD = 0.5 def __init__(self): key_dict = {} # 读取配置文件 with open('config', 'r') as f: for line in f.readlines(): splited = line.split('=') if len(splited) == 2: key_dict[splited[0].strip()] = splited[1].strip() self.output = open('history', 'a+') self.huobiSpot = HuobiSpot(key_dict['HUOBI_ACCESS_KEY2'], key_dict['HUOBI_SECRET_KEY2']) self.binanceClient = BinanceSpot(key_dict['BINANCE_ACCESS_KEY'], key_dict['BINANCE_SECRET_KEY']) self.btc_mat = "BTC :\tfree:{:<20.8f}locked:{:<20.8f}" self.usdt_mat = "USDT:\tfree:{:<20.8f}locked:{:<20.8f}" self.total_format = "BTC:{:<20.8f}USDT:{:<20.8f}" # config logging self.logger = logging.getLogger("Robot") # 指定logger输出格式 formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s') # 文件日志 file_handler = logging.handlers.TimedRotatingFileHandler('log', when='midnight') # 设置日志文件后缀,以当前时间作为日志文件后缀名。 file_handler.suffix = "%Y-%m-%d" # 可以通过setFormatter指定输出格式 file_handler.setFormatter(formatter) # 控制台日志 console_handler = logging.StreamHandler(sys.stdout) console_handler.formatter = formatter # 也可以直接给formatter赋值 # 为logger添加的日志处理器 self.logger.addHandler(file_handler) self.logger.addHandler(console_handler) # 指定日志的最低输出级别,默认为WARN级别 self.logger.setLevel(logging.INFO) # 用于记录huobi连续响应过长时间的次数,超过两次,就退出 self.huobi_timeout = 0 # 收益统计 self.huobi_usdt_inc = 0 self.huobi_usdt_dec = 0 self.binance_usdt_inc = 0 self.binance_usdt_dec = 0 self.huobi_usdt_total_change = 0 self.binance_usdt_total_change = 0 # 账户余额 self.huobi_trade_btc = 0 self.huobi_trade_usdt = 0 self.binance_trade_btc = 0 self.binance_trade_usdt = 0 # 成交量统计 self.usdt_exchange_amount = 0 self.btc_exchange_amount = 0 self.last_deal_time = 0 # 由于数量小于0.001而未能成交 # >0 表示需要卖出的 self.untreated_btc = 0 @staticmethod def sms_notify(msg): url = 'http://221.228.17.88:8080/sendmsg/send' params = { 'phonenum': '18118999630', 'msg': msg } requests.get(url, params=params) def update_profit_rate(self): self.logger.info('更新盈利率') huobi_btc_percent = float('%.2f' % (self.huobi_trade_btc / (self.huobi_trade_btc + self.binance_trade_btc))) binance_btc_percent = 1 - huobi_btc_percent self.logger.info('Huobi: %s\tBinance:%s' % (huobi_btc_percent, binance_btc_percent)) if huobi_btc_percent < 0.1: self.huobi_profit_rate = 0.001 elif huobi_btc_percent < 0.2: self.huobi_profit_rate = 0.0007 elif huobi_btc_percent > 0.9: self.binance_profit_rate = 0.001 elif huobi_btc_percent > 0.8: self.binance_profit_rate = 0.0007 else: self.huobi_profit_rate = 0.0003 self.binance_profit_rate = 0.0003 self.logger.info('huobi_profit_rate: %s\t, binance_profit_rate: %s' % ( self.huobi_profit_rate, self.binance_profit_rate)) def update_account_info(self): # update binance self.logger.info('|--------------------------------------------------') self.logger.info('|' + '更新账户信息') try: account_info = self.binanceClient.get_account() except Exception as e: self.logger.error('Binance get_account error: %s' % e) else: freezed_btc = 0 freezed_usdt = 0 for info in account_info['balances']: if info['asset'] == 'BTC': self.binance_trade_btc = float(info['free']) freezed_btc = float(info['locked']) elif info['asset'] == 'USDT': self.binance_trade_usdt = float(info['free']) freezed_usdt = float(info['locked']) self.logger.info('|' + 'Binance:') self.logger.info('|' + self.btc_mat.format(self.binance_trade_btc, freezed_btc)) self.logger.info('|' + self.usdt_mat.format(self.binance_trade_usdt, freezed_usdt)) # update huobi # 修复了float进位的问题 json_r = self.huobiSpot.get_balance() if json_r['status'] == 'ok': for item in json_r['data']['list']: if item['currency'] == 'btc' and item['type'] == 'trade': self.huobi_trade_btc = ArbitrageStratety.cut2_float(item['balance'], 8) # self.huobi_trade_btc = float(item['balance']) elif item['currency'] == 'btc' and item['type'] == 'frozen': freezed_btc = float(item['balance']) elif item['currency'] == 'usdt' and item['type'] == 'trade': self.huobi_trade_usdt = ArbitrageStratety.cut2_float(item['balance'], 8) elif item['currency'] == 'usdt' and item['type'] == 'frozen': freezed_usdt = float(item['balance']) self.logger.info('|' + 'Huobi:') self.logger.info('|' + self.btc_mat.format(self.huobi_trade_btc, freezed_btc)) self.logger.info('|' + self.usdt_mat.format(self.huobi_trade_usdt, freezed_usdt)) self.logger.info('|' + 'Total:') self.logger.info('|' + self.total_format.format(self.binance_trade_btc + self.huobi_trade_btc, self.binance_trade_usdt + self.huobi_trade_usdt)) self.logger.info('|' + 'Untreated: %s' % self.untreated_btc) self.update_profit_rate() else: print json_r if 'fail' == json_r['status']: self.logger.error('Huobi get_balance error: %s' % json_r['msg']) else: self.logger.error('Huobi get_balance error: %s' % json_r['err-msg']) self.logger.info('|--------------------------------------------------') @staticmethod def merge_depth(depth): new_depth = [] for d in depth: price = ArbitrageStratety.cut2_float(d[0], 1) # price = float(re.match('(\d+\.\d)\d*', '{:.8f}'.format(float(d[0]))).group(1)) amount = float(d[1]) if len(new_depth) == 0: new_depth.append([price, amount]) else: if new_depth[-1][0] == price: new_depth[-1] = [price, new_depth[-1][1] + amount] else: new_depth.append([price, amount]) return new_depth[:5] @staticmethod def cut2_float(s, n): if isinstance(s, float): s = '{:.8f}'.format(s) pattern = re.compile(r'(\d+\.\d{1,%d})\d*' % n) return float(pattern.match(s).group(1)) def go(self): while True: # get depth info print '获取深度信息' h_depth = self.huobiSpot.get_depth('btcusdt', 'step5') if h_depth['status'] == 'ok': h_bids = h_depth['tick']['bids'] h_asks = h_depth['tick']['asks'] else: time.sleep(3) continue try: b_depth = self.binanceClient.get_order_book(symbol='BTCUSDT') except Exception as e: self.logger.error(u'获取Binance市场深度错误: %s' % e) time.sleep(3) continue # print b_depth # 需要合并深度 b_bids = ArbitrageStratety.merge_depth(b_depth['bids']) b_asks = ArbitrageStratety.merge_depth(b_depth['asks']) print b_asks # huobi sell if h_bids[0][0] * 1.0 / float( b_asks[0][0]) > 1 + self.huobi_fee_rate + self.binance_fee_rate + self.huobi_profit_rate: self.logger.info('binance买入,huobi卖出,') # print h_bids h_sum = np.sum(h_bids[:3], axis=0) h_std = np.std(h_bids[:3], axis=0) h_avg = np.mean(h_bids[:3], axis=0) a = [(float(i[0]), float(i[1])) for i in b_asks] b_sum = np.sum(a[:3], axis=0) b_std = np.std(a[:3], axis=0) b_avg = np.mean(a[:3], axis=0) # print a self.logger.info('ASKS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (h_sum[1], h_std[0], h_avg[0])) self.logger.info('BIDS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (b_sum[1], b_std[0], b_avg[0])) self.logger.info( '卖出价:%s, 买入价:%s, 比例:%s' % (h_bids[0][0], float(b_asks[0][0]), h_bids[0][0] * 1.0 / float( b_asks[0][0]))) if h_std[0] > self.STD_THD or h_sum[1] < self.SUM_THD: self.logger.info('标准差过大,本单取消') time.sleep(0.1) continue btc_amount = float( '%.4f' % min(h_bids[0][1], float(b_asks[0][1]), self.btc_exchange_max)) # Binance btc-amount 精度是6位,需要截取前4位,不能产生进位 if btc_amount > float(b_asks[0][1]): # btc_amount = float(re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(float(b_asks[0][1]))).group(1)) btc_amount = ArbitrageStratety.cut2_float(b_asks[0][1], 4) if btc_amount > self.huobi_trade_btc: # btc_amount = float( # re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(self.huobi_trade_btc)).group(1)) btc_amount = ArbitrageStratety.cut2_float(self.huobi_trade_btc, 4) order_price = float(b_asks[0][0]) + 1.1 usdt_amount = float('%.4f' % (btc_amount * order_price)) self.logger.info('本次交易量:%s BTC, %s USDT' % (btc_amount, usdt_amount)) if btc_amount < self.btc_exchange_min: self.logger.info('BTC交易数量不足: %s, 本单取消' % self.btc_exchange_min) time.sleep(1) continue if usdt_amount < self.usdt_exchange_min: self.logger.info('USDT交易数量不足: %s, 本单取消' % self.usdt_exchange_min) time.sleep(1) continue if usdt_amount > self.binance_trade_usdt - 5: self.logger.info('Binance USDT 数量: %s, 不足:%s, 本单取消' % (self.binance_trade_usdt, usdt_amount)) time.sleep(1) continue # 限价买 self.logger.info('开始限价买入') try: buy_order = self.binanceClient.order_limit_buy(symbol='BTCUSDT', quantity=btc_amount, price=order_price, newOrderRespType='FULL') except Exception as e: self.logger.error(u'Binance买入错误: %s' % e) time.sleep(3) continue print buy_order buy_order_id = buy_order['orderId'] self.output.write('\n' + str(buy_order_id)) self.output.flush() self.logger.info('binance buy orderId: %s, state: %s' % (buy_order_id, buy_order['status'])) field_cash_amount = 0 field_amount = 0 if buy_order['status'] == 'NEW' or buy_order['status'] == 'PARTIALLY_FILLED': self.logger.info('撤消未完成委托') try: cancel_r = self.binanceClient.cancel_order(symbol='BTCUSDT', orderId=buy_order_id) print cancel_r # get_my_trades有超时问题,暂时不用此函数 except Exception as e: self.logger.error(u'撤销错误: %s' % e) else: self.logger.info('撤销成功') # 有可能会出现撤销成功,但是撤销完成的过程中,又完成了部分委托,需要更新实际成交量 self.logger.info('更新成交量') times = 0 while times < 10: self.logger.info(u'第%s次查询Binance订单状态' % (times + 1)) try: order = self.binanceClient.get_order(symbol='BTCUSDT', orderId=buy_order_id) self.logger.info(u'当前订单状态为: %s', order['status']) # 撤销成功,状态必定为CANCELED, 撤销成功则为FILLED if order['status'] == 'CANCELED' or order['status'] == 'FILLED': field_amount = float(order['executedQty']) price = float(order['price']) field_cash_amount = float('%.8f' % (field_amount * price)) break except Exception as e: self.logger.error(u'Binance get order error: %s' % e) times += 1 if times == 10: self.logger.info('未知错误,程序终止') break # filled elif buy_order['status'] == 'FILLED': fills = buy_order['fills'] for f in fills: price = float('%.2f' % float(f['price'])) qty = float('%.8f' % float(f['qty'])) field_amount += qty field_cash_amount += price * qty else: self.logger.info('订单状态异常: %s' % buy_order['status']) if field_amount == 0: self.logger.info('未完成任何委托') continue self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( float(field_amount), float(field_cash_amount))) self.binance_trade_btc += field_amount self.binance_trade_usdt -= field_cash_amount self.binance_usdt_dec = field_cash_amount self.binance_usdt_total_change -= self.binance_usdt_dec # 市价卖 self.logger.info('开始市价卖出') btc_amount = float('%.4f' % field_amount) # 记录总共损失的BTC数目,达到0.001时候,进行补全 if btc_amount < self.btc_exchange_min: self.logger.info('BTC卖出数量低于 %s', self.btc_exchange_min) self.logger.info('本次交易终止') self.untreated_btc += field_amount # self.rollback_binance_order(buy_order_id) time.sleep(3) continue # 买入卖出由于精度不同,会存在一定的偏差,这里进行统计调整 else: self.untreated_btc += field_amount - btc_amount sell_order = self.huobiSpot.send_order(btc_amount, 'api', 'btcusdt', 'sell-market') if sell_order['status'] != 'ok': if sell_order['status'] == 'fail': self.logger.error('sell failed : %s' % sell_order['msg']) else: self.logger.error('sell failed : %s' % sell_order['err-msg']) self.logger.info('开始回滚') self.rollback_binance_order(buy_order_id) self.logger.info('终止程序') break sell_order_id = sell_order['data'] self.output.write(':' + str(sell_order_id)) self.output.flush() times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) sell_order_info = self.huobiSpot.order_info(sell_order_id) print sell_order_info if sell_order_info['status'] == 'ok' and sell_order_info['data']['state'] == 'filled': self.logger.info('huobi sell filled, orderId: %s' % sell_order_id) field_cash_amount = sell_order_info['data']['field-cash-amount'] field_amount = sell_order_info['data']['field-amount'] break times += 1 if times == 19: time.sleep(15) if times == 20: self.huobi_timeout += 1 if self.huobi_timeout == self.huobi_max_timeout: self.logger.info('连续%s次超时,终止程序' % self.huobi_timeout) break else: self.logger.info('连续%s次超时,继续程序' % self.huobi_timeout) if self.huobi_timeout == 1: time.sleep(60) else: time.sleep(600) self.btc_exchange_max /= 2 continue else: self.huobi_timeout = 0 self.btc_exchange_max = 0.065 self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( float(field_amount), float(field_cash_amount))) self.huobi_trade_btc -= float(field_amount) self.huobi_trade_usdt += float(field_cash_amount) # 更新交易量统计 self.usdt_exchange_amount += float(field_cash_amount) self.btc_exchange_amount += float(field_amount) self.huobi_usdt_inc = float(field_cash_amount) self.huobi_usdt_total_change += self.huobi_usdt_inc usdt_inc = self.huobi_usdt_inc - self.binance_usdt_dec thistime_earnings = usdt_inc thistime_earnings_rate = thistime_earnings * 1.0 / float(field_cash_amount) total_usdt_earnings = self.huobi_usdt_total_change + self.binance_usdt_total_change self.logger.info('本次交易量: %s BTC, 盈利: %s USDT, 盈利率: %s' % ( float(field_amount), thistime_earnings, thistime_earnings_rate)) self.logger.info( '总BTC成交量: %s, 盈利: %s USDT, 盈利率: %s' % ( self.btc_exchange_amount, total_usdt_earnings, total_usdt_earnings * 1.0 / self.usdt_exchange_amount)) self.logger.info('|--------------------------------------------------') self.logger.info('|' + ' BTC:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_btc + self.binance_trade_btc), self.huobi_trade_btc, self.binance_trade_btc)) self.logger.info( '|' + 'USDT:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_usdt + self.binance_trade_usdt), self.huobi_trade_usdt, self.binance_trade_usdt)) self.logger.info('|--------------------------------------------------') self.update_profit_rate() self.last_deal_time = int(time.time()) # binance sell elif float(b_bids[0][0]) / h_asks[0][ 0] > 1 + self.huobi_fee_rate + self.binance_fee_rate + self.binance_profit_rate: self.logger.info('binance 卖出, huobi买入') b = [(float(i[0]), float(i[1])) for i in b_bids] print b b_sum = np.sum(b[:3], axis=0) b_std = np.std(b[:3], axis=0) b_avg = np.mean(b[:3], axis=0) print h_asks h_sum = np.sum(h_asks[:3], axis=0) h_std = np.std(h_asks[:3], axis=0) h_avg = np.mean(h_asks[:3], axis=0) self.logger.info('ASKS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (b_sum[1], b_std[0], b_avg[0])) self.logger.info('BIDS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (h_sum[1], h_std[0], h_avg[0])) self.logger.info( '卖出价:%s, 买入价:%s, 比例:%s' % (float(b_bids[0][0]), h_asks[0][0], float(b_bids[0][0]) / h_asks[0][0])) if h_std[0] > self.STD_THD or h_sum[1] < self.SUM_THD: self.logger.info('标准差过大,本单取消') time.sleep(0.1) continue order_price = b_bids[0][0] - 1.1 btc_amount = float('%.4f' % min(float(b_bids[0][1]), h_asks[0][1], self.btc_exchange_max)) if btc_amount > float(b_bids[0][1]): # = float(re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(float(b_bids[0][1]))).group(1)) btc_amount = ArbitrageStratety.cut2_float(b_bids[0][1], 4) if btc_amount > self.binance_trade_btc: # btc_amount = float( # re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(self.binance_trade_btc)).group(1)) btc_amount = ArbitrageStratety.cut2_float(self.binance_trade_btc, 4) usdt_amount = float('%.4f' % (btc_amount * h_asks[0][0])) self.logger.info('本次交易量:%s BTC, %s USDT' % (btc_amount, usdt_amount)) if btc_amount < self.btc_exchange_min: self.logger.info('BTC交易数量不足: %s, 本单取消' % self.btc_exchange_min) time.sleep(1) continue if float('%.4f' % (btc_amount * order_price)) < self.usdt_exchange_min: self.logger.info('USDT交易数量不足: %s, 本单取消' % self.usdt_exchange_min) time.sleep(1) continue if usdt_amount > self.huobi_trade_usdt - 5: self.logger.info('Huobi USDT 数量: %s, 不足:%s, 本单取消' % (self.huobi_trade_usdt, usdt_amount)) time.sleep(1) continue # 限价卖 self.logger.info('开始限价卖出') try: sell_r = self.binanceClient.order_limit_sell(symbol='BTCUSDT', quantity=btc_amount, price=order_price, newOrderRespType='FULL') except Exception as e: self.logger.error(u'Binance卖出错误: %s' % e) time.sleep(3) continue print sell_r sell_order_id = sell_r['orderId'] self.output.write('\n' + str(sell_order_id)) self.output.flush() self.logger.info('binance sell orderId: %s, state: %s' % (sell_order_id, sell_r['status'])) field_cash_amount = 0 field_amount = 0 if sell_r['status'] == 'NEW' or sell_r['status'] == 'PARTIALLY_FILLED': # 撤销未完成订单 self.logger.info('撤消未完成委托') try: cancel_r = self.binanceClient.cancel_order(symbol='BTCUSDT', orderId=sell_order_id) print cancel_r except Exception as e: self.logger.error(u'撤销错误: %s' % e) else: self.logger.info('撤销成功') self.logger.info('更新成交量') times = 0 while times < 10: self.logger.info(u'第%s次查询Binance订单状态' % (times + 1)) try: order = self.binanceClient.get_order(symbol='BTCUSDT', orderId=sell_order_id) print order self.logger.info(u'当前订单状态为: %s', order['status']) # 撤销成功,状态必定为CANCELED, 撤销成功则为FILLED if order['status'] == 'CANCELED' or order['status'] == 'FILLED': field_amount = float(order['executedQty']) price = float(order['price']) field_cash_amount = float('%.8f' % (field_amount * price)) break except Exception as e: self.logger.error(u'Binance get order error: %s' % e) times += 1 if times == 10: self.logger.info('未知错误,程序终止') break # filled elif sell_r['status'] == 'FILLED': fills = sell_r['fills'] for f in fills: price = float('%.2f' % float(f['price'])) qty = float('%.8f' % float(f['qty'])) field_amount += qty field_cash_amount += price * qty else: self.logger.info('订单状态异常: %s' % sell_r['status']) if field_amount == 0: self.logger.info('未完成任何委托') continue # 更新统计数据 self.btc_exchange_amount += float(field_amount) self.usdt_exchange_amount += float(field_cash_amount) # update income self.binance_usdt_inc = field_cash_amount self.binance_usdt_total_change += self.binance_usdt_inc self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( field_amount, field_cash_amount)) self.binance_trade_btc -= field_amount self.binance_trade_usdt += field_cash_amount # 限价买,使买价高于市价,最后会已市价成交 self.logger.info('开始伪市价(限价)买入') buy_price = h_asks[0][0] + 20 btc_amount = float('%.4f' % field_amount) if btc_amount < self.btc_exchange_min: self.logger.error('BTC交易数量低于 %s' % self.btc_exchange_min) self.logger.info('本次交易终止,开始回滚') self.untreated_btc -= field_amount time.sleep(3) continue else: self.untreated_btc -= field_amount - btc_amount buy_r = self.huobiSpot.send_order(btc_amount, 'api', 'btcusdt', 'buy-limit', buy_price) print buy_r if buy_r['status'] != 'ok': if buy_r['status'] == 'fail': self.logger.error('buy failed : %s' % buy_r['msg']) else: self.logger.error('buy failed : %s' % buy_r['err-msg']) self.logger.info('开始回滚') self.rollback_binance_order(sell_order_id) self.logger.info('终止程序') break buy_order_id = buy_r['data'] self.output.write(':' + str(buy_order_id)) self.output.flush() times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) buy_order_result = self.huobiSpot.order_info(buy_order_id) print buy_order_result if buy_order_result['status'] == 'ok' and buy_order_result['data']['state'] == 'filled': self.logger.info('huobi buy filled, orderId: %s' % buy_order_id) field_amount = float('%.8f' % float(buy_order_result['data']['field-amount'])) field_cash_amount = float('%.8f' % float(buy_order_result['data']['field-cash-amount'])) break times += 1 if times == 19: time.sleep(15) if times == 20: self.huobi_timeout += 1 if self.huobi_timeout == self.huobi_max_timeout: self.logger.info('连续%s次超时,终止程序' % self.huobi_timeout) break else: self.logger.info('连续%s次超时,继续程序' % self.huobi_timeout) if self.huobi_timeout == 1: time.sleep(60) else: time.sleep(600) # self.btc_exchange_max /= 2 continue else: self.huobi_timeout = 0 self.btc_exchange_max = 0.065 self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( field_amount, field_cash_amount)) # update income self.huobi_usdt_dec = field_cash_amount self.huobi_usdt_total_change -= self.huobi_usdt_dec self.huobi_trade_btc += field_amount self.huobi_trade_usdt -= field_cash_amount # total usdt_inc = self.binance_usdt_inc - self.huobi_usdt_dec thistime_earnings = usdt_inc thistime_earnings_rate = thistime_earnings * 1.0 / field_cash_amount # total_btc_earnings = self.huobi_btc_total_change + self.binance_btc_total_change total_usdt_earnings = self.huobi_usdt_total_change + self.binance_usdt_total_change self.logger.info('本次交易量: %s BTC, 盈利: %s USDT, 盈利率: %s' % ( float(field_amount), thistime_earnings, thistime_earnings_rate)) self.logger.info( '总BTC成交量: %s, 盈利: %s USDT, 盈利率: %s' % ( self.btc_exchange_amount, total_usdt_earnings, total_usdt_earnings * 1.0 / self.usdt_exchange_amount)) self.logger.info('|--------------------------------------------------') self.logger.info('|' + ' BTC:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_btc + self.binance_trade_btc), self.huobi_trade_btc, self.binance_trade_btc)) self.logger.info( '|' + 'USDT:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_usdt + self.binance_trade_usdt), self.huobi_trade_usdt, self.binance_trade_usdt)) self.logger.info('|--------------------------------------------------') self.update_profit_rate() self.last_deal_time = int(time.time()) time.sleep(0.1) nowtime = time.strftime('%H:%M:%S', time.localtime(time.time())) if nowtime.startswith('08:30'): self.order_statistics() self.usdt_exchange_amount = 0 self.btc_exchange_amount = 0 self.huobi_usdt_total_change = 0 self.binance_usdt_total_change = 0 time.sleep(60) # 每5分钟没有交易就更新账户信息 if self.last_deal_time > 0 and int(time.time()) - self.last_deal_time > 300: orderid = 0 if self.untreated_btc > 0.001: sell_amount = float('%.4f' % self.untreated_btc) self.logger.info('平衡账户资产,Huobi卖出: %s BTC' % sell_amount) sell_r = self.huobiSpot.send_order(sell_amount, 'api', 'btcusdt', 'sell-market') if sell_r['status'] != 'ok': if sell_r['status'] == 'fail': self.logger.error('sell failed : %s' % sell_r['msg']) else: self.logger.error('sell failed : %s' % sell_r['err-msg']) break else: orderid = sell_r['data'] self.untreated_btc -= sell_amount elif self.untreated_btc < -0.001: buy_price = h_asks[0][0] + 20 buy_amount = float('%.4f' % self.untreated_btc) self.logger.info('平衡账户资产,Huobi买入: %s BTC' % buy_amount) buy_r = self.huobiSpot.send_order(-1 * buy_amount, 'api', 'btcusdt', 'buy-limit', buy_price) if buy_r['status'] != 'ok': if buy_r['status'] == 'fail': self.logger.error('sell failed : %s' % buy_r['msg']) else: self.logger.error('sell failed : %s' % buy_r['err-msg']) break else: orderid = buy_r['data'] self.untreated_btc -= buy_amount print orderid if orderid: times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_result = self.huobiSpot.order_info(orderid) print order_result if order_result['status'] == 'ok' and order_result['data']['state'] == 'filled': self.logger.info('order filled, orderId: %s' % orderid) field_amount = float('%.8f' % float(order_result['data']['field-amount'])) field_cash_amount = float('%.8f' % float(order_result['data']['field-cash-amount'])) if 'buy' in order_result['data']['type']: self.huobi_trade_btc += field_amount self.huobi_trade_usdt -= field_cash_amount else: self.huobi_trade_btc -= field_amount self.huobi_trade_usdt += field_cash_amount break times += 1 if times == 9: time.sleep(10) if times == 19: time.sleep(300) if times == 20: self.logger.error('未知错误,程序终止') break total_btc_amount_before = self.binance_trade_btc + self.huobi_trade_btc self.logger.info('before: binance:%s\thuobi: %s' % (self.binance_trade_btc, self.huobi_trade_btc)) self.update_account_info() self.logger.info('after : binance:%s\thuobi: %s' % (self.binance_trade_btc, self.huobi_trade_btc)) total_btc_amount_after = self.binance_trade_btc + self.huobi_trade_btc if abs(total_btc_amount_after - total_btc_amount_before) > 0.001: self.logger.info('账户BTC总量发生异常,程序终止') break self.last_deal_time = 0 def rollback_binance_order(self, orderid): order_info = self.binanceClient.get_order(symbol='BTCUSDT', orderId=orderid) side = order_info['side'].upper() field_amount = float('%.6f' % float(order_info['executedQty'])) price = float('%.2f' % float(order_info['price'])) if side == 'BUY': try: order = self.binanceClient.order_limit_sell(symbol='BTCUSDT', quantity=field_amount, price=price - 5, newOrderRespType='FULL') print order except Exception as e: self.logger.error(u'Binance卖出错误: %s, 回滚失败' % e) else: try: order = self.binanceClient.order_limit_buy(symbol='BTCUSDT', quantity=field_amount, price=price + 5, newOrderRespType='FULL') print order except Exception as e: self.logger.error(u'Binance买入错误: %s, 回滚失败' % e) # HUOBI API 提供的只能获取100条,此函数为扩展,提取500条 def get_huobi_orders(self): result = [] last_order_id = None for i in range(5): orders_list = self.huobiSpot.orders_list('btcusdt', '', _from=last_order_id, size=100) if orders_list['status'] != 'ok': print ('获取火币历史委托错误') print orders_list return [] else: if i == 0: for item in orders_list['data']: result.append(item) else: for item in orders_list['data'][1:]: result.append(item) last_order_id = orders_list['data'][-1]['id'] return result def order_statistics(self, start=None, end=None): huobi_order_list = [] binance_order_list = [] # Huobi for order in self.get_huobi_orders(): dict = { 'id': order['id'], 'state': order['state'].upper(), 'amount': float('%.8f' % float(order['amount'])), 'field-cash-amount': float('%.4f' % float(order['field-cash-amount'])), 'field-amount': float('%.8f' % float(order['field-amount'])), 'created-at': order['created-at'], 'finished-at': order['finished-at'], 'canceled-at': order['canceled-at'], 'type': u'LIMIT' if 'limit' in order['type'] else u'MARKET', 'side': u'BUY' if 'buy' in order['type'] else u'SELL', } # print dict if dict['finished-at'] == 0: continue if dict['field-amount'] > 0: dict['price'] = float('%.2f' % (dict['field-cash-amount'] / dict['field-amount'])) huobi_order_list.append(dict) print('获取币安历史委托数据') binance_orders = self.binanceClient.get_all_orders(symbol='BTCUSDT') binance_trades = self.binanceClient.get_my_trades(symbol='BTCUSDT', recvWindow=130000) # print binance_trades for order in binance_orders: dict = { 'id': order['orderId'], 'state': order['status'].upper(), 'amount': float('%.8f' % float(order['origQty'])), 'created-at': order['time'], 'finished-at': '', 'canceled-at': '', 'type': order['type'].upper(), 'side': order['side'].upper(), 'commission': 0, 'field-amount': 0, 'field-cash-amount': 0 } binance_order_list.append(dict) for item in binance_order_list: id = item['id'] for trade in binance_trades: if trade['orderId'] == id: item['commission'] += float('%.8f' % float(trade['commission'])) item['field-amount'] += float('%.8f' % float(trade['qty'])) item['price'] = float('%.2f' % float(trade['price'])) item['field-cash-amount'] += float('%.8f' % (float(trade['qty']) * item['price'])) huobi_order_list = sorted(huobi_order_list, key=lambda x: x['created-at'], reverse=True) binance_order_list = sorted(binance_order_list, key=lambda x: x['created-at'], reverse=True) # print huobi_order_list # print binance_order_list yestoday = datetime.date.today() + datetime.timedelta(days=-1) timearray = time.strptime(str(yestoday) + ' 8:30:00', "%Y-%m-%d %H:%M:%S") timestamp = int(round(time.mktime(timearray)) * 1000) print timestamp workbook = xlsxwriter.Workbook('output.xlsx') worksheet = workbook.add_worksheet(u'成功') worksheet2 = workbook.add_worksheet(u'失败') worksheet3 = workbook.add_worksheet(u'总计') date_format_str = 'yy/mm/dd/ hh:mm:ss' binance_common_format = workbook.add_format({'align': 'left', 'font_name': 'Consolas'}) binance_date_format = workbook.add_format({'num_format': date_format_str, 'align': 'left', 'font_name': 'Consolas'}) huobi_common_format = workbook.add_format({'align': 'left', 'font_name': 'Consolas', 'bg_color': 'yellow'}) huobi_date_format = workbook.add_format({'num_format': date_format_str, 'align': 'left', 'font_name': 'Consolas', 'bg_color': 'yellow'}) merged_format = workbook.add_format({'align': 'center', 'valign': 'vcenter', 'font_name': 'Consolas'}) row_1 = 0 row_2 = 0 row_3 = 0 col = 0 header = [u'委托时间', u'方向', u'类型', u'价格', u'委托数量', u'成交数量', u'成交金额', u'状态', u'盈利(USDT)', u'盈利率'] total_usdt_earnings = 0 total_huobi_usdt_trade = 0 total_binance_commission = 0 total_btc_exchange = 0 i = 0 for h in header: worksheet.write(row_1, col + i, h) worksheet2.write(row_2, col + i, h) i += 1 row_1 += 1 row_2 += 1 with open('history', 'r') as f: for line in f.readlines(): if len(line) < 5: continue splited = line.strip().split(':') huobi_id = 0 if len(splited) == 2: binance_id = int(splited[0]) huobi_id = int(splited[1]) elif len(splited) == 1: binance_id = int(splited[0]) else: continue # print binance_id, huobi_id if binance_id > 0 and huobi_id > 0: binance_order = None huobi_order = None for order in binance_order_list: if order['created-at'] < timestamp: continue if order['id'] == binance_id: binance_order = order break for order in huobi_order_list: if order['created-at'] < timestamp: continue if order['id'] == huobi_id: huobi_order = order break if not binance_order or not huobi_order: continue # print binance_id, huobi_id total_huobi_usdt_trade += float('%.8f' % float(huobi_order['field-cash-amount'])) total_binance_commission += float('%.8f' % float(binance_order['commission'])) total_btc_exchange += float('%.8f' % float(binance_order['field-amount'])) order = huobi_order worksheet.write(row_1, col, datetime.datetime.fromtimestamp( float(str(order['created-at'])[0:-3] + '.' + str(order['created-at'])[-3:0])), huobi_date_format) worksheet.write(row_1, col + 1, order['side'], huobi_common_format) worksheet.write(row_1, col + 2, order['type'], huobi_common_format) worksheet.write(row_1, col + 3, '%.2f' % float(order['price']), huobi_common_format) worksheet.write(row_1, col + 4, '%.4f' % float(order['amount']), huobi_common_format) worksheet.write(row_1, col + 5, '%.8f' % float(order['field-amount']), huobi_common_format) worksheet.write(row_1, col + 6, '%.8f' % float(order['field-cash-amount']), huobi_common_format) worksheet.write(row_1, col + 7, order['state'], huobi_common_format) row_1 += 1 order = binance_order worksheet.write_datetime(row_1, col, datetime.datetime.fromtimestamp( float(str(order['created-at'])[0:-3] + '.' + str(order['created-at'])[-3:0])), binance_date_format) worksheet.write(row_1, col + 1, order['side'], binance_common_format) worksheet.write(row_1, col + 2, order['type'], binance_common_format) worksheet.write(row_1, col + 3, '%.2f' % float(order['price']), binance_common_format) worksheet.write(row_1, col + 4, '%.4f' % float(order['amount']), binance_common_format) worksheet.write(row_1, col + 5, '%.8f' % float(order['field-amount']), binance_common_format) worksheet.write(row_1, col + 6, '%.8f' % float(order['field-cash-amount']), binance_common_format) worksheet.write(row_1, col + 7, order['state'], binance_common_format) earnings = abs(float(huobi_order['field-cash-amount']) - float(binance_order['field-cash-amount'])) earning_rate = earnings / binance_order['field-cash-amount'] total_usdt_earnings += earnings worksheet.merge_range(row_1 - 1, 8, row_1, 8, '%.8f' % earnings, merged_format) worksheet.merge_range(row_1 - 1, 9, row_1, 9, '%.8f' % earning_rate, merged_format) row_1 += 1 else: for order in binance_order_list: # print order['created-at'] if order['created-at'] < timestamp: continue if order['id'] == binance_id and order['field-amount'] > 0: print order if order['id'] == binance_id and 'CANCELED' in order['state']: worksheet2.write_datetime(row_2, col, datetime.datetime.fromtimestamp( float(str(order['created-at'])[0:-3] + '.' + str(order['created-at'])[-3:0])), binance_date_format) worksheet2.write(row_2, col + 1, order['side'], binance_common_format) worksheet2.write(row_2, col + 2, order['type'], binance_common_format) worksheet2.write(row_2, col + 3, 0, binance_common_format) worksheet2.write(row_2, col + 4, '%.4f' % float(order['amount']), binance_common_format) worksheet2.write(row_2, col + 5, 0, binance_common_format) worksheet2.write(row_2, col + 6, 0, binance_common_format) worksheet2.write(row_2, col + 7, order['state'], binance_common_format) row_2 += 1 break total_huobi_commission = total_huobi_usdt_trade * 0.002 total_binance_commission = total_binance_commission * 10.8159 # print total_huobi_commission, total_binance_commission header = [u'BTC总量', u'BTC成交量', u'USDT总量' u'USDT盈亏', u'HUOBI手续费', u'BINANCE手续费'] i = 0 for h in header: worksheet3.write(row_3, i, h) i += 1 row_3 += 1 worksheet3.write(row_3, 0, total_btc_exchange) worksheet3.write(row_3, 1, total_usdt_earnings) worksheet3.write(row_3, 2, total_huobi_commission) worksheet3.write(row_3, 3, total_binance_commission) workbook.close() # 发送邮件 self.logger.info('邮件通知') ArbitrageStratety.send_mail_with_attachment() @staticmethod def send_mail_with_attachment(): from email import encoders from email.header import Header from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.utils import parseaddr, formataddr from email.mime.text import MIMEText import smtplib def _format_addr(s): name, addr = parseaddr(s) return formataddr(( \ Header(name, 'utf-8').encode(), \ addr.encode('utf-8') if isinstance(addr, unicode) else addr)) from_addr = '*****@*****.**' username = '******' password = '******' to_addr = '*****@*****.**' smtp_server = 'smtp.163.com' print 'sending mail to [email protected]' msg = MIMEMultipart() msg['From'] = _format_addr(from_addr) msg['To'] = _format_addr(to_addr) msg['Subject'] = u'收益情况(%s)' % (time.strftime('%Y-%m-%d', time.localtime(time.time()))) msg.attach(MIMEText('send with file...', 'plain', 'utf-8')) # add file: with open('output.xlsx', 'rb') as f: mime = MIMEBase('text', 'txt', filename='output.xlsx') mime.add_header('Content-Disposition', 'attachment', filename='output.xlsx') mime.add_header('Content-ID', '<0>') mime.add_header('X-Attachment-Id', '0') mime.set_payload(f.read()) encoders.encode_base64(mime) msg.attach(mime) server = smtplib.SMTP() server.connect(smtp_server) server.login(username, password) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()
import os from binance.client import Client # init api_key = os.environ.get('binance_api') api_secret = os.environ.get('binance_secret') client = Client(api_key, api_secret) ## main # get balances for all assets & some account information print(client.get_account()) # get balance for a specific asset only (BTC) print(client.get_asset_balance(asset='BTC')) # get balances for futures account print(client.futures_account_balance()) # get balances for margin account # will raise an exception if margin account is not activated #print(client.get_margin_account())
class TradingAccount(): def __init__(self, app={}): """Trading account object model Parameters ---------- app : object PyCryptoBot object """ # config needs to be a dictionary, empty or otherwise if not isinstance(app, object): raise TypeError('App is not a PyCryptoBot object.') if app.getExchange() == 'binance': self.client = Client(app.getAPIKey(), app.getAPISecret(), { 'verify': False, 'timeout': 20 }) # if trading account is for testing it will be instantiated with a balance of 1000 self.balance = pd.DataFrame( [['QUOTE', 1000, 0, 1000], ['BASE', 0, 0, 0]], columns=['currency', 'balance', 'hold', 'available']) self.app = app if app.isLive() == 1: self.mode = 'live' else: self.mode = 'test' self.orders = pd.DataFrame() def __convertStatus(self, val): if val == 'filled': return 'done' else: return val def getOrders(self, market='', action='', status='all'): """Retrieves orders either live or simulation Parameters ---------- market : str, optional Filters orders by market action : str, optional Filters orders by action status : str Filters orders by status, defaults to 'all' """ if self.app.getExchange() == 'coinbasepro' and market != '': # validate market is syntactically correct p = re.compile(r"^[A-Z]{3,4}\-[A-Z]{3,4}$") if not p.match(market): raise TypeError('Coinbase Pro market is invalid.') elif self.app.getExchange() == 'binance': # validate market is syntactically correct p = re.compile(r"^[A-Z]{6,12}$") if not p.match(market): raise TypeError('Binance market is invalid.') if action != '': # validate action is either a buy or sell if not action in ['buy', 'sell']: raise ValueError('Invalid order action.') # validate status is open, pending, done, active or all if not status in [ 'open', 'pending', 'done', 'active', 'all', 'filled' ]: raise ValueError('Invalid order status.') if self.app.getExchange() == 'binance': if self.mode == 'live': resp = self.client.get_all_orders(symbol=market) if len(resp) > 0: df = pd.DataFrame(resp) else: df = pd.DataFrame() if len(df) == 0: return pd.DataFrame() df = df[[ 'time', 'symbol', 'side', 'type', 'executedQty', 'cummulativeQuoteQty', 'status' ]] df.columns = [ 'created_at', 'market', 'action', 'type', 'size', 'value', 'status' ] df['created_at'] = df['created_at'].apply( lambda x: int(str(x)[:10])) df['created_at'] = df['created_at'].astype("datetime64[s]") df['size'] = df['size'].astype(float) df['value'] = df['value'].astype(float) df['action'] = df['action'].str.lower() df['type'] = df['type'].str.lower() df['status'] = df['status'].str.lower() df['price'] = df['size'] * df['value'] # pylint: disable=unused-variable for k, v in df.items(): if k == 'status': df[k] = df[k].map(self.__convertStatus) if action != '': df = df[df['action'] == action] df = df.reset_index(drop=True) if status != 'all' and status != '': df = df[df['status'] == status] df = df.reset_index(drop=True) return df else: # return dummy orders if market == '': return self.orders else: if (len(self.orders) > 0): return self.orders[self.orders['market'] == market] else: return pd.DataFrame() if self.app.getExchange() == 'coinbasepro': if self.mode == 'live': # if config is provided and live connect to Coinbase Pro account portfolio model = CBAuthAPI(self.app.getAPIKey(), self.app.getAPISecret(), self.app.getAPIPassphrase(), self.app.getAPIURL()) # retrieve orders from live Coinbase Pro account portfolio self.orders = model.getOrders(market, action, status) return self.orders else: # return dummy orders if market == '': return self.orders else: return self.orders[self.orders['market'] == market] def getBalance(self, currency=''): """Retrieves balance either live or simulation Parameters ---------- currency: str, optional Filters orders by currency """ if self.app.getExchange() == 'binance': if self.mode == 'live': resp = self.client.get_account() if 'balances' in resp: df = pd.DataFrame(resp['balances']) df = df[(df['free'] != '0.00000000') & (df['free'] != '0.00')] df['free'] = df['free'].astype(float) df['locked'] = df['locked'].astype(float) df['balance'] = df['free'] - df['locked'] df.columns = ['currency', 'available', 'hold', 'balance'] df = df[['currency', 'balance', 'hold', 'available']] df = df.reset_index(drop=True) if currency == '': # retrieve all balances return df else: # retrieve balance of specified currency df_filtered = df[df['currency'] == currency]['available'] if len(df_filtered) == 0: # return nil balance if no positive balance was found return 0.0 else: # return balance of specified currency (if positive) if currency in ['EUR', 'GBP', 'USD']: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 2)) else: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 4)) else: return 0.0 else: # return dummy balances if currency == '': # retrieve all balances return self.balance else: if self.app.getExchange() == 'binance': self.balance = self.balance.replace('QUOTE', currency) else: # replace QUOTE and BASE placeholders if currency in ['EUR', 'GBP', 'USD']: self.balance = self.balance.replace( 'QUOTE', currency) else: self.balance = self.balance.replace( 'BASE', currency) if self.balance.currency[self.balance.currency.isin( [currency])].empty == True: self.balance.loc[len( self.balance)] = [currency, 0, 0, 0] # retrieve balance of specified currency df = self.balance df_filtered = df[df['currency'] == currency]['available'] if len(df_filtered) == 0: # return nil balance if no positive balance was found return 0.0 else: # return balance of specified currency (if positive) if currency in ['EUR', 'GBP', 'USD']: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 2)) else: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 4)) else: if self.mode == 'live': # if config is provided and live connect to Coinbase Pro account portfolio model = CBAuthAPI(self.app.getAPIKey(), self.app.getAPISecret(), self.app.getAPIPassphrase(), self.app.getAPIURL()) if currency == '': # retrieve all balances return model.getAccounts()[[ 'currency', 'balance', 'hold', 'available' ]] else: df = model.getAccounts() # retrieve balance of specified currency df_filtered = df[df['currency'] == currency]['available'] if len(df_filtered) == 0: # return nil balance if no positive balance was found return 0.0 else: # return balance of specified currency (if positive) if currency in ['EUR', 'GBP', 'USD']: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 2)) else: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 4)) else: # return dummy balances if currency == '': # retrieve all balances return self.balance else: # replace QUOTE and BASE placeholders if currency in ['EUR', 'GBP', 'USD']: self.balance = self.balance.replace('QUOTE', currency) elif currency in ['BCH', 'BTC', 'ETH', 'LTC', 'XLM']: self.balance = self.balance.replace('BASE', currency) if self.balance.currency[self.balance.currency.isin( [currency])].empty == True: self.balance.loc[len( self.balance)] = [currency, 0, 0, 0] # retrieve balance of specified currency df = self.balance df_filtered = df[df['currency'] == currency]['available'] if len(df_filtered) == 0: # return nil balance if no positive balance was found return 0.0 else: # return balance of specified currency (if positive) if currency in ['EUR', 'GBP', 'USD']: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 2)) else: return float( self.app.truncate( float(df[df['currency'] == currency] ['available'].values[0]), 4)) def saveTrackerCSV(self, market='', save_file='tracker.csv'): """Saves order tracker to CSV Parameters ---------- market : str, optional Filters orders by market save_file : str Output CSV file """ if self.app.getExchange() == 'coinbasepro' and market != '': # validate market is syntactically correct p = re.compile(r"^[A-Z]{3,4}\-[A-Z]{3,4}$") if not p.match(market): raise TypeError('Coinbase Pro market is invalid.') elif self.app.getExchange() == 'binance': # validate market is syntactically correct p = re.compile(r"^[A-Z]{6,12}$") if not p.match(market): raise TypeError('Binance market is invalid.') if self.mode == 'live': if self.app.getExchange() == 'coinbasepro': # retrieve orders from live Coinbase Pro account portfolio df = self.getOrders(market, '', 'done') elif self.app.getExchange() == 'binance': # retrieve orders from live Binance account portfolio df = self.getOrders(market, '', 'done') else: df = pd.DataFrame() else: # return dummy orders if market == '': df = self.orders else: df = self.orders[self.orders['market'] == market] if list(df.keys()) != [ 'created_at', 'market', 'action', 'type', 'size', 'value', 'status', 'price' ]: # no data, return early return False df_tracker = pd.DataFrame() last_action = '' for market in df['market'].sort_values().unique(): df_market = df[df['market'] == market] df_buy = pd.DataFrame() df_sell = pd.DataFrame() pair = 0 # pylint: disable=unused-variable for index, row in df_market.iterrows(): if row['action'] == 'buy': pair = 1 if pair == 1 and (row['action'] != last_action): if row['action'] == 'buy': df_buy = row elif row['action'] == 'sell': df_sell = row if row['action'] == 'sell' and len(df_buy) != 0: df_pair = pd.DataFrame([[ df_sell['status'], df_buy['market'], df_buy['created_at'], df_buy['type'], df_buy['size'], df_buy['value'], df_buy['price'], df_sell['created_at'], df_sell['type'], df_sell['size'], df_sell['value'], df_sell['price'] ]], columns=[ 'status', 'market', 'buy_at', 'buy_type', 'buy_size', 'buy_value', 'buy_price', 'sell_at', 'sell_type', 'sell_size', 'sell_value', 'sell_price' ]) df_tracker = df_tracker.append(df_pair, ignore_index=True) pair = 0 last_action = row['action'] if list(df_tracker.keys()) != [ 'status', 'market', 'buy_at', 'buy_type', 'buy_size', 'buy_value', 'buy_price', 'sell_at', 'sell_type', 'sell_size', 'sell_value', 'sell_price' ]: # no data, return early return False df_tracker['profit'] = np.subtract(df_tracker['sell_value'], df_tracker['buy_value']) df_tracker['margin'] = np.multiply( np.true_divide(df_tracker['profit'], df_tracker['sell_value']), 100) df_sincebot = df_tracker[df_tracker['buy_at'] > '2021-02-1'] try: df_sincebot.to_csv(save_file, index=False) except OSError: raise SystemExit('Unable to save: ', save_file) def buy(self, cryptoMarket, fiatMarket, fiatAmount=0, manualPrice=0.00000000): """Places a buy order either live or simulation Parameters ---------- cryptoMarket: str Crypto market you wish to purchase fiatMarket, str QUOTE market funding the purchase fiatAmount, float QUOTE amount of crypto currency to purchase manualPrice, float Used for simulations specifying the live price to purchase """ # fiat funding amount must be an integer or float if not isinstance(fiatAmount, float) and not isinstance( fiatAmount, int): raise TypeError('QUOTE amount not numeric.') # fiat funding amount must be positive if fiatAmount <= 0: raise Exception('Invalid QUOTE amount.') if self.app.getExchange() == 'binance': # validate crypto market is syntactically correct p = re.compile(r"^[A-Z]{3,8}$") if not p.match(cryptoMarket): raise TypeError('Binance crypto market is invalid.') # validate fiat market is syntactically correct p = re.compile(r"^[A-Z]{3,8}$") if not p.match(fiatMarket): raise TypeError('Binance fiat market is invalid.') else: # crypto market should be either BCH, BTC, ETH, LTC or XLM if cryptoMarket not in ['BCH', 'BTC', 'ETH', 'LTC', 'XLM']: raise Exception( 'Invalid crypto market: BCH, BTC, ETH, LTC, ETH, or XLM') # fiat market should be either EUR, GBP, or USD if fiatMarket not in ['EUR', 'GBP', 'USD']: raise Exception('Invalid QUOTE market: EUR, GBP, USD') # reconstruct the exchange market using crypto and fiat inputs if self.app.getExchange() == 'binance': market = cryptoMarket + fiatMarket else: market = cryptoMarket + '-' + fiatMarket if self.app.getExchange() == 'binance': if self.mode == 'live': # execute a live market buy resp = self.client.order_market_buy(symbol=market, quantity=fiatAmount) # TODO: not finished print(resp) else: # fiat amount should exceed balance if fiatAmount > self.getBalance(fiatMarket): raise Exception('Insufficient funds.') # manual price must be an integer or float if not isinstance(manualPrice, float) and not isinstance( manualPrice, int): raise TypeError('Optional manual price not numeric.') price = manualPrice # if manualPrice is non-positive retrieve the current live price if manualPrice <= 0: if self.app.getExchange() == 'binance': api = BPublicAPI() price = api.getTicker(market) else: resp = requests.get( 'https://api-public.sandbox.pro.coinbase.com/products/' + market + '/ticker') if resp.status_code != 200: raise Exception( 'GET /products/' + market + '/ticker {}'.format(resp.status_code)) resp.raise_for_status() json = resp.json() price = float(json['price']) # calculate purchase fees fee = fiatAmount * 0.005 fiatAmountMinusFee = fiatAmount - fee total = float(fiatAmountMinusFee / float(price)) # append dummy order into orders dataframe ts = pd.Timestamp.now() price = (fiatAmountMinusFee * 100) / (total * 100) order = pd.DataFrame([[ '', market, 'buy', 'market', float('{:.8f}'.format(total)), fiatAmountMinusFee, 'done', '{:.8f}'.format(float(price)) ]], columns=[ 'created_at', 'market', 'action', 'type', 'size', 'value', 'status', 'price' ], index=[ts]) order['created_at'] = order.index self.orders = pd.concat( [self.orders, pd.DataFrame(order)], ignore_index=False) # update the dummy fiat balance self.balance.loc[ self.balance['currency'] == fiatMarket, 'balance'] = self.getBalance(fiatMarket) - fiatAmount self.balance.loc[ self.balance['currency'] == fiatMarket, 'available'] = self.getBalance(fiatMarket) - fiatAmount # update the dummy crypto balance self.balance.loc[self.balance['currency'] == cryptoMarket, 'balance'] = self.getBalance(cryptoMarket) + ( fiatAmountMinusFee / price) self.balance.loc[ self.balance['currency'] == cryptoMarket, 'available'] = self.getBalance(cryptoMarket) + ( fiatAmountMinusFee / price) else: if self.mode == 'live': # connect to coinbase pro api (authenticated) model = CBAuthAPI(self.app.getAPIKey(), self.app.getAPISecret(), self.app.getAPIPassphrase(), self.app.getAPIURL()) # execute a live market buy if fiatAmount > 0: resp = model.marketBuy(market, fiatAmount) else: resp = model.marketBuy(market, float(self.getBalance(fiatMarket))) # TODO: not finished print(resp) else: # fiat amount should exceed balance if fiatAmount > self.getBalance(fiatMarket): raise Exception('Insufficient funds.') # manual price must be an integer or float if not isinstance(manualPrice, float) and not isinstance( manualPrice, int): raise TypeError('Optional manual price not numeric.') price = manualPrice # if manualPrice is non-positive retrieve the current live price if manualPrice <= 0: resp = requests.get( 'https://api-public.sandbox.pro.coinbase.com/products/' + market + '/ticker') if resp.status_code != 200: raise Exception('GET /products/' + market + '/ticker {}'.format(resp.status_code)) resp.raise_for_status() json = resp.json() price = float(json['price']) # calculate purchase fees fee = fiatAmount * 0.005 fiatAmountMinusFee = fiatAmount - fee total = float(fiatAmountMinusFee / price) # append dummy order into orders dataframe ts = pd.Timestamp.now() price = (fiatAmountMinusFee * 100) / (total * 100) order = pd.DataFrame([[ '', market, 'buy', 'market', float('{:.8f}'.format(total)), fiatAmountMinusFee, 'done', price ]], columns=[ 'created_at', 'market', 'action', 'type', 'size', 'value', 'status', 'price' ], index=[ts]) order['created_at'] = order.index self.orders = pd.concat( [self.orders, pd.DataFrame(order)], ignore_index=False) # update the dummy fiat balance self.balance.loc[ self.balance['currency'] == fiatMarket, 'balance'] = self.getBalance(fiatMarket) - fiatAmount self.balance.loc[ self.balance['currency'] == fiatMarket, 'available'] = self.getBalance(fiatMarket) - fiatAmount # update the dummy crypto balance self.balance.loc[self.balance['currency'] == cryptoMarket, 'balance'] = self.getBalance(cryptoMarket) + ( fiatAmountMinusFee / price) self.balance.loc[ self.balance['currency'] == cryptoMarket, 'available'] = self.getBalance(cryptoMarket) + ( fiatAmountMinusFee / price) def sell(self, cryptoMarket, fiatMarket, cryptoAmount, manualPrice=0.00000000): """Places a sell order either live or simulation Parameters ---------- cryptoMarket: str Crypto market you wish to purchase fiatMarket, str QUOTE market funding the purchase fiatAmount, float QUOTE amount of crypto currency to purchase manualPrice, float Used for simulations specifying the live price to purchase """ if self.app.getExchange() == 'binance': # validate crypto market is syntactically correct p = re.compile(r"^[A-Z]{3,8}$") if not p.match(cryptoMarket): raise TypeError('Binance crypto market is invalid.') # validate fiat market is syntactically correct p = re.compile(r"^[A-Z]{3,8}$") if not p.match(fiatMarket): raise TypeError('Binance fiat market is invalid.') else: # crypto market should be either BCH, BTC, ETH, LTC or XLM if cryptoMarket not in ['BCH', 'BTC', 'ETH', 'LTC', 'XLM']: raise Exception( 'Invalid crypto market: BCH, BTC, ETH, LTC, ETH, or XLM') # fiat market should be either EUR, GBP, or USD if fiatMarket not in ['EUR', 'GBP', 'USD']: raise Exception('Invalid QUOTE market: EUR, GBP, USD') # reconstruct the exchange market using crypto and fiat inputs if self.app.getExchange() == 'binance': market = cryptoMarket + fiatMarket else: market = cryptoMarket + '-' + fiatMarket # crypto amount must be an integer or float if not isinstance(cryptoAmount, float) and not isinstance( cryptoAmount, int): raise TypeError('Crypto amount not numeric.') # crypto amount must be positive if cryptoAmount <= 0: raise Exception('Invalid crypto amount.') if self.app.getExchange() == 'binance': if self.mode == 'live': # execute a live market buy resp = self.client.order_market_sell(symbol=market, quantity=cryptoAmount) # TODO: not finished print(resp) else: # crypto amount should exceed balance if cryptoAmount > self.getBalance(cryptoMarket): raise Exception('Insufficient funds.') # manual price must be an integer or float if not isinstance(manualPrice, float) and not isinstance( manualPrice, int): raise TypeError('Optional manual price not numeric.') # calculate purchase fees fee = cryptoAmount * 0.005 cryptoAmountMinusFee = cryptoAmount - fee price = manualPrice # if manualPrice is non-positive retrieve the current live price if manualPrice <= 0: resp = requests.get( 'https://api-public.sandbox.pro.coinbase.com/products/' + market + '/ticker') if resp.status_code != 200: raise Exception('GET /products/' + market + '/ticker {}'.format(resp.status_code)) resp.raise_for_status() json = resp.json() price = float(json['price']) total = price * cryptoAmountMinusFee # append dummy order into orders dataframe ts = pd.Timestamp.now() price = ((price * cryptoAmount) * 100) / (cryptoAmount * 100) order = pd.DataFrame([[ '', market, 'sell', 'market', cryptoAmountMinusFee, float('{:.8f}'.format(total)), 'done', '{:.8f}'.format( float(price)) ]], columns=[ 'created_at', 'market', 'action', 'type', 'size', 'value', 'status', 'price' ], index=[ts]) order['created_at'] = order.index self.orders = pd.concat( [self.orders, pd.DataFrame(order)], ignore_index=False) # update the dummy fiat balance self.balance.loc[ self.balance['currency'] == fiatMarket, 'balance'] = self.getBalance(fiatMarket) + total self.balance.loc[ self.balance['currency'] == fiatMarket, 'available'] = self.getBalance(fiatMarket) + total # update the dummy crypto balance self.balance.loc[ self.balance['currency'] == cryptoMarket, 'balance'] = self.getBalance(cryptoMarket) - cryptoAmount self.balance.loc[ self.balance['currency'] == cryptoMarket, 'available'] = self.getBalance(cryptoMarket) - cryptoAmount else: if self.mode == 'live': # connect to Coinbase Pro API live model = CBAuthAPI(self.app.getAPIKey(), self.app.getAPISecret(), self.app.getAPIPassphrase(), self.app.getAPIURL()) # execute a live market sell resp = model.marketSell(market, float(self.getBalance(cryptoMarket))) # TODO: not finished print(resp) else: # crypto amount should exceed balance if cryptoAmount > self.getBalance(cryptoMarket): raise Exception('Insufficient funds.') # manual price must be an integer or float if not isinstance(manualPrice, float) and not isinstance( manualPrice, int): raise TypeError('Optional manual price not numeric.') # calculate purchase fees fee = cryptoAmount * 0.005 cryptoAmountMinusFee = cryptoAmount - fee price = manualPrice if manualPrice <= 0: # if manualPrice is non-positive retrieve the current live price resp = requests.get( 'https://api-public.sandbox.pro.coinbase.com/products/' + market + '/ticker') if resp.status_code != 200: raise Exception('GET /products/' + market + '/ticker {}'.format(resp.status_code)) resp.raise_for_status() json = resp.json() price = float(json['price']) total = price * cryptoAmountMinusFee # append dummy order into orders dataframe ts = pd.Timestamp.now() price = ((price * cryptoAmount) * 100) / (cryptoAmount * 100) order = pd.DataFrame([[ market, 'sell', 'market', cryptoAmountMinusFee, float('{:.8f}'.format(total)), 'done', price ]], columns=[ 'market', 'action', 'type', 'size', 'value', 'status', 'price' ], index=[ts]) order['created_at'] = order.index self.orders = pd.concat( [self.orders, pd.DataFrame(order)], ignore_index=False) # update the dummy fiat balance self.balance.loc[ self.balance['currency'] == fiatMarket, 'balance'] = self.getBalance(fiatMarket) + total self.balance.loc[ self.balance['currency'] == fiatMarket, 'available'] = self.getBalance(fiatMarket) + total # update the dummy crypto balance self.balance.loc[ self.balance['currency'] == cryptoMarket, 'balance'] = self.getBalance(cryptoMarket) - cryptoAmount self.balance.loc[ self.balance['currency'] == cryptoMarket, 'available'] = self.getBalance(cryptoMarket) - cryptoAmount
class Exchange: def __init__(self, default): self.client = Client(binance_api_key, binance_api_secret) self.storage_ticker = default def get_asset_price(self, asset_name): if asset_name != "USDT": ticker = self.client.get_ticker(symbol=asset_name + self.storage_ticker) return ticker["lastPrice"] else: return 1 def buy_asset(self, asset_name): if asset_name != self.storage_ticker: # Print on console buy order print("Buying asset: %s" % asset_name) # Set asset name according mapper CoinMarketCap vs Binance asset_name = self.mapper_asset_name(asset_name) # Get free balance to trade result = self.client.get_asset_balance(asset=self.storage_ticker) free_balance = result["free"] # Get price asset vs storage ticker result = self.client.get_symbol_ticker(symbol=asset_name + self.storage_ticker) price = result["price"] # Max quantity to buy quantity = (float(free_balance) / float(price)) factor = 1 * math.pow(10, self.mapper_min_quantity(asset_name)) quantity = math.floor(quantity * factor) / factor print("quantity round %s" % quantity) # Submit a buy order self.client.order_market_buy(symbol=asset_name + self.storage_ticker, quantity=quantity) def sell_all_assets(self): response = self.client.get_account() balance_lenght = len(response["balances"]) for i in range(0, balance_lenght): item = response["balances"][i] if item["asset"] != self.storage_ticker and float( item["free"]) > 0: try: factor = 1 * math.pow( 10, self.mapper_min_quantity(item["asset"])) quantity = math.floor( float(item["free"]) * factor) / factor if quantity > 0: # Print on console buy order print("Selling asset: %s" % (item["asset"])) self.client.order_market_sell(symbol=item["asset"] + self.storage_ticker, quantity=quantity) except Exception as e: print(e) pass def mapper_asset_name(self, asset_name): if asset_name == "USDT": return "USDT" elif asset_name == "BTC": return "BTC" elif asset_name == "ETH": return "ETH" elif asset_name == "XRP": return "XRP" elif asset_name == "BCH": return "BCC" elif asset_name == "ADA": return "ADA" elif asset_name == "LTC": return "LTC" elif asset_name == "XEM": return "XEM" elif asset_name == "NEO": return "NEO" elif asset_name == "XLM": return "XLM" elif asset_name == "MIOTA": return "IOTA" elif asset_name == "BNB": return "BNB" def mapper_min_quantity(self, asset_name): if asset_name == "USDT": return 4 elif asset_name == "BTC": return 6 elif asset_name == "ETH": return 5 elif asset_name == "XRP": return 0 elif asset_name == "BCC": return 5 elif asset_name == "ADA": return 0 elif asset_name == "LTC": return 5 elif asset_name == "XEM": return 0 elif asset_name == "NEO": return 3 elif asset_name == "XLM": return 1 elif asset_name == "IOTA": return 0 elif asset_name == "BNB": return 2
class BinaceConnector(): def __init__(self): self.client = Client(clientConfig.api_key, clientConfig.api_secret, tld='us') self.wsclient = None # setup a websocket client def setupWebsocket(self): self.wsclient = BinanceSocketManager(self.client) return self.wsclient # get a feed in real time for all prices def startAllBookTicker(self, callback): return self.client.start_book_ticker_socket(callback) # setup depth manager def setupDepthCacheManager(self, coin, callback, limit=10): return DepthCacheManager(self.client, coin, callback) def setupCandleSocket(self, coin, callback, interval): self.setupWebsocket() return self.wsclient.start_kline_socket(coin, callback, interval=interval) def setupSocketMultiPlex(self, coins, multimessage): self.wsclient.start_multiplex_socket(coins, multimessage) return self.wsclient def getHistoryCandles(self, coin, interval=Client.KLINE_INTERVAL_5MINUTE, len='100 hour ago EST'): return self.client.get_historical_klines(coin, interval, len) # setup multiple cache managers: def setupDepthForSymbolsUSDT(self, symbols, callback): manager = self.setupWebsocket() streams = [] for coin in symbols: streams.append( DepthCacheManager(self.client, coin + "USDT", callback, bm=manager, ws_interval=30)) return streams # return the current coin price def getCoinPrice(self, coin): for sym in (self.client.get_all_tickers()): if sym['symbol'] == coin: return (sym['price']) # get all orders for a coin def getCoinOrder(self, coin, amount): return self.client.get_all_orders(coin=coin, limit=amount) #get current spending limit def getUSD(self): return float(self.client.get_asset_balance(asset='USD')['free']) #get current coin holding def getCoinBalance(self, coin): return self.client.get_asset_balance(asset=coin) #returns a list of what I own def getAccountBalances(self): coins = [] for coin in self.client.get_account()['balances']: if float(coin['free']) > 0 or float(coin['locked']) > 0: coins.append(coin) return coins #gets assets, asset totals, and totalBalance def getAssetPrices(self): owned = (self.getAccountBalances()) for x in owned: asset = x['asset'] if asset != 'USD': price = float((self.getCoinPrice(f"{x['asset']}USD"))) total = (float(x['free']) + float(x['locked'])) * price x['price'] = price x['total'] = total return owned # get all information about a coin def getCoinInfo(self, coin): return self.client.get_symbol_info(coin) # get the minimum amount of coin that can be purchased and the increments def getCoinPrecision(self, coin): return self.client.get_symbol_info(coin)['baseAssetPrecision'] # get our current buying power def getPower(self): return self.getUSD() # buy a coin at the current market value def buyMarket(self, coin, amount): return self.client.order_market_buy(symbol=coin, quantity=amount) # sell a coin at the current market value def sellMarket(self, coin, amount): return self.client.order_market_sell(symbol=coin, quantity=amount) # sell a coin at the current market value def sellLimit(self, coin, amount, limit): info = self.getCoinInfo(coin) quotePrecision = int(info['quotePrecision']) limit = round(limit, quotePrecision - 2) return self.client.order_limit_sell(symbol=coin, quantity=amount, price=limit) # place a stoploss def stopLoss(self, coin, stop, limit, position): multiplierUp = 0 multiplierDown = 0 info = self.getCoinInfo(coin) quotePrecision = int(info['quotePrecision']) for filt in info['filters']: if filt['filterType'] == "PERCENT_PRICE": multiplierUp = float(filt['multiplierUp']) multiplierDown = float(filt['multiplierDown']) currentPrice = float(self.getCoinPrice(coin)) upPrice = currentPrice * multiplierUp downPrice = currentPrice * multiplierDown stoper = round(stop, quotePrecision - 2) limiter = round(limit, quotePrecision - 2) print(f"up {upPrice}") print(f"down {downPrice}") print(f"stop {stoper}") print(f"limit {limiter}") if stoper > upPrice: print("TOHIGH") return None if stoper < downPrice: print("TOLOW") return None if limiter > upPrice: print("TOHIGH") return None if limiter < downPrice: print("TOLOW") return None output = self.client.create_order(symbol=coin, timeInForce='GTC', type='STOP_LOSS_LIMIT', quantity=position, side="sell", price=limiter, stopPrice=stoper) return output #test order will return {} if great success def testOrder(self, coin, act, amount): print( self.client.create_test_order(symbol=coin, side=act, type=ORDER_TYPE_MARKET, quantity=amount)) # check the status of an order def getSellAmount(self, coin, order): order = self.client.get_order(symbol=coin, origClientOrderId=order) #add logic here for complete return round(float(order['cummulativeQuoteQty']), 2) def checkStatus(self, coin, order): order = self.client.get_order(symbol=coin, origClientOrderId=order) #add logic here for complete return order['status'] # cancel an order that might get hung def cancelOrder(self, coin, order): return self.client.cancel_order(symbol=coin, origClientOrderId=order) def getBook(self): return self.client.get_orderbook_tickers() def getAssets(self): return self.client.get_account()
class Binance(Exchange): ORDER_KEYS_MAP = { 's': 'symbol', 'X': 'status', 'i': 'orderId', 'p': 'price', 'q': 'origQty', 'S': 'side', } def __init__(self, *args, **kwargs): super(Binance, self).__init__('binance', *args, **kwargs) key = self.config['api_key'] secret = self.config['api_secret'] self.client = BinanceClient(key, secret) self._debpth_data_buffer = Queue() self._load_depth_snapshot_thread = None def new_order(self, amount, price): type = 'LIMIT' side = 'BUY' if amount > 0 else 'SELL' data = { 'price': price, 'quantity': abs(amount), 'symbol': self.target_pair, 'timeInForce': 'FOK', 'side': side, 'type': type, } log.critical('new_order: %s', data) ret = self.client.create_order(**data) log.critical('new_order: %s', ret) if ret['status'] == 'FILLED': return True else: return ret def withdraw(self, token_type, amount, address): ret = self.client.withdraw(token_type, amount, address) return ret def _update_order_book_list(self, book, list): for item in list: price, amount, _ = item price = float(price) amount = float(amount) if amount == 0: book.remove(price) else: book.add_or_update(price, amount) def _update_order_book(self, bid_list, ask_list): log.debug('_update_order_book') self._update_order_book_list(self.bids_book, bid_list) self._update_order_book_list(self.asks_book, ask_list) self.notify_order_book_update() def _process_order(self, data, keys=None): if keys is not None: data = utils.map_dict(data, keys) symbol = data['symbol'] if symbol != self.target_pair: return status = data['status'] remove = status in 'CANCELED FILLED EXPIRED CANCELED' self.update_order_list(data['orderId'], float(data['price']), float(data['origQty']), data['side'] == 'SELL', remove) def _process_assets(self, balances, keys=None): for item in balances: if keys is not None: item = utils.map_dict(item, keys) free_amount_str = item['free'] if free_amount_str == '0.00000000': continue free_amount = float(free_amount_str) self.asset_list[item['asset']] = free_amount self.notify_account_update() def connect(self): self._load_init_data() self._new_queue_poller(self.client.queue, self.process_message) self.client.start_depth_socket(self.target_pair) self.client.start_user_socket() def _load_init_data(self): open_orders = self.client.get_open_orders() for open_order in open_orders: self._process_order(open_order) log.info('_load_init_data, open_orders: %s', open_orders) my_account = self.client.get_account() log.info('_load_init_data, my_account: %s', my_account) self._process_assets(my_account['balances']) def _load_depth_data(self): depth_data = self.client.get_order_book(symbol=self.target_pair) log.info('_load_init_data, depth_data: %s', depth_data) last_update_id = depth_data['lastUpdateId']; self._update_order_book(depth_data['bids'], depth_data['asks']) while not self._debpth_data_buffer.empty(): item = self._debpth_data_buffer.get() if item['u'] <= last_update_id: log.info('_load_init_data, skip: %s', item) continue else: self._update_order_book(item['b'], item['a']) self.order_book_ready.set() log.info('_load_depth_data finish'); def _new_queue_poller(self, queue, handler): poller = QueuePoller(queue, handler) poller.start() self.queue_poller_list.append(poller) def process_message(self, msg): log.debug('process_message: %s', msg) payload, ts = msg event = payload['e'] if event == 'executionReport': self._process_order(payload, self.ORDER_KEYS_MAP) if event == 'outboundAccountInfo': self._process_assets(payload['B'], {'f': 'free', 'a': 'asset'}) if event == 'depthUpdate': if self.order_book_ready.is_set(): self._update_order_book(payload['b'], payload['a']) else: self._debpth_data_buffer.put(payload) if self._debpth_data_buffer.qsize() >= 2 and self._load_depth_snapshot_thread is None: self._load_depth_snapshot_thread = Thread(target=self._load_depth_data) self._load_depth_snapshot_thread.start() def disconnect(self): self.client.close() for poller in self.queue_poller_list: poller.join()
from private.keys import binance_paper_keys, connection_strings import pandas as pd import os from binance.websockets import BinanceSocketManager from twisted.internet import reactor ## binance_client = Client(binance_paper_keys.get('api'), binance_paper_keys.get('secret_key')) ## binance_client.API_URL = 'https://testnet.binance.vision/api' ## # get balances for all assets & some account information pprint(binance_client.get_account()) # %% # get balance for a specific asset only (BTC) pprint(binance_client.get_asset_balance(asset='BTC')) # %% # get latest price from Binance API btc_price = binance_client.get_symbol_ticker(symbol="BTCUSDT") # print full output (dictionary) print(btc_price) # %% hist = binance_client.get_historical_klines( 'BTCUSDT', binance_client.KLINE_INTERVAL_5MINUTE,
class BinanceClient(object): def __init__(self, app=None): self.app = app if app: self.init_app(app) def init_app(self, app): self.app = app self.name = 'binance' app.extensions[self.name] = self self.api_key = app.config[self.name.upper()+'_API_KEY'] self.api_secret = app.config[self.name.upper()+'_API_SECRET'] self.client = Client(self.api_key, self.api_secret) def get_balances(self): def append_total(d): d['total'] = float(d['free']) + float(d['locked']) return d def standardize(d): return { 'exchange': self.name, 'asset': d['asset'], 'balance': d['total'] } return filter( lambda x: x['balance'] > 0.0, [standardize(append_total(x)) for x in self.client.get_account()['balances']] ) def get_trades(self, pair): params = { 'symbol': pair.upper(), 'fromId': 0 } out = [] while True: data = self.client.get_my_trades(**params) if data is None or len(data) == 0: return out out.extend(data) params['fromId'] = data[-1]['id'] + 1 def get_transfers(self): def with_type(item, type): item['type'] = type return item out = [] data = self.client.get_deposit_history() if data and 'depositList' in data: out.extend([ with_type(item, 'Deposit') for item in data['depositList'] ]) data = self.client.get_withdraw_history() if data and 'withdrawList' in data: out.extend([ with_type(item, 'Withdraw') for item in data['withdrawList'] ]) return out
class AuthAPI(AuthAPIBase): 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 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 handle_init_error(self, err: str) -> None: if self.debug: raise TypeError(err) else: raise SystemExit(err) def getClient(self) -> Client: return self.client def getAccount(self): """Retrieves a specific account""" account = self.client.get_account() if 'balances' in account: df = pd.DataFrame(account['balances']) df = df[(df['free'] != '0.00000000') & (df['free'] != '0.00')] df['free'] = df['free'].astype(float) df['locked'] = df['locked'].astype(float) df['balance'] = df['free'] - df['locked'] df.columns = ['currency', 'available', 'hold', 'balance'] df = df[['currency', 'balance', 'hold', 'available']] df = df.reset_index(drop=True) return df else: return 0.0 def getFees(self, market: str = '') -> pd.DataFrame: if market != '': resp = self.client.get_trade_fee(symbol=market) if 'tradeFee' in resp and len(resp['tradeFee']) > 0: df = pd.DataFrame(resp['tradeFee'][0], index=[0]) df['usd_volume'] = None df.columns = [ 'maker_fee_rate', 'market', 'taker_fee_rate', 'usd_volume' ] return df[[ 'maker_fee_rate', 'taker_fee_rate', 'usd_volume', 'market' ]] return pd.DataFrame( columns=['maker_fee_rate', 'taker_fee_rate', 'market']) else: resp = self.client.get_trade_fee() if 'tradeFee' in resp: df = pd.DataFrame(resp['tradeFee']) df['usd_volume'] = None df.columns = [ 'maker_fee_rate', 'market', 'taker_fee_rate', 'usd_volume' ] return df[[ 'maker_fee_rate', 'taker_fee_rate', 'usd_volume', 'market' ]] return pd.DataFrame( columns=['maker_fee_rate', 'taker_fee_rate', 'market']) def getMakerFee(self, market: str = '') -> float: if market == '': fees = self.getFees() else: fees = self.getFees(market) if len(fees) == 0 or 'maker_fee_rate' not in fees: Logger.error( f"error: 'maker_fee_rate' not in fees (using {DEFAULT_MAKER_FEE_RATE} as a fallback)" ) return DEFAULT_MAKER_FEE_RATE if market == '': return fees else: return float(fees['maker_fee_rate'].to_string(index=False).strip()) 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 __convertStatus(self, val: str) -> str: if val == 'filled': return 'done' else: return val def getOrders(self, market: str = '', action: str = '', status: str = 'all') -> pd.DataFrame: """Retrieves your list of orders with optional filtering""" # if market provided if market != '': # validates the market is syntactically correct if not self._isMarketValid(market): raise ValueError('Binance market is invalid.') # if action provided if action != '': # validates action is either a buy or sell if not action in ['buy', 'sell']: raise ValueError('Invalid order action.') # validates status is either open, pending, done, active, or all if not status in ['open', 'pending', 'done', 'active', 'all']: raise ValueError('Invalid order status.') resp = self.client.get_all_orders(symbol=market) if len(resp) > 0: df = pd.DataFrame(resp) else: df = pd.DataFrame() if len(df) == 0: return pd.DataFrame() # replace null NaN values with 0 df.fillna(0, inplace=True) df = df[[ 'time', 'symbol', 'side', 'type', 'executedQty', 'cummulativeQuoteQty', 'status' ]] df.columns = [ 'created_at', 'market', 'action', 'type', 'filled', 'size', 'status' ] df['created_at'] = df['created_at'].apply(lambda x: int(str(x)[:10])) df['created_at'] = df['created_at'].astype("datetime64[s]") df['size'] = df['size'].astype(float) df['filled'] = df['filled'].astype(float) df['action'] = df['action'].str.lower() df['type'] = df['type'].str.lower() df['status'] = df['status'].str.lower() df['price'] = df['size'] / df['filled'] # pylint: disable=unused-variable for k, v in df.items(): if k == 'status': df[k] = df[k].map(self.__convertStatus) if action != '': df = df[df['action'] == action] df = df.reset_index(drop=True) if status != 'all' and status != '': df = df[df['status'] == status] df = df.reset_index(drop=True) return df def marketBuy(self, market: str = '', quote_quantity: float = 0) -> 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 Logger.info('Order quantity after rounding and fees: ' + str(truncated)) return self.client.order_market_buy(symbol=market, quantity=truncated) except Exception as err: ts = datetime.now().strftime("%d-%m-%Y %H:%M:%S") Logger.error(ts + ' Binance ' + ' marketBuy ' + str(err)) return [] 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 getTradeFee(self, market: str) -> float: resp = self.client.get_trade_fee(symbol=market, timestamp=self.getTime()) if 'tradeFee' not in resp: Logger.info('*** getTradeFee(' + market + ') - missing "tradeFee" ***') Logger.info(resp) else: if len(resp['tradeFee']) == 0: Logger.info('*** getTradeFee(' + market + ') - "tradeFee" empty ***') Logger.info(resp) else: if 'taker' not in resp['tradeFee'][0]: Logger.info('*** getTradeFee(' + market + ') - missing "trader" ***') Logger.info(resp) if 'success' in resp: return resp['tradeFee'][0]['taker'] else: return DEFAULT_TRADE_FEE_RATE def getMarketInfo(self, market: str) -> dict: # validates the market is syntactically correct if not self._isMarketValid(market): raise TypeError('Binance market required.') return self.client.get_symbol_info(symbol=market) def getMarketInfoFilters(self, market: str) -> pd.DataFrame: return pd.DataFrame( self.client.get_symbol_info(symbol=market)['filters']) def getTicker(self, market: str) -> tuple: # validates the market is syntactically correct if not self._isMarketValid(market): raise TypeError('Binance market required.') resp = self.client.get_symbol_ticker(symbol=market) if 'price' in resp: return (self.getTime().strftime('%Y-%m-%d %H:%M:%S'), float('{:.8f}'.format(float(resp['price'])))) now = datetime.today().strftime('%Y-%m-%d %H:%M:%S') return (now, 0.0) def getTime(self) -> datetime: """Retrieves the exchange time""" try: resp = self.client.get_server_time() epoch = int(str(resp['serverTime'])[0:10]) return datetime.fromtimestamp(epoch) except: return None
class Binance(Broker): def __init__(self, base_currency, market_currency, pair_code, api_key=None, api_secret=None): super().__init__(base_currency, market_currency, pair_code) self.client = Client( api_key if api_key else config.Binance_API_KEY, api_secret if api_secret else config.Binance_SECRET_TOKEN) def _place_order(self, amount, price, side): order = client.create_order(symbol=self.pair_code, side=side, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=amount, price=str(price)) logging.verbose('_place_order: %s %s' % (side, order)) return order['orderId'] def _buy_limit(self, amount, price): """Create a buy limit order""" return self._place_order(amount, price, SIDE_BUY) def _sell_limit(self, amount, price): """Create a sell limit order""" return self._place_order(amount, price, SIDE_SELL) def _order_status(self, res): resp = {} resp['order_id'] = res['orderId'] resp['amount'] = float(res['origQty']) resp['price'] = float(res['price']) resp['deal_amount'] = float(res['executedQty']) resp['avg_price'] = float(res['price']) if res['status'] == ORDER_STATUS_NEW or res[ 'status'] == ORDER_STATUS_PARTIALLY_FILLED: resp['status'] = 'OPEN' else: resp['status'] = 'CLOSE' return resp def _get_order(self, order_id): res = self.client.get_order(orderId=int(order_id), symbol=self.pair_code) logging.verbose('get_order: %s' % res) assert str(res['symbol']) == str(self.pair_code) assert str(res['orderId']) == str(order_id) return self._order_status(res['data']) def _cancel_order(self, order_id): res = self.client.cancel_order(orderId=int(order_id), symbol=self.pair_code) logging.verbose('cancel_order: %s' % res) assert str(res['orderId']) == str(order_id) return True def _get_balances(self): """Get balance""" res = self.client.get_account() logging.debug("get_balances: %s" % res) balances = res['balances'] for entry in balances: currency = entry['asset'].upper() if currency not in ('BTC', 'BCH', 'USD'): continue if currency == 'BCH': self.bch_available = float(entryfree['free']) self.bch_balance = float(entry['amount']) + float( entry['locked']) elif currency == 'BTC': self.btc_available = float(entry['free']) self.btc_balance = float(entry['amount']) + float( entry['locked']) return res
def get_account_balances(api_key, api_secret): client = Client(api_key, api_secret) account = client.get_account() balances = account['balances'] return filter(lambda bl: float(bl['free']) > 0.0, balances)
class magic_bomb: def __init__(self, api_key, api_secret): self.api_key = api_key self.api_secret = api_secret try: self.client = Client(self.api_key, self.api_secret) print("Client authenticated") except BinanceAPIException as e: print(e.status_code) print(e.message) def account(self): account = self.client.get_account() all_values = [] account_values = [] for value in account['balances']: if (float(value['free']) != float(0)) or (float(value['locked']) != float(0)): account_values.append(value) all_values.append(value['asset']) # return json.dumps({"all_values": all_values,"count_values": account_values}) return all_values # def get_pairs(self): def get_historical_trades(self): trades = self.client.get_historical_trades(symbol='CHZBUSD') print(trades) def get_all_orders(self, pair): orders = self.client.get_all_orders(symbol=pair) success_orders = [] for order in orders: if order['status'] != 'CANCELED': success_orders.append(order) return success_orders def get_balance_pair(self): orders = self.get_all_orders('ALL') print(orders) # BUYS=0 # SELLS=0 # for order in orders: # if order['side'] == 'SELL': # SELLS = SELLS + (float(order['executedQty'])*float(order['price'])) # else: # if order['side'] == 'BUY': # BUYS = BUYS + (float(order['executedQty'])*float(order['price'])) # else: # print("hay mas mierda") # balance = SELLS - BUYS # return balance def get_coins(self): details = self.client.get_all_tickers() coins = [] for coin in details: coins.append(coin['symbol']) return coins def total_balance(self): pairs = self.get_coins() # print(pairs) # print(values['all_values']) # pairs = values['all_values'] balances = [] for pair in pairs: print('pair: {}', pair) balances.append(self.get_balance_pair(pair)) return balances def get_asset_dividend_histrory(self): print(self.client.get_asset_dividend_history()) def get_my_trades(self): print(self.client.get_my_trades(symbol='')) def get_historical_trades(self, pair): trades = self.client.get_historical_trades(symbol=pair) return (trades) def get_historical_klines(self, pair, period): klines = self.client.get_historical_klines( pair, Client.KLINE_INTERVAL_1MINUTE, period) clean_klines = [] for kline in klines: clean_klines.append({ 'date': datetime.fromtimestamp(int(kline[0]) / 1000), 'high': kline[2], 'low': kline[3], 'avg': (float(kline[2]) + float(kline[3])) / 2 }) return clean_klines
def spread_order(api_key, api_secret, symbol, amount, side, first_order_percentage, spread, spread_step_size_percentage, dry_mode): list = ['starting run'] client = Client(api_key, api_secret) side_str = 'selling' if side == 'SELL' else 'buying' account = client.get_account() symbol_info = client.get_symbol_ticker(symbol=symbol) market_price = symbol_info['price'] list.append('current market price: ' + market_price) precision = common.get_symbol_precision(client, symbol) quantity_first_order = round( Decimal((first_order_percentage / 100.0) * amount), precision) quantity_spread = amount - quantity_first_order list.append(side_str + ' ' + str(quantity_first_order) + ' ' + symbol + ' at MARKET price') list.append(side_str + ' ' + str(quantity_spread) + ' ' + symbol + ' at SPREAD') if (quantity_first_order > 0): #1. Place Market BUY order if not dry_mode: client.create_order(symbol=symbol, type=Client.ORDER_TYPE_MARKET, side=side, quantity=quantity_first_order) if (quantity_spread > 0): base = quantity_spread / (spread - 1) curr = quantity_spread for i in range(1, spread): quantity_spread_i = base - (i * random.uniform(0.01, 0.03)) quantity_spread_i_rounded = round(Decimal(quantity_spread_i), precision) if i == spread - 1: #last cycle - adjust quantity if curr != quantity_spread_i: quantity_spread_i_rounded = round(Decimal(curr), precision) curr = curr - quantity_spread_i_rounded price_i = float(market_price) if side == Client.SIDE_SELL: price_i += i * (float(market_price) * (spread_step_size_percentage / 100.0) ) #sell above market price else: price_i -= i * (float(market_price) * (spread_step_size_percentage / 100.0) ) #buy below market price price_i_rounded = float("{0:.7f}".format(price_i)) list.append(side_str + ' ' + str(quantity_spread_i_rounded) + ' ' + symbol[0:3] + ' at ' + str(price_i_rounded) + ' price') if not dry_mode: client.create_order(symbol=symbol, quantity=quantity_spread_i_rounded, side=side, type=Client.ORDER_TYPE_LIMIT, price=price_i_rounded, timeInForce=Client.TIME_IN_FORCE_GTC) return list
class BinanceAPIManager: def __init__(self, config: Config, db: Database, logger: Logger): self.binance_client = Client( config.BINANCE_API_KEY, config.BINANCE_API_SECRET_KEY, tld=config.BINANCE_TLD, ) self.db = db self.logger = logger @cached(cache=TTLCache(maxsize=1, ttl=43200)) def get_trade_fees(self) -> Dict[str, float]: return { ticker["symbol"]: ticker["taker"] for ticker in self.binance_client.get_trade_fee()["tradeFee"] } @cached(cache=TTLCache(maxsize=1, ttl=60)) def get_using_bnb_for_fees(self): return self.binance_client.get_bnb_burn_spot_margin()["spotBNBBurn"] def get_fee(self, origin_coin: Coin, target_coin: Coin, selling: bool): base_fee = self.get_trade_fees()[origin_coin + target_coin] if not self.get_using_bnb_for_fees(): return base_fee # The discount is only applied if we have enough BNB to cover the fee amount_trading = (self._sell_quantity(origin_coin.symbol, target_coin.symbol) if selling else self._buy_quantity( origin_coin.symbol, target_coin.symbol)) fee_amount = amount_trading * base_fee * 0.75 if origin_coin.symbol == "BNB": fee_amount_bnb = fee_amount else: origin_price = self.get_market_ticker_price(origin_coin + Coin("BNB")) if origin_price is None: return base_fee fee_amount_bnb = fee_amount * origin_price bnb_balance = self.get_currency_balance("BNB") if bnb_balance >= fee_amount_bnb: return base_fee * 0.75 return base_fee def get_all_market_tickers(self) -> AllTickers: """ Get ticker price of all coins """ return AllTickers(self.binance_client.get_all_tickers()) def get_market_ticker_price(self, ticker_symbol: str): """ Get ticker price of a specific coin """ for ticker in self.binance_client.get_symbol_ticker(): if ticker["symbol"] == ticker_symbol: return float(ticker["price"]) return None def get_currency_balance(self, currency_symbol: str): """ Get balance of a specific coin """ for currency_balance in self.binance_client.get_account()["balances"]: if currency_balance["asset"] == currency_symbol: return float(currency_balance["free"]) return None def retry(self, func, *args, **kwargs): time.sleep(1) attempts = 0 while attempts < 20: try: return func(*args, **kwargs) except Exception as e: # pylint: disable=broad-except self.logger.info("Failed to Buy/Sell. Trying Again.") if attempts == 0: self.logger.info(e) attempts += 1 return None def get_symbol_filter(self, origin_symbol: str, target_symbol: str, filter_type: str): return next(_filter for _filter in self.binance_client.get_symbol_info( origin_symbol + target_symbol)["filters"] if _filter["filterType"] == filter_type) @cached(cache=TTLCache(maxsize=2000, ttl=43200)) def get_alt_tick(self, origin_symbol: str, target_symbol: str): step_size = self.get_symbol_filter(origin_symbol, target_symbol, "LOT_SIZE")["stepSize"] if step_size.find("1") == 0: return 1 - step_size.find(".") return step_size.find("1") - 1 @cached(cache=TTLCache(maxsize=2000, ttl=43200)) def get_min_notional(self, origin_symbol: str, target_symbol: str): return float( self.get_symbol_filter(origin_symbol, target_symbol, "MIN_NOTIONAL")["minNotional"]) def wait_for_order(self, origin_symbol, target_symbol, order_id): while True: try: order_status = self.binance_client.get_order( symbol=origin_symbol + target_symbol, orderId=order_id) break except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.info(f"Unexpected Error: {e}") time.sleep(1) self.logger.info(order_status) while order_status["status"] != "FILLED": try: order_status = self.binance_client.get_order( symbol=origin_symbol + target_symbol, orderId=order_id) except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.info(f"Unexpected Error: {e}") time.sleep(1) return order_status def buy_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers: AllTickers): return self.retry(self._buy_alt, origin_coin, target_coin, all_tickers) def _buy_quantity(self, origin_symbol: str, target_symbol: str, target_balance: float = None, from_coin_price: float = None): target_balance = target_balance or self.get_currency_balance( target_symbol) from_coin_price = from_coin_price or self.get_all_market_tickers( ).get_price(origin_symbol + target_symbol) origin_tick = self.get_alt_tick(origin_symbol, target_symbol) return math.floor(target_balance * 10**origin_tick / from_coin_price) / float(10**origin_tick) def _buy_alt(self, origin_coin: Coin, target_coin: Coin, all_tickers): """ Buy altcoin """ trade_log = self.db.start_trade_log(origin_coin, target_coin, False) origin_symbol = origin_coin.symbol target_symbol = target_coin.symbol origin_balance = self.get_currency_balance(origin_symbol) target_balance = self.get_currency_balance(target_symbol) from_coin_price = all_tickers.get_price(origin_symbol + target_symbol) order_quantity = self._buy_quantity(origin_symbol, target_symbol, target_balance, from_coin_price) self.logger.info(f"BUY QTY {order_quantity}") # Try to buy until successful order = None while order is None: try: order = self.binance_client.order_limit_buy( symbol=origin_symbol + target_symbol, quantity=order_quantity, price=from_coin_price, ) self.logger.info(order) except BinanceAPIException as e: self.logger.info(e) time.sleep(1) except Exception as e: # pylint: disable=broad-except self.logger.info(f"Unexpected Error: {e}") trade_log.set_ordered(origin_balance, target_balance, order_quantity) stat = self.wait_for_order(origin_symbol, target_symbol, order["orderId"]) self.logger.info(f"Bought {origin_symbol}") trade_log.set_complete(stat["cummulativeQuoteQty"]) return order def sell_alt(self, origin_coin: Coin, target_coin: Coin): return self.retry(self._sell_alt, origin_coin, target_coin) def _sell_quantity(self, origin_symbol: str, target_symbol: str, origin_balance: float = None): origin_balance = origin_balance or self.get_currency_balance( origin_symbol) origin_tick = self.get_alt_tick(origin_symbol, target_symbol) return math.floor(origin_balance * 10**origin_tick) / float( 10**origin_tick) def _sell_alt(self, origin_coin: Coin, target_coin: Coin): """ Sell altcoin """ trade_log = self.db.start_trade_log(origin_coin, target_coin, True) origin_symbol = origin_coin.symbol target_symbol = target_coin.symbol origin_balance = self.get_currency_balance(origin_symbol) target_balance = self.get_currency_balance(target_symbol) order_quantity = self._sell_quantity(origin_symbol, target_symbol, origin_balance) self.logger.info(f"Selling {order_quantity} of {origin_symbol}") self.logger.info(f"Balance is {origin_balance}") order = None while order is None: order = self.binance_client.order_market_sell( symbol=origin_symbol + target_symbol, quantity=order_quantity) self.logger.info("order") self.logger.info(order) trade_log.set_ordered(origin_balance, target_balance, order_quantity) # Binance server can take some time to save the order self.logger.info("Waiting for Binance") stat = self.wait_for_order(origin_symbol, target_symbol, order["orderId"]) new_balance = self.get_currency_balance(origin_symbol) while new_balance >= origin_balance: new_balance = self.get_currency_balance(origin_symbol) self.logger.info(f"Sold {origin_symbol}") trade_log.set_complete(stat["cummulativeQuoteQty"]) return order
class BinanceExchange(Exchange): exchange_name = "Binance" isMargin = False def __init__(self, apiKey, apiSecret, pairs, name): super().__init__(apiKey, apiSecret, pairs, name) self.connection = Client(self.api['key'], self.api['secret']) symbol_info_arr = self.connection.get_exchange_info() dict_symbols_info = { item['symbol']: item for item in symbol_info_arr["symbols"] } actual_symbols_info = { symbol: dict_symbols_info[symbol] for symbol in self.pairs } self.symbols_info = actual_symbols_info self.update_balance() self.socket = BinanceSocketManager(self.connection) self.socket.start_user_socket(self.on_balance_update) self.socket.start() self.is_last_order_event_completed = True self.step_sizes = {} self.balance_updated = True for symbol_info in symbol_info_arr['symbols']: if symbol_info['symbol'] in self.pairs: self.step_sizes[symbol_info['symbol']] = \ [f['stepSize'] for f in symbol_info['filters'] if f['filterType'] == 'LOT_SIZE'][0] def start(self, caller_callback): self.socket.start_user_socket(caller_callback) def update_balance(self): account_information = self.connection.get_account() self.set_balance(account_information['balances']) def get_trading_symbols(self): symbols = set() if not self.symbols_info: raise RuntimeError("Cant get exchange info") for key, value in self.symbols_info.items(): symbols.add(value["quoteAsset"]) symbols.add(value["baseAsset"]) return symbols def set_balance(self, balances): symbols = self.get_trading_symbols() dict_balances = {item['asset']: item for item in balances} actual_balance = {symbol: dict_balances[symbol] for symbol in symbols} self.balance = actual_balance def on_balance_update(self, upd_balance_ev): if upd_balance_ev['e'] == 'outboundAccountInfo': balance = [] for ev in upd_balance_ev['B']: balance.append({ 'asset': ev['a'], 'free': ev['f'], 'locked': ev['l'] }) self.balance.update({item['asset']: item for item in balance}) def get_open_orders(self): orders = self.connection.get_open_orders() general_orders = [] for o in orders: quantityPart = self.get_part(o['symbol'], o["origQty"], o['price'], o['side']) general_orders.append( Order(o['price'], o["origQty"], quantityPart, o['orderId'], o['symbol'], o['side'], o['type'], self.exchange_name)) return general_orders def _cancel_order(self, order_id, symbol): self.connection.cancel_order(symbol=symbol, orderId=order_id) self.logger.info(f'{self.name}: Order canceled') async def on_cancel_handler(self, event: Actions.ActionCancel): try: slave_order_id = self._cancel_order_detector(event.price) self._cancel_order(slave_order_id, event.symbol) except BinanceAPIException as error: self.logger.error(f'{self.name}: error {error.message}') except: self.logger.error( f"{self.name}: error in action: {event.name} in slave {self.name}" ) def stop(self): self.socket.close() def _cancel_order_detector(self, price): # detect order id which need to be canceled slave_open_orders = self.connection.get_open_orders() for ordr_open in slave_open_orders: if float(ordr_open['price']) == float(price): return ordr_open['orderId'] def process_event(self, event): # return event in generic type from websocket # if this event in general type it was send from start function and need call firs_copy if 'exchange' in event: return event if event['e'] == 'outboundAccountPosition': self.is_last_order_event_completed = True if event['e'] == 'executionReport': if event['X'] == 'FILLED': return elif event['x'] == 'CANCELED': return Actions.ActionCancel(event['s'], event['p'], event['i'], self.exchange_name, event) self.last_order_event = event # store event order_event coz we need in outboundAccountInfo event # sometimes can came event executionReport x == filled and x == new together so we need flag self.is_last_order_event_completed = False return elif event['e'] == 'outboundAccountInfo': if self.is_last_order_event_completed: return order_event = self.last_order_event if order_event['s'] not in self.pairs: return if order_event[ 'o'] == 'MARKET': # if market order, we haven't price and cant calculate quantity order_event['p'] = self.connection.get_ticker( symbol=order_event['s'])['lastPrice'] # part = self.get_part(order_event['s'], order_event['q'], order_event['p'], order_event['S']) self.on_balance_update(event) # shortcut mean https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#order-update order = Order( order_event['p'], order_event['q'], self.get_part(order_event['s'], order_event['q'], order_event['p'], order_event['S']), order_event['i'], order_event['s'], order_event['S'], order_event['o'], self.exchange_name, order_event['P']) return Actions.ActionNewOrder(order, self.exchange_name, event) async def on_order_handler(self, event: Actions.ActionNewOrder): self.create_order(event.order) def create_order(self, order): """ :param order: """ quantity = self.calc_quantity_from_part(order.symbol, order.quantityPart, order.price, order.side) self.logger.info('Slave ' + self.name + ' ' + str(self._get_quote_balance(order.symbol)) + ' ' + str(self._get_base_balance(order.symbol)) + ', Create Order:' + ' amount: ' + str(quantity) + ', price: ' + str(order.price)) try: if order.type == 'STOP_LOSS_LIMIT' or order.type == "TAKE_PROFIT_LIMIT": self.connection.create_order(symbol=order.symbol, side=order.side, type=order.type, price=order.price, quantity=quantity, timeInForce='GTC', stopPrice=order.stop) if order.type == 'MARKET': self.connection.create_order(symbol=order.symbol, side=order.side, type=order.type, quantity=quantity) else: self.connection.create_order(symbol=order.symbol, side=order.side, type=order.type, quantity=quantity, price=order.price, timeInForce='GTC') self.logger.info(f"{self.name}: order created") except Exception as e: self.logger.error(str(e)) def _get_quote_balance(self, symbol): return self.balance[self.symbols_info[symbol]['quoteAsset']] def _get_base_balance(self, symbol): return self.balance[self.symbols_info[symbol]['baseAsset']] def get_part(self, symbol: str, quantity: float, price: float, side: str): # get part of the total balance of this coin # if order[side] == sell: need obtain coin balance if side == 'BUY': get_context_balance = self._get_quote_balance market_value = float(quantity) * float(price) else: get_context_balance = self._get_base_balance market_value = float(quantity) balance = float(get_context_balance(symbol)['free']) # if first_copy the balance was update before if self.balance_updated: balance += float(get_context_balance(symbol)['locked']) else: balance += market_value part = market_value / balance part = part * 0.99 # decrease part for 1% for avoid rounding errors in calculation return part def calc_quantity_from_part(self, symbol, quantityPart, price, side): # calculate quantity from quantityPart # if order[side] == sell: need obtain coin balance if side == 'BUY': get_context_balance = self._get_quote_balance buy_koef = float(price) else: get_context_balance = self._get_base_balance buy_koef = 1 cur_bal = float(get_context_balance(symbol)['free']) if self.balance_updated: cur_bal += float(get_context_balance(symbol)['locked']) quantity = quantityPart * cur_bal / buy_koef stepSize = float(self.step_sizes[symbol]) precision = int(round(-math.log(stepSize, 10), 0)) quantity = round(quantity, precision) return quantity
class TradingClient: def __init__(self, base_currency, trade_currency, key, secret): self.base_currency = base_currency self.trade_currency = trade_currency self.client = Client(key, secret) self.symbol = trade_currency + base_currency def get_historical_klines(self, interval, start_ts): return self.client.get_historical_klines(symbol=self.symbol, interval=interval, start_str=start_ts) def get_recent_trades(self): return self.client.get_recent_trades(symbol=self.symbol, limit=500) def get_order_book(self): return self.client.get_order_book(symbol=self.symbol, limit=1000) def get_order_book_ticker(self): return self.client.get_orderbook_ticker(symbol=self.symbol) def get_base_balance(self): return self.client.get_asset_balance(asset=self.base_currency) def get_trading_balance(self): return self.client.get_asset_balance(asset=self.trade_currency) @staticmethod def asset_balance_to_float(balance): return float(balance['free']) + float(balance['locked']) def get_all_trading_balance(self): trading_balance = self.get_trading_balance() return self.asset_balance_to_float(trading_balance) @staticmethod def trading_balance_available(trading_balance): return trading_balance > 1 def get_balances(self): return self.client.get_account()['balances'] def get_open_orders(self): return self.client.get_open_orders(symbol=self.symbol) def cancel_all_orders(self): open_orders = self.get_open_orders() for order in open_orders: self.client.cancel_order(symbol=self.symbol, orderId=order['orderId']) # GTC(Good-Til-Canceled) orders are effective until they are executed or canceled. # IOC(Immediate or Cancel) orders fills all or part of an order immediately and cancels the remaining part of the # order. def buy(self, quantity, price): price = '.8f' % price logging.info('Buying %d for %s', quantity, price) self.client.order_limit_buy(symbol=self.symbol, timeInForce=Client.TIME_IN_FORCE_GTC, quantity=quantity, price=price) def sell(self, quantity, price): price = '.8f' % price logging.info('Selling %d for %s', quantity, price) self.client.order_limit_sell(symbol=self.symbol, timeInForce=Client.TIME_IN_FORCE_GTC, quantity=quantity, price=price) def sell_market(self, quantity): if quantity > 0: logging.info('Selling to MARKET with quantity ' + str(quantity)) self.client.order_market_sell(symbol=self.symbol, quantity=quantity) else: logging.info('Not executing - 0 quantity sell') def get_order(self, order_id): return self.client.get_order(symbol=self.symbol, orderId=order_id) def last_price(self): return float( self.client.get_symbol_ticker(symbol=self.symbol)['price']) def cancel_order(self, order_id): logging.info('Cancelling order ' + order_id) self.client.cancel_order(symbol=self.symbol, orderId=order_id) def panic_sell(self, last_known_amount, last_known_price): logging.error('!!! PANIC SELL !!!') logging.warn('Probably selling %.8f for %.8f', last_known_amount, last_known_price) self.cancel_all_orders() self.sell_market(float(self.get_trading_balance()['free']))
class Account(StoppableThread): def __init__(self, api_key, api_secret, master): StoppableThread.__init__(self) self.symbol_counter = 0 try: self.rest_client = Client(api_key, api_secret) self.ws_client = BinanceSocketManager(self.rest_client) self.keep_running = True self.api_key = api_key self.api_secret = api_secret self.name = f"Account {self.api_key}" self.master = master except ConnectionError as e: logger.error(e) def get_account_trade_history(self, symbol, start_time=None, from_id=None): try: if start_time: trades = self.rest_client.get_my_trades(symbol=symbol, startTime=start_time) elif from_id: trades = self.rest_client.get_my_trades(symbol=symbol, fromId=from_id) else: trades = self.rest_client.get_my_trades(symbol=symbol) return trades except Exception as e: raise e def get_asset_balances(self): try: account_info = self.rest_client.get_account() portfolio = account_info['balances'] balances = [ bal for bal in portfolio if float(bal['free']) or float(bal['locked']) ] return balances except Exception as e: raise e def post_assets(self, balances): with create_session() as session: account = session.query(AccountModel).filter_by( api_key=self.api_key).first() account_assets = account.my_assets account_assets_names = [asset.name for asset in account_assets] for asset_params in balances: if not asset_params[ 'asset'] in account_assets_names: # asset is not yet created asset = Asset(name=asset_params['asset'], free=asset_params['free'], fixed=asset_params['locked'], account=account) session.add(asset) continue for account_asset in account_assets: if asset_params['asset'] == account_asset.name: account_asset.free = asset_params['free'] account_asset.fixed = asset_params['locked'] session.add(account_asset) #do the reverse, check if there is an asset whose balance is zero balances_assets_names = [asset['asset'] for asset in balances] for asset_name in account_assets_names: if asset_name not in balances_assets_names: asset = [ asset for asset in account_assets if asset.name == asset_name ] if asset: asset = asset[0] session.delete(asset) logger.info(f"asset {asset_name} has been deleted") session.commit() def post_error(self, e): error_params = { 'exception_name': e.__class__.__name__, 'message': e.message if hasattr(e, 'message') else f"{e}" } logger.error(f"{self.name} {e}") with create_session() as session: account = session.query(AccountModel).filter_by( api_key=self.api_key).first() error = ErrorModel(message=error_params['message'], exception_name=error_params['exception_name'], account=account) session.add(error) session.commit() def post_trades(self, trades, raw_json=None): with create_session() as session: account = session.query(AccountModel).filter_by( api_key=self.api_key).first() for trade_params in trades: trade_id = trade_params['id'] trade_in_db = session.query(Trade).filter_by( tradeId=trade_id).first() if not trade_in_db: trade = Trade( tradeId=trade_params['id'], orderId=trade_params['orderId'], symbol=trade_params['symbol'], price=trade_params['price'], qty=trade_params['qty'], commission=trade_params['commission'], commissionAsset=trade_params['commissionAsset'], time=datetime.fromtimestamp( float(trade_params['time']) / 1000), isBuyer=trade_params['isBuyer'], isMaker=trade_params['isMaker'], isBestMatch=trade_params['isBestMatch'], account=account, raw_json=trade_params if not raw_json else raw_json) session.add(trade) session.commit() return True def process_user_socket_message(self, msg): # throw it in the database try: print(msg) payload = msg if payload['e'] == "outboundAccountInfo": balances_all = payload['B'] balances = [{ 'asset': bal['a'], 'free': bal['f'], 'locked': bal['l'] } for bal in balances_all if float(bal['f']) or float(bal['l'])] self.post_assets(balances) elif payload['e'] == "executionReport": if payload['x'] == "TRADE": logger.info(f"{self.name} received a trading event") trade_params = { 'id': payload['t'], 'orderId': payload['i'], 'symbol': payload['s'], 'price': payload['L'], 'qty': payload['l'], 'commission': payload['n'], 'commissionAsset': payload['N'], 'time': payload['T'], 'isBuyer': not bool(payload['m']), 'isMaker': payload['m'], 'isBestMatch': None } self.post_trades([trade_params], raw_json=payload) elif payload['e'] == 'error': error = payload['m'] logger.error(f"A network connection error occured, {error}") self.ws_client.stop_socket(self.user_socket_conn_key) #time.sleep(30) #self.start_account_socket() elif payload['e'] == 'connection_lost': #update last_update_time message = payload['m'] logger.error(f"{message}") elif payload['e'] == 'connection_started': #update connection established message = payload['m'] logger.info(f"{message}") self.master.restart_scrapper( ) #the only way to cover up for lost time self.ws_client._keepalive_user_socket() else: logger.error(f"unknown event, {msg}") except json.JSONDecodeError as e: logger.error(f"error occured, {e}") self.post_error(e) except Exception as e: logger.error(f"unknown error occurred, {e}") self.post_error(e) def start_account_socket(self): logger.info(f"{self.name} starting account socket") self.user_socket_conn_key = self.ws_client.start_user_socket( self.process_user_socket_message) self.ws_client.start() def update_account_portfolio(self): with create_session() as session: timestamp = datetime.utcnow() account = session.query(AccountModel).filter_by( api_key=self.api_key).first() if not account: raise ValueError("account not found") assets = account.my_assets portfolio_in_eth = get_portfolio_in_eth(assets) default_quotes = portfolio_in_eth['default_quotes'] quote_dict = {} for default_quote in default_quotes: quote_dict[default_quote['symbol']] = default_quote['price'] for asset_portfolio in portfolio_in_eth['portfolio']: asset = [ asset for asset in assets if asset.name == asset_portfolio['symbol'] ] if asset: asset = asset[0] asset.eth_value = asset_portfolio['eth_cost'] asset.quote_price = asset_portfolio['price'] asset.pair_name = asset_portfolio['pair_name'] asset.timestamp = timestamp #last time the asset eth value was update session.add(asset) port = Portfolio(account=account, asset=asset, asset_name=asset.name, total_value=asset.free + asset.fixed, eth_value=asset_portfolio['eth_cost'], quote_price=asset_portfolio['price'], pair_name=asset_portfolio['pair_name'], timestamp=timestamp, eth_btc_quote=quote_dict['ETHBTC'], bnb_eth_quote=quote_dict['BNBETH'], eth_usdt_quote=quote_dict['ETHUSDT']) session.add(port) session.commit() def update_account_trade_history(self, resume_update=False): ''' brute force, there being no other way. :return: ''' # 1. get all symbols. info = self.rest_client.get_exchange_info() symbols = [sym['symbol'] for sym in info['symbols']] if not resume_update: #a neat way to stop update from restart when it hits max requests per second self.symbol_counter = 0 logger.info("updating all history") else: logger.info(f"starting the update at {self.symbol_counter}") while self.keep_running: try: symbol = symbols[self.symbol_counter] print(f"[+] Fetching the trades for {symbol}") start = time.time() trade_history = self.get_account_trade_history(symbol) if trade_history: print(f"I got new trades, {trade_history}") self.post_trades(trade_history) left = time.time() - start sleep_time = 0.2 - left if sleep_time > 0: print(f"sleeping for {sleep_time}") time.sleep(sleep_time) self.symbol_counter += 1 except IndexError: self.symbol_counter = 0 break except Exception as e: raise e def run(self): ''' 1.get account balances - check keys are okay and inform on bad keys - get balances 2. start trade socket. :NOTE: updating history is done by scrapper class. :return: ''' while self.keep_running: try: balances = self.get_asset_balances() self.post_assets(balances) self.start_account_socket() # break except BinanceAPIException as e: self.post_error(e) if int(e.code) in [-2010, -1010, -2011]: logger.info(f"{self.name}, {e.message}") elif int(e.code) == -1013: # balance below minimum allowable, should get here if we checking balances, end trade logger.info(f"{self.name} cannot , {e.message}") elif int(e.code) == -2015: # api key error. logger.error(f"{self.name} {e.message}, exiting") break elif int(e.status_code) == 429: logger.warning( f"{self.name} hit a rate limit, backing dowm for 1 minute" ) time.sleep(60) elif int(e.status_code) == 418: logger.error(f"{self.name} Ooops, IP has been auto banned") time.sleep(300) else: logger.error( f"{self.name} uncaught API exception, {e.message}, {e.code}, {e.status_code}" ) except Exception as e: # default, for all uncaught exceptions. logger.error(f"{self.name} Exceptiion, {e}") self.post_error(e) raise e