def CryptoArbBinanceOrderBookProcess(stopProcessesEvent): try: # Init Kafka producer kafka_producer = KafkaProducer( bootstrap_servers=config['kafka']['address'], value_serializer=lambda v: json.dumps(v).encode('utf-8')) credentials = getCredentials() client = Client(api_key=credentials['api_key'], api_secret=credentials['api_secret']) # Init CloudWatch metrics metrics = CWMetrics(config['exchange']['name']) bm = BinanceSocketManager(client) bm.start_multiplex_socket( pairList, partial(process_message, kafka_producer, metrics)) bm.start() logger.info('BinanceSocketManager started') stopProcessesEvent.wait() bm.close() logger.info('BinanceSocketManager closed') except Exception as error: logger.error("Error CryptoArbBinanceOrderBookProcess: " + type(error).__name__ + " " + str(error.args)) metrics.putError()
class BinanceKlines(Thread): def __init__(self, symbol, interval, subscribers): Thread.__init__(self) self.symbol = symbol self.interval = interval self.subscribers = subscribers self.rest_client = Client(None, None) self.ws_client = BinanceSocketManager(self.rest_client) self.restart_count = 1 def run(self): conn_key = self.ws_client.start_kline_socket(self.symbol, self.on_message, self.interval) self.ws_client.start() self.ws_client.join() def on_message(self, msg): #to recieve notifications, subscibers should have an array called klines and an event called kline_event #kline = Kline(msg) kline = KLineEvent.object_from_dictionary(msg) for subscriber in self.subscribers: subscriber.klines.append(kline) subscriber.kline_event.set() def __del__(self): self.ws_client = None logger.info("Klines stopping") def close(self): self.keep_running = False self.ws_client.close() reactor.stop()
class DataCatcher: active = 0 def callback(self, query): global run if not self.norm['norm']: self.finish() return price = (float(query['data']['asks'][0][0]) + float(query['data']['bids'][0][0])) / 2 record = Record.objects.create(price=price, run_id=run, asks=str(query['data']['asks']), bids=str(query['data']['bids'])) def __init__(self, run_id, timeout=600): self.manager = multiprocessing.Manager() self.norm = self.manager.dict() self.norm['norm'] = True self.streams = ['btcusdt@depth5'] self.timeout = timeout self.manager = multiprocessing.Manager() self.client = init_client() global run run = run_id self.main_socket = BinanceSocketManager(self.client) self.connection_key = self.main_socket.start_multiplex_socket( self.streams, self.callback) self.socket_process = multiprocessing.Process(target=self.process_func) self.timeout_timer = None def finish(self): self.main_socket.stop_socket(self.connection_key) self.main_socket.close() reactor.stop() print('Datacatcher process ended') def stop(self): self.norm['norm'] = False def process_func(self): print('Datacatcher process started') self.timeout_timer = threading.Timer(self.timeout, self.finish) self.timeout_timer.start() self.main_socket.run() def start(self): DataCatcher.active = 1 self.socket_process.start() return timezone.now(), self.timeout
class BinanceStream: """ This class defines a binance data stream that's setup using BinanceSocketManager """ bm = None # type: BinanceSocketManager symbol = None # type: str key = None # type: str fresh = True # type: bool def __init__(self, client, symbol): # type: (Client, SupportedSymbols) -> None self.bm = BinanceSocketManager(client) self.symbol = symbol.value def start_kline(self, callback=None, interval=KLINE_INTERVAL_1HOUR): # type: (Callable, str) -> None if not callback: callback = self._store_data self.key = self.bm.start_kline_socket(self.symbol, callback, interval=interval) def kline_with_firebase(self, interval=Intervals.H1): # type: (Intervals) -> None self.key = self.bm.start_kline_socket(self.symbol, self._update_firebase, interval=interval) def _update_firebase(self): # type: () -> None raise NotImplementedError def refresh(self, client): # type: (Client) -> None self.bm = BinanceSocketManager(client) self.fresh = True def start(self): # type: () -> None if self.fresh: self.fresh = False self.bm.start() else: raise StreamNotFreshError def close(self): # type: () -> None self.bm.close() def _store_data(self, data): # type: (Dict[str, Any]) -> None self.data = data
class BINANCE_ORDER_BOOK(threading.Thread): def __init__(self, threadId, name, order_book, api_details): threading.Thread.__init__(self) self.threadId = threadId self.name = name self.order_book = order_book self.process_functions = { pair: functools.partial(self.p_data, symbol=pair) for pair, _ in order_book.items() } self.api_details = api_details self.reset_time = 108000 self.client = Client(self.api_details['API_KEY'], self.api_details['API_SECRET']) self.bm = BinanceSocketManager(self.client) self.conn_keys = None def run(self): while True: self.bm_init() sleep(self.reset_time) self.bm_reset() def convert_order_data(self, res): conv_data = [] for r in res: conv_data.append([Decimal(r[0]), Decimal(r[1])]) return conv_data def p_data(self, msg, symbol=None): self.order_book[symbol]['sell'] = self.convert_order_data(msg['asks']) self.order_book[symbol]['buy'] = self.convert_order_data(msg['bids']) self.order_book[symbol]['lastUpdate'] = time() def bm_init(self): print("***START***") self.client = Client(self.api_details['API_KEY'], self.api_details['API_SECRET']) self.bm = BinanceSocketManager(self.client) self.conn_keys = [ self.bm.start_depth_socket( pair, f, depth=BinanceSocketManager.WEBSOCKET_DEPTH_20) for pair, f in self.process_functions.items() ] self.bm.start() def bm_reset(self): print("***STOP***") self.bm.close() self.bm = None self.conn_keys = None self.client = None
class BinanceSocket(ClientSetup): def __init__(self, symbol): self.symbol = symbol self.bsm = None self.conn_key = None self.new_data_available = True self.ticker = None self.condition = None self.trans_range = None def connect(self): self.bsm = BinanceSocketManager(self.client) self.conn_key = self.bsm.start_kline_socket(self.symbol, self.process_msg) self.bsm.start() def disconnect(self): self.bsm.stop_socket(self.conn_key) self.bsm.close() self.reactor.stop() def process_msg(self, msg): self.ticker = KlineTicker(msg) print(self.ticker.__str__()) self.new_data_available = True self.process_order() def process_order(self): close = float(self.ticker.close) if self.condition == 'buy': if close >= self.trans_range: print(datetime.now(), 'BUY', self.condition, self.trans_range) elif self.condition == 'sell': if close <= self.trans_range: print(datetime.now(), 'SELL', self.condition, self.trans_range) else: print(datetime.now(), 'NO ORDERS', self.condition, self.trans_range) def set_new_data_available(self, bol_value): self.new_data_available = bol_value def get_new_data_available(self): return self.new_data_available def set_market_conditions(self, condition): self.condition = condition def set_trans_range(self, trans_range): self.trans_range = trans_range
def exercise08(): ''' Using the below linked documentation from Binance, use websockets to pull and stream back ANY streaming market data (like limit order book) using websockets. The stream should be displayed to console. Feel free to import binance python libs. Relevant docs https://python-binance.readthedocs.io/en/latest/overview.html https://python-binance.readthedocs.io/en/latest/websockets.html https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md ''' #states secret and public key secret_key = 'AhYt4B9YJsH6cVCqUhmxgOpHvvmoXYLaITkYIJZKRvo4HNK03Wu1JxOKfBi3N0a5' public_key = 'B9zEhWB5ODpEYcuoBdur2XupW0cqyqsDi1N1S0KWKvJU4NEeVfD9kxgKRtwoMnlg' #initializes client client = Client(public_key,secret_key) #creates message processor function to handle payload from websocket def process_message(msg): print(msg) #initializes socket manager bm = BinanceSocketManager(client) #creates connection to fetch Etherium trade data conn_key = bm.start_trade_socket('ETHBTC',process_message) #starts socket bm.start() #wait 10 seconds to let data come in time.sleep(10) #stops socket connection bm.stop_socket(conn_key) #closes socket manager bm.close()
class BinanceStream(object): def __init__(self): self.client = Client(api_key=config.api_key, api_secret=config.api_secret) self.manager = BinanceSocketManager(self.client, user_timeout=30) self.user_socket = self.manager.start_user_socket(self.process_order) self.ticker_socket = self.manager.start_symbol_book_ticker_socket( config.symbol, self.process_tick) self.r = redis.Redis(host='localhost', port=6379, db=0) self.buy_orders = [] self.sell_orders = [] def process_order(self, msg): if msg['e'] == 'error': # close and restart the socket print("ERROR:", msg) else: print("message type: {}".format(msg['e'])) if msg['e'] == 'executionReport' and msg['s'] == config.symbol: self.r.set(msg['c'], json.dumps(msg)) print("Saved: ", msg) def process_tick(self, msg): if msg['s'] == config.symbol: name = 'bookTicker_' + msg['s'] json_msg = json.dumps(msg) self.r.set(name, json_msg) self.r.xadd("stream_" + name, msg, maxlen=1000) print("Saved: ", json_msg) def start(self): self.manager.start() def stop(self): self.manager.close()
def stop_trading(self): bm = BinanceSocketManager(self.client) bm.stop_socket(self.conn_key) bm.close() reactor.stop()
class BinanceExchangeService(ExchangeClientBase): """ Binance Exchange Services (Depends on 3rd party binance websockets library, python-binanc)e https://github.com/sammchardy/python-binance binance_response_mapping: ("s", "symbol"), ("E", "timestamp"), ("h", "high"), ("l", "low"), ("b", "bid"), ("B", "bid_volume"), ("a", "ask"), ("A", "ask_volume"), ("w", "w_av_price"), ("o", "open"), ("c", "close"), ("x", "previous_close"), ("p", "change"), ("P", "percentage"), ("v", "base_volume"), ("q", "quote_volume") """ _binance_item_getter, _binance_quote_keys = zip( *[("E", "timestamp"), ("h", "high"), ("l", "low"), ( "b", "bid"), ("B", "bid_volume"), ("a", "ask"), ( "A", "ask_volume"), ("v", "base_volume"), ("q", "target_volume"), ("o", "open"), ("c", "close"), ("x", "previous_close"), ("p", "change"), ("s", "ticker")]) _binance_item_getter = itemgetter(*_binance_item_getter) def __init__(self, **kwargs): ExchangeClientBase.__init__(self, "binance", **kwargs) symbol_infos = requests.get( "https://api.binance.com/api/v1/exchangeInfo").json()["symbols"] self.binance_symbol_mapping = { info["symbol"]: (info["baseAsset"], info["quoteAsset"]) for info in symbol_infos } self.binance_socket_manager = BinanceSocketManager( Client(KeyChain.get_key("binance"), KeyChain.get_secret("binance"))) def process_ticker_response(self, tickers_response): self.logger.debug( "Received ticker response {}".format(tickers_response), truncate=480) quotes = [] append_to_quotes = quotes.append for ticker_response in tickers_response: quote = dict( zip(self._binance_quote_keys, self._binance_item_getter(ticker_response))) quote["base"], quote["target"] = self.get_base_target( quote["ticker"]) append_to_quotes(quote) self.save_quotes(quotes) self.logger.info("Saved {} tickers".format(len(quotes))) def get_base_target(self, ticker): return self.binance_symbol_mapping[ticker] def start(self): super().start() self.binance_socket_manager.start_ticker_socket( self.process_ticker_response) self.binance_socket_manager.start() while not self.EXCHANGE_STOPPED: pass def stop(self): try: reactor.stop() # pylint: disable=E1101 self.binance_socket_manager.close() except ReactorNotRunning: pass super().stop()
class BinanceWebSocket(ExchangeWebSocket): def __init__(self, pairs_n_streams): super().__init__('Binance', pairs_n_streams) self.client = Client('', '') self.bm = BinanceSocketManager(self.client) self.streams = [] self.possible_streams = ['trade', 'depth20', 'arr'] def init_streams(self): for pair, stream in self.pairs_n_streams.items(): if self.has_stream(stream): for sub_stream in stream.split(','): self.streams.append('{0}@{1}'.format(pair, sub_stream)) else: logger.warning('%s. There is no %s stream', pair, stream) logger.info('%s streams are initialized: %s', len(self.streams), self.streams) def close_socket(self): logger.debug('Closing socket') self.exited = True self.bm.close() def start_multiple_websocket(self, init_streams=True): super().start_multiple_websocket(init_streams=init_streams) if len(self.streams) > 0: self.bm.start_multiplex_socket(self.streams, self.multiple_data_handle) self.bm.start() logger.info('{} socket is started:\n{}\n{}'.format( self.exchange, self.node, '\n'.join(self.streams))) def save_depth_to_file(self, msg): stream = msg.get('stream', '') stream_parts = stream.split('@') pair = stream_parts[0] data = msg.get('data') last_update_id = data.get('lastUpdateId', 0) if last_update_id >= self.depth_last_update.get(pair, 0): self.depth_last_update[pair] = last_update_id bids = data.get('bids', []) asks = data.get('asks', []) if len(bids) == 0 and len(asks) == 0: bids = data.get('b', []) asks = data.get('a', []) if len(bids) > 0 or len(asks) > 0: time_now = int(time.time()) data_to_append = ["{},{},{},{}\n".format(time_now, 'B', bid[0], bid[1]) for bid in bids] + \ ["{},{},{},{}\n".format(time_now, 'A', ask[0], ask[1]) for ask in asks] data_to_append = "".join(data_to_append) self.file_manager.save_data_to_file( self.exchange, stream_parts[1], pair, data_to_append, columns='time,side,price,qty') else: if len(bids) == 0: logger.debug('%s. Bids are empty', pair) if len(asks) == 0: logger.debug('%s. Asks are empty', pair) else: logger.debug('%s. last_update_id is whether none or not new', pair) def save_trade_to_file(self, msg): stream = msg.get('stream', '') stream_parts = stream.split('@') pair = stream_parts[0] data = msg.get('data') del data['e'] del data['s'] data_to_insert = "{},{},{},{},{},{},{},{},{}\n".format( data['m'], data['E'], data['M'], data['q'], data['T'], data['t'], data['b'], data['p'], data['a']) self.file_manager.save_trades_to_file(self.exchange, stream_parts[1], pair, data_to_insert, columns="m,E,M,q,T,t,b,p,a") def save_min_ticker_to_file(self, msg): stream = msg.get('stream', '') stream_parts = stream.split('@') pair = stream_parts[0] data = msg.get('data') res = '' for cur in data: res += "{0},{1},{2},{3},{4}," \ "{5},{6},{7},{8}\n".format(cur['e'], cur['E'], cur['s'], cur['c'], cur['o'], cur['h'], cur['l'], cur['v'], cur['q']) self.file_manager.save_trades_to_file(self.exchange, stream_parts[1], pair, res, columns='e,E,s,c,o,h,l,v,q') def multiple_data_handle(self, msg): stream = msg.get('stream', '') try: if 'depth' in stream: self.save_depth_to_file(msg) elif 'trade' in stream: self.save_trade_to_file(msg) elif '!miniTicker' in stream: self.save_min_ticker_to_file(msg) else: logger.debug('%s data handler not implemented', stream) self.last_msg_time = int(time.time()) except Exception as e: logger.error(str(e))
def socket_manager(self): with sqlite3.connect('TAdb.db') as conn: db = conn.cursor() bot = telebot.TeleBot(self.token) def telegram_sender(): percent_difference_for_pep8 = str( round(((100 * (self.previous_price - self.current_price)) / self.previous_price), 3)) + '%' bot.send_message( self.room, 'Current Bitcoin Price is : ' + str(self.current_price) + '$' '\n' + 'Previous Price is : ' + str(self.previous_price) + '$' '\n' + 'The difference is : ' + percent_difference_for_pep8) # Proccesing messages from socket binance def process_message(msg): if msg['e'] == 'error': bm.close() bm.start() else: self.current_price = float(msg['p']) # Counting difference in percent between previous and current price def counting_difference(): if float(((100 * (self.previous_price - self.current_price)) / self.previous_price)) > self.percent: return True else: return False # Just database SQL def adding_to_database(event): self.dict_for_database['time'] = int(time.time()) self.dict_for_database['price'] = str(self.current_price) self.dict_for_database['event'] = str( str(event) + '{}%'.format(self.percent)) db.execute( 'INSERT INTO price(time, price) VALUES ("{database[time]}", "{database[price]}")' .format(database=self.dict_for_database)) db.execute( 'INSERT INTO event(time, event) VALUES ("{database[time]}", "{database[event]}")' .format(database=self.dict_for_database)) conn.commit() # creating variables for current and previous prices, then in infinity loop adding events to database and if # necessary send to the Telegram def main_res(): while True: if self.current_price is not None: self.previous_price = self.current_price break else: time.sleep(0.1) while True: if counting_difference(): adding_to_database('Price changed for more than ') telegram_sender() self.previous_price = self.current_price time.sleep(self.timing) elif not counting_difference(): adding_to_database('Price changed less than ') self.previous_price = self.current_price time.sleep(self.timing) # Just creating sockets bm = BinanceSocketManager([self.login, self.password]) bm.start_trade_socket('BTCUSDT', process_message) bm.start() main_res() bm.close()
class BinanceTradeEngine: def __init__(self, market='BTCUSDT'): self.api_key = os.getenv("binance_api_key") self.api_secret = os.getenv("binance_api_secret") self.client = Client(self.api_key, self.api_secret) logger.info("Starting Binance Trading Engine.") self.market = market self.__process_initial_book_state() logger.info("Book state initialized.") self.trader = TestTrader() #self.trader = Playbook.OGTrader_BTC() self.event_buffer = queue.Queue() self.event_thread = threading.Thread(target=self.event_loop) self.socketManager = BinanceSocketManager(self.client, user_timeout=60) self.book_diff = self.socketManager.start_depth_socket( market, self.on_depth_message) self.trade_stream = self.socketManager.start_trade_socket( market, self.on_trade_message) self.PRINT_BOOK = True self.grapher = Grapher() self.graphing = False self.tradingOn = True self.alive = True def start(self): self.socketManager.start() logger.info("Socket Manager started.") self.event_thread.start() logger.info("Event thread started.") def stop(self): self.socketManager.close() logger.info("Socket Manager closed.") self.alive = False self.event_thread.join() logger.info("Event thread killed.") def __process_initial_book_state(self): self.market_state = MarketState(self.market) book = self.client.get_order_book(symbol=self.market) # print(book, type(book)) self.order_book = self.market_state.order_book for bid in book['bids']: self.order_book.add_modify_delete(Order(float(bid[0]), float(bid[1])), side='b') for ask in book['asks']: self.order_book.add_modify_delete(Order(float(ask[0]), float(ask[1])), side='a') print(self.order_book) def event_loop(self): global done while self.alive: if not self.event_buffer.empty(): (event_type, msg) = self.event_buffer.get() if event_type == EventType.DEPTH: self.depthUpdateHandler(msg) elif event_type == EventType.TRADE: self.tradeUpdateHandler(msg) def on_depth_message(self, msg): self.event_buffer.put((EventType.DEPTH, msg)) def on_trade_message(self, msg): self.event_buffer.put((EventType.TRADE, msg)) def depthUpdateHandler(self, msg): try: self.order_book.binance_incremental_book_update_handler(msg) self.market_state.binance_incremental_depth_update_handler(msg) print(self.trader) if self.PRINT_BOOK: print(self.market_state) if self.tradingOn: self.trader.on_depth_update(self.market_state) except Exception as e: print("Exception caught in depth update") print(e) print(e.args) print("LAST TRADE: " + str(self.market_state.last_trade)) print() print(self.market_state) logger.critical("Exception caught in depth update") logger.critical(msg=e) logger.critical(msg="LAST TRADE: " + str(self.market_state.last_trade)) logger.critical(msg=self.market_state) self.socketManager.close() traceback.print_exc() done = False def tradeUpdateHandler(self, msg): try: self.market_state.binance_incremental_trade_update_handler(msg) if self.graphing: self.grapher.update_graph(float(msg['p']), msg['m']) if self.tradingOn: self.trader.on_trade_update(self.market_state) #self.done = True print(self.trader) if self.PRINT_BOOK: print(self.market_state) except Exception as e: print("Exception caught in trade update") print(e) print(e.args) print("LAST TRADE: " + str(self.market_state.last_trade)) print() print(self.market_state) logger.critical("Exception caught in trade update") logger.critical(msg=e) logger.critical(msg="LAST TRADE: " + str(self.market_state.last_trade)) logger.critical(msg=self.market_state) self.socketManager.close() traceback.print_exc()
#logger.info('Updating analysis database.') logger.info('Creating new analysis document.') #update_result = db[collections['analysis']].update_one({'_id': user_market}, {'$set': analysis_document}, upsert=True) #logger.debug('update_result.matched_count: ' + str(update_result.matched_count)) #logger.debug('update_result.modified_count: ' + str(update_result.modified_count)) inserted_id = db[collections['analysis']].insert_one( analysis_document).inserted_id logger.debug('inserted_id: ' + str(inserted_id)) else: logger.error('Error while analyzing trade data.') except Exception as e: logger.exception(e) except KeyboardInterrupt: logger.info('Exit signal received.') finally: if reactor.running: logger.info('Closing Binance socket manager.') binance_ws.close() logger.info('Stopping reactor.') reactor.stop() else: logger.info('No websocket connected or reactor running.') logger.info('Exiting.')
class LiveTrading: def __init__(self, client:object, strategy:object, klines:dict={}): self.client:object = client self.strategy = strategy self.trades = [] self.verbose = False self.debug = False self.precision:dict = {} self.klines:dict = klines self.message_no:int = 0 for coin in self.strategy.tradeCoins: if self.verbose: print(f"Getting precision for {coin}") self.precision[coin] = self.get_precision(coin) self.klines[coin] = [] self.active_order:bool = False self.open_trade:bool = False self.balance:float = self.get_balance(self.strategy.baseCoin) self.starting_balance:float = self.balance print(f"\nStarting trading with the following:") print(f"Trade coins: {self.strategy.tradeCoins}") print(f"Starting {self.strategy.baseCoin}: {self.balance}") print(f"Indicator: {self.strategy.indicator}") print(f"Strategy: {self.strategy.strategy}") self.open_kline_sockets(self.strategy.tradeCoins) def get_balance(self, coin): return float(self.client.get_asset_balance(asset=coin)["free"]) def open_kline_sockets(self, coins:list): print(f"\nOpening kline socket for {coins}") self.bm = BinanceSocketManager(self.client) sockets = [] for coin in coins: sockets.append(f"{coin.lower()}{self.strategy.baseCoin.lower()}@kline_{self.strategy.interval}") self.kline_socket = self.bm.start_multiplex_socket(sockets, self.process_message) self.bm.start() @staticmethod def print_with_timestamp(message): print(f"\n{time.strftime('%H:%M:%S', time.localtime())} | {message}") def process_message(self, msg): msg = msg["data"] if self.verbose: self.print_with_timestamp(f"Recieved kline for {msg['s']}") if self.debug: print(msg) if (msg['e'] == "kline"): self.process_kline(msg['k']) elif msg['e'] == 'error': print("Socket error... restarting socket") self.bm.close() self.open_kline_sockets(self.strategy.tradeCoins) def process_kline(self, kline): if kline['x'] == True: self.message_no += 1 trade_coin = f"{kline['s'][:len(kline['s']) - len(self.strategy.baseCoin)]}" self.klines[trade_coin].append(self.kline_to_ohlcv(kline, self.verbose, self.debug)) if (self.verbose): self.print_with_timestamp(f"Obtained closed kline and converted to ohlcv. Count for {kline['s']}: {len(self.klines[trade_coin])}") if (self.debug): print(self.klines) if self.message_no == len(self.strategy.tradeCoins): self.message_no = 0 self.print_with_timestamp("Checking for any actions...") self.strategy.refresh(self.klines) # If the strategy is returning more trades than we have, there must be new ones if len(self.strategy.trades) > len(self.trades): new_trades = self.strategy.trades[len(self.trades):] print(f"New trades: {new_trades}") self.process_trades(new_trades) self.trades += new_trades def process_trades(self, trades): for trade in trades: if not trade.completed: if trade.action == "BUY": self.place_buy(trade.trade_coin, trade.price) elif trade.action == "SELL": self.place_sell(trade.trade_coin, trade.price) trade.completed = True def place_buy(self, coin, price): self.balance = self.get_balance(self.strategy.baseCoin) quantity = self.round_down((self.balance * 0.99) / float(price), self.precision[coin]) print(f"Buying {quantity} {coin} at {price}") order = self.client.create_order( symbol=f"{coin}{self.strategy.baseCoin}", side=SIDE_BUY, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=quantity, price=price ) print(order) def place_sell(self, coin, price): balance = self.get_balance(coin) quantity = self.round_down(balance, self.precision[coin]) if (quantity != 0): print(f"\nSelling {quantity} {coin} at {price}") order = self.client.create_order( symbol=f"{coin}{self.strategy.baseCoin}", side=SIDE_SELL, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=quantity, price=price ) print(order) else: print(f"Something went wrong. Bot is trying to sell 0 {coin}") def get_precision(self, coin): for filt in self.client.get_symbol_info(f"{coin}{self.strategy.baseCoin}")['filters']: if filt['filterType'] == 'LOT_SIZE': return int(round(-math.log(float(filt['stepSize']), 10), 0)) @staticmethod def round_down(n, decimals=0): return math.floor(n * (10 ** decimals)) / 10 ** decimals @staticmethod def kline_to_ohlcv(kline, verbose, debug): # This converts the kline from the socket stream to the ohclv data # so it is the same as the backtesting historical data returned from API # which is what a Strategy accepts ohlcv = [ # Open time kline['t'], # Open kline['o'], # High kline['h'], # Low kline['l'], # Close kline['c'], # Volume kline['v'], # Close time kline['T'], # Quote asset volume kline['q'], # Number of trades kline['n'], # Taker buy base asset volume kline['V'], # Taker buy quote asset volume kline['Q'], # Ignore kline['B'] ] if verbose: print("\nUnpacked closed kline to ohlcv") if debug: print(ohlcv) return ohlcv
quantity = 0.0021 while True: h = raw_input('Pres enter') s = raw_input('symbol: ') s = s.upper() if s == 'BTC': symbol = s + "USDT" else: symbol = s + "BTC" bm = BinanceSocketManager(client) conn_key = bm.start_symbol_ticker_socket( symbol, process_message) # Connect to price socket from Binance bm.start() # Start connection to price socket from Binance # Calculate step size for price and quantity for x in range(len(data['symbols'])): if (symbol in data['symbols'][x]['symbol']): min_price = float(data['symbols'][x]['filters'][0] ['tickSize']) # minimum price step min_Qty = float(data['symbols'][x]['filters'][1] ['stepSize']) # minimum quantity step # Start listening to your key presses lis = keyboard.Listener(on_press=on_pres) lis.start() lis.join() bm.close()
class BinanceAccount(Account): def __init__(self, get_data=True): #self.last_updated = 'xxx' #self.sandbox = sandbox #gdax sandbox cred #config = configparser.ConfigParser() #cur_dir = os.path.dirname(__file__) #config.read(os.path.join(cur_dir,"config.txt")) #self.api_key = config.get("binance","key") #self.api_secret = config.get("binance","secret") #self.api_pass = '' self._init_client(get_data) self._stop_loss = None self._take_profit = None def _init_client(self, get_data=True): self.client = Client('key', 'secret', get_data=get_data) self.socket_manager = BinanceSocketManager(self.client) def get_price(self, symbol): if symbol.upper() == "BTC": symbol = "BTCUSDT" elif symbol.upper() == "ETH": symbol = "ETHUSDT" elif symbol.upper() == "USD": symbol = "USDBTCT" order_book = self.client.get_orderbook_ticker(symbol=symbol) price = float(order_book['askPrice']) return price def get_net_value(self, itemized=False): """Get value of all owned assets in USD. params: itemized : bool : if False (default) return total account value in dollars (int) if True, return dictionary of each assets value`{asset:usd}` """ base_asset = "BTCUSDT" current_base_price = self.get_price(base_asset) # is_held = lambda x: float(x["free"]) + float(x["locked"]) > 0 # all_assets = self.client.get_account() # owned_assets = filter(is_held,all_assets["balances"]) owned_assets = self.get_balance() asset_usd = {} for asset, asset_value in owned_assets.items(): asset_name = asset + base_asset[:3] try: asset_base_price = self.get_price(asset_name) usd_amt = asset_value * asset_base_price * current_base_price asset_usd[asset] = usd_amt except Exception as e: asset_usd[asset] = 0 if itemized: total = asset_usd else: total = sum([i for i in asset_usd.values()]) return total def get_state(self): state = [] money_available = int(self.get_net_value() > 0) state.append(money_available) short_pos_open = int(self.client.pos_direction < 0) state.append(short_pos_open) long_pos_open = int(self.client.pos_direction > 0) state.append(long_pos_open) return state def get_balance(self, symbol=None): """Get balance of free coins. params: symbol : str : if None, returns all free coins and their balance in a dictionary.p else returns the balance of inputted coin. """ is_held = lambda x: float(x["free"]) + float(x["locked"]) > 0 all_assets = self.client.get_account() owned_assets = list(filter(is_held, all_assets["balances"])) if symbol is not None: symbol = symbol.upper( )[:3] # only take first three of symbol i.e. ETH isntead of ETHBTC fn = lambda x: x["asset"] == symbol asset_value = list( filter(fn, owned_assets) ) # there could be a bug here? need to set asset_value to owned_assests['free'] if len(asset_value) > 0: asset_value = asset_value[0]["free"] else: asset_value = 0 #asset_value.append(0) # add 0 to list in case symbol not owned total = float(asset_value) else: total = {x["asset"]: float(x["free"]) for x in owned_assets} return total def get_positions(self, symbol): """return current positions""" pass def place_order(self, order_type, quantity, symbol, stop_loss=None, take_profit=None): """Places an order. params: order_type : str : if "BUY", Buys quantity amount of symbol. if "SELL", Sells quantity amount of symbol. quantity : float : how much you want to buy or sell. symbol : str : ex. ETHBTC """ if order_type.upper() == "BUY": order = self.client.order_market_buy(symbol=symbol, quantity=quantity) elif order_type.upper() == "SELL": order = self.client.order_market_sell(symbol=symbol, quantity=quantity) else: order = "Something is wrong!" self._stop_loss = stop_loss self._take_profit = take_profit return order def _set_stop_loss(self, price, symbol): self._stop_loss = price def _set_take_profit(self, price, symbol): self._take_profit = price def execute_stop_loss_or_take_profit(self): if self.cur_pos > 0: self.place_order("SELL", abs(cur_pos), 'btc') elif self.cur_pos < 0: self.place_order("BUY", abs(cur_pos), 'btc') def process_message(self, msg): os.system('clear') print("message type: {}".format(msg['e'])) print(msg) def start_ticker_stream(self, symbol, callback=None): if callback is None: self.process_message self.socket_manager.start_symbol_ticker_socket(symbol, callback) self.socket_manager.start() def stop_socket_stream(self): self.socket_manager.close() def start_kline_stream(self, symbol): self.socket_manager.start_kline_socket(symbol, self.process_message) self.socket_manager.start() def start_multi_stream(self, streams, callback=None): if callback is None: self.process_message self.socket_manager.start_multiplex_socket(streams, callback) self.socket_manager.start()
class LiveTrading: def __init__(self, client: object, strategy: object, debug: bool, verbose: bool, klines: dict = {}): self.client: object = client self.strategy: object = strategy self.trades: list = strategy.trades self.verbose: bool = verbose self.debug: bool = debug self.precision: dict = {} self.klines: dict = klines self.message_no: int = 0 for coin in self.strategy.tradeCoins: if self.verbose: print(f"Getting precision for {coin}") self.precision[coin] = self.get_precision(coin) self.klines[coin] = [] self.active_order: bool = False self.open_trade: bool = False self.balance: float = self.get_balance(self.strategy.baseCoin) self.starting_balance: float = self.balance print("\nStarting trading with the following:") print(f"Trade coins: {self.strategy.tradeCoins}") print(f"Starting {self.strategy.baseCoin}: {self.balance}") print(f"Indicator: {self.strategy.indicator}") print(f"Strategy: {self.strategy.strategy}") self.open_kline_sockets(self.strategy.tradeCoins) def get_balance(self, coin): return float(self.client.get_asset_balance(asset=coin)["free"]) def open_kline_sockets(self, coins: list): print(f"\nOpening kline socket for {coins}") self.bm = BinanceSocketManager(self.client) sockets = [] for coin in coins: sockets.append( f"{coin.lower()}{self.strategy.baseCoin.lower()}@kline_{self.strategy.interval}" ) self.kline_socket = self.bm.start_multiplex_socket( sockets, self.process_message) self.print_with_timestamp("Going live...") self.bm.start() @staticmethod def print_with_timestamp(message): print( f"{time.strftime('%d/%m/%y %H:%M:%S', time.localtime())} | {message}" ) def process_message(self, msg): msg = msg["data"] if self.verbose: self.print_with_timestamp(f"Recieved kline for {msg['s']}") if self.debug: print(msg) if (msg['e'] == "kline"): self.process_kline(msg['k']) elif msg['e'] == 'error': print("Socket error... restarting socket") self.bm.close() self.open_kline_sockets(self.strategy.tradeCoins) def process_kline(self, kline): if kline['x']: self.message_no += 1 trade_coin = f"{kline['s'][:len(kline['s']) - len(self.strategy.baseCoin)]}" self.klines[trade_coin].append(Data.process_socket_data(kline)) if (self.verbose): self.print_with_timestamp( f"Interval closed for {kline['s']}: {len(self.klines[trade_coin])}" ) if (self.debug): print(self.klines) if self.message_no == len(self.strategy.tradeCoins): self.message_no = 0 self.print_with_timestamp( "Got closed interval for all coins. Checking for any actions..." ) self.strategy.refresh(self.klines) # If the strategy is returning more trades than we have, there must be new ones if len(self.strategy.trades) > len(self.trades): new_trades = self.strategy.trades[len(self.trades):] print(f"New trades: {new_trades}") self.process_trades(new_trades) self.trades += new_trades def process_trades(self, trades): for trade in trades: if not trade.completed: if trade.action == "BUY": self.place_buy(trade.trade_coin, trade.price) elif trade.action == "SELL": self.place_sell(trade.trade_coin, trade.price) trade.completed = True def place_buy(self, coin, price): self.balance = self.get_balance(self.strategy.baseCoin) quantity = self.round_down((self.balance * 0.99) / float(price), self.precision[coin]) print(f"Buying {quantity} {coin} at {price}") order = self.client.create_order( symbol=f"{coin}{self.strategy.baseCoin}", side=SIDE_BUY, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=quantity, price=price) self.print_with_timestamp(order) def place_sell(self, coin, price): balance = self.get_balance(coin) quantity = self.round_down(balance, self.precision[coin]) if (quantity != 0): print(f"\nSelling {quantity} {coin} at {price}") order = self.client.create_order( symbol=f"{coin}{self.strategy.baseCoin}", side=SIDE_SELL, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=quantity, price=price) self.print_with_timestamp(order) else: print(f"Something went wrong. Bot is trying to sell 0 {coin}") def get_precision(self, coin): for filt in self.client.get_symbol_info( f"{coin}{self.strategy.baseCoin}")['filters']: if filt['filterType'] == 'LOT_SIZE': return int(round(-math.log(float(filt['stepSize']), 10), 0)) @staticmethod def round_down(n, decimals=0): return math.floor(n * (10**decimals)) / 10**decimals
class OLDFXConnector(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 = BinanceSocketManager(self.client) # self.connection = None self.ticker_connection = None self.user_data_connection = None def listen_symbols(self, symbols, on_ticker_received, user_data_handler): # self.client = Client(self.__key, self.__secret) self.bs = BinanceSocketManager(self.client) # self.connection = self.bs.start_multiplex_socket(['{}@trade'.format(s.lower()) for s in symbols], # socket_handler) self.bs.start_ticker_socket(on_ticker_received) # self.ticker_connection = self.bs.start_multiplex_socket(['{}@ticker'.format(s.lower()) for s in symbols], # on_ticker_received) self.user_data_connection = self.bs.start_user_socket( user_data_handler) self.logInfo('Ticker and User WS initialized') self.bs.name = 'Binance WS' def start_listening(self): self.bs.start() self.logInfo('WS listening started') def stop_listening(self): self.bs.close() self.logInfo('Socket stopped') # if self.ticker_connection: # # self.bs.stop_socket(self.connection) # self.bs.stop_socket(self.ticker_connection) # self.bs.stop_socket(self.user_data_connection) @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)
class BinanceManager: def __init__(self, api_key=None, secret_key=None): # Binance API Client self.client = Client(api_key, secret_key) # Binance API websocket self.socket = BinanceSocketManager(self.client) # Panda dataFrame self.df = None # Trading asset self.symbol = None # Interval self.interval = None # Logger self.logger = Logger() # File to save data self.filename = None def start(self, symbol, interval, startTime=None, filename="data.csv"): self.logger.success("Manager start") self.symbol = symbol self.interval = interval self.filename = filename self.get_historical_klines(self.symbol, interval, startTime) self.logger.success("Socket start") self.socket.start() self.logger.success("Socket kline start") self.socket.start_kline_socket(self.symbol, self.klines_callback, interval=self.interval) return self def get_historical_klines(self, symbol="btcusdt", interval="1h", startTime=None, endTime=None, limit=500): self.logger.info("Get historical klines") # fetch klines data = self.client.futures_klines(symbol=symbol, interval=interval, startTime=startTime, endTime=endTime, limit=limit) # Keep only the first 6 columns data = np.array(data)[:, 0:6] # Create a DataFrame with annotated columns df = pd.DataFrame( data, columns=["Date", "Open", "High", "Low", "Close", "Volume"]) # Convert Date from ms to Datetime df["Date"] = pd.to_datetime(df["Date"], unit="ms") # Convert columns to numeric values df["Open"] = pd.to_numeric(df["Open"], errors="coerce") df["High"] = pd.to_numeric(df["High"], errors="coerce") df["Low"] = pd.to_numeric(df["Low"], errors="coerce") df["Close"] = pd.to_numeric(df["Close"], errors="coerce") df["Volume"] = pd.to_numeric(df["Volume"], errors="coerce") # Set Date column as index df.set_index("Date", inplace=True) self.df = df self.write() return df def klines_callback(self, msg): self.logger.info("Websocket: Updating last kline") if msg['e'] == 'error': print("[!] ERROR: Klines websocket does not work.") return self # [Kline start time, Open price, High price, Low price, Close price, Base asset volume kline = np.array([ msg['k']['t'], msg['k']['o'], msg['k']['h'], msg['k']['l'], msg['k']['c'], msg['k']['v'] ]) # Create a Tableau with annotated columns df = pd.DataFrame( [kline], columns=["Date", "Open", "High", "Low", "Close", "Volume"]) # Convert Date from ms to Datetime df["Date"] = pd.to_datetime(df["Date"], unit="ms") # Convert columns to numeric values df["Open"] = pd.to_numeric(df["Open"], errors="coerce") df["High"] = pd.to_numeric(df["High"], errors="coerce") df["Low"] = pd.to_numeric(df["Low"], errors="coerce") df["Close"] = pd.to_numeric(df["Close"], errors="coerce") df["Volume"] = pd.to_numeric(df["Volume"], errors="coerce") df.set_index("Date", inplace=True) # If is not a new kline if self.df.last_valid_index() == df.last_valid_index(): # Update last kline value self.df.drop(self.df.last_valid_index(), inplace=True) # Append df to main df self.df = self.df.append(df, ignore_index=False) # Update data self.write() return self def write(self): if self.filename is not None: lock = FileLock(self.filename + '.lock') with lock: self.df.to_csv(self.filename) def stop(self): self.logger.success("Manager stop") self.socket.close() return self
class BalanceGUI(tk.Frame): def __init__(self, parent, coins): ''' Initialize the GUI and read the config file ''' tk.Frame.__init__(self, parent) parent.protocol('WM_DELETE_WINDOW', self.on_closing) self.parent = parent parent.deiconify() self.coins = coins self.coins_base = coins self.queue = queue.Queue() self.trades_placed = 0 self.trades_completed = 0 self.trades = [] self.headers = self.column_headers() self.read_config() self.initalize_records() #portfolio display self.portfolio_view = tk.LabelFrame(parent, text='Portfolio') self.portfolio_view.grid(row=0, column=0, columnspan=2, sticky=tk.E + tk.W + tk.N + tk.S) self.portfolio = tkinter.ttk.Treeview(self.portfolio_view, height=len(self.coins), selectmode='extended') self.portfolio['columns'] = ('Stored', 'Exchange', 'Locked', 'Target', 'Actual', 'Bid', 'Ask', 'Action', 'Status', 'Event') for label in self.portfolio['columns']: if label == 'Status' or label == 'Event': self.portfolio.column(label, width=200) elif label == 'Action': self.portfolio.column(label, width=120) else: self.portfolio.column(label, width=100) self.portfolio.heading(label, text=label) self.portfolio.grid(row=0, column=0) for i in range(2): self.parent.columnconfigure(i, weight=1, uniform='parent') #options display self.controls_view = tk.LabelFrame(parent, text='Controls') for i in range(4): self.controls_view.columnconfigure(i, weight=1, uniform='controls') self.controls_view.grid(row=1, column=0, sticky=tk.E + tk.W + tk.N + tk.S) self.key_label = tk.Label(self.controls_view, text='API Key', relief='ridge') self.key_label.grid(row=0, column=0, sticky=tk.E + tk.W) self.secret_label = tk.Label(self.controls_view, text='API Secret', relief='ridge') self.secret_label.grid(row=1, column=0, sticky=tk.E + tk.W) self.key_entry = tk.Entry(self.controls_view, show='*') self.key_entry.grid(row=0, column=1, columnspan=2, sticky=tk.E + tk.W) self.secret_entry = tk.Entry(self.controls_view, show='*') self.secret_entry.grid(row=1, column=1, columnspan=2, sticky=tk.E + tk.W) self.login = tk.Button(self.controls_view, text='Login', command=self.api_enter) self.login.grid(row=0, column=3, rowspan=2, sticky=tk.E + tk.W + tk.N + tk.S) #Statistics display self.stats_view = tk.LabelFrame(parent, text='Statistics') self.stats_view.grid(row=1, column=1, sticky=tk.E + tk.W + tk.N + tk.S) for i in range(4): self.stats_view.columnconfigure(i, weight=1, uniform='stats') self.trade_currency_value_label = tk.Label(self.stats_view, text=self.trade_currency + ' Value:', relief='ridge') self.trade_currency_value_label.grid(row=0, column=0, sticky=tk.E + tk.W) self.trade_currency_value_string = tk.StringVar() self.trade_currency_value_string.set('0') self.trade_currency_value = tk.Label( self.stats_view, textvariable=self.trade_currency_value_string) self.trade_currency_value.grid(row=0, column=1, sticky=tk.E + tk.W) self.imbalance_label = tk.Label(self.stats_view, text='Imbalance:', relief='ridge') self.imbalance_label.grid(row=1, column=0, sticky=tk.E + tk.W) self.imbalance_string = tk.StringVar() self.imbalance_string.set('0%') self.imbalance_value = tk.Label(self.stats_view, textvariable=self.imbalance_string) self.imbalance_value.grid(row=1, column=1, sticky=tk.E + tk.W) self.messages_queued_label = tk.Label(self.stats_view, text='Status', relief='ridge') self.messages_queued_label.grid(row=0, column=2, sticky=tk.E + tk.W) self.messages_string = tk.StringVar() self.messages_string.set('Up to Date') self.messages_queued = tk.Label(self.stats_view, textvariable=self.messages_string) self.messages_queued.grid(row=0, column=3, sticky=tk.E + tk.W) self.trades_label = tk.Label(self.stats_view, text='Trades Placed:', relief='ridge') self.trades_label.grid(row=1, column=2, sticky=tk.E + tk.W) self.trades_count = tk.IntVar() self.trades_count.set(0) self.trades_count_display = tk.Label(self.stats_view, textvariable=self.trades_count) self.trades_count_display.grid(row=1, column=3, sticky=tk.E + tk.W) def read_config(self): s_to_ms = 1000 config = configparser.RawConfigParser(allow_no_value=False) config.read('config.ini') self.trade_currency = config.get('trades', 'trade_currency') if self.trade_currency != 'BTC': self.display_error( 'Config Error', '{0} trading pairs are not supported yet, only BTC'.format( self.trade_currency), quit_on_exit=True) self.rebalance_time = int(config.get('trades', 'rebalance_period')) * s_to_ms if self.rebalance_time <= 0: self.display_error( 'Config Error', 'Rebalance period must be a positive integer (seconds)', quit_on_exit=True) self.min_trade_value = float(config.get('trades', 'min_trade_value')) if self.min_trade_value <= 0: self.min_trade_value = None self.trade_type = config.get('trades', 'trade_type') if self.trade_type != 'MARKET' and self.trade_type != 'LIMIT': self.display_error( 'Config Error', '{0} is not a supported trade type. Use MARKET or LIMIT'. format(trade_type), quit_on_exit=True) self.ignore_backlog = int(config.get('websockets', 'ignore_backlog')) def on_closing(self): ''' Check that all trades have executed before starting the save and exit process ''' if self.trades_placed > 0 and self.trades_completed < self.trades_placed: if messagebox.askokcancel( 'Quit', 'Not all trades have completed. Quit anyway?'): self.save_and_quit() else: self.save_and_quit() def save_and_quit(self): ''' If trades have been executed in the current session, save them to file. Stop all websockets and exit the GUI. ''' if self.trades: df = pd.DataFrame(self.trades) if os.path.isfile('trade_history.csv'): with open('trade_history.csv', 'a') as f: df.to_csv(f, sep=',', header=False, index=False) else: with open('trade_history.csv', 'w') as f: df.to_csv(f, sep=',', header=True, index=False) for coin in self.coins['coin']: pair = coin + self.trade_currency self.records[pair].close() try: self.bm.close() reactor.stop() except AttributeError: self.parent.destroy() else: self.parent.destroy() def exit_error(self): if self.quit_on_exit: self.top.destroy() self.save_and_quit() else: self.top.destroy() def display_error(self, title, error, quit_on_exit=False): self.quit_on_exit = quit_on_exit self.top = tk.Toplevel() self.top.title('Login Error') msg = tk.Message(self.top, text=error) msg.grid(row=0, column=0) button = tk.Button(self.top, text="Dismiss", command=self.exit_error) button.grid(row=1, column=0) self.top.attributes('-topmost', 'true') def api_enter(self): ''' Log in to Binance with the provided credentials, update user portfolio and start listening to price and account update websockets. ''' api_key = self.key_entry.get() self.key_entry.delete(0, 'end') api_secret = self.secret_entry.get() self.secret_entry.delete(0, 'end') try: self.client = Client(api_key, api_secret) status = self.client.get_system_status() except (BinanceRequestException, BinanceAPIException) as e: self.display_error('Login Error', e.message) else: try: self.populate_portfolio() except BinanceAPIException as e: self.display_error('API Error', e.message, quit_on_exit=True) else: self.start_websockets() def start_websockets(self): ''' Start websockets to get price updates for all coins in the portfolio, trade execution reports, and user account balance updates. Start the message queue processor. ''' self.bm = BinanceSocketManager(self.client) trade_currency = self.trade_currency symbols = self.coins['symbol'].tolist() symbols.remove(trade_currency + trade_currency) self.sockets = {} for symbol in symbols: self.sockets[symbol] = self.bm.start_symbol_ticker_socket( symbol, self.queue_msg) self.sockets['user'] = self.bm.start_user_socket(self.queue_msg) self.bm.start() self.parent.after_idle(self.parent.after, 1, self.process_queue) def initalize_records(self): self.records = dict() for coin in self.coins['coin']: pair = coin + self.trade_currency self.records[pair] = open(pair + '.csv', 'a+', 1) #unbuffered def populate_portfolio(self): ''' Get all symbol info from Binance needed to populate user portfolio data and execute trades ''' self.coins = self.coins_base self.portfolio.delete(*self.portfolio.get_children()) exchange_coins = [] trade_currency = self.trade_currency self.trade_coin = trade_currency #update the GUI context self.key_label.destroy() self.key_entry.destroy() self.secret_label.destroy() self.secret_entry.destroy() self.login.destroy() updatetext = tk.StringVar() updatetext.set('Initializing') self.progresslabel = tk.Label(self.controls_view, textvariable=updatetext) self.progresslabel.grid(row=1, column=0, columnspan=4, sticky=tk.E + tk.W) progress_var = tk.DoubleVar() progress = 0 progress_var.set(progress) self.progressbar = tkinter.ttk.Progressbar(self.controls_view, variable=progress_var, maximum=len(self.coins)) self.progressbar.grid(row=0, column=0, columnspan=4, sticky=tk.E + tk.W) for coin in self.coins['coin']: self.progressbar.update() progress += 1 progress_var.set(progress) updatetext.set('Fetching {0} account information'.format(coin)) self.progresslabel.update() pair = coin + trade_currency balance = self.client.get_asset_balance(asset=coin) if coin != trade_currency: price = float( self.client.get_symbol_ticker(symbol=pair)['price']) symbolinfo = self.client.get_symbol_info( symbol=pair)['filters'] minvalue = float(symbolinfo[3]['minNotional']) if self.min_trade_value is not None: minvalue = self.min_trade_value row = { 'coin': coin, 'exchange_balance': float(balance['free']), 'locked_balance': float(balance['locked']), 'minprice': float(symbolinfo[0]['minPrice']), 'maxprice': float(symbolinfo[0]['maxPrice']), 'ticksize': float(symbolinfo[0]['tickSize']), 'minqty': float(symbolinfo[2]['minQty']), 'maxqty': float(symbolinfo[2]['maxQty']), 'stepsize': float(symbolinfo[2]['stepSize']), 'minnotional': minvalue, 'symbol': pair, 'askprice': price, 'bidprice': price, 'price': price, 'last_placement': None, 'last_execution': None } else: fixed_balance = self.coins.loc[self.coins['coin'] == coin]['fixed_balance'] row = { 'coin': coin, 'exchange_balance': float(balance['free']), 'locked_balance': float(balance['locked']), 'minprice': 0, 'maxprice': 0, 'ticksize': 0, 'minqty': 0, 'maxqty': 0, 'stepsize': 0, 'minnotional': 0, 'symbol': coin + coin, 'askprice': 1.0, 'bidprice': 1.0, 'price': 1.0, 'last_placement': None, 'last_execution': None } exchange_coins.append(row) exchange_coins = pd.DataFrame(exchange_coins) self.coins = pd.merge(self.coins, exchange_coins, on='coin', how='outer') self.coins['value'] = self.coins.apply( lambda row: row.price * (row.exchange_balance + row.fixed_balance), axis=1) self.total = np.sum(self.coins['value']) self.coins['actual'] = self.coins.apply( lambda row: 100.0 * row.value / self.total, axis=1) self.update_status() i = 0 for row in self.coins.itertuples(): self.portfolio.insert( '', i, iid=row.coin, text=row.coin, values=(row.fixed_balance, row.exchange_balance, row.locked_balance, '{0} %'.format(row.allocation), '{0:.2f} %'.format(row.actual), round_decimal(row.price, row.ticksize), round_decimal(row.price, row.ticksize), '', '')) i += 1 updatetext.set('Testing connection'.format(coin)) self.dryrun() self.progressbar.destroy() self.progresslabel.destroy() self.automate = tk.BooleanVar() self.automate.set(False) self.automate_text = tk.StringVar() self.automate_text.set('Start Automation') self.toggle_automate = tk.Button( self.controls_view, textvariable=self.automate_text, command=lambda: self.automation(toggle=True)) self.toggle_automate.grid(row=0, column=0, rowspan=2, columnspan=2, sticky=tk.E + tk.W + tk.N + tk.S) self.sell_button = tk.Button(self.controls_view, text='Execute Sells', command=self.execute_sells) self.sell_button.grid(row=0, column=2, columnspan=2, sticky=tk.E + tk.W) self.buy_button = tk.Button(self.controls_view, text='Execute Buys', command=self.execute_buys) self.buy_button.grid(row=1, column=2, columnspan=2, sticky=tk.E + tk.W) def update_status(self): '''Update the statistics frame whenever a change occurs in balance or price''' value = '{0:.8f}'.format(self.total) diff = np.diff(self.coins['actual'].values - self.coins['allocation'].values) imbalance = '{0:.2f}%'.format(np.sum(np.absolute(diff))) self.trade_currency_value_string.set(value) self.imbalance_string.set(imbalance) def queue_msg(self, msg): ''' Whenever a weboscket receives a message, check for errors. If an error occurs, restart websockets. If no error, add it to the message queue. ''' if msg['e'] == 'error': self.bm.close() reactor.stop() self.start_websockets() else: self.queue.put(msg) def get_msg(self): '''Reroute new websocket messages to the appropriate handler''' try: msg = self.queue.get(block=False) except queue.Empty: pass else: if msg['e'] == '24hrTicker': self.update_price(msg) elif msg['e'] == 'outboundAccountInfo': self.update_balance(msg) elif msg['e'] == 'executionReport': self.update_trades(msg) def process_queue(self, flush=False): ''' Check for new messages in the queue periodically. Recursively calls itself to perpetuate the process. ''' if flush: while not self.queue.empty(): self.get_msg() else: self.get_msg() self.master.after_idle(self.master.after, 1, self.process_queue) n = self.queue.qsize() if n > self.ignore_backlog: self.messages_string.set('{0} Updates Queued'.format(n)) else: self.messages_string.set('Up to Date') def update_trades(self, msg): ''' Update balances whenever a partial execution occurs ''' coin = msg['s'][:-len(self.trade_coin)] savemsg = { self.headers[key]: value for key, value in list(msg.items()) } filled = float(savemsg['cumulative_filled_quantity']) orderqty = float(savemsg['order_quantity']) side = savemsg['side'] if filled >= orderqty: self.coins.loc[self.coins['coin'] == coin, 'last_execution'] = time.mktime( datetime.now().timetuple()) self.trades_completed += 1 self.trades_count.set(self.trades_completed) self.portfolio.set(coin, column='Event', value='{0} {1}/{2} {3}'.format( side, filled, orderqty, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) self.trades.append(savemsg) def update_balance(self, msg): ''' Update user balances internally and on the display whenever an account update message is received. ''' balances = msg['B'] coins = self.coins['coin'].values for balance in balances: coin = balance['a'] if coin in coins: exchange_balance = float(balance['f']) + float(balance['l']) locked_balance = float(balance['l']) self.portfolio.set( coin, column='Exchange', value=round_decimal( exchange_balance, self.coins.loc[ self.coins['coin'] == coin]['stepsize'].values[0])) self.portfolio.set( coin, column='Locked', value=round_decimal( locked_balance, self.coins.loc[ self.coins['coin'] == coin]['stepsize'].values[0])) self.coins.loc[self.coins['coin'] == coin, 'exchange_balance'] = exchange_balance self.coins.loc[self.coins['coin'] == coin, 'locked_balance'] = locked_balance ask = self.coins.loc[self.coins['coin'] == coin, 'askprice'].values[0] value = (self.coins.loc[self.coins['coin'] == coin, 'exchange_balance'].values[0] + self.coins.loc[self.coins['coin'] == coin, 'fixed_balance'].values[0]) * ask self.coins.loc[self.coins['coin'] == coin, 'value'] = value self.total = np.sum(self.coins['value']) self.coins['actual'] = self.coins.apply( lambda row: 100.0 * row.value / self.total, axis=1) for row in self.coins.itertuples(): coin = row.coin actual = '{0:.2f}%'.format( self.coins.loc[self.coins['coin'] == coin, 'actual'].values[0]) self.portfolio.set(coin, column='Actual', value=actual) self.update_actions() self.update_status() def update_price(self, msg): ''' Update symbol prices and user allocations internally and on the display whenever a price update is received. ''' coin = msg['s'][:-len(self.trade_currency)] ask = float(msg['a']) bid = float(msg['b']) askprice = round_decimal( ask, self.coins.loc[self.coins['coin'] == coin, 'ticksize'].values[0]) bidprice = round_decimal( bid, self.coins.loc[self.coins['coin'] == coin, 'ticksize'].values[0]) self.portfolio.set(coin, column='Ask', value=askprice) self.coins.loc[self.coins['coin'] == coin, 'askprice'] = ask self.portfolio.set(coin, column='Bid', value=bidprice) self.coins.loc[self.coins['coin'] == coin, 'bidprice'] = bid value = (self.coins.loc[self.coins['coin'] == coin, 'exchange_balance'].values[0] + self.coins.loc[self.coins['coin'] == coin, 'fixed_balance'].values[0]) * ask self.coins.loc[self.coins['coin'] == coin, 'value'] = value self.total = np.sum(self.coins['value']) self.coins['actual'] = self.coins.apply( lambda row: 100.0 * row.value / self.total, axis=1) for row in self.coins.itertuples(): coin = row.coin actual = '{0:.2f}%'.format( self.coins.loc[self.coins['coin'] == coin, 'actual'].values[0]) self.portfolio.set(coin, column='Actual', value=actual) self.update_actions() self.update_status() self.print_price(msg) def print_price(self, msg): pair = msg['s'] avg_price = float(msg['w']) time = float(msg['E']) mid_price = (float(msg['b']) + float(msg['a'])) / 2.0 self.records[pair].write('{0},{1},{2}\n'.format( time, avg_price, mid_price)) def update_actions(self): ''' Calcuate required trades and update the main GUI ''' for row in self.coins.itertuples(): tradecoin_balance = np.squeeze( self.coins[self.coins['coin'] == self.trade_coin]['exchange_balance'].values) tradecoin_locked = np.squeeze( self.coins[self.coins['coin'] == self.trade_coin]['locked_balance'].values) tradecoin_free = tradecoin_balance - tradecoin_locked dif = (row.allocation - row.actual) / 100.0 * self.total / row.price if dif < 0: side = SIDE_SELL if dif > 0: side = SIDE_BUY status = '' coin = row.coin pair = coin + self.trade_coin balance = float(row.exchange_balance) - float(row.locked_balance) actual = row.actual qty = np.absolute(dif) action = '{0} {1}'.format(side, round_decimal(qty, row.stepsize)) if side == SIDE_SELL: price = row.bidprice if side == SIDE_BUY: price = row.askprice if side == SIDE_SELL and qty > balance and coin != self.trade_coin: status = 'Insufficient ' + coin + ' for sale' if coin == self.trade_coin: status = 'Ready' elif qty < row.minqty or qty * price < row.minnotional: status = status = 'Trade value too small ({0:.0f}%)'.format( 100.0 * qty * price / row.minnotional) elif qty > row.maxqty: status = 'Trade quantity too large' elif side == SIDE_BUY and qty * price > tradecoin_free: status = 'Insufficient ' + self.trade_coin + ' for purchase' else: status = 'Trade Ready' self.portfolio.set(coin, column='Status', value=status) self.portfolio.set(coin, column='Action', value=action) def execute_transactions(self, side, dryrun): ''' Calculate the required trade for each coin and execute them if they belong to the appropriate side ''' for row in self.coins.itertuples(): self.process_queue(flush=True) tradecoin_balance = np.squeeze( self.coins[self.coins['coin'] == self.trade_coin]['exchange_balance'].values) tradecoin_locked = np.squeeze( self.coins[self.coins['coin'] == self.trade_coin]['locked_balance'].values) tradecoin_free = tradecoin_balance - tradecoin_locked dif = (row.allocation - row.actual) / 100.0 * self.total / row.price if dif < 0 and side == SIDE_BUY: continue if dif > 0 and side == SIDE_SELL: continue status = '' coin = row.coin pair = coin + self.trade_coin balance = float(row.exchange_balance) - float(row.locked_balance) actual = row.actual qty = np.absolute(dif) action = '{0} {1}'.format(side, round_decimal(qty, row.stepsize)) last_placement = np.squeeze(self.coins[self.coins['coin'] == coin] ['last_placement'].values) last_execution = np.squeeze(self.coins[self.coins['coin'] == coin] ['last_execution'].values) if side == SIDE_SELL: price = row.bidprice if side == SIDE_BUY: price = row.askprice if side == SIDE_SELL and qty > balance and coin != self.trade_coin: status = 'Insufficient ' + coin + ' for sale' if coin == self.trade_coin: status = 'Ready' elif qty < row.minqty or qty * price < row.minnotional: status = 'Trade value too small ({0:.0f}%)'.format( 100.0 * qty * price / row.minnotional) elif qty > row.maxqty: status = 'Trade quantity too large' elif side == SIDE_BUY and qty * price > tradecoin_free: status = 'Insufficient ' + self.trade_coin + ' for purchase' elif last_placement == None or last_execution >= last_placement: trade_currency = self.trade_coin try: self.place_order(coin, pair, self.trade_type, qty, price, side, dryrun, row.stepsize, row.ticksize) except (BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException) as e: self.portfolio.set(coin, column='Event', value=e.message) else: status = 'Trade Ready' if not dryrun: self.trades_placed += 1 status = 'Trade Placed' self.portfolio.set(coin, column='Event', value='Trade Placed') self.portfolio.set(coin, column='Status', value=status) self.portfolio.set(coin, column='Action', value=action) def automation(self, toggle=False): if toggle: if not self.automate.get(): self.automate_text.set('Stop Automation') else: self.automate_text.set('Start Automation') self.automate.set(not self.automate.get()) if self.automate.get(): self.execute_sells() self.execute_buys() self.rebalance_callback = self.parent.after( self.rebalance_time, self.automation) else: self.parent.after_cancel(self.rebalance_callback) def execute_sells(self): ''' Perform any sells required by overachieving coins ''' self.execute_transactions(side=SIDE_SELL, dryrun=False) def execute_buys(self): ''' Perform any buys required by underachieving coins ''' self.execute_transactions(side=SIDE_BUY, dryrun=False) def dryrun(self): ''' perform a dry run to list what trades are required ''' self.execute_transactions(side=SIDE_SELL, dryrun=True) self.execute_transactions(side=SIDE_BUY, dryrun=True) def place_order(self, coin, pair, trade_type, quantity, price, side, dryrun, stepsize, ticksize): ''' Format and place an order using the Binance API ''' if trade_type == 'LIMIT': if dryrun: order = self.client.create_test_order( symbol=pair, side=side, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=round_decimal(quantity, stepsize), price=round_decimal(price, ticksize)) else: order = self.client.create_order( symbol=pair, side=side, type=ORDER_TYPE_LIMIT, timeInForce=TIME_IN_FORCE_GTC, quantity=round_decimal(quantity, stepsize), price=round_decimal(price, ticksize)) elif trade_type == 'MARKET': if dryrun: order = self.client.create_test_order(symbol=pair, side=side, type=ORDER_TYPE_MARKET, quantity=round_decimal( quantity, stepsize)) else: order = self.client.create_order(symbol=pair, side=side, type=ORDER_TYPE_MARKET, quantity=round_decimal( quantity, stepsize)) if not dryrun: self.coins.loc[self.coins['coin'] == coin, 'last_placement'] = time.mktime( datetime.now().timetuple()) def column_headers(self): ''' define human readable aliases for the headers in trade execution reports. ''' return { 'e': 'event_type', 'E': 'event_time', 's': 'symbol', 'c': 'client_order_id', 'S': 'side', 'o': 'type', 'O': 'order_creation_time', 'f': 'time_in_force', 'q': 'order_quantity', 'p': 'order_price', 'P': 'stop_price', 'F': 'iceberg_quantity', 'g': 'ignore_1', 'C': 'original_client_order_id', 'x': 'current_execution_type', 'X': 'current_order_status', 'r': 'order_reject_reason', 'i': 'order_id', 'l': 'last_executed_quantity', 'z': 'cumulative_filled_quantity', 'Z': 'cumulative_quote_asset_transacted_qty', 'L': 'last_executed_price', 'n': 'commission_amount', 'N': 'commission_asset', 'T': 'transaction_time', 't': 'trade_id', 'I': 'ignore_2', 'w': 'order_working', 'm': 'maker_side', 'M': 'ignore_3', 'Y': 'last_quote_asset_transacted_qty' }
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 DataCatcher: # Функция для обработки потока ордерной книги def orderbook_callback(self, query): pair = query['stream'].split('@')[0] # Записываем все, что можем вытащить из ордербука, в хранилище for side in ('asks', 'bids'): for level, (price, quantity) in enumerate(query['data'][side]): self.storage[pair + '_' + side + '_orderbook_price_level_' + str(level)] = price self.storage[pair + '_' + side + '_orderbook_quantity_level_' + str(level)] = quantity # Функция для обработки свечей def kline_callback(self, query): pair = query['stream'].split('@')[0] # Если свеча еще не завершена (данные неполные), ничего не делаем if not query['data']['k']['x']: return name_dict = { 'open_price': 'o', 'close_price': 'c', 'high_price': 'h', 'low_price': 'l', 'base_volume': 'v', 'trade_number': 'n', 'quote_volume': 'q', 'taker_base_volume': 'V', 'taker_quote_volume': 'Q', 'update_time': 'T' } for here in name_dict: self.storage[pair + '_kline_' + here] = query['data']['k'][name_dict[here]] # Функция, которая вызывается, когда мы ловим сообщение def general_callback(self, query): stream_type = query['stream'].split('@')[1] # Ниже мы определяем, какой поток какая функция обрабатывает и запускаем # обработку в отдельном потоке, чтобы не тормозить stream_callbacks = { 'kline_1m': self.kline_callback, 'depth20': self.orderbook_callback } action = threading.Thread(target=stream_callbacks[stream_type], args=(query, )) action.start() def init_streams(self): # Считываем из настроек, какие мы рассматриваем крипты и потоки и инициализируем потоки with open('settings/pairs.txt') as file: self.pairs = file.readlines() self.pairs = [el[:-1] for el in self.pairs] with open('settings/streams.txt') as file: streams = file.readlines() streams = [el[:-1] for el in streams] for pair in self.pairs: for stream in streams: self.streams.append(pair + stream) # saver - функция, вызываемая каждую секунду и сохраняющая данные # timeout - через сколько вырубать обработку, period - длина одного тика def __init__(self, saver=None, storage=None, timeout=23 * 60 * 60, period=1., process_inside=True): self.timeout = timeout self.period = period self.manager = multiprocessing.Manager() # storage - куда мы сохраняем данные if storage is None: self.storage = self.manager.dict() else: self.storage = storage self.pairs = [] self.streams = [] self.init_streams() self.start_time = time.time() self.client = init_client() # Сокет, который все слушает (запускаем в отдельном процессе) self.main_socket = BinanceSocketManager(self.client) self.connection_key = self.main_socket.start_multiplex_socket( self.streams, self.general_callback) self.process_inside = process_inside if self.process_inside: self.socket_process = multiprocessing.Process( target=self.main_socket.run) self.data_saver = saver self.timer = RepeatTimer(self.period, self.give_data) logging.info('DataCatcher initialization successful') # Функция завершения работы def finish(self): logging.info('finishing listening') self.timer.cancel() self.main_socket.stop_socket(self.connection_key) self.main_socket.close() self.socket_process.terminate() # Функция передачи данных saver-у def give_data(self): # Смотри комментарии к kline_callback for pair in self.pairs: self.storage[pair + '_kline_time_since_update'] = time.time() * 1000 - \ self.storage[pair + '_kline_update_time'] self.storage['time'] = time.time() self.data_saver.push_data(self.storage) def start(self): logging.info('started listening') if self.process_inside: self.socket_process.start() # Ждем 2 минуты, чтобы все успело прийти logging.info('waiting for 2 minutes') time.sleep(120.) logging.info('started transferring data') timeout_timer = threading.Timer(self.timeout, self.finish) timeout_timer.start() if self.data_saver is not None: self.timer.start() self.socket_process.join()
class BinanceConnector(Connector): BINANCE_ORDER_STATUS_FILLED = "FILLED" BINANCE_ORDER_STATUS_CANCELED = "CANCELED" def __init__(self): super().__init__() self.client: Client = Client("", "") self.socket = BinanceSocketManager(self.client) self.connected: bool = False def connect(self, key: str, secret: str) -> None: self.client = Client(api_key=key, api_secret=secret) self.socket = BinanceSocketManager(self.client) self.connected = True def start_listen(self): if not self.connected: return self.socket.start_user_socket(self.user_handler) self.socket.start_miniticker_socket(self.ticker_handler) self.socket.daemon = True self.socket.start() def user_handler(self, message: Dict): message_type = message["e"] if not message_type == "executionReport": return order_id = message["i"] binance_order_status = message["X"] if binance_order_status == BinanceConnector.BINANCE_ORDER_STATUS_FILLED: self.emit( UserEvent({ "external_id": order_id, "status": BinanceConnector.ORDER_STATUS_FILLED })) elif binance_order_status == BinanceConnector.BINANCE_ORDER_STATUS_CANCELED: self.emit( UserEvent({ "external_id": order_id, "status": BinanceConnector.ORDER_STATUS_CANCELED })) def ticker_handler(self, message: List[Dict]): for ticker in message: message_type = ticker["e"] if not message_type == "24hrMiniTicker": continue self.emit(TickerEvent({})) def stop_listen(self): self.socket.close() def satisfied(self, execution_order: SingleExecutionOrder) -> bool: execution_conditions = execution_order.conditions return True def submit(self, execution_order: SingleExecutionOrder) -> int: execution_params = execution_order.params side = self.client.SIDE_BUY if execution_params.command == ExecutionParams.CMD_SELL: side = self.client.SIDE_SELL params = { "newClientOrderId": execution_order.order_id, "symbol": execution_params.symbol, "quantity": execution_params.quantity, "side": side, "type": self.client.ORDER_TYPE_MARKET } if not execution_params.price == 0: params["type"] = self.client.ORDER_TYPE_LIMIT params["price"] = execution_params.price params["timeInForce"] = self.client.TIME_IN_FORCE_GTC try: binance_order = self.client.create_order(**params) except (BinanceRequestException, BinanceOrderException, BinanceAPIException) as exception: raise ConnectorException(exception.message, execution_order) return binance_order["orderId"] def is_filled(self, execution_order: SingleExecutionOrder) -> bool: try: binance_order = self.client.get_order( symbol=execution_order.params.symbol, orderId=execution_order.external_id) except (BinanceRequestException, BinanceAPIException) as exception: raise ConnectorException(exception.message, execution_order) return binance_order["status"] == self.client.ORDER_STATUS_FILLED def cancel(self, execution_order: SingleExecutionOrder) -> None: try: self.client.cancel_order(symbol=execution_order.params.symbol, orderId=execution_order.external_id) except (BinanceRequestException, BinanceAPIException) as exception: raise ConnectorException(exception.message, execution_order)
class BinanceExchange(Exchange): exchange_name = "Binance" isMargin = False def __init__(self, apiKey, apiSecret, pairs, name): super().__init__(apiKey, apiSecret, pairs, name) self.connection = Client(self.api['key'], self.api['secret']) symbol_info_arr = self.connection.get_exchange_info() dict_symbols_info = { item['symbol']: item for item in symbol_info_arr["symbols"] } actual_symbols_info = { symbol: dict_symbols_info[symbol] for symbol in self.pairs } self.symbols_info = actual_symbols_info self.update_balance() self.socket = BinanceSocketManager(self.connection) self.socket.start_user_socket(self.on_balance_update) self.socket.start() self.is_last_order_event_completed = True self.step_sizes = {} self.balance_updated = True for symbol_info in symbol_info_arr['symbols']: if symbol_info['symbol'] in self.pairs: self.step_sizes[symbol_info['symbol']] = \ [f['stepSize'] for f in symbol_info['filters'] if f['filterType'] == 'LOT_SIZE'][0] def start(self, caller_callback): self.socket.start_user_socket(caller_callback) def update_balance(self): account_information = self.connection.get_account() self.set_balance(account_information['balances']) def get_trading_symbols(self): symbols = set() if not self.symbols_info: raise RuntimeError("Cant get exchange info") for key, value in self.symbols_info.items(): symbols.add(value["quoteAsset"]) symbols.add(value["baseAsset"]) return symbols def set_balance(self, balances): symbols = self.get_trading_symbols() dict_balances = {item['asset']: item for item in balances} actual_balance = {symbol: dict_balances[symbol] for symbol in symbols} self.balance = actual_balance def on_balance_update(self, upd_balance_ev): if upd_balance_ev['e'] == '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 BinanceAPIManagerNew(BinanceAPIManager): def __init__(self, config: ConfigNew, db: Database, logger: Logger): self.db = db self.logger = logger self.config = config api_key = self.config.BINANCE_API_KEY api_secret = self.config.BINANCE_API_SECRET_KEY self.binance_client = BinanceClientNew(api_key, api_secret, config, logger) if self.binance_client.is_client_ok: self.socket_manager = BinanceSocketManager(self.binance_client) self.binance_client.socket_manager = self.socket_manager def start_multiplex_socket(self): conn_key = self.binance_client.start_multiplex_socket() return conn_key def start_user_socket(self): conn_key = self.binance_client.start_user_socket() return conn_key def start_sock_manager(self): self.socket_manager.start() self.logger.info("BinanceSocketManager started") def stop_socket(self): self.binance_client.stop_multiplex_socket() self.binance_client.stop_user_socket() def stop_sock_manager(self): self.stop_socket() self.socket_manager.close() reactor.stop() self.logger.info("BinanceSocketManager closed") def get_all_market_tickers(self) -> AllTickers: """ Get ticker price of all coins """ # return super().get_all_market_tickers() return AllTickers(self.binance_client.get_all_tickers()) def get_all_balances(self): try: account_info = self.binance_client.get_account() except BinanceAPIException as e: self.logger.error(f"Error in get_all_balances(): {e}") if e.code == -1003: pass return None return account_info["balances"] def get_currency_balance(self, currency_symbol: str): """ Get balance of a specific coin """ try: account_info = self.binance_client.get_account() except BinanceAPIException as e: self.logger.error(f"Error in get_currency_balance(): {e}") if e.code == -1003: pass return None for currency_balance in account_info["balances"]: if currency_balance["asset"] == currency_symbol: return float(currency_balance["free"]) return None 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 delete_old_data(self, file_dir, file_cnt=1): self.binance_client.delete_old_data(file_dir, file_cnt=file_cnt)
class BinanceInfluxdb(): def __init__(self,symbol = "IOTAUSDT",is_new_db = False,database ='binance',measurement= 'minute_tick',host='localhost', port=8086,api_key = "api_key",api_secret = "private_api_key" ): self.database = database self.client = Client(api_key, api_secret) self.InfluxClient = InfluxDBClient(host='localhost', port=8086) if is_new_db: self.InfluxClient.create_database(self.database) self.InfluxClient.switch_database(self.database) self.InfluxClient.create_retention_policy(name='coin_policy',duration='INF',database=self.database,replication=1, default=True, shard_duration='4w') self.symbol = symbol self.measurement_name = measurement self.need_data_actualization = True def online_process_message(self,msg): measurement = self.measurement_name msg_type = "raw" self.insert_data_point_influxdb(msg,measurement,msg_type) if self.need_data_actualization: print("##############################one time update #####################################") current_time_num=msg["k"]["t"]/1000.0 self.get_previous_point(current_time_num) self.need_data_actualization = False def get_previous_point(self,current_time_num): count = 1 # current_time_num=previous_msg["k"]["t"]/1000.0 pre_previous_time = zulu.parse(current_time_num-60).isoformat() #I rest 60 seconds pre_previous_time_query=self.InfluxClient.query("select * from {2} WHERE time = '{0}' AND pair = '{1}';".format(pre_previous_time,self.symbol,self.measurement_name)) while not len(list(pre_previous_time_query)): count = count+1 pre_previous_time_loopty_loop = zulu.parse(current_time_num-count*60).isoformat() pre_previous_time_query=self.InfluxClient.query("select * from {2} WHERE time = '{0}' AND pair = '{1}';".format(pre_previous_time_loopty_loop,self.symbol,self.measurement_name)) print("testing_time: {0}, count:".format(pre_previous_time_loopty_loop),count) event_type = 'kline' interval ='1m' units = 'minute' num_of_units = count+2 #Just in case I added 2 units more msg_type = 'raw' self.insert_offline_tick_data(event_type,interval,units,num_of_units,msg_type) def insert_data_point_influxdb(self,msg,measurement,msg_type): if msg_type == 'raw': json_body4 = [ { "measurement": measurement, "tags": { "event_type": msg["e"], "base_currency": msg["s"][:int(len(msg["s"])/2)], "quote_currency": msg["s"][int(len(msg["s"])/2):], "pair": msg["s"], "interval": msg["k"]["i"] }, "time": zulu.parse(msg["k"]["t"]/1000.0).isoformat(),#datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),#+'Z', "fields": { "open": float(msg["k"]["o"]), "close":float(msg["k"]["c"]), "high":float(msg["k"]["h"]), "low":float(msg["k"]["l"]), "high-low":float(msg["k"]["h"])-float(msg["k"]["l"]), "close-open":float(msg["k"]["c"])-float(msg["k"]["o"]), "volume":float(msg["k"]["v"]), #Base asset volume "number_of_trades":int(msg["k"]["n"]), "quote_volume":float(msg["k"]["q"]), #Quote asset volume "active_buy_volume":float(msg["k"]["V"]), #Taker buy base asset volume "active_buy_quote_volume":float(msg["k"]["Q"]), #Taker buy quote asset volume "gain":-1000, "lose":-1000, "avg_gain":-1000, "avg_lose":-1000, "RSI":-1000, "MACD":-1000, "KDJ":-1000, "DMI":-1000, "OBV":-1000, "MTM":-1000, "EMA":-1000, "VWAP":-1000, "AVL":-1000, "TRIX":-1000, "StochRSI":-1000, "EMV":-1000, "WR":-1000, "BOLL":-1000, "SAR":-1000, "CCI":-1000, "MA":-1000, "VOL":-1000 } } ] self.InfluxClient.write_points(points=json_body4,retention_policy='coin_policy') print("inserting message with time: {0}, message type: {1}, measurement: {2}".format(json_body4[0]["time"],msg_type,measurement)) def create_msg_from_history(self,event_type,interval,symbol,units,num_of_units,from_now=True,from_date=0,to_date=0): #update this to accept more ranges if from_now: #http://dateparser.readthedocs.io/en/latest/ if units == 'hour': raw_data=self.client.get_historical_klines(symbol, interval, "{0} hour ago UTC".format(num_of_units)) elif units == 'day': raw_data=self.client.get_historical_klines(symbol, interval, "{0} day ago UTC".format(num_of_units)) elif units == 'week': raw_data=self.client.get_historical_klines(symbol, interval, "{0} week ago UTC".format(num_of_units)) elif units == 'month': raw_data=self.client.get_historical_klines(symbol, interval, "{0} month ago UTC".format(num_of_units)) elif units == 'minute': raw_data=self.client.get_historical_klines(symbol, interval, "{0} minute ago UTC".format(num_of_units)) else: raw_data=self.client.get_historical_klines(symbol, interval,from_date,to_date) #klines = client.get_historical_klines("ETHBTC", Client.KLINE_INTERVAL_30MINUTE, "1 Dec, 2017", "1 Jan, 2018") list_of_msgs = list(np.zeros(len(raw_data),dtype=object)) for i, raw_msg in enumerate(raw_data): if i%10000 == 0: print(i) list_of_msgs[i] = { "measurement": self.measurement_name, "tags": { "event_type": event_type, "base_currency": symbol[:int(len(symbol)/2)], "quote_currency": symbol[int(len(symbol)/2):], "pair": symbol , "interval": interval }, "time": zulu.parse(raw_msg[0]/1000.0).isoformat(),#datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),#+'Z', "fields": { "open": float(raw_msg[1]), "close":float(raw_msg[4]), "high":float(raw_msg[2]), "low":float(raw_msg[3] ), "high-low":float(raw_msg[2])-float(raw_msg[3]), "close-open":float(raw_msg[4])-float(raw_msg[1]), "volume":float(raw_msg[5]), #Base asset volume "number_of_trades":int(raw_msg[8] ), "quote_volume":float(raw_msg[7] ), #Quote asset volume "active_buy_volume":float(raw_msg[9] ), #Taker buy base asset volume "active_buy_quote_volume":float(raw_msg[10]), #Taker buy quote asset volume "gain":-1000, "lose":-1000, "avg_gain":-1000, "avg_lose":-1000, "RSI":-1000, "MACD":-1000, "KDJ":-1000, "DMI":-1000, "OBV":-1000, "MTM":-1000, "EMA":-1000, "VWAP":-1000, "AVL":-1000, "TRIX":-1000, "StochRSI":-1000, "EMV":-1000, "WR":-1000, "BOLL":-1000, "SAR":-1000, "CCI":-1000, "MA":-1000, "VOL":-1000 } } return list_of_msgs def insert_offline_data(self,list_of_msgs,measurement,msg_type): self.InfluxClient.write_points(points=list_of_msgs,retention_policy='coin_policy') def insert_offline_tick_data(self,event_type,interval,units,num_of_units,msg_type,from_now=True,from_date=0,to_date=0): ''' event_type = 'kline' interval ='1m' symbol = "IOTAUSDT" units = 'hour' num_of_units = 24 measurement_off ="offline_minute_tick" msg_type = 'raw' from_now = False from_date = "26Jun, 2018" to_date = "27 Jun, 2018" ''' measurement =self.measurement_name symbol = self.symbol # forced symbol to be self.symbol because it has to be coherent to the websocket type of messages, this is not pretty but it works. list_of_msgs = self.create_msg_from_history(event_type,interval,symbol,units,num_of_units,from_now,from_date,to_date) self.insert_offline_data(list_of_msgs,measurement,msg_type) return list_of_msgs def websocket_start(self): self.bm =BinanceSocketManager(self.client) self.bm.start_kline_socket( self.symbol, self.online_process_message) self.bm.start() def websocket_close(self): self.bm.close()
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.symbol = coin_refer + coin_target self.retries = retries self.step_size = None self.tick_size = None self.get_filters() self._cash = 0 self._value = 0 self.get_balance() def _format_value(self, value, step): precision = step.find('1') - 1 if precision > 0: return '{:0.0{}f}'.format(value, precision) return floor(int(value)) def retry(func): @wraps(func) def wrapper(self, *args, **kwargs): for attempt in range(1, self.retries + 1): time.sleep(60 / 1200) # API Rate Limit try: return func(self, *args, **kwargs) except (BinanceAPIException, ConnectTimeout, ConnectionError) as err: if isinstance(err, BinanceAPIException) and err.code == -1021: # Recalculate timestamp offset between local and Binance's server res = self.binance.get_server_time() self.binance.timestamp_offset = res[ 'serverTime'] - int(time.time() * 1000) if attempt == self.retries: raise return wrapper @retry def cancel_open_orders(self): orders = self.binance.get_open_orders(symbol=self.symbol) for o in orders: self.cancel_order(o['orderId']) @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): params = dict() if type in [ORDER_TYPE_LIMIT, ORDER_TYPE_STOP_LOSS_LIMIT]: params.update({'timeInForce': TIME_IN_FORCE_GTC}) if type != ORDER_TYPE_MARKET: params.update({'price': self.format_price(price)}) return self.binance.create_order(symbol=self.symbol, side=side, type=type, quantity=self.format_quantity(size), **params) def format_price(self, price): return self._format_value(price, self.tick_size) def format_quantity(self, size): return self._format_value(size, self.step_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_filters(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'] elif f['filterType'] == 'PRICE_FILTER': self.tick_size = f['tickSize'] def get_interval(self, timeframe, compression): return self._GRANULARITIES.get((timeframe, compression)) @retry def get_symbol_info(self, symbol): return self.binance.get_symbol_info(symbol) def start_socket(self): if self.binance_socket.is_alive(): return self.binance_socket.daemon = True self.binance_socket.start() def stop_socket(self): self.binance_socket.close() reactor.stop()
class BinanceStream(AggregateWebsocketApi): TAG = ExchangeAbbr.BINANCE def __init__(self, api): super(BinanceStream, self).__init__(api) self._socket_manager: BinanceSocketManager = None def subscribe_bar(self, symbol, frequency): symbol_exchange = self.api.contracts[symbol].symbol_exchange self._socket_manager.start_kline_socket( symbol_exchange, self._on_bar_impl(symbol, frequency), frequency) def subscribe_depth(self, symbol): symbol_exchange = self.api.contracts[symbol].symbol_exchange self._socket_manager.start_depth_socket( symbol_exchange, self._on_depth_impl(symbol), binance.enums.WEBSOCKET_DEPTH_5) def _on_bar_impl(self, symbol, frequency): bar = BarData() bar.symbol = symbol bar.frequency = frequency bar.datetime = None self._bar_dict[(symbol, frequency)] = bar def callback(packet): origin_dt = bar.datetime kline = packet['k'] dt = datetime.datetime.utcfromtimestamp(kline['t'] / 1000.0) if origin_dt is not None and dt > origin_dt: self.api.on_bar(copy.copy(bar)) self._parse_bar(packet, bar) bar.datetime = dt return callback def _parse_bar(self, packet, bar: BarData): kline = packet['k'] bar.open = float(kline['o']) bar.high = float(kline['h']) bar.low = float(kline['l']) bar.close = float(kline['c']) bar.volume = float(kline['v']) def _parse_depth(self, packet, depth: DepthData): depth.datetime = datetime.datetime.utcnow() bids = packet['bids'] for index, (price, volume) in enumerate(bids[:DepthData.N_DEPTH]): depth.bid_prices[index] = float(price) depth.bid_volumes[index] = float(volume) asks = packet['asks'] for index, (price, volume) in enumerate(asks[:DepthData.N_DEPTH]): depth.ask_prices[index] = float(price) depth.ask_volumes[index] = float(volume) def start(self): self._socket_manager = BinanceSocketManager( Client(self.api.api_key, self.api.api_secret)) self.on_connected() self.back_fill() self._socket_manager.start() def stop(self): self._socket_manager.close() import twisted.internet.error from twisted.internet import reactor try: reactor.stop() except twisted.internet.error.ReactorNotRunning: pass def subscribe_user_stream(self): self._socket_manager.start_user_socket(self.on_user_stream) def on_user_stream(self, data): if data['e'] == 'executionReport': logger.debug(f'{self} received raw data {data}') if data['C'] != 'null': cli_id = data['C'] else: cli_id = data['c'] order = self.api.oms.clone(cli_id) if order is None: return order.order_id = str(data['i']) order.executed_volume = float(data['z']) order.executed_notional = float(data['Z']) order.status = STATUS_MAP_REVERSE[data['X']] if float(data['l']): trade = TradeData.from_order(order) trade.trade_id = str(data['t']) trade.price = float(data['L']) trade.volume = float(data['l']) trade.commission = float(data['n']) trade.commission_asset = '.'.join((data['N'], self.TAG)) trade.datetime = datetime.datetime.utcfromtimestamp(data['E'] / 1000.0) trade.strategy_id = order.strategy_id trade.client_order_id = order.client_order_id contract = trade.contract if trade.commission != 0 and trade.commission_asset == 'BNB.BNC': if (trade.commission_asset != contract.asset_base) and \ (trade.commission_asset != contract.asset_quote): commission_trade = trade.copy() commission_trade.commission = 0.0 commission_trade.symbol = 'BNB' + contract.symbol_quote + '.BNC' commission_trade.direction = EnumOrderDirection.SELL notional = trade.volume * trade.price * 0.00075 commission_trade.price = notional / trade.commission commission_trade.volume = notional / commission_trade.price commission_trade.trade_id = generate_id() self.api.on_trade(commission_trade) trade.commission_asset = contract.asset_quote trade.commission = notional self.api.on_trade(trade) logger.debug("%s: receive %s", self, trade) self.api.on_order(order) if order.is_closed(): self.api.oms.pop(order.client_order_id) logger.debug("%s: receive %s", self, order)
def main(): #READ API CONFIG api_config = {} with open('api_config.json') as json_data: api_config = json.load(json_data) json_data.close() TOKEN = api_config['telegram_bot_token'] tb = telebot.TeleBot(TOKEN) #create a new Telegram Bot object def send_message(chat_id, msg): try: tb.send_message(chat_id, msg) except: pass def send_to_all_chat_ids(msg): for chat_id in chat_ids: send_message(chat_id, msg) @tb.message_handler(commands=['start', 'help']) def send_welcome(message): set_chat_id(message.chat.id) tb.reply_to(message, "Welcome to BinancePump Bot, Binance Top Tick Count, Top Price and Volume Change Feeds will be shared with you. One of it could be start of pump or dump, keep an eye on me!") def process_message(tickers): # print("stream: {} data: {}".format(msg['stream'], msg['data'])) # print("Len {}".format(len(msg))) # print("Currentb Price Of {} is {}".format(msg[0]['s'], msg[0]['c'])) for ticker in tickers: symbol = ticker['s'] if not show_only_pair in symbol: continue price = float(ticker['c']) total_trades = int(ticker['n']) open = float(ticker['o']) volume = float(ticker['v']) event_time = dt.datetime.fromtimestamp(int(ticker['E'])/1000) if len(price_changes) > 0: price_change = filter(lambda item: item.symbol == symbol, price_changes) price_change = list(price_change) if (len(price_change) > 0): price_change = price_change[0] price_change.event_time = event_time price_change.prev_price = price_change.price price_change.prev_volume = price_change.volume price_change.price = price price_change.total_trades = total_trades price_change.open = open price_change.volume = volume price_change.isPrinted = False else: price_changes.append(PriceChange(symbol, price, price, total_trades, open, volume, False, event_time, volume)) else: price_changes.append(PriceChange(symbol, price, price, total_trades, open, volume, False, event_time, volume)) price_changes.sort(key=operator.attrgetter('price_change_perc'), reverse=True) #print(len(price_changes)) for price_change in price_changes: console_color = 'green' if price_change.price_change_perc < 0: console_color = 'red' if (not price_change.isPrinted and abs(price_change.price_change_perc) > min_perc and price_change.volume_change_perc > min_perc): price_change.isPrinted = True if not price_change.symbol in price_groups: price_groups[price_change.symbol] = PriceGroup(price_change.symbol, 1, abs(price_change.price_change_perc), price_change.price_change_perc, price_change.volume_change_perc, price_change.price, price_change.event_time, price_change.open, price_change.volume, False, ) else: price_groups[price_change.symbol].tick_count += 1 price_groups[price_change.symbol].last_event_time = price_change.event_time price_groups[price_change.symbol].volume = price_change.volume price_groups[price_change.symbol].last_price = price_change.price price_groups[price_change.symbol].isPrinted = False price_groups[price_change.symbol].total_price_change += abs(price_change.price_change_perc) price_groups[price_change.symbol].relative_price_change += price_change.price_change_perc price_groups[price_change.symbol].total_volume_change += price_change.volume_change_perc if len(price_groups)>0: anyPrinted = False sorted_price_group = sorted(price_groups, key=lambda k:price_groups[k]['tick_count']) if (len(sorted_price_group)>0): sorted_price_group = list(reversed(sorted_price_group)) for s in range(show_limit): header_printed=False if (s<len(sorted_price_group)): max_price_group = sorted_price_group[s] max_price_group = price_groups[max_price_group] if not max_price_group.isPrinted: if not header_printed: msg = "Top Ticks" print(msg) send_to_all_chat_ids(msg) header_printed = True print(max_price_group.to_string(True)) send_to_all_chat_ids(max_price_group.to_string(False)) anyPrinted = True sorted_price_group = sorted(price_groups, key=lambda k:price_groups[k]['total_price_change']) if (len(sorted_price_group)>0): sorted_price_group = list(reversed(sorted_price_group)) for s in range(show_limit): header_printed=False if (s<len(sorted_price_group)): max_price_group = sorted_price_group[s] max_price_group = price_groups[max_price_group] if not max_price_group.isPrinted: if not header_printed: msg = "Top Total Price Change" print(msg) send_to_all_chat_ids(msg) header_printed = True print(max_price_group.to_string(True)) send_to_all_chat_ids(max_price_group.to_string(False)) anyPrinted = True sorted_price_group = sorted(price_groups, key=lambda k:abs(price_groups[k]['relative_price_change'])) if (len(sorted_price_group)>0): sorted_price_group = list(reversed(sorted_price_group)) for s in range(show_limit): header_printed=False if (s<len(sorted_price_group)): max_price_group = sorted_price_group[s] max_price_group = price_groups[max_price_group] if not max_price_group.isPrinted: if not header_printed: msg = "Top Relative Price Change" print(msg) send_to_all_chat_ids(msg) header_printed = True print(max_price_group.to_string(True)) send_to_all_chat_ids(max_price_group.to_string(False)) anyPrinted = True sorted_price_group = sorted(price_groups, key=lambda k:price_groups[k]['total_volume_change']) if (len(sorted_price_group)>0): sorted_price_group = list(reversed(sorted_price_group)) for s in range(show_limit): header_printed=False if (s<len(sorted_price_group)): max_price_group = sorted_price_group[s] max_price_group = price_groups[max_price_group] if not max_price_group.isPrinted: if not header_printed: msg = "Top Total Volume Change" print(msg) send_to_all_chat_ids(msg) header_printed = True print(max_price_group.to_string(True)) send_to_all_chat_ids(max_price_group.to_string(False)) anyPrinted = True if anyPrinted: print("") client = Client(api_config['api_key'], api_config['api_secret']) # prices = client.get_all_tickers() # pairs = list(pd.DataFrame(prices)['symbol'].values) # pairs = [pair for pair in pairs if 'BTC' in pair] # print(pairs) bm = BinanceSocketManager(client) conn_key = bm.start_ticker_socket(process_message) bm.start() print('bm socket started') tb.polling() print('tb socket started') input("Press Enter to continue...") bm.stop_socket(conn_key) bm.close() print('Socket Closed') return