class order_api(object): def __init__(self): self.client = Client("KCl8UvtnFcrlEgpqiwJlA0dr2SM3DuhKHHxiRtHA8oe7yl0JZYqas8oN2XNK0Lfz", "2wj46v8IAKpXoeigAmzfPg1VEjYi3oItNTeeNcEkDrK1HIev4nZHJr1Cd8tiJ5L0", {"verify": True, "timeout": 20}) def get_all_orders(self,symbol='VENETH',limit=10): return self.get_all_orders(symbol,limit) # def create_order(self, symbol='VENETH', side='BUY', type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=100, price='0.00001'): # self.client.create_order(symbol,side,type,timeInForce,quantity,price) # def create_sell_limit_order(self): # self.order = client.order_limit_buy(symbol,quantity,price) # def create_market_order(self): # pass def create_test_order(self,symbol,quantity,price): return self.client.create_test_order(symbol=symbol,quantity=quantity,price=price,side='BUY',type='LIMIT',timeInForce='GTC') def get_all_orders(self,symbol='VENETH'): return self.client.get_all_orders(symbol=symbol) def get_open_orders(self,symbol='VENETH'): return self.client.get_open_orders(symbol=symbol) def cancel_order(self,symbol='VENETH',orderId=0): return self.client.cancel_order(symbol=symbol,orderId=orderId) def order_status(self,symbol='VENETH',orderId=0): return self.client.get_order(symbol=symbol,orderId=orderId)
def CANCEL_OPEN_ORDERS(LOG,univ,out_file): LOG.write(TimeStamp() + "CANCEL_OPEN_ORDERS\n") try: client = Client(api_key, api_secret) except BinanceAPIException as e: print(e.status_code) print(e.message) F = open(out_file,'w') F.write(TimeStamp() + "\n") for u in univ: if (u == "BTC"): continue try: sym = u + "BTC" #print(sym) data = client.get_open_orders(symbol=sym) if data is None: continue for T in data: TS = TimeStampUnixDateTime(T['time']) S = 'Order:\n' F.write(S) S = '\tSymbol: \t%s\t%s\n' % (u,sym) F.write(S) S = '\tQty: \t%s\n' % (T['origQty']) F.write(S) S = '\tPrice: \t%s\n' % (T['price']) F.write(S) S = '\tOrder Id: \t%s\n\n' % (T['orderId']) F.write(S) try: RV = client.cancel_order(symbol=sym,orderId=T['orderId']) if (RV): S = 'Return Value:\n' F.write(S) S = '\tSymbol: \t%s\n' % (RV['symbol']) F.write(S) S = '\torigClientOrderId: \t%s\n' % (RV['origClientOrderId']) F.write(S) S = '\torderId: \t%s\n' % (RV['orderId']) F.write(S) S = '\tclientOrderId: \t%s\n\n\n' % (RV['clientOrderId']) F.write(S) except BinanceAPIException as e: print(e.status_code) print(e.message) LOG.write(e.message + "\n") except BinanceAPIException as e: print(e.status_code) print(e.message) LOG.write(e.message + "\n") F.close()
class _Binance(Exchange): def __init__(self, key="", secret=""): super(_Binance, self).__init__("binance", key, secret, "") self.client = Client(key, secret) def __transfer_symbol(self, s): return s.replace("_", "").upper() def fetch_depth(self, symbol): symbol = self.__transfer_symbol(symbol) return self.client.get_order_book(symbol=symbol) def account(self): data = self.client.get_account() return self._parse_account(data) def _parse_account(self, data): acc = Account(self) for x in data['balances']: coin = x['asset'].lower() freeze = float(x['locked']) avail = float(x['free']) if freeze > 0 or avail > 0: acc.set_avail(coin, avail).set_freeze(coin, freeze) return acc def order(self, symbol, side, type="limit", amount=None, price=None): assert type == 'limit' # 暂时支持limit assert side in self.TRADE_SIDE symbol = self.__transfer_symbol(symbol) if side == 'buy': return self.client.order_limit_buy(symbol=symbol, quantity=amount, price=price)['orderId'] else: return self.client.order_limit_sell(symbol=symbol, quantity=amount, price=price)['orderId'] def order_info(self, symbol, order_id): data = self.client.get_order(symbol=self.__transfer_symbol(symbol), orderId=order_id) return self._parse_order(data) def _parse_order(self, data): return data def cancel_order(self, symbol, order_id): self.client.cancel_order(symbol=self.__transfer_symbol(symbol), orderId=order_id) return True
def execute_trades(api_object, trade_df): ''' :param api_object: binance api object param trade_df: pandas DataFrame with index ticker, columns Market (string), price(float, in BTC), Curr_Dist(float, [0,1]), Target_Dist(float,[0,1]),Trade_Perc(float,[-1,1]), Trade_Amt (in BTC,float), Trade_Amt_Coin (float, in the target currency) ''' for coin, row in trade_df.iterrows(): try: #close any open orders open_orders = api_object.get_open_orders(symbol=row.market) market = row.market #Binance only allows certain order sizes by coin trade_amt = round(row.Trade_Amt_Coin, 6) buy = bool(trade_amt > 0) sell = bool(trade_amt < 0) trade_amt = abs(trade_amt) #filter to make sure in tick size increments above min trade size market_lot_size_info = api_object.get_symbol_info( market)['filters'][1] trade_amt -= (trade_amt - float(market_lot_size_info['minQty']) ) % float(market_lot_size_info['stepSize']) if trade_amt <= 0: continue if open_orders: print('Cancelling open orders for {}.'.format(coin)) for order in open_orders: api_object.cancel_order(symbol=market, orderId=order['orderId']) if buy: print('Buying {} of {}'.format(trade_amt, coin)) print( api_object.order_market_buy(symbol=market, quantity=trade_amt)) elif sell: print('Selling {} of {}'.format(trade_amt, coin)) print( api_object.order_market_sell(symbol=market, quantity=trade_amt)) except Exception as e: print(e)
def binance_cancel_order(chat_id, **params): full_api = getbinanceapi(chat_id)['binance_api'] api_key = full_api.split(':')[0].strip() api_secret = full_api.split(':')[1].strip() client = Client(api_key, api_secret) binance_timesync(client) try: js_info = client.cancel_order(**params) print(js_info) return js_info except Exception as e: print(e)
def cancel_order(request): if request.method == 'POST': symbol = request.POST.get("symbol") order_id = request.POST.get("order_id") api_key = request.user.apikey api_secret = request.user.Secret client = Client(api_key, api_secret) cancel_order = Order.objects.get(orderId=order_id) try: ifo = client.cancel_order(symbol=symbol, orderId=order_id) if ifo["orderId"]: cancel_order.status = 'CANCELED' response = {'orderId': ifo["orderId"]} except BinanceAPIException: ifo = client.get_order(symbol=symbol, orderId=order_id) cancel_order.status = ifo['status'] response = {'aa': "失败!", "status": ifo['status']} cancel_order.save() return JsonResponse(response)
class TradingPortfolio(): """Connect Binance account. The core class to connect Binance with API, in order to connect account info, register strategt. Attributes: binance_key: A str of is binance authorization key. binance_secret: A str of is binance authorization secret. """ def __init__(self, binance_key, binance_secret, execute_before_candle_complete=False): self._client = Client(api_key=binance_key, api_secret=binance_secret) self._trading_methods = [] self._margins = {} self.ticker_info = TickerInfo(self._client) self.default_stable_coin = 'USDT' self.execute_before_candle_complete = execute_before_candle_complete def set_default_stable_coin(self, token): self.default_stable_coin = token def register(self, trading_method): """Rigister TradingMethod object. Args: trading_method: A object of TradingMethod(). """ if trading_method.execution_price == 'open' and self.execute_before_candle_complete: raise Exception("Detect execute_before_candle_complete=True and trading_method.execution_price is open" + "Please set trading_method.execute_before_candle_complete to False" + " and execute live trading right after candles are complete.") self._trading_methods.append(trading_method) def register_margin(self, asset, weight_btc): """Rigister weight_btc as operation amount. Args: asset: A str of asset name (ex: 'USDT') weight_btc: A float of btc for each commodity operation (ex: 0.2) """ self._margins[asset] = weight_btc def get_all_symbol_lookback(self): """Get all symbol lookback. Use in get_ohlcvs(self) function. Returns: A dict of OHLCV lookback. """ symbol_lookbacks = {} addition = {} weight_units = set() max_lookback = 0 for method in self._trading_methods: weight_units.add((method.weight_unit, method.freq)) max_lookback = max(max_lookback, method.lookback) for a in method.symbols: quote_asset = self.ticker_info.get_quote_asset(a) base_asset = self.ticker_info.get_base_asset(a) if (a, method.freq) not in symbol_lookbacks or method.lookback > symbol_lookbacks[(a, method.freq)]: symbol_lookbacks[(a, method.freq)] = method.lookback if base_asset != method.weight_unit: new_symbol = base_asset + method.weight_unit if (new_symbol, method.freq) not in addition or method.lookback > addition[(new_symbol, method.freq)]: addition[(new_symbol, method.freq)] = method.lookback for w, f in weight_units: if w != 'BTC': addition[('BTC' + w, f)] = max_lookback # add quote asset historical data # for (symbol, freq), lookback in symbol_lookbacks.items(): # base_asset = self.ticker_info.get_base_asset(symbol) # if base_asset != self.quote_asset: # new_symbol = base_asset + self.quote_asset # addition[(new_symbol, freq)] = lookback return {**symbol_lookbacks, **addition} def get_ohlcvs(self): """Getting histrical price data through binance api. Returns: A DataFrame of OHLCV data , the number of data length is lookback. """ symbol_lookbacks = self.get_all_symbol_lookback() ohlcvs = {} for (symbol, freq), lookback in symbol_lookbacks.items(): ohlcvs[(symbol, freq)] = get_nbars_binance(symbol, freq, lookback, self._client) return ohlcvs def get_full_ohlcvs(self): """Getting all histrical price data through binance api. Returns: A DataFrame of OHLCV data for all. """ symbol_lookbacks = self.get_all_symbol_lookback() ohlcvs = {} for (symbol, freq), lookback in symbol_lookbacks.items(): ohlcvs[(symbol, freq)] = get_all_binance(symbol, freq) time.sleep(3) return ohlcvs def get_latest_signals(self, ohlcvs, html=False): """Get latest signals dataframe. Choose which strategy to implement on widgets GUI. Args: ohlcvs: A dataframe of symbel. html: A bool of controlling html generation. Returns: A dataframe of latest_signals data, The last_signals column is bool value of whether to execute the transaction. The value_in_btc column is present value of assets. """ ret = [] for method in self._trading_methods: for symbol in method.symbols: ohlcv = ohlcvs[(symbol, method.freq)].copy() # remove incomplete candle if self.execute_before_candle_complete == False and method.execution_price == 'close': t = datetime.datetime.utcnow().replace(tzinfo=timezone.utc) delta_t = ohlcv.index[-1] - ohlcv.index[-2] ohlcv = ohlcv.loc[:t-delta_t] htmlname = f'{symbol}-{method.freq}-{method.name}.html' if html else None result = method.strategy.backtest(ohlcv, method.variables, filters=method.filters, plot=html, html=htmlname, freq=method.freq, fees=0., slippage=0., execution_price=method.execution_price) signal = result.cash().iloc[-1] == 0 return_ = 0 # find weight if it is in the nested dictionary weight = method.weight if isinstance(weight, dict): weight = (weight[symbol] if symbol in weight else weight['default']) entry_price = 0 entry_time = 0 value_in_btc = 0 trade_price_type = method.execution_price if signal: txn = result.positions().records rds = result.orders().records return_ = ohlcv[trade_price_type].iloc[-1] / rds['price'].iloc[-1] - 1 entry_price = rds['price'].iloc[-1] entry_time = ohlcv.index[int(rds.iloc[-1]['idx'])] base_asset = self.ticker_info.get_base_asset(symbol) quote_asset = self.ticker_info.get_quote_asset(symbol) if base_asset != method.weight_unit: quote_symbol = base_asset + method.weight_unit quote_history = ohlcvs[(quote_symbol, method.freq)] quote_asset_price_previous = quote_history[trade_price_type].loc[entry_time] quote_asset_price_now = quote_history[trade_price_type].iloc[-1] else: quote_asset_price_previous = 1 quote_asset_price_now = 1 if method.weight_unit != 'BTC': btc_quote_price_previous = ohlcvs[('BTC' + method.weight_unit, method.freq)][trade_price_type].loc[entry_time] btc_quote_price_now = ohlcvs[('BTC' + method.weight_unit, method.freq)][trade_price_type].iloc[-1] else: btc_quote_price_previous = 1 btc_quote_price_now = 1 previous_weight_btc = (weight / btc_quote_price_previous) value_in_btc = previous_weight_btc / quote_asset_price_previous * quote_asset_price_now / (btc_quote_price_now / btc_quote_price_previous) weight_btc = previous_weight_btc previous_price_btc = quote_asset_price_previous / btc_quote_price_previous present_price_btc = quote_asset_price_now / btc_quote_price_now amount = previous_weight_btc / previous_price_btc present_amount = value_in_btc / present_price_btc assert 0.9999 < amount / present_amount < 1.0001 else: if method.weight_unit != 'BTC': btc_quote_price_now = ohlcvs[('BTC' + method.weight_unit, method.freq)][trade_price_type].iloc[-1] else: btc_quote_price_now = 1 weight_btc = weight / btc_quote_price_now amount = 0 ret.append({ 'symbol': symbol, 'method name': method.name, 'latest_signal': signal, 'weight_btc': weight_btc, 'freq': method.freq, 'return': return_, 'amount': amount, 'value_in_btc': value_in_btc * signal, 'latest_price': ohlcv[trade_price_type].iloc[-1], 'entry_price': entry_price, 'entry_time': entry_time, 'html': htmlname, }) ret = pd.DataFrame(ret) return ret def calculate_position_size(self, signals, rebalance_threshold=0.03, excluded_assets=list()): """Calculate the proportion of asset orders. Calculate data is based on latest signals dataframe. Args: signals: A dataframe of signals. rebalance_threshold: A float of rebalance_threshold. excluded_assets: A list of asset name which are excluded calculation. Returns: diff_value: A dataframe of how many assets to deposit for each cryptocurrency. diff_value_btc: A dataframe of converting cryptocurrency to BTC. transaction: A dataframe of transaction(new order) data. """ if self.default_stable_coin not in excluded_assets: excluded_assets.append(self.default_stable_coin) signals['base_asset'] = signals.symbol.map(self.ticker_info.get_base_asset) signals['quote_asset'] = signals.symbol.map(self.ticker_info.get_quote_asset) signals['base_value_btc'] = signals.latest_signal * signals.value_in_btc signals['quote_value_btc'] = -(signals.latest_signal.astype(int) * signals.weight_btc) quote_asset_list = list(set(signals.quote_asset)) # calculate base and quote assets (in btc term) base_asset_value = pd.Series(signals.base_value_btc.values, index=signals.base_asset) quote_asset_value = pd.Series(signals.quote_value_btc.values, index=signals.quote_asset) base_asset_value = base_asset_value.groupby(level=0).sum() quote_asset_value = quote_asset_value.groupby(level=0).sum() # get position position = pd.Series({i['asset']: i['free'] for i in self.ticker_info.info['balances'] if float(i['free']) != 0}).astype(float) position = position[position.index.str[:2] != 'LD'] # refine asset index all_assets = base_asset_value.index | quote_asset_value.index | position.index base_asset_value = base_asset_value.reindex(all_assets).fillna(0) quote_asset_value = quote_asset_value.reindex(all_assets).fillna(0) position = position.reindex(all_assets).fillna(0) # calculate algo value algo_value_in_btc = base_asset_value + quote_asset_value asset_price_in_btc = position.index.map(self.ticker_info.get_asset_price_in_btc) algo_value = algo_value_in_btc / asset_price_in_btc # calculate diffierence margin_position = pd.Series(self._margins).reindex(all_assets).fillna(0) diff_value_btc = pd.DataFrame({ 'algo_p': algo_value_in_btc, 'margin_p': margin_position * asset_price_in_btc, 'estimate_p': algo_value_in_btc + margin_position * asset_price_in_btc, 'present_p': position * asset_price_in_btc, 'difference': (algo_value_in_btc + margin_position * asset_price_in_btc).clip(0,None) - position * asset_price_in_btc, 'rebalance_threshold': (algo_value_in_btc + margin_position * asset_price_in_btc).abs() * rebalance_threshold, }) diff_value_btc['rebalance'] = diff_value_btc['difference'].abs() > diff_value_btc['rebalance_threshold'] diff_value_btc.loc[quote_asset_list, 'rebalance'] = True # excluding checking of asset positions excluded = pd.Series(True, diff_value_btc.index) excluded[diff_value_btc.index.isin(signals.quote_asset) | diff_value_btc.index.isin(signals.base_asset)] = False excluded[diff_value_btc.index.isin(excluded_assets)] = True diff_value_btc['excluded'] = excluded diff_value = diff_value_btc.copy() diff_value = diff_value.div(asset_price_in_btc, axis=0) diff_value.rebalance = diff_value.rebalance != 0 diff_value.excluded = diff_value.excluded != 0 # calculate transactions rebalance_value_btc = diff_value_btc.rebalance * diff_value_btc.difference * (~diff_value_btc.excluded) increase_asset_amount = rebalance_value_btc[rebalance_value_btc > 0] decrease_asset_amount = rebalance_value_btc[rebalance_value_btc < 0] diff_value_btc['rebalance'] = diff_value_btc['difference'].abs() > diff_value_btc['rebalance_threshold'] diff_value['rebalance'] = diff_value_btc.rebalance txn_btc = {} for nai, ai in increase_asset_amount.items(): for nad, ad in decrease_asset_amount.items(): symbol = nad + nai amount = min(-ad, ai) is_valid = self.ticker_info._list_select(self.ticker_info.tickers, 'symbol', symbol) is not None and nai in quote_asset_list if is_valid: increase_asset_amount.loc[nai] -= amount decrease_asset_amount.loc[nad] += amount txn_btc[symbol] = -amount continue symbol = nai + nad is_valid = self.ticker_info._list_select(self.ticker_info.tickers, 'symbol', symbol) is not None and nad in quote_asset_list if is_valid: increase_asset_amount.loc[nai] -= amount decrease_asset_amount.loc[nad] += amount txn_btc[symbol] = amount continue # assumption: self.default_stable_coin can be the quote asset for all alt-coins transaction_btc = increase_asset_amount.append(decrease_asset_amount) transaction_btc.index = transaction_btc.index + self.default_stable_coin if self.default_stable_coin in transaction_btc.index: transaction_btc.pop(self.default_stable_coin+self.default_stable_coin) transaction_btc = transaction_btc.append(pd.Series(txn_btc)) transaction = transaction_btc.to_frame(name='value_in_btc') transaction['base_asset'] = transaction.index.map(self.ticker_info.get_base_asset) transaction['quote_asset'] = transaction.index.map(self.ticker_info.get_quote_asset) transaction['value'] = transaction['value_in_btc'] / transaction.base_asset.map( self.ticker_info.get_asset_price_in_btc) transaction['price'] = transaction.index.map( lambda s: self.ticker_info._list_select(self.ticker_info.tickers, 'symbol', s)['price']) transaction = transaction.groupby(level=0).agg( dict(value_in_btc='sum', value='sum', base_asset='first', quote_asset='first', price='first')) transaction = transaction[transaction.value != 0] # check difference after transaction def asset_distributed(v): asset_increase = v.value_in_btc.groupby(v.base_asset).sum() asset_decrease = v.value_in_btc.groupby(v.quote_asset).sum() return asset_increase.reindex(all_assets).fillna(0) - asset_decrease.reindex(all_assets).fillna(0) verify_assets = asset_distributed(transaction) verify_assets = verify_assets[verify_assets != 0] verify = (verify_assets / diff_value_btc.difference.reindex(verify_assets.index) - 1).abs() < 0.001 try: assert verify[verify.index != self.default_stable_coin].all() except: print(diff_value_btc) print(transaction) print(verify_assets) print(verify) raise Exception("validation fail") # filter out orders where base asset is in quote asset list (ex: btcusdt) # assumption: base asset should only be paired by one quote asset transaction = transaction[~(transaction.base_asset.isin(quote_asset_list) & ( transaction.value_in_btc.abs() < diff_value_btc.loc[ transaction['base_asset']].rebalance_threshold.values))] # verify diff_value def get_filters(exinfo, symbol): filters = self.ticker_info._list_select(self.ticker_info.exinfo['symbols'], 'symbol', symbol)['filters'] min_lot_size = self.ticker_info._list_select(filters, 'filterType', 'LOT_SIZE')['minQty'] step_size = self.ticker_info._list_select(filters, 'filterType', 'LOT_SIZE')['stepSize'] min_notional = self.ticker_info._list_select(filters, 'filterType', 'MIN_NOTIONAL')['minNotional'] return { 'min_lot_size': min_lot_size, 'step_size': step_size, 'min_notional': min_notional, } filters = pd.DataFrame( {s: get_filters(self.ticker_info.exinfo, s) for s in transaction.index}).transpose().astype(float) if len(transaction) != 0: min_notional = filters.min_notional minimum_lot_size = filters.min_lot_size step_size = filters.step_size # rebalance filter: diff = transaction['value'] # step size filter diff = round((diff / step_size).astype(int) * step_size, 9) # minimum lot filter diff[diff.abs() < minimum_lot_size] = 0 # minimum notional filter diff[diff.abs() * transaction.price.astype(float) < min_notional] = 0 transaction['final_value'] = diff transaction['final_value_in_btc'] = diff * transaction.base_asset.map( self.ticker_info.get_asset_price_in_btc) else: transaction = pd.DataFrame(None, columns=['final_value']) transaction = transaction[transaction['final_value'] != 0] return diff_value, diff_value_btc, transaction def execute_orders(self, transactions, mode='TEST'): """Execute orders to Binance. Execute orders by program order. Args: transactions: A dataframe which is generated by transaction in calculate_position_size() function result. mode: A str of transactions mode, we have 3 method. 'TEST' is simulation. 'MARKET' is market order which is transaction at the current latest price. 'LIMIT' is The transaction is done at the specified price. If the specified price is not touched, the transaction has not been completed. Returns: A dataframe of trades. """ def cancel_orders(symbol): orders = self._client.get_open_orders(symbol=symbol) for o in orders: self._client.cancel_order(symbol=symbol, orderId=o['orderId']) order_func = self._client.create_order if mode == 'MARKET' or mode == 'LIMIT' else self._client.create_test_order print('|---------EXECUTION LOG----------|') print('| time: ', datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) trades = {} for s, lot in transactions.final_value.items(): cancel_orders(s) if lot == 0: continue side = SIDE_BUY if lot > 0 else SIDE_SELL try: args = dict( side=side, type=ORDER_TYPE_MARKET, symbol=s, quantity=abs(lot)) if mode == 'LIMIT': args['price'] = transactions.price.loc[s] args['type'] = ORDER_TYPE_LIMIT args['timeInForce'] = 'GTC' order_func(**args) order_result = 'success' print('|', mode, s, side, abs(lot), order_result) except Exception as e: print('| FAIL', s, s, side, abs(lot), str(e)) order_result = 'FAIL: ' + str(e) trades[s] = { **args, 'result': order_result, } return pd.DataFrame(trades).transpose() def status(self, ohlcvs): """Strategy list widgets. Choose which strategy to implement on widgets GUI. Args: ohlcvs: A dataframe of symbol. Returns: widget GUI """ import ipywidgets as widgets ret = pd.DataFrame() full_results = [] for method in self._trading_methods: for symbol in method.symbols: ohlcv = ohlcvs[(symbol, method.freq)] result = method.strategy.backtest(ohlcv, method.variables, filters=method.filters, freq=method.freq) ret[method.name + '-' + symbol + '-' + method.freq] = result.cumulative_returns weight_btc = method.weight_btc if isinstance(weight_btc, dict): weight_btc = (weight_btc[symbol] if symbol in weight_btc else weight_btc['default']) full_results.append({ 'name': method.name, 'symbol': symbol, 'freq': method.freq, 'weight': weight_btc, 'portfolio': result, 'trading_method': method, 'signal': result.cash().iloc[-1] == 0, }) method_dropdown = widgets.Dropdown(options=[m.name + '-' + str(i) for i, m in enumerate(self._trading_methods)]) symbol_dropdown = widgets.Dropdown(options=[symbol + '-' + freq for symbol, freq in ohlcvs.keys()]) backtest_btn = widgets.Button(description='status') backtest_panel = widgets.Output() option_panel = widgets.Output() def plotly_df(df): """Display plot. """ # Plot fig = px.line() for sname, s in df.items(): fig.add_scatter(x=s.index, y=s.values, name=sname) # Not what is desired - need a line # fig.show() @backtest_panel.capture(clear_output=True) def backtest(_): """Display single strategy backtest result. """ method_id = int(method_dropdown.value.split('-')[-1]) history_id = tuple(symbol_dropdown.value.split('-')) ohlcv = ohlcvs[history_id] strategy = self._trading_methods[method_id].strategy svars = self._trading_methods[method_id].variables filters = self._trading_methods[method_id].filters strategy.backtest(ohlcv, variables=svars, filters=filters, freq=history_id[-1], plot=True) backtest_btn.on_click(backtest) dropdowns = widgets.HBox([method_dropdown, symbol_dropdown, backtest_btn]) with option_panel: plotly_df(ret) display(pd.DataFrame(full_results)) return widgets.VBox([option_panel, dropdowns, backtest_panel]) def portfolio_backtest(self, ohlcvs, min_freq, quote_assets=['BTC', 'USDT', 'BUSD', 'USDC'], fee=0.002, delay=0): """Display portfolio backtest result. Calculate overall account asset changes. Unit is USD Args: ohlcvs: A dataframe of symbel. min_freq: A str of calculation frequency ex('4h'). quote_assets: A list of assets name ex(['BTC', 'USDT', 'BUSD', 'ETH']). fee: A float of trading fee. delay: A int of delayed entry and exit setting. Returns: widget GUI """ # backtest_results results = [] for method in self._trading_methods: for symbol in method.symbols: ohlcv = ohlcvs[(symbol, method.freq)] result = method.strategy.backtest(ohlcv, method.variables, filters=method.filters, freq=method.freq) # find weight_btc if it is in the nested dictionary weight_btc = method.weight if isinstance(weight_btc, dict): weight_btc = (weight_btc[symbol] if symbol in weight else weight_btc['default']) weight_btc *= self.ticker_info.get_asset_price_in_btc(method.weight_unit) results.append({ 'name': method.name, 'symbol': symbol, 'freq': method.freq, 'weight': weight_btc, 'portfolio': result, 'trading_method': method, 'signal': result.cash().iloc[-1] == 0, }) results = pd.DataFrame(results) import matplotlib.pyplot as plt position = {} quote_substract = {} for index, value in results.transpose().items(): position[value.loc['name'] + '|' + value.symbol + '|' + value.freq] = (value.portfolio.cash() == 0).shift( delay).ffill() * value.weight position = pd.DataFrame(position).resample(min_freq).last().ffill() position.columns = position.columns.str.split('|').str[1] position = position.ffill().fillna(0) position = position.groupby(position.columns, axis=1).sum() # find quote assets quote_asset_col = [] for symbol in position.columns: for q in quote_assets: if symbol[-len(q):] == q: quote_asset_col.append(q) break quote_position = position.copy() quote_position.columns = quote_asset_col quote_position = -quote_position.groupby(quote_position.columns, axis=1).sum() # calculate return in usdt assets = position.columns.str.split('|').str[0].to_list() for i, a in enumerate(assets): for q in quote_assets: if len(a) > 5 and a[-len(q):] == q: assets[i] = a[:-len(q)] position.columns = assets position = position.groupby(position.columns, axis=1).sum() quote_position = quote_position.groupby(quote_position.columns, axis=1).sum() all_symbols = list(set(quote_position.columns) | set(position.columns) | set(self._margins.keys())) if 'USDT' not in all_symbols: all_symbols.append('USDT') position = position.reindex(all_symbols, axis=1).fillna(0) + quote_position.reindex(all_symbols, axis=1).fillna( 0) ohlcv_usdt = {a: get_all_binance(a + 'USDT', min_freq) for a in position.columns if a != 'USDT'} initial_margin_sum_btc = 0 for a, w in self._margins.items(): position[a] += self.ticker_info.get_asset_price_in_btc(a) * w initial_margin_sum_btc += self.ticker_info.get_asset_price_in_btc(a) * w # remove negative position negative_position = ((position < 0) * position).drop('USDT', axis=1, errors='ignore').sum(axis=1) pusdt = position['USDT'].copy() position = position.clip(0, None) position.USDT = pusdt + negative_position addition_usdt = -min(position.USDT.min(), 0) / self.ticker_info.get_asset_price_in_btc('USDT') if addition_usdt > 0: print('WARRN**: additional usdt is required: ', addition_usdt, ' USD') p = position.loc[position.index[(position != position.shift()).abs().sum(axis=1) != 0] | position.index[-1:]] p.index = p.index.tz_localize(None) ohlcv_usdt_close = pd.DataFrame({name: s.close for name, s in ohlcv_usdt.items()}) ohlcv_usdt_close.index = ohlcv_usdt_close.index.tz_localize(None) rebalance_time = (p.index & ohlcv_usdt_close.index) ohlcv_usdt_close = ohlcv_usdt_close.loc[rebalance_time] p = p.loc[rebalance_time].fillna(0) asset_return = ((ohlcv_usdt_close.pct_change().shift(-1).fillna(0)) * p) - fee * (p - p.shift()).abs() asset_return.fillna(0, inplace=True) (asset_return.cumsum() / self.ticker_info.get_asset_price_in_btc('USDT')).plot() plt.show() s = (asset_return.sum(axis=1).cumsum() + initial_margin_sum_btc) / self.ticker_info.get_asset_price_in_btc( 'USDT') s.plot() plt.show() (s / s.cummax()).plot() plt.show() return results
class BinanceClient(AbstractClient): def __init__(self, listener): self.exchange = 'binance' self.trade_fee = Decimal(0.0005) self.client = Client(api_keys.binance[0], api_keys.binance[1]) super(BinanceClient, self).__init__(listener) self._get_exchange_info() self.get_all_balances() def _buy(self, symbol, price, amount): result = self.client.create_order(symbol=symbol, quantity=amount, price=price, side=SIDE_BUY, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC) self.open_orders[symbol] = result['orderId'] def _sell(self, symbol, price, amount): result = self.client.create_order(symbol=symbol, quantity=amount, price=price, side=SIDE_SELL, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC) self.open_orders[symbol] = result['orderId'] def _cancel(self, symbol): orderId = self.open_orders.pop(symbol) try: self.client.cancel_order(symbol=symbol, orderId=orderId) # Binance throws API exception if the order is not open so we'll have to check if it's fulfilled here. except BinanceAPIException as e: time.sleep(1) if not self.is_order_fulfilled(symbol, orderId): # Order was not fulfilled, re-throw exception raise e except BinanceRequestException as e: self.errors += 1 raise e def is_order_fulfilled(self, symbol, orderId): try: for data in self.client.get_my_trades(symbol=symbol): if data['orderId'] == int(orderId): return True except (BinanceRequestException, BinanceAPIException) as e: print datetime.now(), "[Binance] is order fulfilled error", e self.errors += 1 return False def get_all_balances(self): try: result = self.client.get_account() for balance in result['balances']: self.coin_balances[str(balance['asset'])] = Decimal( str(balance['free'])) # + Decimal(balance['locked']) return self.coin_balances except (BinanceRequestException, BinanceAPIException) as e: print datetime.now(), "[Binance] get all balances error:", str( e).replace('\r\n', '') self.errors += 1 def _market_poll(self): result = {'success': False} result['client'] = self result['timestamp'] = int(time.time() * 1000) result['coinpairs'] = {} try: coins = self.client.get_orderbook_tickers() for coin in coins: symbol = str(coin['symbol']) if symbol in self.exchange_symbols: result['coinpairs'][self._get_arbitrager_coinpair( symbol)] = { 'buy': Decimal(str(coin['bidPrice'])), 'buyAmount': Decimal(str(coin['bidQty'])), 'sell': Decimal(str(coin['askPrice'])), 'sellAmount': Decimal(str(coin['askQty'])) } except (BinanceRequestException, BinanceAPIException) as e: print datetime.now(), "[Binance] market poll error", str( e).replace('\r\n', '') self.errors += 1 if len(result['coinpairs']) > 0: result['success'] = True return result def _get_exchange_info(self): result = self.client.get_exchange_info() for symbol in result['symbols']: sym = str(symbol['symbol']) self.trade_rules[sym] = {} trade_rule = self.trade_rules[sym] for filter in symbol['filters']: if filter['filterType'] == 'PRICE_FILTER': trade_rule['minPrice'] = Decimal(filter['minPrice']) trade_rule['maxPrice'] = Decimal(filter['maxPrice']) trade_rule['stepPrice'] = Decimal(filter['tickSize']) elif filter['filterType'] == 'LOT_SIZE': if sym == 'NEOETH': trade_rule['minAmount'] = Decimal(0.1) elif sym == 'LTCETH': trade_rule['minAmount'] = Decimal(0.05) else: trade_rule['minAmount'] = Decimal(filter['minQty']) trade_rule['maxAmount'] = Decimal(filter['maxQty']) trade_rule['stepAmount'] = Decimal(filter['stepSize']) elif filter['filterType'] == 'MIN_NOTIONAL': trade_rule['minNotional'] = Decimal(filter['minNotional'])
class ApiCalls: """Collection of api related methods.""" def __init__(self, mw, tp): self.mw = mw self.client = Client(mw.cfg_manager.api_key, mw.cfg_manager.api_secret, { "verify": True, "timeout": 10 }) app.client = self.client self.threadpool = tp def initialize(self): # print("setting client: " + str(self.client)) try: val["coins"] = self.availablePairs() val["accHoldings"] = self.getHoldings() val["tickers"] = self.getTickers() val["apiCalls"] += 3 # userMsg = dict() # accHoldings = dict() self.set_pair_values() self.mw.is_connected = True except (BinanceAPIException, NameError) as e: print("API ERROR") print(str(e)) if "code=-1003" in str(e): print("ja ein api error :)") elif "code=-2014": print("API KEY INVALID") # def get_tether(client): # tether_info = client.get_ticker(symbol="BTCUSDT") # return tether_info def set_pair_values(self): """Set various values based on the chosen pair.""" pair = self.mw.cfg_manager.pair val["decimals"] = len(str(val["coins"][pair]["tickSize"])) - 2 if int(val["coins"][pair]["minTrade"]) == 1: val["assetDecimals"] = 0 else: val["assetDecimals"] = len(str(val["coins"][pair]["minTrade"])) - 2 def availablePairs(self): """ Create a dictonary containing all BTC tradepairs excluding USDT. Keys are: {'symbol': 'ETHBTC', 'tradedMoney': 3024.89552855, 'baseAssetUnit': 'Ξ', 'active': True, 'minTrade': '0.00100000', 'baseAsset': 'ETH', 'activeSell': 66254.102, 'withdrawFee': '0', 'tickSize': '0.000001', 'prevClose': 0.044214, 'activeBuy': 0, 'volume': '66254.102000', 'high': '0.047998', 'lastAggTradeId': 2809764, 'decimalPlaces': 8, 'low': '0.043997', 'quoteAssetUnit': '฿', 'matchingUnitType': 'STANDARD', 'close': '0.047656', 'quoteAsset': 'BTC', 'open': '0.044214', 'status': 'TRADING', 'minQty': '1E-8'} """ # create a local dictionary coins = dict() # API Call products = self.client.get_products() # For every entry in API answer: for i, pair in enumerate(products["data"]): # Check if pair contains BTC, does not contain USDT and if volume is >0 if "BTC" in pair["symbol"] and "USDT" not in pair[ "symbol"] and float(products["data"][i]["volume"]) > 0.0: # Create a temporary dictionary to store keys and values tempdict = dict() # Add every key-value pair to the temp dictionary for key, value in pair.items(): tempdict[key] = value # Add every temp dictionary to the coin dictionary coins[tempdict["symbol"]] = tempdict return coins def getHoldings(self): """Make an inital API call to get BTC and coin holdings.""" # API Call: order = self.client.get_account() accHoldings = dict() for i in range(len(order["balances"])): accHoldings[order["balances"][i]["asset"]] = { "free": order["balances"][i]["free"], "locked": order["balances"][i]["locked"] } return accHoldings def getTickers(self): """Make an initial API call to get ticker data.""" ticker = self.client.get_ticker() # print(str(ticker)) all_tickers = dict() for _, ticker_data in enumerate(ticker): if "BTC" in ticker_data["symbol"]: # print(str(ticker_data)) all_tickers[ticker_data["symbol"]] = ticker_data return all_tickers def getTradehistory(self, pair): """Make an initial API call to get the trade history of a given pair. This is used until updated by websocket data.""" # API call globalList = list() trades = self.client.get_aggregate_trades(symbol=pair, limit=50) for _, trade in enumerate(reversed(trades)): globalList.insert( 0, { "price": str(trade["p"]), "quantity": str(trade["q"]), "maker": bool(trade["m"]), "time": str(trade["T"]) }) return list(reversed(globalList)) def getDepth(self, symbol): """Make an initial API call to get market depth (bids and asks).""" # API Call depth = self.client.get_order_book(symbol=symbol, limit=20) asks = depth["asks"] bids = depth["bids"] return {"bids": bids, "asks": asks} def api_create_order(self, side, pair, price, amount, progress_callback): print("create order: " + str(price) + " " + str(amount)) try: if side == "Buy": order = self.client.order_limit_buy(symbol=pair, quantity=str(amount), price=str(price)) elif side == "Sell": order = self.client.order_limit_sell(symbol=pair, quantity=str(amount), price=str(price)) print("order status: " + str(order)) return order except BinanceAPIException as e: print("create order failed: " + str(e)) print(str(order)) def api_cancel_order(self, client, order_id, symbol, progress_callback): print("cancel order " + str(symbol) + " " + str(order_id)) try: self.client.cancel_order(symbol=symbol, orderId=order_id) except BinanceAPIException as e: print("cancel failed " + str(e)) print(str(self.client)) print(str(symbol)) print(str(order_id)) def api_order_history(self, pair, progress_callback): orders = self.client.get_all_orders(symbol=pair) progress_callback.emit(orders) val["apiCalls"] += 1 def api_history(self, progress_callback): trade_history = self.getTradehistory(self.mw.cfg_manager.pair) progress_callback.emit({"history": reversed(trade_history)}) val["apiCalls"] += 1 def api_depth(self, progress_callback): depth = self.getDepth(self.mw.cfg_manager.pair) val["asks"] = depth["asks"] progress_callback.emit({"asks": val["asks"]}) val["bids"] = depth["bids"] progress_callback.emit({"bids": val["bids"]}) val["apiCalls"] += 1 def api_all_orders(self, progress_callback): print("CLEINT;" + str(self.client)) orders = self.client.get_open_orders() progress_callback.emit(orders) numberPairs = sum(val["pairs"].values()) print("number pairs: " + str(numberPairs)) def api_calls(self): """Inital and coin specific api calls""" worker = Worker(self.api_history) worker.signals.progress.connect(self.mw.live_data.batch_history) self.mw.threadpool.start(worker) worker = Worker(self.api_depth) worker.signals.progress.connect(self.mw.live_data.batch_orderbook) worker.signals.finished.connect(self.mw.limit_pane.t_complete) self.mw.threadpool.start(worker) self.get_trade_history(self.mw.cfg_manager.pair) def get_trade_history(self, pair): worker = Worker(partial(self.api_order_history, pair)) worker.signals.progress.connect(self.mw.history_table.orders_received) self.mw.threadpool.start(worker) def get_kline(self, pair, progress_callback): """Make an API call to get historical data of a coin pair.""" interval = "1m" try: # try since this is used heavily klines = self.client.get_klines(symbol=pair, interval=interval) except (ConnectionError, BinanceAPIException) as e: print(str(e)) progress_callback.emit([klines, pair, interval]) val["apiCalls"] += 1 def cancel_order_byId(self, order_id, symbol): """Cancel an order by id from within a separate thread.""" worker = Worker( partial(self.mw.api_manager.api_cancel_order, app.client, order_id, symbol)) # worker.signals.progress.connect(self.cancel_callback) self.threadpool.start(worker)
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): 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()) #seco 27-04-21 def get_coinlist(self) -> Get_Coinlist return Get_Coinlist(self.binance.client.exchangeinfo(“quotename”)) 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) 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 """ 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"]) if stat is None: return None 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, 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.logger.info(f"Balance is {origin_balance}") order = None while order is None: # Should sell at calculated price to avoid lost coin order = self.binance_client.order_limit_sell( symbol=origin_symbol + target_symbol, quantity=(order_quantity), price=from_coin_price ) 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"]) 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}") trade_log.set_complete(stat["cummulativeQuoteQty"]) return order
class BinanceWrapper(BaseExchangeWrapper): INTERVAL_MAP = { CandleTicks.one_minute : Client.KLINE_INTERVAL_1MINUTE, CandleTicks.five_minutes : Client.KLINE_INTERVAL_5MINUTE, CandleTicks.thirty_minutes : Client.KLINE_INTERVAL_30MINUTE, CandleTicks.one_hour : Client.KLINE_INTERVAL_1HOUR, CandleTicks.one_day : Client.KLINE_INTERVAL_1DAY, } def __init__(self, api_key, api_secret): BaseExchangeWrapper.__init__(self, exposes_confirmations=False) self._handle = Client(api_key, api_secret) self._filters = {} self._load_markets() def _perform_request(self, request_lambda): try: output = request_lambda() if type(output) is dict and output.get('success', True) == False: if output['msg'] is dict: if output['msg']['code'] == -1021: raise ExchangeAPIException('Request timed out') raise ExchangeAPIException(output['msg']['msg']) else: raise ExchangeAPIException('Failed to fetch data: {0}'.format(output['msg'])) return output except Exception as ex: raise ExchangeAPIException(ex.message) def _load_names(self): try: result = requests.get('https://api.coinmarketcap.com/v1/ticker/') data = json.loads(result.text) except Exception as ex: print 'Failed to parse coinmarketcap data: {0}'.format(ex) return {} names = {} for item in data: names[item['symbol']] = item['name'] return names def _load_withdraw_info(self): try: result = requests.get('https://www.binance.com/assetWithdraw/getAllAsset.html') data = json.loads(result.text) except Exception as ex: print 'Failed to parse withdraw fees data: {0}'.format(ex) return {} fees = {} for item in data: fees[item['assetCode']] = { 'fee' : item['transactionFee'], 'min' : float(item['minProductWithdraw']) } return fees def _make_exchange_name(self, base_currency_code, market_currency_code): if base_currency_code not in self._markets or \ market_currency_code not in self._markets[base_currency_code]: raise UnknownMarketException(base_currency_code, market_currency_code) return '{0}{1}'.format(market_currency_code, base_currency_code) def _split_sumbol(self, symbol): for base_code, markets in self._markets.items(): for market_code in markets: if market_code + base_code == symbol: return (base_code, market_code) raise ExchangeAPIException('Failed to decode symbol {0}'.format(symbol)) def _add_filter(self, exchange, filters): min_price = None max_price = None price_tick = None min_amount = None max_amount = None amount_step = None min_notional = None for f in filters: if f['filterType'] == 'PRICE_FILTER': min_price = float(f['minPrice']) max_price = float(f['maxPrice']) price_tick = float(f['tickSize']) elif f['filterType'] == 'LOT_SIZE': min_amount = float(f['minQty']) max_amount = float(f['maxQty']) amount_step = float(f['stepSize']) elif f['filterType'] == 'MIN_NOTIONAL': min_notional = float(f['minNotional']) self._filters[exchange] = OrderFilter( min_price, max_price, price_tick, min_amount, max_amount, amount_step, min_notional ) def _load_markets(self): names = self._load_names() self.withdraw_info = self._load_withdraw_info() result = self._perform_request(lambda: self._handle.get_exchange_info()) for symbol in result['symbols']: base_currency = symbol['quoteAsset'] market_currency = symbol['baseAsset'] self.add_currency(Currency( base_currency, names.get(base_currency, base_currency), 0, self.withdraw_info.get(base_currency, {}).get('fee', None) )) self.add_currency(Currency( market_currency, names.get(market_currency, market_currency), 0, self.withdraw_info.get(market_currency, {}).get('fee', None) )) self.add_market(base_currency, market_currency) self._add_filter(symbol['symbol'], symbol['filters']) def get_open_orders(self): result = self._perform_request(lambda: self._handle.get_open_orders()) output = [] for item in result: base_code, market_code = self._split_sumbol(item['symbol']) amount = float(item['origQty']) output.append(TradeOrder( item["orderId"], self._currencies[base_code], self._currencies[market_code], dateparser.parse(str(item["time"])), None, amount, amount - float(item["executedQty"]), float(item["price"]), float(item["price"]), OrderType.limit_buy if item["side"] == "BUY" else OrderType.limit_sell )) return output # Order history in Binance requires a symbol... This doesn't really work def get_order_history(self, base_currency_code=None, market_currency_code=None): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) result = self._perform_request(lambda: self._handle.get_my_trades(symbol=exchange_name)) output = [] for item in result: amount = float(item['qty']) output.append(TradeOrder( item["orderId"], self._currencies[base_currency_code], self._currencies[market_currency_code], None, dateparser.parse(str(item["time"])), amount, 0, # Amount remaining float(item["price"]), float(item["price"]), OrderType.limit_buy if item["isBuyer"] else OrderType.limit_sell )) return output def get_withdrawal_history(self, currency_code=None): result = self._perform_request(lambda: self._handle.get_withdraw_history()) output = [] for item in result.get("withdrawList", []): output.append(Transfer( self._currencies[item['asset']], item['amount'], item['txId'], None, # Confirmation None, # Tx cost item['status'] == 1, # Cancelled utils.datetime_from_utc_time(str(item['applyTime'])) )) return output def cancel_order(self, base_currency_code, market_currency_code, order_id): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) result = self._perform_request(lambda: self._handle.cancel_order(symbol=exchange_name, orderId=order_id)) def get_market_state(self, base_currency_code, market_currency_code): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) result = self._perform_request(lambda: self._handle.get_all_tickers()) price = None for entry in result: if entry['symbol'] == exchange_name: price = entry['price'] if price is not None: result = self._handle.get_orderbook_tickers() for entry in result: if entry['symbol'] == exchange_name: return MarketState( float(entry['askPrice']), float(entry['bidPrice']), float(price) ) raise ExchangeAPIException('Failed to fetch information for given market') def get_orderbook(self, base_currency_code, market_currency_code): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) result = self._perform_request(lambda: self._handle.get_order_book(symbol=exchange_name)) buy_orderbook = Orderbook() sell_orderbook = Orderbook() for item in result['bids']: buy_orderbook.add_order(Order(float(item[0]), float(item[1]))) for item in result['asks']: sell_orderbook.add_order(Order(float(item[0]), float(item[1]))) return (buy_orderbook, sell_orderbook) def get_wallets(self): result = self._perform_request(lambda: self._handle.get_account()) output = [] for data in result['balances']: currency = data['asset'] # Shouldn't happen. TODO: log this if currency not in self._currencies: continue free = float(data['free']) locked = float(data['locked']) wallet = Wallet( self._currencies[currency], free + locked, free, locked ) output.append(wallet) return output def get_wallet(self, currency_code): self.check_valid_currency(currency_code) result = self._perform_request(lambda: self._handle.get_asset_balance(currency_code)) free = float(result['free']) locked = float(result['locked']) return Wallet( self._currencies[currency_code], free + locked, free, locked ) def get_deposit_history(self, currency_code=None): result = self._perform_request(lambda: self._handle.get_deposit_history()) output = [] for i in result['depositList']: # TODO: somehow log this if i["asset"] not in self._currencies: continue output.append( Transfer( self._currencies[i["asset"]], float(i["amount"]), i["txId"], i["status"], # Status == 1 means success 0, False, dateparser.parse(str(i["insertTime"])), ) ) return output def buy(self, base_currency_code, market_currency_code, amount, rate): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) result = self._perform_request(lambda: self._handle.order_limit_buy(symbol=exchange_name, quantity=amount, price=rate)) return result['orderId'] def sell(self, base_currency_code, market_currency_code, amount, rate): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) result = self._perform_request(lambda: self._handle.order_limit_sell(symbol=exchange_name, quantity=amount, price=rate)) return result['orderId'] def withdraw(self, currency_code, amount, address, address_tag): params = { 'asset' : currency_code, 'amount' : amount, 'address' : address, } if address_tag is not None: params['addressTag'] = address_tag result = self._perform_request(lambda:self._handle.withdraw(**params)) return result['id'] def get_deposit_address(self, currency_code): self.check_valid_currency(currency_code) result = self._perform_request(lambda: self._handle.get_deposit_address(asset=currency_code)) if not result['success']: raise ExchangeAPIException('Failed to fetch address for {0} wallet'.format(currency_code)) return CryptoAddress( currency_code, result['address'], result.get('addresTag', None) ) def get_candles(self, base_currency_code, market_currency_code, interval, limit): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) result = self._perform_request(lambda: self._handle.get_klines( symbol=exchange_name, interval=BinanceWrapper.INTERVAL_MAP[interval], limit=limit ) ) output = [] for i in result: output.append(Candle( float(i[3]), # Low float(i[2]), # High float(i[1]), # Open float(i[4]), # Close dateparser.parse(str(i[0])) )) return output def is_order_rate_valid(self, base_currency_code, market_currency_code, rate): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) if exchange_name not in self._filters: return True order_filter = self._filters[exchange_name] if rate < order_filter.min_price: return OrderInvalidity(OrderInvalidity.Comparison.greater_eq, order_filter.min_price) elif rate > order_filter.max_price: return OrderInvalidity(OrderInvalidity.Comparison.lower_eq, order_filter.max_price) else: return True def is_order_amount_valid(self, base_currency_code, market_currency_code, amount): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) if exchange_name not in self._filters: return True order_filter = self._filters[exchange_name] if amount < order_filter.min_amount: return OrderInvalidity(OrderInvalidity.Comparison.greater_eq, order_filter.min_amount) elif amount > order_filter.max_amount: return OrderInvalidity(OrderInvalidity.Comparison.lower_eq, order_filter.max_amount) else: return True def is_order_notional_value_valid(self, base_currency_code, market_currency_code, rate, amount): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) if exchange_name not in self._filters: return True order_filter = self._filters[exchange_name] notional_value = rate * amount if notional_value < order_filter.min_notional: return OrderInvalidity(OrderInvalidity.Comparison.greater_eq, order_filter.min_notional) else: return True def minimum_withdraw_limit(self, currency_code): if currency_code in self.withdraw_info: return self.withdraw_info[currency_code]['min'] return None def adjust_order_rate(self, base_currency_code, market_currency_code, rate): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) if exchange_name not in self._filters: return amount order_filter = self._filters[exchange_name] return utils.round_order_value(order_filter.price_tick, rate) def adjust_order_amount(self, base_currency_code, market_currency_code, amount): exchange_name = self._make_exchange_name(base_currency_code, market_currency_code) if exchange_name not in self._filters: return amount order_filter = self._filters[exchange_name] return utils.round_order_value(order_filter.amount_step, amount) def order_history_needs_asset(self): return True
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'] == 'outboundAccountPosition': 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.on_balance_update(event) elif 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) elif event['X'] == 'NEW': order_event = 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']) # 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) return 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 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"]: float(ticker["takerCommission"]) for ticker in self.binance_client.get_trade_fee() } @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_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.info( 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, None) 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 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) @staticmethod def float_as_decimal_str(num: float): return f"{num:0.08f}".rstrip("0").rstrip( ".") # remove trailing zeroes too def _make_order( self, side: str, symbol: str, quantity: float, price: float, quote_quantity: float, ): params = { "symbol": symbol, "side": side, "quantity": self.float_as_decimal_str(quantity), "type": self.config.BUY_ORDER_TYPE if side == Client.SIDE_BUY else self.config.SELL_ORDER_TYPE, } if params["type"] == Client.ORDER_TYPE_LIMIT: params["timeInForce"] = self.binance_client.TIME_IN_FORCE_GTC params["price"] = self.float_as_decimal_str(price) elif side == Client.SIDE_BUY: del params["quantity"] params["quoteOrderQty"] = self.float_as_decimal_str(quote_quantity) return self.binance_client.create_order(**params) 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"BUY QTY {order_quantity} of <{origin_symbol}>") # Try to buy until successful order = None order_guard = self.stream_manager.acquire_order_guard() while order is None: try: order = self._make_order( side=Client.SIDE_BUY, symbol=origin_symbol + target_symbol, quantity=order_quantity, quote_quantity=target_balance, 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.warning(f"Unexpected Error: {e}") executed_qty = float(order.get("executedQty", 0)) if executed_qty > 0 and order["status"] == "FILLED": order_quantity = executed_qty # Market buys provide QTY of actually bought asset 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 self.logger.info(f"Bought {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} of {origin_symbol}") self.logger.info(f"Balance is {origin_balance}") order = None order_guard = self.stream_manager.acquire_order_guard() while order is None: try: order = self._make_order( side=Client.SIDE_SELL, symbol=origin_symbol + target_symbol, quantity=order_quantity, quote_quantity=target_balance, 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.warning(f"Unexpected Error: {e}") self.logger.info("order") self.logger.info(order) 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) self.logger.info(f"Sold {origin_symbol}") trade_log.set_complete(order.cumulative_quote_qty) return order
Highest_Bid_Price = 1.00009 #買單最高價格 Lowest_Ask_Price = 1.00399 #賣單最低價格 Refresh_Time = 116.5 #更新頻率(秒) x = -2 while True: ##_1_清空所有訂單 orders = client.get_open_orders(symbol='WBTCBTC') time.sleep(0.5) for i in range(0, len(orders), 1): cancel = client.cancel_order(symbol='WBTCBTC', orderId=orders[i]['orderId']) time.sleep(0.1) ##_2_取得帳戶當前之BTC與WBTC餘額 BTC = client.get_asset_balance(asset='BTC') BTC_Balance = float(BTC.get('free')) WBTC = client.get_asset_balance(asset='WBTC') WBTC_Balance = float(WBTC.get('free')) Available_WBTC_Balance_For_Ask = math.floor(WBTC_Balance * 10000) / 10000 time.sleep(0.5) ##_3_計算市場買賣單深度並依條件掛單
class Operation: def __init__(self, exchange_list, apikey_trt=None, secret_trt=None, apikey_krk=None, secret_krk=None, apikey_bnb=None, secret_bnb=None, pair="BTCEUR"): self.apikey_trt = apikey_trt self.secret_trt = secret_trt self.apikey_krk = apikey_krk self.secret_krk = secret_krk self.apikey_bnb = apikey_bnb self.secret_bnb = secret_bnb self.exchange_list = exchange_list self.client = Client(self.apikey_bnb, self.secret_bnb) self.trt = [] self.prtrt = [] self.bnb = [] self.prbnb = [] self.len = 0 self.pair = pair def thread_func(self): while (1): if len(self.prtrt) < 20000: tr = Thread( target=lambda q, arg1, arg2: q.put(self.query(arg1, arg2)), args=(q1, "trt", self.pair)) self.prtrt.append(tr) bn = Thread( target=lambda q, arg1, arg2: q.put(self.query(arg1, arg2)), args=(q2, "bnb", self.pair)) self.prbnb.append(bn) else: time.sleep(1) if len(self.trt) < 25: self.bnb = self.bnb + self.prbnb self.trt = self.trt + self.prtrt self.prtrt.clear() self.prbnb.clear() time.sleep(30) def threadCreation(self): x = Thread(target=self.thread_func) x.start() def trade(self, exchange, fund_id, side, amount, price): nonce = str(int(time.time() * 1e6)) print(amount) amount = round(amount, 8) if exchange == "trt": url = "https://api.therocktrading.com/v1/funds/" + fund_id + "/orders" payload_trt = { "fund_id": "BTCEUR", "side": side, "amount": amount, "price": 0 } signature = hmac.new(self.secret_trt.encode(), msg=(str(nonce) + url).encode(), digestmod=hashlib.sha512).hexdigest() _headers = { 'User-Agent': 'PyRock v1', "Content-Type": "application/json", "X-TRT-KEY": self.apikey_trt, "X-TRT-SIGN": signature, "X-TRT-NONCE": nonce } resp = requests.post(url, data=json.dumps(payload_trt), headers=_headers) try: return json.loads(resp.text) except KeyError: return "ERROR" elif exchange == "krk": api = krakenex.API(self.apikey_krk, self.secret_krk) k = KrakenAPI(api) resp = k.add_standard_order(fund_id, side, "limit", str(amount), str(price)) resp = str(resp).replace("\'", "\"") return resp elif exchange == "bnb": if side == "buy": order = self.client.order_limit_buy(symbol=fund_id, quantity=round(amount, 8), price=price) elif side == "sell": order = self.client.order_limit_sell(symbol=fund_id, quantity=round(amount, 8), price=price) return dict(order) def __balance(self, exchange): nonce = str(int(time.time() * 1e6)) d = dict() if exchange == "trt": url = "https://api.therocktrading.com/v1/balances" signature = hmac.new(self.secret_trt.encode(), msg=(str(nonce) + url).encode(), digestmod=hashlib.sha512).hexdigest() _headers = { "Content-Type": "application/json", "X-TRT-KEY": self.apikey_trt, "X-TRT-SIGN": signature, "X-TRT-NONCE": nonce } resp = requests.get(url, headers=_headers) d["trtbtc"] = json.loads(resp.text)["balances"][0]["balance"] d["trteur"] = json.loads(resp.text)["balances"][8]["balance"] return d elif exchange == "krk": api = krakenex.API(self.apikey_krk, self.secret_krk) k = KrakenAPI(api) resp = k.get_account_balance() d["krkbtc"] = resp["vol"]["XXBT"] d["krkbch"] = resp["vol"]["BCH"] return d elif exchange == "bnb": is_fine = True while is_fine: try: is_fine = False except ConnectionError: print( f"{Fore.RED}[ERR] CHECK INTERNET CONNECTION{Style.RESET_ALL}" ) try: d["bnbtrx"] = float( self.client.get_asset_balance(asset="TRX")["free"]) d["bnbbnb"] = float( self.client.get_asset_balance(asset="BNB")["free"]) except Exception: d["bnbtrx"] = float( self.client.get_asset_balance(asset="TRX")["free"]) d["bnbbnb"] = float( self.client.get_asset_balance(asset="BNB")["free"]) return d def balancethreading(self): d = dict() if "krk" in self.exchange_list: krk_balance = Thread( target=lambda q, arg1: q.put(self.__balance(arg1)), args=(q2, "krk")) krk_balance.start() if "trt" in self.exchange_list: trt_balance = Thread( target=lambda q, arg1: q.put(self.__balance(arg1)), args=(q1, "trt")) trt_balance.start() if "bnb" in self.exchange_list: bnb_balance = Thread( target=lambda q, arg1: q.put(self.__balance(arg1)), args=(q2, "bnb")) bnb_balance.start() try: trt_balance.join() d.update(q1.get()) except: pass try: krk_balance.join() d.update(q2.get()) except: pass try: bnb_balance.join() d.update(q2.get()) except: pass return d def orderthreading(self, order1, order2=None): d = dict() bnb_order = Thread( target=lambda q, arg1, arg2: q.put(self.__order(arg1, arg2)), args=(q3, "bnb", order1)) bnb_order.start() try: bnb_order.join() d.update(q3.get()) except Exception: pass return d def cancel(self, order): self.client.cancel_order(symbol="TRXBNB", orderId=order) def __order(self, exchange, order): nonce = str(int(time.time() * 1e6)) d = dict() resp = self.client.get_order(symbol="TRXBNB", orderId=order) print("binance", resp) d["status_bnb"] = resp["status"] return d def __fee(self, exchange): nonce = str(int(time.time() * 1e6)) d = dict() if exchange == "trt": url = "https://api.therocktrading.com/v1/funds/BTCEUR" signature = hmac.new(self.secret_trt.encode(), msg=(str(nonce) + url).encode(), digestmod=hashlib.sha512).hexdigest() _headers = { "Content-Type": "application/json", "X-TRT-KEY": self.apikey_trt, "X-TRT-SIGN": signature, "X-TRT-NONCE": nonce } resp = requests.get(url, headers=_headers) d["feetrttaker"] = json.loads(resp.text)["buy_fee"] d["feetrtmaker"] = json.loads(resp.text)["sell_fee"] return d elif exchange == "krk": api = krakenex.API(self.apikey_krk, self.secret_krk) k = KrakenAPI(api) resp = pd.DataFrame(k.get_trade_volume("BTCEUR")[2]) d["feekrk"] = resp["XXBTZEUR"][0] return d elif exchange == "bnb": resp = self.client.get_trade_fee(symbol="BNBTRX") print(resp) d["feebnbtaker"] = resp["tradeFee"][0]["taker"] d["feebnbmaker"] = resp["tradeFee"][0]["maker"] return d def feethreading(self): d = dict() if "krk" in self.exchange_list: krk_fee = Thread(target=lambda q, arg1: q.put(self.__fee(arg1)), args=(q1, "krk")) krk_fee.start() if "trt" in self.exchange_list: trt_fee = Thread(target=lambda q, arg1: q.put(self.__fee(arg1)), args=(q2, "trt")) trt_fee.start() if "bnb" in self.exchange_list: bnb_fee = Thread(target=lambda q, arg1: q.put(self.__fee(arg1)), args=(q3, "bnb")) bnb_fee.start() try: krk_fee.join() d.update(q1.get()) except: pass try: trt_fee.join() d.update(q2.get()) except: pass try: bnb_fee.join() d.update(q3.get()) except Exception: pass return d def tradethreading(self, side, exchange, fund_id, amount, price, side2=None, exchange2=None, fund_id2=None, amount2=None, price2=None): d = dict() print("bnb") bnb_trade = Thread(target=lambda q, arg1, arg2, arg3, arg4, arg5: q. put(self.trade(arg1, arg2, arg3, arg4, arg5)), args=(q1, exchange, fund_id, side, amount, price)) bnb_trade.start() try: bnb_trade.join() except: pass try: d["bnb"] = q1.get() except: d["bnb"] = "ERROR" return d def min_qty_trt(self): try: resp_trt = requests.get( "https://api.therocktrading.com/v1/funds/?id=BTCEUR") return json.loads(resp_trt.text) except requests.exceptions.ConnectionError: print( f"{Fore.RED}[ERR] CHECK INTERNET CONNECTION{Style.RESET_ALL}") except json.decoder.JSONDecodeError: print( f"{Fore.RED}[ERR] ERROR WHILE CONVERTING TO JSON [expecting value]{Style.RESET_ALL}" ) def query(self, exchange, pair): if exchange == "trt": self.trt.pop(1) try: resp_trt = requests.get( 'https://api.therocktrading.com/v1/funds/' + self.pair + '/orderbook?limit=1') return json.loads(resp_trt.text) except requests.exceptions.ConnectionError: print( f"{Fore.RED}[ERR] CHECK INTERNET CONNECTION{Style.RESET_ALL}" ) except json.decoder.JSONDecodeError: print( f"{Fore.RED}[ERR] ERROR WHILE CONVERTING TO JSON [expecting value]{Style.RESET_ALL}" ) elif exchange == "krk": resp_krk = requests.get( 'https://api.kraken.com/0/public/Depth') # , params=params) return resp_krk.text elif exchange == "bnb": self.bnb.pop(1) try: resp_bnb = self.client.get_order_book(symbol=self.pair, limit=5) return resp_bnb except requests.exceptions.ConnectionError: print( f"{Fore.RED}[ERR] CHECK INTERNET CONNECTION{Style.RESET_ALL}" ) except requests.exceptions.ReadTimeout: print( f"{Fore.RED}[ERR] CHECK INTERNET CONNECTION{Style.RESET_ALL}" ) except binance.exceptions.BinanceAPIException: print( f"{Fore.RED}[ERR] CHECK INTERNET CONNECTION{Style.RESET_ALL}" ) def querythread(self): d = dict() if "trt" in self.exchange_list: self.len = len(self.trt) trt_thread = self.trt[1] trt_thread.start() if "bnb" in self.exchange_list: bnb_thread = self.bnb[1] bnb_thread.start() try: trt_thread.join() d["trt"] = q1.get() except: pass try: bnb_thread.join() d["bnb"] = q2.get() except: pass return d
class RmtSrvObj(BaseObj): def __init__(self, symbol, line_type, size=0, since='', debug=False, balance_min=0.01): self.rmt_srv_obj = Client(api_key, secret_key) self.balance_min = balance_min super(RmtSrvObj, self).__init__(symbol, line_type, size, since, debug) def get_kline(self): return self.rmt_srv_obj.get_klines(symbol=self.symbol, interval=self.type,limit=self.size) def get_kline_pd(self): return pd.DataFrame(self.get_kline(),columns=['open_time', 'open','high','low','close','volume','close_time', 'quote_asset_volume','number_of_trades','taker_buy_base_asset_volume','taker_buy_quote_asset_volume','ignore']) def balance(self, coin=''): balance = {'free': 0, 'frozen': 0} for item in self.account: if coin == item['asset']: balance = {'free': item['free'], 'frozen': item['locked']} break return balance """ 'balances': [{'asset': 'BTC', 'free': '0.00000000', 'locked': '0.00000000'}, {'asset': 'ETH', 'free': '0.43767306', 'locked': '2.74720975'}, {'asset': 'ICO', 'free': '0.00000000', 'locked': '0.00000000'}] """ def get_account(self): user = self.rmt_srv_obj.get_account() # print(user) regular_user = user['balances'] return regular_user def get_balance(self, coin): coin_info = {'coin': coin, 'free': 0, 'frozen': 0, 'balance': 0} for item in self.account: if item['asset'] == coin: coin_info['free'] = float(item['free']) coin_info['frozen'] = float(item['locked']) coin_info['balance'] = coin_info['free'] + coin_info['frozen'] break return coin_info def get_available_coins(self): coins = [] for item in self.account: free_coin = float(item['free']) frozen_coin = float(item['locked']) balance = free_coin + frozen_coin if balance > self.balance_min: coins.append({'coin': item['asset'], 'free': free_coin, 'frozen': frozen_coin, 'balance': balance}) return coins def get_all_orders(self): return self.rmt_srv_obj.get_all_orders(symbol=self.symbol) def get_order(self, order_id): order = self.rmt_srv_obj.get_order(symbol=self.symbol, orderId=order_id) if not order: return None new_order = {} if order['status'] == 'CANCELED': new_order['status'] = 'Cancelled' elif order['status'] == 'NEW': new_order['status'] = 'Not deal' elif order['status'] == 'PARTIALLY_FILLED': new_order['status'] = 'Part dealt' elif order['status'] == 'FILLED': new_order['status'] = 'Dealt' elif order['status'] == 'REJECTED': new_order['status'] = 'Rejected' elif order['status'] == 'EXPIRED': new_order['status'] = 'Expired' else: new_order['status'] = 'Error' if order['side'] == 'BUY': new_order['type'] = 'buy' else: new_order['type'] = 'sell' new_order['symbol'] = order['symbol'] new_order['timestamp'] = order['time']/1000 new_order['create_date'] = datetime.fromtimestamp(new_order['timestamp']).strftime("%Y-%m-%d %H:%M:%S") new_order['order_id'] = str(order['orderId']) new_order['amount'] = order['origQty'] new_order['avg_price'] = order['price'] new_order['deal_amount'] = order['executedQty'] new_order['price'] = order['price'] return new_order def buy(self, price, amount): self.debug('Buy order: pair(%s), price(%s), amount(%s)' % (self.symbol, price, amount)) ret = self.rmt_srv_obj.create_order(symbol=self.symbol, side=SIDE_BUY, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, price=price, quantity=amount) # self.debug(ret) try: if ret['orderId']: # self.debug('Return buy order ID: %s' % ret['orderId']) return ret['orderId'] else: self.debug('Place order failed') return None except Exception: self.debug('Error result: %s' % ret) return None def sell(self, price, amount): self.debug('Sell order: pair(%s), price(%s), amount(%s)' % (self.symbol, price, amount)) ret = self.rmt_srv_obj.create_order(symbol=self.symbol, side=SIDE_SELL, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, price=price, quantity=amount) # self.debug(ret) try: if ret['orderId']: # self.debug('Return sell order ID: %s' % ret['orderId']) return ret['orderId'] else: self.debug('Place order failed') return None except Exception: self.debug('Error result: %s' % ret) return None def cancel_orders(self): orders = self.rmt_srv_obj.get_open_orders(symbol=self.symbol) for order in orders: self.rmt_srv_obj.cancel_order(symbol=self.symbol, orderId=order['orderId'])
class FXConnector(Logger): ORDER_STATUS_NEW = 'NEW' ORDER_STATUS_PARTIALLY_FILLED = 'PARTIALLY_FILLED' ORDER_STATUS_FILLED = 'FILLED' ORDER_STATUS_CANCELED = 'CANCELED' ORDER_STATUS_PENDING_CANCEL = 'PENDING_CANCEL' ORDER_STATUS_REJECTED = 'REJECTED' ORDER_STATUS_EXPIRED = 'EXPIRED' SIDE_BUY = 'BUY' SIDE_SELL = 'SELL' ORDER_TYPE_LIMIT = 'LIMIT' ORDER_TYPE_MARKET = 'MARKET' ORDER_TYPE_STOP_LOSS = 'STOP_LOSS' ORDER_TYPE_STOP_LOSS_LIMIT = 'STOP_LOSS_LIMIT' ORDER_TYPE_TAKE_PROFIT = 'TAKE_PROFIT' ORDER_TYPE_TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT' ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' TIME_IN_FORCE_GTC = 'GTC' # Good till cancelled TIME_IN_FORCE_IOC = 'IOC' # Immediate or cancel TIME_IN_FORCE_FOK = 'FOK' # Fill or kill ORDER_RESP_TYPE_ACK = 'ACK' ORDER_RESP_TYPE_RESULT = 'RESULT' ORDER_RESP_TYPE_FULL = 'FULL' def __init__(self, key=None, secret=None): super().__init__() self.__key = key self.__secret = secret self.client = Client(key, secret) self.bs: BinanceWebsocket = None # self.connection = None self.ticker_connection = None self.user_data_connection = None def listen_symbols(self, symbols, on_ticker_received, user_data_handler): self.bs = BinanceWebsocket(self.client) self.bs.start_ticker(symbols, on_ticker_received) self.bs.start_user_info(user_data_handler) self.logInfo('Ticker and User WS initialized') def start_listening(self): self.bs.start() self.logInfo('WS listening started') def stop_listening(self): self.bs.stop_sockets() self.logInfo('Socket stopped') @retry(**DEFAULT_RETRY_SETTINGS) def cancel_order(self, sym, id): return self.client.cancel_order(symbol=sym, orderId=id) @retry(**DEFAULT_RETRY_SETTINGS) def cancel_open_orders(self, sym): orders = self.get_open_orders(sym) if orders: for order_id in orders: self.client.cancel_order(symbol=sym, orderId=order_id) def get_server_time(self): return self.client.get_server_time() @retry(**DEFAULT_RETRY_SETTINGS) def get_open_orders(self, sym): return [o['orderId'] for o in self.client.get_open_orders(symbol=sym)] @retry(**DEFAULT_RETRY_SETTINGS) def get_all_orders(self, sym, limit=500): return { o['orderId']: { 'status': o['status'], 'price': o['price'], 'stop_price': o['stopPrice'], 'vol': o['origQty'], 'vol_exec': o['executedQty'] } for o in self.client.get_all_orders(symbol=sym, limit=limit) } @retry(**DEFAULT_RETRY_SETTINGS) def get_all_tickers(self): return self.client.get_all_tickers() @retry(**DEFAULT_RETRY_SETTINGS) def get_orderbook_tickers(self): return self.client.get_orderbook_tickers() @retry(**DEFAULT_RETRY_SETTINGS) def get_order_status(self, sym, id): return self.client.get_order(symbol=sym, orderId=id) # @retry(stop_max_attempt_number=MAX_ATTEMPTS, wait_fixed=DELAY) def create_makret_order(self, sym, side, volume): return self.client.create_order( symbol=sym, side=side, type=FXConnector.ORDER_TYPE_MARKET, quantity=FXConnector.format_number(volume)) # @retry(stop_max_attempt_number=MAX_ATTEMPTS, wait_fixed=DELAY) def create_limit_order(self, sym, side, price, volume): return self.client.create_order( symbol=sym, side=side, type=FXConnector.ORDER_TYPE_LIMIT, timeInForce=FXConnector.TIME_IN_FORCE_GTC, quantity=FXConnector.format_number(volume), price=FXConnector.format_number(price)) # @retry(stop_max_attempt_number=MAX_ATTEMPTS, wait_fixed=DELAY) def create_stop_order(self, sym, side, stop_price, price, volume): return self.client.create_order( symbol=sym, side=side, type=FXConnector.ORDER_TYPE_STOP_LOSS_LIMIT, timeInForce=FXConnector.TIME_IN_FORCE_GTC, quantity=FXConnector.format_number(volume), stopPrice=FXConnector.format_number(stop_price), price=FXConnector.format_number(price)) # @retry(stop_max_attempt_number=MAX_ATTEMPTS, wait_fixed=DELAY) def create_test_stop_order(self, sym, side, price, volume): return self.client.create_test_order( symbol=sym, side=side, type=FXConnector.ORDER_TYPE_STOP_LOSS_LIMIT, timeInForce=FXConnector.TIME_IN_FORCE_GTC, quantity=FXConnector.format_number(volume), stopPrice=FXConnector.format_number(price), price=FXConnector.format_number(price)) @retry(**DEFAULT_RETRY_SETTINGS) def get_balance(self, asset): bal = self.client.get_asset_balance(asset=asset) return float(bal['free']), float(bal['locked']) @retry(**DEFAULT_RETRY_SETTINGS) def get_all_balances(self, assets: dict): res = self.client.get_account() if 'balances' in res: for bal in res['balances']: if bal['asset'] in assets: assets[bal['asset']] = { 'f': float(bal['free']), 'l': float(bal['locked']) } @retry(**DEFAULT_RETRY_SETTINGS) def get_all_balances_dict(self): res = self.client.get_account() if 'balances' in res: return { bal['asset']: { 'f': float(bal['free']), 'l': float(bal['locked']) } for bal in res['balances'] } return {} @retry(**DEFAULT_RETRY_SETTINGS) def get_exchange_info(self): return self.client.get_exchange_info() # info = self.client.get_exchange_info() # # symbol_info = None # for s in info['symbols']: # if s['symbol'] == sym: # symbol_info = s # break # # props = {} # for f in symbol_info['filters']: # props.update(f) # # props.pop('filterType', None) # # return FXConnector.ExchangeInfo(**props) @classmethod def format_number(cls, num): return '{:.08f}'.format(num)
OrderID = order["clientOrderId"] if len(order["fills"]): #if I did not buy anything, fills is empty coinOrderInfo = order["fills"][0] coinPriceBought = float(coinOrderInfo['price']) coinOrderQty = float(coinOrderInfo['qty']) else: coinPriceBought = 0 coinOrderQty = 0 break except: log("Error getting data from Binance servers, retrying.") # if I got out by timeout if Buy_Timeout: # cancel the order result = client.cancel_order(symbol=tradingPair, origClientOrderId=OrderID) if ORDER_STATUS_NEW in OrderStatus: #I did not buy anything, therefore stop here print("Did not succeed to buy") log("Did not succeed to buy") quitProgram() # when order compelted reset to false for next order orderCompleted = False print('Buy order has been made!') log("Buy order successfully made.") # once finished waiting for buy order we can process the sell order print('Processing sell order.') log("Processing sell order.")
#calculate trades - This is the magic math! price = round(((B / ratio) - B) / A, 2) price_h = round((B * (1 - ratio)) / ((ratio * A) - size), 2) price_l = round((B * (1 - ratio)) / ((ratio * A) + size), 2) print('our price is {}, attempting to buy at {}, sell at {}'.format( price, price_l, price_h)) #send trades to exchange buy_order_id = client.order_limit_buy(symbol=MARKET, quantity=AMOUNT, price=price_l)['orderId'] sell_order = client.order_limit_sell(symbol=MARKET, quantity=AMOUNT, price=price_h)['orderId'] #poll for trade execution while (True): #check each order and cancel/break on order filled time.sleep(POLL_TIME) if client.get_order(symbol=MARKET, orderId=buy_order_id)['tatus'] is "FILLED": client.cancel_order(symbol=MARKET, orderId=sell_order_id) print('{}: bought {} {} at {}'.format(time.strftime('%x %X')), AMOUNT, CURRENCY_A, price_l) break if client.get_order(symbol=MARKET, orderId=sell_order_id)['tatus'] is "FILLED": client.cancel_order(symbol=MARKET, orderId=buy_order_id) print('{}: sold {} {} at {}'.format(time.strftime('%x %X')), AMOUNT, CURRENCY_A, price_h) break
class BnOrder(): def __init__(self) -> None: self.chat_id = BN_CHAT_ID_GROUP # self.chat_id = BN_CHAT_ID self.client = Client(BN_API_KEY, BN_API_SECRET) # self.client = Client(BN_TEST_API_KEY, BN_TEST_API_SECRET) # self.client.API_URL = 'https://testnet.binance.vision/api' self.client.PUBLIC_API_VERSION = "v3" self.bm = bn_UserSocket(self.client) self.tso = None def process_message(self, msg): try: self.send_chat_message("Account Update:\n" + json.dumps(msg)) except Exception as e: logging.error("Process Account Update Error:" + str(e)) self.send_chat_message("Process Account Update Error: " + str(e)) def create_test_order(self, chat_id, symbol, buy_price, amount): try: if self.is_authorized(chat_id): symbol = symbol.strip().upper() + "BTC" logging.error("SYMBOL: " + symbol) order = self.client.create_test_order( symbol=symbol, side=SIDE_BUY, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=amount, price=buy_price) text = "TEST ORDER CREATED: " + json.dumps(order) self.send_chat_message(text) except Exception as e: logging.error("Test Order Failed error:" + str(e)) self.send_chat_message("CREATE TEST ORDER FAILED: " + str(e)) def create_market_buy(self, chat_id, symbol, amount, to_currency="BTC"): try: if self.is_authorized(chat_id): symbol = symbol.strip().upper() + to_currency.strip().upper() amount = round(amount, 8) order = self.client.order_market_buy(symbol=symbol, quantity=amount) text = "REAL BUY CREATED: " + json.dumps(order) self.send_chat_message(text) except Exception as e: logging.error("Test Order Failed error:" + str(e)) self.send_chat_message("CREATE ORDER FAILED: " + str(e)) raise e def get_exchange_symbol(self, sell_coin, buy_coin): try: symbol = buy_coin.strip().upper() + sell_coin.strip().upper() info = self.client.get_symbol_info(symbol) if info is not None: result = self.client.get_symbol_ticker(symbol=symbol) s, t = self.get_step_size(info) return symbol, "BUY", result["price"], info, s, t except Exception as e: logging.error("Symbol fail:" + str(e)) try: symbol = sell_coin.strip().upper() + buy_coin.strip().upper() info = self.client.get_symbol_info(symbol) if info is not None: result = self.client.get_symbol_ticker(symbol=symbol) s, t = self.get_step_size(info) return symbol, "SELL", result["price"], info, s, t except Exception as e: logging.error("Symbol fail:" + str(e)) raise e def get_step_size(self, info): step_size = 0.0 tick_size = 0.0 logging.error("INFO:" + json.dumps(info)) for f in info['filters']: if f['filterType'] == 'LOT_SIZE': step_size = float(f['stepSize']) if f['filterType'] == 'PRICE_FILTER': tick_size = float(f['tickSize']) return step_size, tick_size def create_market_conversion(self, chat_id, sell_coin, total_spend, buy_coin): try: if self.is_authorized(chat_id): symbol, sale_type, price, _, step_size, _ = self.get_exchange_symbol( sell_coin, buy_coin) precision = int(round(-math.log(step_size, 10), 0)) logging.error("AMOUNT:" + str(total_spend)) logging.error("step_size:" + str(step_size)) logging.error("precision:" + str(precision)) logging.error("SALE TYPE:" + str(sale_type)) if sale_type == "SELL": amt_str = "{:0.0{}f}".format(total_spend, precision) logging.error("QUANTITY:" + str(amt_str)) order = self.client.order_market_sell(symbol=symbol, quantity=amt_str) text = "SELL " + str( amt_str) + " of " + symbol + "\nOrderId:" + str( order["orderId"]) + " STATUS:" + str( order["status"]) + "\nFILLS:\n" + json.dumps( order["fills"]) else: amount = total_spend / float(price) amt_str = "{:0.0{}f}".format(amount, precision) logging.error("QUANTITY:" + str(amt_str)) order = self.client.order_market_buy(symbol=symbol, quantity=amt_str) text = "BUY " + str( amt_str) + " of " + symbol + "\nOrderId:" + str( order["orderId"]) + " STATUS:" + str( order["status"]) + "\nFILLS:\n" + json.dumps( order["fills"]) self.send_chat_message(text) return amt_str, sale_type, symbol, buy_coin, sell_coin except Exception as e: logging.error("Order Failed error:" + str(e)) self.send_chat_message("CREATE ORDER FAILED: " + str(e)) raise e def create_trailing_stop_limit(self, market, buy_coin, sell_coin, type, stop_percentage, interval): try: if self.tso is not None and self.tso.running is True: self.send_chat_message( "OPEN Trailing Stop Limit, Cancel First. ") else: self.tso = TrailingStopLimit(chat_id=self.chat_id, client=self.client, market=market, buy_coin=buy_coin, sell_coin=sell_coin, type=type, stop_percentage=stop_percentage, interval=interval) run(self.tso) except Exception as e: logging.error("Order Failed error:" + str(e)) self.send_chat_message("CREATE ORDER FAILED: " + str(e)) raise e def create_oco_conversion(self, chat_id, sell_coin, amount, buy_coin): try: if self.is_authorized(chat_id): symbol, sale_type, price, info, step_size, price_tick_size = self.get_exchange_symbol( sell_coin, buy_coin) precision = int(round(-math.log(step_size, 10), 0)) amt_str = "{:0.0{}f}".format(float(amount), precision) price_precision = int(round(-math.log(price_tick_size, 10), 0)) logging.error("QUANTITY:" + str(amt_str)) if sale_type == "SELL": # BUY Orders: Limit Price < Last Price < Stop Price sell_price = "{:0.0{}f}".format( float(price) * 0.97, price_precision) stop_price = "{:0.0{}f}".format( float(price) * 1.01, price_precision) stop_limit_price = "{:0.0{}f}".format( float(price) * 1.01, price_precision) order_oco = self.client.create_oco_order( symbol=symbol, side='BUY', quantity=amt_str, price=sell_price, stopPrice=stop_price, stopLimitPrice=stop_limit_price, stopLimitTimeInForce='GTC') else: # TODO check filters # quantity >= minQty # quantity <= maxQty # (quantity-minQty) % stepSize == 0 # SELL Orders: Limit Price > Last Price > Stop Price sell_price = "{:0.0{}f}".format( float(price) * 1.03, price_precision) stop_price = "{:0.0{}f}".format( float(price) * 0.99, price_precision) stop_limit_price = "{:0.0{}f}".format( float(price) * 0.989, price_precision) logging.error("CURRENT PRICE: " + str(price) + " SELL PRICE: " + str(sell_price) + " STOP PRICE:" + str(stop_price) + " STOP LIMIT PRICE:" + str(stop_limit_price)) order_oco = self.client.create_oco_order( symbol=symbol, side='SELL', quantity=amt_str, price=sell_price, stopPrice=stop_price, stopLimitPrice=stop_limit_price, stopLimitTimeInForce='GTC') oco_text = order_oco[ "listOrderStatus"] + " " + self.format_orders( order_oco["orderReports"]) self.send_chat_message(oco_text) except Exception as e: logging.error("OCO Failed error:" + str(e)) self.send_chat_message("CREATE OCO FAILED: " + str(e)) raise e def format_orders(self, orders): oco_text = "OPEN ORDERS:\n" for o in orders: if o["type"] == "STOP_LOSS_LIMIT": oco_text = oco_text + "\nSTOP LOSS:\n" + o["side"] + " " + o[ "symbol"] + "- Stop Limit: " + o[ "stopPrice"] + " Price: " + o["price"] + " Qty:" + o[ "origQty"] + "\n" elif o["type"] == "LIMIT_MAKER": oco_text = oco_text + "\nPROFIT:\n" + o["side"] + " " + o[ "symbol"] + "- Price: " + o["price"] + " Qty:" + o[ "origQty"] + "\n" else: oco_text = oco_text + "\n" + json.dumps(o) + "\n" oco_text = oco_text + "\nCheck Order Status with: /checkorders\n" oco_text = oco_text + "\nCancel All Orders with: /cancelorders\n" return oco_text def create_order(self, chat_id, selling_coin, buying_coin, price, amount): try: if self.is_authorized(chat_id): symbol, sale_type, price, info, step_size = self.get_exchange_symbol( selling_coin, buying_coin) precision = int(round(-math.log(step_size, 10), 0)) if sale_type == "SELL": amt_str = "{:0.0{}f}".format(float(amount), precision) order = self.client.order_limit_sell(symbol=symbol, quantity=amt_str, price=round( float(price), 5)) else: amt_str = "{:0.0{}f}".format(float(amount), precision) order = self.client.order_limit_buy(symbol=symbol, quantity=amt_str, price=round( float(price), 5)) text = "LIMIT ORDER CREATED:\n" + json.dumps(order) self.send_chat_message(text) # self.last_order_id = order['orderId'] # saved_orders = r.get(LIVE_ORDER_KEY.format(self.chat_id)) # if saved_orders is None: # r.set(LIVE_ORDER_KEY.format(self.chat_id), json.dumps({"orders": [order]})) # else: # ar = json.loads(saved_orders.decode("utf-8")) # ar["orders"].append(order) # r.set(LIVE_ORDER_KEY.format(self.chat_id), json.dumps(ar)) self.check_orders(chat_id=chat_id) except Exception as e: logging.error("Test Order Failed error:" + str(e)) self.send_chat_message("CREATE ORDER FAILED: " + str(e)) def check_orders(self, chat_id): try: if self.is_authorized(chat_id): orders = self.client.get_open_orders() self.send_chat_message(self.format_orders(orders)) # saved_orders = r.get(LIVE_ORDER_KEY.format(self.chat_id)) # if saved_orders is not None: # ar = json.loads(saved_orders.decode("utf-8")) # self.send_chat_message("SAVED ORDERS: " + json.dumps(ar)) except Exception as e: logging.error("Check Order Failed error:" + str(e)) self.send_chat_message("CHECK ORDERS FAILED: " + str(e)) def cancel_open_orders(self, chat_id): try: if self.is_authorized(chat_id): if self.tso is not None: self.tso.running = False FORCE_STOP = True orders = self.client.get_open_orders() for order in orders: result = self.client.cancel_order(symbol=order['symbol'], orderId=order["orderId"]) text = "CANCEL RESULT:\n" + json.dumps(result) self.send_chat_message(text) except Exception as e: logging.error("Cancel Order Failed error:" + str(e)) orders = self.client.get_open_orders() if len(orders) > 0: self.send_chat_message("FAILED TO CANCEL ORDER: " + str(e)) def get_usd_price(self, symbol): usd_price = 0 try: usd_price = self.client.get_symbol_ticker(symbol=symbol.upper() + "USDT") return usd_price["price"] except Exception as e: logging.error("USD Price Failed error:" + symbol + " -- " + str(e)) return usd_price def get_btc_price(self, symbol): btc_price = 0 try: btc_price = self.client.get_symbol_ticker(symbol=symbol.upper() + "BTC") return btc_price["price"] except Exception as e: logging.error("BTC Price Failed error:" + symbol + " -- " + str(e)) return btc_price def round_sense(self, price): price = float(price) if price is None or price == 0: return 0 if price > 1000: return int(price) if price > 100: return round(price, 1) if price > 10: return round(price, 2) if price > 0.01: return round(price, 4) if price > 0.001: return round(price, 5) return round(price, 8) def get_user_balance(self, symbol): try: balance = self.client.get_asset_balance(asset=symbol) logging.error("CHeck" + json.dumps(balance)) return float(balance["free"]) except Exception as e: logging.error("Account settings error:" + str(e)) self.send_chat_message("FAILED TO GET BALANCE: " + str(e)) return 0 def get_wallet(self, chat_id): try: if self.is_authorized(chat_id): info = self.client.get_account() balances = info["balances"] # "balances": [{"asset": "BNB", "free": "1014.21000000", "locked": "0.00000000"}, {"asset": "BTC", "free": "0.92797152", "locked": "0.00000000"}, {"asset": "BUSD", "free": "10000.00000000", "locked": "0.00000000"}, {"asset": "ETH", "free": "100.00000000", "locked": "0.00000000"}, {"asset": "LTC", "free": "500.00000000", "locked": "0.00000000"}, {"asset": "TRX", "free": "500000.00000000", "locked": "0.00000000"}, {"asset": "USDT", "free": "10000.00000000", "locked": "0.00000000"}, {"asset": "XRP", "free": "50000.00000000", "locked": "0.00000000"}] out = "<pre>FREE LOCKED BTC USD\n" val = 0 btc_val = 0 for b in balances: quantity = float(b["free"]) + float(b["locked"]) if quantity > 0: if b["asset"].upper() != "BETH": usd_price = float(self.get_usd_price(b["asset"])) btc_price = float(self.get_btc_price(b["asset"])) if b["asset"].upper() in ["BUSD", "USDT"]: usd_value = float(b["free"]) + float( b["locked"]) if btc_price > 0: btc_value = usd_value / btc_price else: btc_value = 0 else: usd_value = usd_price * quantity if b["asset"].upper() == "BTC": btc_price = 1 btc_value = float(b["free"]) + float( b["locked"]) else: btc_value = btc_price * quantity val = val + usd_value btc_val = btc_val + btc_value out = out + "\n" + b["asset"] + " @ $" + str( round(usd_price, 2)) + " BTC" + str(btc_price) + "\n" out = out + str(self.round_sense(b["free"])).ljust( 10, ' ') + " " + str( self.round_sense(b["locked"])).ljust( 8, ' ') + " " + str( self.round_sense(btc_value) ).ljust(8, ' ') + " " + str( self.round_sense(usd_value)) + "\n" out = out + "</pre>\n$" + str(round(val, 2)) + "\n₿" + str( round(btc_val, 6)) self.send_chat_message(out) except Exception as e: logging.error("Account settings error:" + str(e)) self.send_chat_message("FAILED TO GET WALLET: " + str(e)) def send_chat_message(self, text): try: bot_key = TELEGRAM_BOT send_message_url = f'https://api.telegram.org/bot{bot_key}/sendMessage?chat_id={self.chat_id}&text={text}&parse_mode=HTML' resp = requests.post(send_message_url) except Exception as e: logging.error("Failed to send chat message:" + str(e)) def is_authorized(self, chat_id): if self.chat_id is None or int(self.chat_id) != int(chat_id): raise Exception("Unauthorized Chat, use only correct chat.") starter = self.bm.start_user_socket(self.process_message) logging.error("Stream resp:" + str(starter)) return True def get_symbol_trades(self, chat_id, symbol): try: if self.is_authorized(chat_id): trades = self.client.get_my_trades(symbol=symbol.upper() + 'BTC') if len(trades) > 0: sorted_trades = sorted(trades, key=lambda k: k['time'], reverse=True) out = "<pre>DATE TIME SYMBOL SIDE PRICE QUANTITY\n" for t in sorted_trades: if t["isBuyer"] == True: action = "BUY" else: action = "SELL" time_str = datetime.datetime.fromtimestamp( int(t["time"]) / 1000).strftime('%d-%m %H:%M') out = out + time_str + " " + t[ "symbol"] + " " + action + " " + t[ "price"] + " " + t["qty"] + "\n" out = "TRADES vs BTC:\n" + out + "</pre>" self.send_chat_message(out) trades = self.client.get_my_trades(symbol=symbol.upper() + 'USDT') if len(trades) > 0: sorted_trades = sorted(trades, key=lambda k: k['time'], reverse=True) out = "<pre>DATE TIME SYMBOL SIDE PRICE QUANTITY\n" for t in sorted_trades: if t["isBuyer"] == True: action = "BUY" else: action = "SELL" time_str = datetime.datetime.fromtimestamp( int(t["time"]) / 1000).strftime('%d-%m %H:%M') out = out + time_str + " " + t[ "symbol"] + " " + action + " " + t[ "price"] + " " + t["qty"] + "\n" out = "TRADES vs USDT:\n" + out + "</pre>" self.send_chat_message(out) except Exception as e: logging.error("Failed to get trades for symbol chat message:" + str(e)) self.send_chat_message("Failed to get trades for " + symbol + " -- " + str(e))
" Oops 2 ! \n") client = Client(config.API_KEY, config.API_SECRET, tld='com') continue for tick_2 in list_of_tickers: if tick_2['symbol'] == symbolTicker: symbolPrice = float(tick_2['price']) # END GET PRICE if (symbolPrice < auxPrice): try: result = client.cancel_order( symbol=symbolTicker, orderId=buyOrder.get('orderId')) time.sleep(3) except Exception as e: with open("BNBBTC_scalper.txt", "a") as myfile: myfile.write( str(datetime.datetime.now()) + " - an exception occured - {}".format(e) + "Error Canceling Oops 4 ! \n") break buyOrder = client.create_order( symbol=symbolTicker, side='BUY', type='STOP_LOSS_LIMIT',
if prev == 'BTC' and current == 'None': pair_symbol = 'BTCUSDT' order_type = 'SELL' if prev == 'ETH' and current == 'None': pair_symbol = 'ETHUSDT' order_type = 'SELL' # about buy/sell precision = round(np.log(lot_size_dict[pair_symbol])/np.log(10)) if precision >= 0: round_size = 0 else: round_size = int(-precision) min_notional = min_notional_dict[pair_symbol] orders = client.get_open_orders(symbol=pair_symbol) print orders for order in orders: cancel_res = client.cancel_order( symbol=pair_symbol, orderId=order['orderId']) time.sleep(0.1) if order_type == 'BUY': slowly_buy(client, pair_symbol, min_notional, round_size, 1) elif order_type == 'SELL': slowly_sell(client, pair_symbol, min_notional, round_size, 1)
class Binance(object): def __init__(self): print("-- Connecting to binance ...") self.client = Client(os.environ['BINANCE_API_KEY'], os.environ['BINANCE_API_SECRET']) def get_balance(self, coin): print("-- Getting balance for " + coin) assetJSON = self.client.get_asset_balance(coin) return float(assetJSON['free']) def get_price(self, coin, coinfrom): priceJSON = self.client.get_symbol_ticker(symbol=coin + coinfrom) return float(priceJSON['price']) def buy_market(self, coin, coinfrom, ignored, quantity, testmode): print("-- Buying market " + str(quantity) + " " + coin + " from " + coinfrom) self.symbol = coin + coinfrom if testmode: print("[TEST] " + str(quantity) + " " + coin + " buy at : " + str(ignored) + " " + coinfrom + " (price is ignored on Binance)") else: orderBuy = self.client.create_order( symbol=coin + coinfrom, side=self.client.SIDE_BUY, type=self.client.ORDER_TYPE_MARKET, # timeInForce=clientBinance.TIME_IN_FORCE_GTC, quantity=quantity, # price=repr(originalPrice) ) completed = False while not completed: time.sleep(0.2) self.orderID = orderBuy['clientOrderId'] orderBuySt = self.client.get_order(symbol=coin + coinfrom, orderId=self.orderID) print("+ Order buy status : " + orderBuySt['status'] + " at : " + orderBuySt['price']) if not orderBuySt == self.client.ORDER_STATUS_NEW: completed = True def sell_market(self, coin, coinTo, ignored, quantity, testmode): print("-- Selling market " + str(quantity) + " " + coin + " to " + coinTo) if testmode: print("[TEST] " + str(quantity) + " " + coin + " sell at : " + str(ignored) + " " + coinTo + " (price is ignored on Binance)") else: orderSell = self.client.create_test_order( symbol=coin + coinTo, side=self.client.SIDE_SELL, type=self.client.ORDER_TYPE_MARKET, # timeInForce=clientBinance.TIME_IN_FORCE_GTC, quantity=quantity, # price=repr(newPrice) ) completed = False while not completed: time.sleep(0.2) orderSellId = orderSell['clientOrderId'] orderSellSt = self.client.get_order(symbol=coin + coinTo, orderId=orderSellId) print("+ Order sell status : " + orderSellSt['status'] + " at : " + orderSellSt['price']) if not orderSellSt == self.client.ORDER_STATUS_NEW: completed = True def cancel_order(self): print("-- Canceling order") self.client.cancel_order(symbol=self.symbol, orderId=self.orderID) self.symbol = None self.orderID = None
class Binance(Exchange): def __init__(self, api_key: str = None, secret_key: str = None): super().__init__() self.client = Client(api_key, secret_key) filters = self.client.get_exchange_info()['symbols'] self.filters = { filt['symbol']: { 'min_order_size': Decimal(filt['filters'][1]['minQty']), 'max_order_size': Decimal(filt['filters'][1]['maxQty']), 'order_step': Decimal(filt['filters'][1]['stepSize']), 'min_notional': Decimal(filt['filters'][2]['minNotional']), 'min_price': Decimal(filt['filters'][0]['minPrice']), 'max_price': Decimal(filt['filters'][0]['maxPrice']), 'price_step': Decimal(filt['filters'][0]['tickSize']), 'base': filt['quoteAsset'], 'commodity': filt['baseAsset'], } for filt in filters } def get_mid_price_orderbooks(self, products=None): prices_list = self.client.get_all_tickers() orderbooks = [] for price_symbol in prices_list: currency_pair = binance_product_to_currencies( price_symbol['symbol']) product = '_'.join(currency_pair) if products is not None and product not in products: continue orderbook = OrderBook(product, Decimal(price_symbol['price'])) if orderbook.get_wall_ask() <= Decimal('1e-8'): continue orderbooks.append(orderbook) return orderbooks def get_orderbooks(self, products=None, depth: int = 1): if depth != 1: raise NotImplementedError if products is not None: products = set(products) return self.get_orderbooks_of_depth1(products) def get_orderbooks_of_depth1(self, products): """ get all orderbooks with depth equal to 1, then filter out those, which symbol is not in specified products """ books_list = self.client.get_orderbook_tickers() orderbooks = [] for book in books_list: currency_pair = binance_product_to_currencies(book['symbol']) product = '_'.join(currency_pair) if products is not None and product not in products: continue orderbook = OrderBook(product, { 'ask': Decimal(book['askPrice']), 'bid': Decimal(book['bidPrice']) }) if orderbook.get_wall_ask() <= Decimal('1e-8'): continue orderbooks.append(orderbook) return orderbooks def get_taker_fee(self, product): return Decimal('0.001') def get_maker_fee(self, product): return Decimal('0.001') def through_trade_currencies(self): return {'BTC', 'BNB', 'ETH', 'USDT'} def get_resources(self): return { asset_balance['asset']: Decimal(asset_balance['free']) for asset_balance in self.client.get_account()['balances'] if Decimal(asset_balance['free']) > Decimal(0) } def place_limit_order(self, order): logger.info("creating limit order - {}".format(str(order))) order = self._validate_order(order) logger.info("validated order - {}".format(str(order))) if order is None: return symbol = ''.join(order.product.split('_')) side = order._action.name quantity = order._quantity new_order_resp_type = 'FULL' price = order._price try: resp = self.client.create_order( side=side, symbol=symbol, quantity=quantity, newOrderRespType=new_order_resp_type, price=price.to_eng_string(), type=self.client.ORDER_TYPE_LIMIT_MAKER) except BinanceAPIException as e: return e order_id = resp['orderId'] client_order_id = resp['clientOrderId'] logger.info("order response - {}".format(str(resp))) return { 'symbol': resp['symbol'], 'orderId': order_id, 'clientOrderId': client_order_id } def place_market_order(self, order, price_estimates): """ place market order uses price estimates to check min notional and resources for buy orders returns dict with keys {'orderId', 'clientOrderId', 'executed_quantity', 'mean_price'} with corresponding values also returns commission_asset: fee for example { "orderId": 28, "clientOrderId": "6gCrw2kRUAF9CvJDGP16IP", "executed_quantity": Decimal("10.00000000"), "mean_price": Decimal("3998.30000000"), "commission_USDT": Decimal("19.99150000"), "commission_BNB": Decimal("11.66365227") } """ logger.info("creating market order - {}".format(str(order))) order = self._validate_order(order, price_estimates) logger.info("validated order - {}".format(str(order))) if order is None: return symbol = ''.join(order.product.split('_')) side = order._action.name quantity = order._quantity newOrderRespType = 'FULL' try: resp = self.client.order_market(side=side, symbol=symbol, quantity=quantity, newOrderRespType=newOrderRespType) except BinanceAPIException as e: return e parsed_response = self.parse_market_order_response(resp) parsed_response['price_estimates'] = price_estimates parsed_response['product'] = '_'.join( binance_product_to_currencies(parsed_response['symbol'])) logger.info("parsed order response - {}".format(str(parsed_response))) return parsed_response def parse_market_order_response(self, resp): order_id = resp['orderId'] client_order_id = resp['clientOrderId'] executed_quantity = Decimal(resp['executedQty']) fills = resp['fills'] value = sum( Decimal(fill['qty']) * Decimal(fill['price']) for fill in fills) total_quantity = sum(Decimal(fill['qty']) for fill in fills) mean_price = value / total_quantity commissions = {} for fill in fills: if fill['commissionAsset'] not in commissions: commissions[fill['commissionAsset']] = Decimal() commissions[fill['commissionAsset']] += Decimal(fill['commission']) ret = { 'symbol': resp['symbol'], 'orderId': order_id, 'clientOrderId': client_order_id, 'executed_quantity': executed_quantity, 'mean_price': mean_price, 'side': resp['side'] } for asset, fee in commissions.items(): ret['commission_' + asset] = fee return ret def _validate_order(self, order, price_estimates=None): resources = self.get_resources() symbol = ''.join(order.product.split('_')) filt = self.filters[symbol] if order._quantity < filt['min_order_size']: return if order._quantity > filt['max_order_size']: order._quantity = filt['max_order_size'] order._quantity = quantize(order._quantity, filt['order_step']) if order._price is not None: if order._price < filt['min_price']: return if order._price > filt['max_price']: return if order._price % filt['price_step'] != 0: order._price = quantize(order._price, filt['price_step'], down=order._action.name == 'BUY') if order._price is None: value = (price_estimates[filt['commodity']] / price_estimates[filt['base']]) * order._quantity else: value = order._price * order._quantity if value < filt['min_notional']: return if order._action.name == 'SELL': if resources[filt['commodity']] < order._quantity: order._quantity = resources[filt['commodity']] order = self._validate_order(order, price_estimates) else: if order._price is None: price_commodity = price_estimates[filt['commodity']] price_base = price_estimates[filt['base']] price = price_commodity / price_base else: price = order._price if resources[filt['base']] < order._quantity * price: order._quantity = resources[filt['base']] / (price * Decimal('1.0001')) # any number order = self._validate_order(order, price_estimates) return order def get_order(self, params): """ symbol or product: str order_id or orderId: int client_order_id or origClientOrderId: str, optional :return: similiar to example { "symbol": "LTCBTC", "orderId": 1, "clientOrderId": "myOrder1", "price": "0.1", "origQty": "1.0", "orig_quantity": "1.0", "executedQty": "0.0", "executed_quantity": "0.0", "status": "NEW", "timeInForce": "GTC", "type": "LIMIT", "side": "BUY", "stopPrice": "0.0", "icebergQty": "0.0", "time": 1499827319559 } """ logger.info("get order = {}".format(str(params))) d = self._parse_params(params) resp = self.client.get_order(**d) resp.update({ 'orig_quantity': resp['origQty'], 'executed_quantity': resp['executedQty'] }) logger.info("get order response - {}".format(str(resp))) return resp def cancel_limit_order(self, params): """ symbol or product: str order_id or orderId: int client_order_id or origClientOrderId: str, optional :return: similiar to example { "symbol": "LTCBTC", "origClientOrderId": "myOrder1", "orderId": 1, "clientOrderId": "cancelMyOrder1" } """ logger.info("canceled order - {}".format(str(params))) d = self._parse_params(params) try: resp = self.client.cancel_order(**d) except BinanceAPIException as e: if e.message != "UNKNOWN_ORDER": raise e logger.warning("Exception{code} with message ={message}".format( code=e.code, message=e.message)) resp = {} return resp def _parse_params(self, params): d = {} assert 'product' in params or 'symbol' in params if 'product' in params: d['symbol'] = ''.join(params['product'].split('_')) else: d['symbol'] = params['symbol'] if 'order_id' in params or 'orderId' in params: d['orderId'] = params.get('order_id') or params['orderId'] if 'origClientOrderId' in params or 'orig_client_order_id' in params: d['origClientOrderId'] = params.get( 'orig_client_order_id') or params['origClientOrderId'] assert d.get('origClientOrderId') is not None or d.get( 'orderId') is not None return d
class Bclient(): def __init__(self, apiKey, apiSecret): self.client = Client(apiKey, apiSecret) def showBalances(self): try: accountInfo = self.client.get_account() balances = accountInfo['balances'] for balance in balances: if float(balance['free']) > 0 or float(balance['locked']) > 0: print(balance['asset'] + " - " + "Quantity: " + balance['free'] + " - " + "Locked: " + balance['locked']) except Exception as e: logging.error(e) def showAssetBalance(self, asset): try: accountInfo = self.client.get_asset_balance(asset=asset) return accountInfo['free'] except Exception as e: logging.error(e) def getTicker(self, symbol): try: ticker = self.client.get_ticker(symbol=symbol) print("") print(ticker['symbol'] + " - " + "Last Price: " + ticker['lastPrice'] + " - " + "Volume: " + ticker['volume']) print("Price Change: " + ticker['priceChange'] + " - " + "Percentage Change: " + ticker['priceChangePercent'] + "%") print("Bid Price: " + ticker['bidPrice'] + " - " + "Ask Price: " + ticker['askPrice']) print("") except Exception as e: logging.error(e) def getSymbolDetails(self, symbol): try: info = self.client.get_symbol_info(symbol) assetDetails = [] assetDetails.append(info["baseAsset"]) assetDetails.append(info["baseAssetPrecision"]) assetDetails.append(info["quoteAsset"]) assetDetails.append(info["quotePrecision"]) assetDetails.append(info["filters"][2]["stepSize"]) return assetDetails except Exception as e: logging.error(e) def getOrderVolume(self, assetAmount, percentage): try: percentageDecimal = float(percentage) / 100.0 return (float(assetAmount) / percentageDecimal) / 100 except Exception as e: logging.error(e) def getOrderPrecision(self, assetVolume, precision): try: precision = precision.rstrip("0") decimals = str(precision[::-1].find('.')) strAssetVolume = str(assetVolume) volumeBeforeSplit = strAssetVolume.split(".") volumeBeforeDecimal = str(volumeBeforeSplit[0]) volumeAfterDecimal = str(volumeBeforeSplit[1]) afterDecimalPrecision = volumeAfterDecimal[:int(decimals)] return str(volumeBeforeDecimal) + "." + str(afterDecimalPrecision) except Exception as e: logging.error(e) def createBuyLimitOrder(self, symbol, price, percentage): try: symbolDetails = self.getSymbolDetails(symbol) stepSize = symbolDetails[4] quoteBalance = self.showAssetBalance(asset=symbolDetails[2]) volumeSize = self.getOrderVolume(quoteBalance, percentage) preciseVolumeSize = self.getOrderPrecision(volumeSize, stepSize) print("Quote Balance: " + quoteBalance) print("Quote Volume: " + str(preciseVolumeSize)) print(self.client.order_limit_buy(symbol=symbol, quantity=str(preciseVolumeSize), price=str(price))) except Exception as e: logging.error(e) def createSellLimitOrder(self, symbol, price, percentage): try: symbolDetails = self.getSymbolDetails(symbol) stepSize = symbolDetails[4] baseBalance = self.showAssetBalance(asset=symbolDetails[0]) volumeSize = self.getOrderVolume(baseBalance, percentage) preciseVolumeSize = self.getOrderPrecision(volumeSize, stepSize) print("Base Balance: " + baseBalance) print("Base Volume: " + str(preciseVolumeSize)) self.client.order_limit_sell(symbol=symbol, quantity=str(preciseVolumeSize), price=str(price)) except Exception as e: logging.error(e) def getOpenOrders(self, symbol): try: orders = self.client.get_open_orders(symbol=symbol) orderList = [] for order in orders: time = order['time'] / 1000.0 cleaned_quantity = ("%.2f" % float(order['origQty'])) cleaned_time = datetime.datetime.fromtimestamp(time).strftime('%Y-%m-%d %H:%M') orderList.append(order['orderId']) print("OrderId: " + str(order['orderId']) + " - Price: " + str(order['price']) + " - Quantity: " + str(cleaned_quantity)+ " - Time: " + str(cleaned_time) + " - Executed Qty: " + order['executedQty']) return orderList except Exception as e: logging.error(e) def cancelAllOpenOrders(self, symbol): try: orders = self.getOpenOrders(symbol) for order in orders: cancel = self.client.cancel_order(symbol=symbol, orderId=order) print("Order Id: " + str(cancel['orderId']) + " - " + "is " + cancel['status']) except Exception as e: logging.error(e)
class BinanceStore(with_metaclass(MetaSingleton, object)): _GRANULARITIES = { (TimeFrame.Minutes, 1): '1m', (TimeFrame.Minutes, 3): '3m', (TimeFrame.Minutes, 5): '5m', (TimeFrame.Minutes, 15): '15m', (TimeFrame.Minutes, 30): '30m', (TimeFrame.Minutes, 60): '1h', (TimeFrame.Minutes, 120): '2h', (TimeFrame.Minutes, 240): '4h', (TimeFrame.Minutes, 360): '6h', (TimeFrame.Minutes, 480): '8h', (TimeFrame.Minutes, 720): '12h', (TimeFrame.Days, 1): '1d', (TimeFrame.Days, 3): '3d', (TimeFrame.Weeks, 1): '1w', (TimeFrame.Months, 1): '1M', } BrokerCls = None # Broker class will autoregister DataCls = None # Data class will auto register @classmethod def getdata(cls, *args, **kwargs): """Returns ``DataCls`` with args, kwargs""" return cls.DataCls(*args, **kwargs) @classmethod def getbroker(cls, *args, **kwargs): """Returns broker with *args, **kwargs from registered ``BrokerCls``""" return cls.BrokerCls(*args, **kwargs) def __init__(self, api_key, api_secret, coin_refer, coin_target, retries=5): self.binance = Client(api_key, api_secret) self.binance_socket = BinanceSocketManager(self.binance) self.coin_refer = coin_refer self.coin_target = coin_target self.retries = retries self._precision = None self._step_size = None self._cash = 0 self._value = 0 self.get_balance() def retry(method): @wraps(method) def retry_method(self, *args, **kwargs): for i in range(self.retries): time.sleep(500 / 1000) # Rate limit try: return method(self, *args, **kwargs) except BinanceAPIException: if i == self.retries - 1: raise return retry_method @retry def cancel_order(self, order_id): try: self.binance.cancel_order(symbol=self.symbol, orderId=order_id) except BinanceAPIException as api_err: if api_err.code == -2011: # Order filled return else: raise api_err except Exception as err: raise err @retry def create_order(self, side, type, size, price): return self.binance.create_order(symbol=self.symbol, side=side, type=type, timeInForce=TIME_IN_FORCE_GTC, quantity=self.format_quantity(size), price=self.strprecision(price)) @retry def close_open_orders(self): orders = self.binance.get_open_orders(symbol=self.symbol) for o in orders: self.cancel_order(o['orderId']) def format_quantity(self, size): precision = self.step_size.find('1') - 1 if precision > 0: return '{:0.0{}f}'.format(size, precision) return floor(int(size)) @retry def get_asset_balance(self, asset): balance = self.binance.get_asset_balance(asset) return float(balance['free']), float(balance['locked']) def get_balance(self): free, locked = self.get_asset_balance(self.coin_target) self._cash = free self._value = free + locked def get_interval(self, timeframe, compression): return self._GRANULARITIES.get((timeframe, compression)) def get_precision(self): symbol_info = self.get_symbol_info(self.symbol) self._precision = symbol_info['baseAssetPrecision'] def get_step_size(self): symbol_info = self.get_symbol_info(self.symbol) for f in symbol_info['filters']: if f['filterType'] == 'LOT_SIZE': self._step_size = f['stepSize'] @retry def get_symbol_info(self, symbol): return self.binance.get_symbol_info(symbol) @property def precision(self): if not self._precision: self.get_precision() return self._precision def start_socket(self): if self.binance_socket.is_alive(): return self.binance_socket.daemon = True self.binance_socket.start() @property def step_size(self): if not self._step_size: self.get_step_size() return self._step_size def stop_socket(self): self.binance_socket.close() reactor.stop() self.binance_socket.join() def strprecision(self, value): return '{:.{}f}'.format(value, self.precision) @property def symbol(self): return '{}{}'.format(self.coin_refer, self.coin_target)
class BinanceApi(object): def __init__(self, api_key, api_secret, order_currency, payment_currency): self.api_key = api_key self.api_secret = api_secret self.client = Client(self.api_key, self.api_secret) self.bsm = BinanceSocketManager(client=self.client) self.orders = {} self.asset_balance = {} self.order_currency = order_currency self.payment_currency = payment_currency self.symbol = self.order_currency + self.payment_currency self.current_price = {} self.current_depth = {} self.bsm.start_symbol_ticker_socket(self.symbol, self._process_ticker_message) self.bsm.start_depth_socket(self.symbol, self._process_depth_message, BinanceSocketManager.WEBSOCKET_DEPTH_5) self.bsm.start_user_socket(self._process_user_info_message) self.bsm.start() self._set_asset_balance() def _process_ticker_message(self, msg): self.current_price = { "price": float(msg['c']), "timestamp": time.time() * 1000 } #print self.current_price def _process_depth_message(self, msg): asks_depth = [] bids_depth = [] for ask in msg['asks']: asks_depth.append({ "price": float(ask[0]), "quantity": float(ask[1]) }) for bid in msg['bids']: bids_depth.append({ "price": float(bid[0]), "quantity": float(bid[1]) }) self.current_depth = { "asks": asks_depth, "bids": bids_depth, "timestamp": time.time() * 1000 } #print self.current_depth def _process_user_info_message(self, msg): #print msg if msg['e'].encode('utf-8') == "outboundAccountInfo": '''TODO: 用户数据入库''' found = 0 for balance in msg['B']: if balance['a'].encode('utf-8') == self.order_currency: self.asset_balance[self.order_currency] = { "free": float(balance['f']), "locked": float(balance['l']) } found += 1 elif balance['a'].encode('utf-8') == self.payment_currency: self.asset_balance[self.payment_currency] = { "free": float(balance['f']), "locked": float(balance['l']) } found += 1 elif found >= 2: break else: continue conn.execute('INSERT INTO asset_info VALUES (?,?,?,?,?,?)', [ str(uuid.uuid1()), self.asset_balance[self.order_currency]['free'] + self.asset_balance[self.order_currency]['locked'], 0.0, self.asset_balance[self.payment_currency]['free'] + self.asset_balance[self.payment_currency]['locked'], 'binance', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) ]) conn.commit() elif msg['e'].encode('utf-8') == "executionReport": if not self.orders.has_key(msg['i']): time.sleep(1) self.orders[msg['i']]['status'] = msg['X'].encode('utf-8') if msg['X'].encode('utf-8') in ["FILLED", "PARTIALLY_FILLED"]: trade = { 'trade_id': msg['t'], 'commission': float(msg['n']), 'commissionAsset': msg['N'].encode('utf-8'), 'price': float(msg['L']), 'quantity': float(msg['l']), 'order_id': msg['i'], 'date_time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(msg['T'] / 1000)) } self.orders[msg['i']]['trades'][trade['trade_id']] = trade self.orders[msg['i']]['deal_quantity'] += trade['quantity'] self.orders[msg['i']][ 'deal_amount'] += trade['quantity'] * trade['price'] self.orders[msg['i']]['deal_fee'] += trade['commission'] '''手续费记录方式可能会存在问题,多币种咋办''' if self.orders[ msg['i']]['fee_asset'] != trade['commissionAsset']: self.orders[msg['i']][ 'fee_asset'] += trade['commissionAsset'] + ' ' self.orders[msg['i']]['deal_price'] = self.orders[msg['i']][ 'deal_amount'] / self.orders[msg['i']]['deal_quantity'] def _set_asset_balance(self): tries = 0 '''sleep?''' while tries < 5: try: asset = self.client.get_asset_balance(self.payment_currency) except BinanceAPIException as e: print e.code, e.message tries += 1 continue if asset is not None: self.asset_balance[asset['asset']] = { "free": float(asset['free']), 'locked': float(asset['locked']) } break time.sleep(0.5) if tries == 5: print "get asset balance failed! " return tries = 0 while tries < 5: try: asset = self.client.get_asset_balance(self.order_currency) except BinanceAPIException as e: print e.code, e.message tries += 1 continue if asset is not None: self.asset_balance[asset['asset']] = { "free": float(asset['free']), 'locked': float(asset['locked']) } break time.sleep(0.5) if tries == 5: print "get asset balance failed! " return def get_current_price(self): return self.current_price def get_current_depth(self): return self.current_depth def create_order(self, side, price, quantity): try: order_info = self.client.create_order( symbol=self.symbol, side=side, type=ORDER_TYPE_LIMIT, price=str(price), quantity=quantity, timeInForce=TIME_IN_FORCE_GTC) except BinanceAPIException as e: return -1, {"code": e.code, "message": e.message} self.orders[order_info['orderId']] = { "side": side, "order_quantity": float(order_info['origQty']), "symbol": self.symbol, "order_price": float(order_info['price']), "status": order_info['status'].encode('utf-8'), 'date_time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(order_info['transactTime'] / 1000)), 'deal_quantity': 0, 'order_amount': float(order_info['price']) * float(order_info['origQty']), 'deal_amount': 0, 'deal_fee': 0, 'fee_asset': '', 'deal_price': 0, 'trades': {} } return order_info['orderId'], self.orders[order_info['orderId']] '''def order_buy(self, price, quantity, order_type=ORDER_TYPE_LIMIT): if order_type is ORDER_TYPE_LIMIT: try: order = self.client.order_limit_buy(symbol=self.symbol, quantity=quantity, price=str(price)) except BinanceAPIException as e: return -1, {"code": e.code, "message": e.message} else: try: order = self.client.order_market_buy(symbol=self.symbol, quantity=quantity, price=str(price)) except BinanceAPIException as e: return -1, {"code": e.code, "message": e.message} self.orders[order['orderId']] = {"side": "bid", "executedQty": [], "origQty": float(order['origQty']), "currency": self.symbol, "price": float(order['price']), "status": order['status'].encode('utf-8')} return order['orderId'], self.orders[order['orderId']] def order_sell(self, price, quantity, order_type=ORDER_TYPE_LIMIT): if order_type is ORDER_TYPE_LIMIT: try: order = self.client.order_limit_sell(symbol=self.symbol, quantity=quantity, price=str(price)) except BinanceAPIException as e: return -1, {"code": e.code, "message": e.message} else: try: order = self.client.order_market_sell(symbol=self.symbol, quantity=quantity, price=str(price)) except BinanceAPIException as e: return -1, {"code": e.code, "message": e.message} #self.orders[order_id] = "NEW" self.orders[order['orderId']] = {"side": "ask", "executedQty": [], "origQty": float(order['origQty']),"symbol": self.symbol, "price": float(order['price']), "status": order['status'].encode('utf-8')} return order['orderId'], self.orders[order['orderId']]''' def order_cancel(self, order_id): try: result = self.client.cancel_order(symbol=self.symbol, orderId=order_id) except BinanceAPIException as e: return -1, {"code": e.code, "message": e.message} #self.orders[result['orderId']]["status"] = "CANCELED" return result['orderId'], self.orders[result['orderId']] def get_order_info(self, order_id): if self.orders.has_key(order_id): if abs(self.orders[order_id]['order_quantity'] - self.orders[order_id]['deal_quantity']) < 0.0001: self.orders[order_id]['status'] = 'FILLED' elif self.orders[order_id]['deal_quantity'] == 0: self.orders[order_id]['status'] = 'PARTIALLY_FILLED' return self.orders[order_id] return None def check_balance(self, currency): return self.asset_balance[currency] def get_asset_balance(self): return self.asset_balance def get_all_trade(self, order_id): return self.client.get_my_trades(symbol=self.symbol) def get_symbol_info(self, symbol): return self.client.get_symbol_info(symbol=symbol)
class Market: def __init__(self): self.client = Client(api_key=apiKey, api_secret=apiSecret, tld='us') def buy_coin_possible(self, symbol, percentOfEquity): # Get equity amount of our USD, BTC, ETH accountValue = float(self.client.get_asset_balance("USD")["free"]) accountValue += float( self.client.get_asset_balance("BTC")["free"]) * float( self.client.get_symbol_ticker(symbol="BTCUSD")["price"]) accountValue += float( self.client.get_asset_balance("ETH")["free"]) * float( self.client.get_symbol_ticker(symbol="ETHUSD")["price"]) # calculate how much of the symbol that is moneyToSpend = percentOfEquity * accountValue / 100.0 symbolQuantity = moneyToSpend / float( self.client.get_symbol_ticker(symbol=symbol)["price"]) # get recent trades to see current volatility recentTrades = self.client.get_recent_trades(symbol=symbol) lastTrade = {} totalSum = 0 weightedSum = 0 for trade in recentTrades: if lastTrade == {} or int(trade["time"]) > int(lastTrade["time"]): lastTrade = trade totalSum += float(trade["qty"]) weightedSum += float(trade["price"]) * float(trade["qty"]) weightedAvg = weightedSum / totalSum # calculate the price we should strive for with current volatility symbolQtyAdjustedBefore = symbolQuantity * (1.0 - takerFee) symbolQtyAdjustedAfter = symbolQtyAdjustedBefore * (1.0 - takerFee) endProfitPrice = 0 if weightedAvg > float(lastTrade["price"]): endProfitPrice = weightedAvg + (weightedAvg - float(lastTrade["price"])) * .5 else: endProfitPrice = float( lastTrade["price"]) + abs(weightedAvg - float(lastTrade["price"])) * .5 # calculate stop loss at 3 : 1 risk ratio using expected profit expectedProfit = (endProfitPrice * symbolQtyAdjustedAfter) - ( float(lastTrade["price"]) * symbolQtyAdjustedAfter) if expectedProfit <= 0: return stopLossPrice = float(lastTrade["price"]) - expectedProfit * (1 / 3) # possibleLoss = (stopLossPrice * symbolQtyAdjusted) - (float(lastTrade["price"]) * symbolQtyAdjusted) # for reference # set the limit buy so we get it at the price we want hopefully order = None try: order = self.client.order_limit_buy( symbol=symbol, quantity="{:0.0{}f}".format(symbolQuantity, 3), price="{:0.0{}f}".format( float(lastTrade["price"]) + float(lastTrade["price"]) * .001, 2), ) except exceptions.BinanceAPIException as e: print(e) return # wait 3 seconds. usually small orders will go through immediately but if this scales it wouldn't time.sleep(5) # see if it went through at our price, otherwise cancel it if order is not None: for openOrder in self.client.get_open_orders(): if order["orderId"] == openOrder["orderId"]: self.client.cancel_order(symbol=symbol, orderId=order["orderId"]) return try: # set our end/expected price for this trade self.client.order_limit_sell( symbol=symbol, quantity="{:0.0{}f}".format(symbolQtyAdjustedBefore, 3), price="{:0.0{}f}".format(endProfitPrice, 2)) self.client.create_order( symbol=symbol, side=Client.SIDE_SELL, type=Client.ORDER_TYPE_STOP_LOSS_LIMIT, quantity="{:0.0{}f}".format(symbolQtyAdjustedBefore, 3), price="{:0.0{}f}".format(stopLossPrice, 2), stopPrice="{:0.0{}f}".format(stopLossPrice + .01, 2), timeInForce=Client.TIME_IN_FORCE_GTC) except exceptions.BinanceAPIException as e: print(e) return def get_symbol_price(self, symbol): recentTrades = self.client.get_recent_trades(symbol=symbol) lastTrade = {} for trade in recentTrades: if lastTrade == {} or int(trade["time"]) > int(lastTrade["time"]): lastTrade = trade return float(lastTrade["price"]) def get_account_value(self): valueInUSD = 0.0 usdBalance = self.client.get_asset_balance("USD") valueInUSD = valueInUSD + float(usdBalance["free"]) + float( usdBalance["locked"]) time.sleep(.1) ethBalance = self.client.get_asset_balance("ETH") ethPrice = self.get_symbol_price("ETHUSD") valueInUSD = valueInUSD + float(ethBalance["free"]) * ethPrice + float( ethBalance["locked"]) * ethPrice time.sleep(.1) btcBalance = self.client.get_asset_balance("BTC") btcPrice = self.get_symbol_price("BTCUSD") valueInUSD = valueInUSD + float(btcBalance["free"]) * btcPrice + float( btcBalance["locked"]) * btcPrice time.sleep(.1) return valueInUSD
from binance.exceptions import BinanceAPIException, BinanceWithdrawException, BinanceRequestException try: client = Client(os.getenv('BINANCE_APIKEY', 'NOTDEF_APIKEY'), os.getenv('BINANCE_SEKKEY', 'NOTDEF_APIKEY'), { "verify": True, "timeout": 20 }) print('--- OPEN ORDERS ---------------------------------------') print(client.get_open_orders()) print('--- CANCEL ORDER --------------------------------------') if len(sys.argv) == 3: try: result = client.cancel_order(symbol=sys.argv[1], orderId=sys.argv[2]) except BinanceAPIException as e: print(f'Binance API exception: {e.status_code} - {e.message}') sys.exit(1) print(f'Result: {result}') else: print(f'run: {sys.argv[0]} SYMBOL ORDER_ID') except BinanceAPIException as e: print(f'Binance API exception: {e.status_code} - {e.message}') except BinanceRequestException as e: print(f'Binance request exception: {e.status_code} - {e.message}') except BinanceWithdrawException as e:
class TradeEngineBinance(TradeEngineBase): def __init__(self, marketname): super(TradeEngineBinance, self).__init__(marketname) # variables which start with uppercase letter may have configuration in setting.json self.ApiKey = '' self.SeceretKey = '' self.LotSize = 0.001 self.PriceSpread = 0.01 self.VolumePrecision = 6 self.PricePrecision = 2 self.RecvWindow = 30000 self.load_setting() self.client_api = None SQLog.info("__init__,marketname=", marketname, "self.__dict__=", self.__dict__) def open_api(self): SQLog.info("open_api,quote already open=", self.is_open()) self.client_api = Client(self.ApiKey, self.SeceretKey) for stockcode in self.get_stockcode_pools(): cc_coin = stockcode.split('.') instrument_id = cc_coin[1].upper() + 'USDT' result = self.client_api.get_open_orders( symbol=instrument_id, recvWindow=self.RecvWindow) for op in result: self.order_handler( 'CC.' + cc_coin[1].upper() + '.' + str(op.get('orderId')), 'CC.' + cc_coin[1].upper(), False, SIDE_BUY == op.get('side').upper(), None, float(op.get('executedQty')), float(op.get('origQty')), float(op.get('price'))) return True def close_api(self): super().close_api() self.client_api = None SQLog.info("close_api") return True def is_open(self): return not self.client_api is None def call_get_account(self): result = self.client_api.get_account(recvWindow=self.RecvWindow) with self.account_lock: self.nowstocks_dict.clear() self.nowasserts_total = 0 if 'balances' in result: for coin in result['balances']: if 'USDT' == coin.get('asset').upper(): self.nowbalance = float(coin.get('free')) + float( coin.get('locked')) self.nowpower = float(coin.get('free')) self.nowasserts_total += self.nowbalance elif float(coin.get('free')) + float( coin.get('locked')) > self.LotSize: self.nowstocks_dict['CC.' + coin.get('asset').upper()] = { 'qty': float(coin.get('free')) + float(coin.get('locked')) } self.nowasserts_total += self.quotes_dict.get( 'CC.' + coin.get('asset').upper(), {}).get( 'last_price', 0) * (float(coin.get('free')) + float(coin.get('locked'))) SQLog.info("call_get_account,nowbalance=", self.nowbalance, "nowpower=", self.nowpower, "nowasserts_total=", self.nowasserts_total, "nowstocks_dict=", self.nowstocks_dict) return [True, self.nowbalance, self.nowstocks_dict] def call_get_market_snapshot(self, stockcodes): self.nowasserts_total = self.nowbalance for code in stockcodes: if code not in self.get_stockcode_pools( ) and not code == self.get_default_stock(): continue cc_coin = code.split('.') ticker = self.client_api.get_ticker(symbol=cc_coin[1].upper() + 'USDT') if code not in self.quotes_dict: self.quotes_dict[code] = {} q = self.quotes_dict[code] q['lot_size'] = self.LotSize q['price_spread'] = self.PriceSpread q['suspension'] = False q['last_price'] = float(ticker['lastPrice']) q['ask_price'] = float(ticker['askPrice']) q['bid_price'] = float(ticker['bidPrice']) if code in self.nowstocks_dict: self.nowasserts_total += q['last_price'] * float( self.nowstocks_dict.get(code, {}).get('qty', 0)) SQLog.info("call_get_market_snapshot,nowasserts_total=", self.nowasserts_total, "quotes_dict=", self.quotes_dict) return [True, self.quotes_dict] def call_get_average_volatility(self, stockcode): cc_coin = stockcode.split('.') result = self.client_api.get_klines( symbol=cc_coin[1].upper() + 'USDT', interval=Client.KLINE_INTERVAL_1DAY, limit=50) #period = 7 period = 3 if len(result) >= max(period, 6): sumcloses = 0.0 for j in range(-1, -1 - period, -1): sumcloses += float(result[j][4]) average = round(sumcloses / period, self.precision(stockcode)) sumv = 0.0 for i in range(-1, -6, -1): sumv += abs(float(result[i][2]) / float(result[i][3]) - 1) if float(result[i][3]) > 0 else 0 sumv += abs(float(result[i][4]) / float(result[i - 1][4]) - 1) if float(result[i - 1][4]) > 0 else 0 volatility = round(sumv / 10, 6) SQLog.info("call_get_average_volatility,stockcode=", stockcode, "average=", average, "volatility=", volatility) return [average, volatility] else: SQLog.warn( "call_get_average_volatility,result too small,stockcode=", stockcode, "result=", result) return [0, 0] def call_place_order(self, stockcode, volume, price, isbuy, ismarketorder): volume_v, price_v = self.round_order_param(stockcode, volume, price, isbuy, ismarketorder) if volume_v <= 0: SQLog.info("call_place_order failed,volume_v<=0,stockcode=", stockcode, "volume=", volume, "volume_v=", volume_v, "price=", price, "price_v=", price_v, "isbuy=", isbuy, "ismarketorder=", ismarketorder) return None if 0 == price_v and not ismarketorder: SQLog.info("call_place_order failed,price==0,stockcode=", stockcode, "volume=", volume, "price=", price, "isbuy=", isbuy, "ismarketorder=", ismarketorder) return None cc_coin = stockcode.split('.') instrument_id = cc_coin[1].upper() + 'USDT' if isbuy: result = self.client_api.order_limit_buy( symbol=instrument_id, quantity=round(volume_v, self.VolumePrecision), price=str(round(price_v, self.PricePrecision)), recvWindow=self.RecvWindow) else: result = self.client_api.order_limit_sell( symbol=instrument_id, quantity=round(volume_v, self.VolumePrecision), price=str(round(price_v, self.PricePrecision)), recvWindow=self.RecvWindow) if result.get('status') in [ ORDER_STATUS_NEW, ORDER_STATUS_PARTIALLY_FILLED, ORDER_STATUS_FILLED ]: ret_oid = stockcode + '.' + str(result.get('orderId')) if isbuy: self.nowpower -= (volume_v * price_v * self.MaxFees) self.order_handler(ret_oid, stockcode, None, isbuy, None, None, volume_v, price_v) SQLog.info("call_place_order,stockcode=", stockcode, "volume=", volume, "volume_v=", volume_v, "price=", price, "price_v=", price_v, "isbuy=", isbuy, "ismarketorder=", ismarketorder, "orderid=", ret_oid) SQLog.info("--------------------PlaceOrderOK--------------------", stockcode, "--------------------", 'BUY' if isbuy else 'SELL', volume_v, '@ ', price_v, "--------------------", ret_oid) return stockcode + '.' + str(result.get('orderId')) else: raise Exception("call_place_order failed,timeout,stockcode=" + stockcode + ",volume=" + str(volume) + ",price=" + str(price) + ",isbuy=" + str(isbuy) + ",ismarketorder=" + str(ismarketorder)) def call_get_order(self, orderid): if orderid is None: SQLog.info("call_get_order,orderid=", orderid, "order=None") return None cco = orderid.split('.') coin = cco[1] oid = cco[2] try: result = self.client_api.get_order(symbol=coin.upper() + 'USDT', orderId=int(oid), recvWindow=self.RecvWindow) self.order_handler( orderid, 'CC.' + coin.upper(), result.get('status') in [ ORDER_STATUS_FILLED, ORDER_STATUS_CANCELED, ORDER_STATUS_REJECTED, ORDER_STATUS_EXPIRED ], None, None, float(result.get('executedQty')), None, None) except Exception as e: SQLog.warn("call_get_order,failed,orderid=", orderid, "Exception,e=", e) order = self.get_order_from_cache(orderid) SQLog.info("call_get_order,orderid=", orderid, "order=", order) return order def call_list_order(self): orders_notclose = {} with self.orders_dict_lock: for orderid, order in self.orders_dict.items(): if not order.get('isclose'): orders_notclose[orderid] = order.copy() SQLog.info("call_list_order,result=", orders_notclose) for oid, o in orders_notclose.items(): self.call_get_order(oid) return True def call_cancel_order(self, orderid): if orderid is None: SQLog.info("call_cancel_order,orderid=", orderid, "return False") return False cco = orderid.split('.') coin = cco[1] oid = cco[2] try: result = self.client_api.cancel_order(symbol=coin.upper() + 'USDT', orderId=int(oid), recvWindow=self.RecvWindow) SQLog.info("call_cancel_order,orderid=", orderid, ",result=", result) return True except Exception as e: SQLog.info("call_cancel_order,orderid=", orderid, ",result=False,e=", e) return False def call_cancel_all_orders(self): orders_notclose = {} with self.orders_dict_lock: for orderid, order in self.orders_dict.items(): if not order.get('isclose'): orders_notclose[orderid] = order.copy() for oid, o in orders_notclose.items(): self.call_cancel_order(oid) SQLog.info("call_cancel_all_orders") return True