def __init__(self, args: list): parser = argparse.ArgumentParser(prog='bitzlato-market-maker-keeper') self.add_arguments(parser) self.arguments = parser.parse_args(args) setup_logging(self.arguments) self.bands_config = ReloadableConfig(self.arguments.config) self.price_feed = PriceFeedFactory().create_price_feed(self.arguments) self.spread_feed = create_spread_feed(self.arguments) self.control_feed = create_control_feed(self.arguments) self.order_history_reporter = create_order_history_reporter( self.arguments) self.history = History() self.bittrex_api = BittrexApi( api_server=self.arguments.bittrex_api_server, api_key=self.arguments.bittrex_api_key, secret_key=self.arguments.bittrex_secret_key, timeout=self.arguments.bittrex_timeout) self.order_book_manager = OrderBookManager( refresh_frequency=self.arguments.refresh_frequency) self.order_book_manager.get_orders_with( lambda: self.bittrex_api.get_orders(self.pair())) self.order_book_manager.get_balances_with( lambda: self.bittrex_api.get_balances()) self.order_book_manager.cancel_orders_with( lambda order: self.bittrex_api.cancel_order(order.order_id)) self.order_book_manager.enable_history_reporting( self.order_history_reporter, self.our_buy_orders, self.our_sell_orders) self.order_book_manager.start()
def setup_method(self): self.bittrex = BittrexApi( api_server="localhost", api_key="00000000-0000-0000-0000-000000000000", secret_key="secretkey", timeout=15.5) self.bittrexMockServer = BittrexMockServer()
class TestBittrex: def setup_method(self): self.bittrex = BittrexApi( api_server="localhost", api_key="00000000-0000-0000-0000-000000000000", secret_key="secretkey", timeout=15.5) self.bittrexMockServer = BittrexMockServer() def test_order(self): price = Wad.from_number(4.8765) amount = Wad.from_number(0.222) order = Order(order_id="153153", timestamp=int(time.time()), pair="DAI-ETH", is_sell=False, price=price, amount=amount) assert (order.price == order.sell_to_buy_price) assert (order.price == order.buy_to_sell_price) def test_get_balances(self, mocker): mocker.patch("requests.request", side_effect=self.bittrexMockServer.handle_request) response = self.bittrex.get_balances() eth_balance = list( filter(lambda b: b['currencySymbol'] == "ETH", response))[0] dai_balance = list( filter(lambda b: b['currencySymbol'] == "DAI", response))[0] assert (len(response) > 0) assert (float(dai_balance['total']) > 100) assert (float(dai_balance["available"]) > 80) assert (float(eth_balance['total']) > 17) assert (float(eth_balance["available"]) > 10) @staticmethod def check_orders(orders): by_oid = {} duplicate_count = 0 duplicate_first_found = -1 current_time = int(time.time() * 1000) for index, order in enumerate(orders): assert (isinstance(order, Order)) assert (order.order_id is not None) assert (order.timestamp < current_time) # Check for duplicates if order.order_id in by_oid: duplicate_count += 1 if duplicate_first_found < 0: duplicate_first_found = index else: by_oid[order.order_id] = order if duplicate_count > 0: print(f"{duplicate_count} duplicate orders were found, " f"starting at index {duplicate_first_found}") else: print("no duplicates were found") assert (duplicate_count == 0) def test_get_orders(self, mocker): pair = "DAI-ETH" mocker.patch("requests.request", side_effect=self.bittrexMockServer.handle_request) response = self.bittrex.get_orders(pair) assert (len(response) > 0) for order in response: assert (isinstance(order.is_sell, bool)) assert (Wad(order.price) > Wad(0)) TestBittrex.check_orders(response) def test_order_placement_and_cancellation(self, mocker): pair = "DAI-ETH" side = "ask" mocker.patch("requests.request", side_effect=self.bittrexMockServer.handle_request) order_id = self.bittrex.place_order(pair, True, Wad.from_number(241700), Wad.from_number(10)) assert (isinstance(order_id, str)) assert (order_id is not None) cancel_result = self.bittrex.cancel_order(order_id) assert (cancel_result == True) @staticmethod def check_trades(trades): by_tradeid = {} duplicate_count = 0 duplicate_first_found = -1 missorted_found = False last_timestamp = 0 for index, trade in enumerate(trades): assert (isinstance(trade, Trade)) if trade.trade_id in by_tradeid: print(f"found duplicate trade {trade.trade_id}") duplicate_count += 1 if duplicate_first_found < 0: duplicate_first_found = index else: by_tradeid[trade.trade_id] = trade if not missorted_found and last_timestamp > 0: if trade.timestamp > last_timestamp: print(f"missorted trade found at index {index}") missorted_found = True last_timestamp = trade.timestamp if duplicate_count > 0: print(f"{duplicate_count} duplicate trades were found, " f"starting at index {duplicate_first_found}") else: print("no duplicates were found") assert (duplicate_count == 0) assert (missorted_found is False) def test_get_trades(self, mocker): pair = "DAI-ETH" mocker.patch("requests.request", side_effect=self.bittrexMockServer.handle_request) response = self.bittrex.get_trades(pair) assert (len(response) > 0) TestBittrex.check_trades(response) def test_get_precisions(self, mocker): pair = "DAI-USD" mocker.patch("requests.request", side_effect=self.bittrexMockServer.handle_request) response = self.bittrex.get_precision(pair) assert (isinstance(response, int)) assert (response == 8)
# the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import sys from pyexchange.bittrex import BittrexApi from pymaker import Wad bittrex = BittrexApi('https://bittrex.com', sys.argv[1], sys.argv[2], 9.5) # print(bittrex.get_markets()) # print(bittrex.get_pair('ETH-DAI')) # print(bittrex.get_all_trades('ETH-DAI')) # print(f"Balance: {bittrex.get_balances()}") # order = bittrex.place_order('ETH-DAI', True, Wad.from_number(0.1), Wad.from_number(50)) # order = bittrex.place_order('ETH-DAI', False, Wad.from_number(0.00001), Wad.from_number(50)) # print(f"Placed order: {order}") # print(f"Balance: {bittrex.get_balances()}") # print(bittrex.get_trades('ETH-DAI')) # print(bittrex.get_all_trades('ETH-DAI')) print(bittrex.cancel_order("16bb9e73-92b6-4e1f-8f59-8d34397eff47")) print(bittrex.get_orders('ETH-DAI')) print(f"Balance: {bittrex.get_balances()}")
def __init__(self, args: list): parser = argparse.ArgumentParser(prog='bittrex-market-maker-keeper') parser.add_argument( "--bittrex-api-server", type=str, default="https://bittrex.com", help= "Address of the bittrex API server (default: 'https://bittrex.com')" ) parser.add_argument("--bittrex-api-key", type=str, required=True, help="API key for the bittrex API") parser.add_argument("--bittrex-secret-key", type=str, required=True, help="Secret key for the bittrex API") parser.add_argument( "--bittrex-timeout", type=float, default=9.5, help= "Timeout for accessing the bittrex API (in seconds, default: 9.5)") parser.add_argument( "--pair", type=str, required=True, help="Token pair (sell/buy) on which the keeper will operate") parser.add_argument("--config", type=str, required=True, help="Bands configuration file") parser.add_argument("--price-feed", type=str, required=True, help="Source of price feed") parser.add_argument( "--price-feed-expiry", type=int, default=120, help="Maximum age of the price feed (in seconds, default: 120)") parser.add_argument("--spread-feed", type=str, help="Source of spread feed") parser.add_argument( "--spread-feed-expiry", type=int, default=3600, help="Maximum age of the spread feed (in seconds, default: 3600)") parser.add_argument("--control-feed", type=str, help="Source of control feed") parser.add_argument( "--control-feed-expiry", type=int, default=86400, help="Maximum age of the control feed (in seconds, default: 86400)" ) parser.add_argument("--order-history", type=str, help="Endpoint to report active orders to") parser.add_argument( "--order-history-every", type=int, default=30, help= "Frequency of reporting active orders (in seconds, default: 30)") parser.add_argument( "--refresh-frequency", type=int, default=3, help="Order book refresh frequency (in seconds, default: 3)") parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) setup_logging(self.arguments) self.bands_config = ReloadableConfig(self.arguments.config) self.price_feed = PriceFeedFactory().create_price_feed(self.arguments) self.spread_feed = create_spread_feed(self.arguments) self.control_feed = create_control_feed(self.arguments) self.order_history_reporter = create_order_history_reporter( self.arguments) self.history = History() self.bittrex_api = BittrexApi( api_server=self.arguments.bittrex_api_server, api_key=self.arguments.bittrex_api_key, secret_key=self.arguments.bittrex_secret_key, timeout=self.arguments.bittrex_timeout) self.order_book_manager = OrderBookManager( refresh_frequency=self.arguments.refresh_frequency) self.order_book_manager.get_orders_with( lambda: self.bittrex_api.get_orders(self.pair())) self.order_book_manager.get_balances_with( lambda: self.bittrex_api.get_balances()) self.order_book_manager.cancel_orders_with( lambda order: self.bittrex_api.cancel_order(order.order_id)) self.order_book_manager.enable_history_reporting( self.order_history_reporter, self.our_buy_orders, self.our_sell_orders) self.order_book_manager.start()
class BittrexMarketMakerKeeper: """Keeper acting as a market maker on bittrex.""" logger = logging.getLogger() def __init__(self, args: list): parser = argparse.ArgumentParser(prog='bittrex-market-maker-keeper') parser.add_argument( "--bittrex-api-server", type=str, default="https://bittrex.com", help= "Address of the bittrex API server (default: 'https://bittrex.com')" ) parser.add_argument("--bittrex-api-key", type=str, required=True, help="API key for the bittrex API") parser.add_argument("--bittrex-secret-key", type=str, required=True, help="Secret key for the bittrex API") parser.add_argument( "--bittrex-timeout", type=float, default=9.5, help= "Timeout for accessing the bittrex API (in seconds, default: 9.5)") parser.add_argument( "--pair", type=str, required=True, help="Token pair (sell/buy) on which the keeper will operate") parser.add_argument("--config", type=str, required=True, help="Bands configuration file") parser.add_argument("--price-feed", type=str, required=True, help="Source of price feed") parser.add_argument( "--price-feed-expiry", type=int, default=120, help="Maximum age of the price feed (in seconds, default: 120)") parser.add_argument("--spread-feed", type=str, help="Source of spread feed") parser.add_argument( "--spread-feed-expiry", type=int, default=3600, help="Maximum age of the spread feed (in seconds, default: 3600)") parser.add_argument("--control-feed", type=str, help="Source of control feed") parser.add_argument( "--control-feed-expiry", type=int, default=86400, help="Maximum age of the control feed (in seconds, default: 86400)" ) parser.add_argument("--order-history", type=str, help="Endpoint to report active orders to") parser.add_argument( "--order-history-every", type=int, default=30, help= "Frequency of reporting active orders (in seconds, default: 30)") parser.add_argument( "--refresh-frequency", type=int, default=3, help="Order book refresh frequency (in seconds, default: 3)") parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") self.arguments = parser.parse_args(args) setup_logging(self.arguments) self.bands_config = ReloadableConfig(self.arguments.config) self.price_feed = PriceFeedFactory().create_price_feed(self.arguments) self.spread_feed = create_spread_feed(self.arguments) self.control_feed = create_control_feed(self.arguments) self.order_history_reporter = create_order_history_reporter( self.arguments) self.history = History() self.bittrex_api = BittrexApi( api_server=self.arguments.bittrex_api_server, api_key=self.arguments.bittrex_api_key, secret_key=self.arguments.bittrex_secret_key, timeout=self.arguments.bittrex_timeout) self.order_book_manager = OrderBookManager( refresh_frequency=self.arguments.refresh_frequency) self.order_book_manager.get_orders_with( lambda: self.bittrex_api.get_orders(self.pair())) self.order_book_manager.get_balances_with( lambda: self.bittrex_api.get_balances()) self.order_book_manager.cancel_orders_with( lambda order: self.bittrex_api.cancel_order(order.order_id)) self.order_book_manager.enable_history_reporting( self.order_history_reporter, self.our_buy_orders, self.our_sell_orders) self.order_book_manager.start() def main(self): with Lifecycle() as lifecycle: lifecycle.initial_delay(10) lifecycle.on_startup(self.startup) lifecycle.every(1, self.synchronize_orders) lifecycle.on_shutdown(self.shutdown) def startup(self): self.precision = self.bittrex_api.get_precision(self.pair()) def shutdown(self): self.logger.info(f'Keeper shutting down...') self.order_book_manager.cancel_all_orders(final_wait_time=60) def pair(self): return self.arguments.pair.upper() def token_sell(self) -> str: return self.arguments.pair.split('-')[0].upper() def token_buy(self) -> str: return self.arguments.pair.split('-')[1].upper() def our_available_balance(self, our_balances: dict, token: str) -> Wad: token_balances = list( filter(lambda coin: coin['currencySymbol'].upper() == token, our_balances)) if token_balances: return Wad.from_number(token_balances[0]['available']) else: return Wad(0) def our_sell_orders(self, our_orders: list) -> list: return list(filter(lambda order: order.is_sell, our_orders)) def our_buy_orders(self, our_orders: list) -> list: return list(filter(lambda order: not order.is_sell, our_orders)) def synchronize_orders(self): bands = Bands.read(self.bands_config, self.spread_feed, self.control_feed, self.history) order_book = self.order_book_manager.get_order_book() target_price = self.price_feed.get_price() # Cancel orders cancellable_orders = bands.cancellable_orders( our_buy_orders=self.our_buy_orders(order_book.orders), our_sell_orders=self.our_sell_orders(order_book.orders), target_price=target_price) if len(cancellable_orders) > 0: self.order_book_manager.cancel_orders(cancellable_orders) return # Do not place new orders if order book state is not confirmed if order_book.orders_being_placed or order_book.orders_being_cancelled: self.logger.debug( "Order book is in progress, not placing new orders") return # Place new orders new_orders = bands.new_orders( our_buy_orders=self.our_buy_orders(order_book.orders), our_sell_orders=self.our_sell_orders(order_book.orders), our_buy_balance=self.our_available_balance(order_book.balances, self.token_buy()), our_sell_balance=self.our_available_balance( order_book.balances, self.token_sell()), target_price=target_price)[0] self.place_orders(new_orders) def place_orders(self, new_orders: List[NewOrder]): def place_order_function(new_order_to_be_placed): price = round(new_order_to_be_placed.price, self.precision) amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount order_id = self.bittrex_api.place_order( self.pair(), new_order_to_be_placed.is_sell, price, amount) return Order(order_id=order_id, pair=self.pair(), is_sell=new_order_to_be_placed.is_sell, price=price, timestamp=int(datetime.now().timestamp()), amount=amount) for new_order in new_orders: self.order_book_manager.place_order( lambda new_order=new_order: place_order_function(new_order))
# This file is part of Maker Keeper Framework. # # Copyright (C) 2017-2018 reverendus # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from pyexchange.bittrex import BittrexApi bittrex = BittrexApi('https://bittrex.com', 9.5) print(bittrex.get_all_trades('BTC-ZRX')) # print(bittrex.get_all_trades('BTC-AAA'))
class BitzlatoMarketMakerKeeper: """Keeper acting as a market maker on bitzlato.""" logger = logging.getLogger() def add_arguments(self, parser): """exchange settings""" parser.add_argument( "--bitzlato-api-server", type=str, default="https://bitzlato.com/api/", help= "Address of the bitzlato API server (default: 'https://bitzlato.com/api/')" ) # parser.add_argument("--bitzlato-api-key", type=str, required=True, # help="API key for the bitzlato API") parser.add_argument("--bitzlato-secret-key", type=str, required=True, help="Secret key for the bitzlato API") parser.add_argument( "--bitzlato-timeout", type=float, default=9.5, help= "Timeout for accessing the bitzlato API (in seconds, default: 9.5)" ) parser.add_argument( "--pair", type=str, required=True, help="Token pair (sell/buy) on which the keeper will operate") """price settings""" parser.add_argument("--config", type=str, required=True, help="Bands configuration file") parser.add_argument("--price-feed", type=str, required=True, help="Source of price feed") parser.add_argument( "--price-feed-expiry", type=int, default=120, help="Maximum age of the price feed (in seconds, default: 120)") parser.add_argument("--spread-feed", type=str, help="Source of spread feed") parser.add_argument( "--spread-feed-expiry", type=int, default=3600, help="Maximum age of the spread feed (in seconds, default: 3600)") parser.add_argument("--control-feed", type=str, help="Source of control feed") parser.add_argument( "--control-feed-expiry", type=int, default=86400, help="Maximum age of the control feed (in seconds, default: 86400)" ) parser.add_argument("--order-history", type=str, help="Endpoint to report active orders to") parser.add_argument( "--order-history-every", type=int, default=30, help= "Frequency of reporting active orders (in seconds, default: 30)") parser.add_argument( "--refresh-frequency", type=int, default=3, help="Order book refresh frequency (in seconds, default: 3)") parser.add_argument("--debug", dest='debug', action='store_true', help="Enable debug output") parser.add_argument( "--telegram-log-config-file", type=str, required=False, help= "config file for send logs to telegram chat (e.g. 'telegram_conf.json')", default=None) parser.add_argument( "--keeper-name", type=str, required=False, help="market maker keeper name (e.g. 'Uniswap_V2_MDTETH')", default="bitzlato") def __init__(self, args: list): parser = argparse.ArgumentParser(prog='bitzlato-market-maker-keeper') self.add_arguments(parser) self.arguments = parser.parse_args(args) setup_logging(self.arguments) self.bands_config = ReloadableConfig(self.arguments.config) self.price_feed = PriceFeedFactory().create_price_feed(self.arguments) self.spread_feed = create_spread_feed(self.arguments) self.control_feed = create_control_feed(self.arguments) self.order_history_reporter = create_order_history_reporter( self.arguments) self.history = History() self.bittrex_api = BittrexApi( api_server=self.arguments.bittrex_api_server, api_key=self.arguments.bittrex_api_key, secret_key=self.arguments.bittrex_secret_key, timeout=self.arguments.bittrex_timeout) self.order_book_manager = OrderBookManager( refresh_frequency=self.arguments.refresh_frequency) self.order_book_manager.get_orders_with( lambda: self.bittrex_api.get_orders(self.pair())) self.order_book_manager.get_balances_with( lambda: self.bittrex_api.get_balances()) self.order_book_manager.cancel_orders_with( lambda order: self.bittrex_api.cancel_order(order.order_id)) self.order_book_manager.enable_history_reporting( self.order_history_reporter, self.our_buy_orders, self.our_sell_orders) self.order_book_manager.start() def main(self): with Lifecycle() as lifecycle: lifecycle.initial_delay(10) lifecycle.every(1, self.synchronize_orders) lifecycle.on_shutdown(self.shutdown) def shutdown(self): self.order_book_manager.cancel_all_orders() def pair(self): # Bittrex is inconsistent here. They call the pair `ETH-DAI`, but in reality all prices are # calculated like it was an `DAI-ETH` pair. Same for `ETH-MKR` return self.arguments.pair.upper().split( '-')[1] + "-" + self.arguments.pair.upper().split('-')[0] def token_sell(self) -> str: return self.arguments.pair.split('-')[0].upper() def token_buy(self) -> str: return self.arguments.pair.split('-')[1].upper() def our_available_balance(self, our_balances: dict, token: str) -> Wad: token_balances = list( filter(lambda coin: coin['Currency'].upper() == token, our_balances)) if token_balances: return Wad.from_number(token_balances[0]['Available']) else: return Wad(0) def our_sell_orders(self, our_orders: list) -> list: return list(filter(lambda order: order.is_sell, our_orders)) def our_buy_orders(self, our_orders: list) -> list: return list(filter(lambda order: not order.is_sell, our_orders)) def synchronize_orders(self): bands = Bands.read(self.bands_config, self.spread_feed, self.control_feed, self.history) order_book = self.order_book_manager.get_order_book() target_price = self.price_feed.get_price() # Cancel orders cancellable_orders = bands.cancellable_orders( our_buy_orders=self.our_buy_orders(order_book.orders), our_sell_orders=self.our_sell_orders(order_book.orders), target_price=target_price) if len(cancellable_orders) > 0: self.order_book_manager.cancel_orders(cancellable_orders) return # Do not place new orders if order book state is not confirmed if order_book.orders_being_placed or order_book.orders_being_cancelled: self.logger.debug( "Order book is in progress, not placing new orders") return # Place new orders new_orders = bands.new_orders( our_buy_orders=self.our_buy_orders(order_book.orders), our_sell_orders=self.our_sell_orders(order_book.orders), our_buy_balance=self.our_available_balance(order_book.balances, self.token_buy()), our_sell_balance=self.our_available_balance( order_book.balances, self.token_sell()), target_price=target_price)[0] self.place_orders(new_orders) def place_orders(self, new_orders: List[NewOrder]): def place_order_function(new_order_to_be_placed): price = new_order_to_be_placed.price amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount order_id = self.bittrex_api.place_order( self.pair(), new_order_to_be_placed.is_sell, price, amount) return Order(order_id=order_id, pair=self.pair(), is_sell=new_order_to_be_placed.is_sell, price=price, amount=amount, remaining_amount=amount) for new_order in new_orders: self.order_book_manager.place_order( lambda new_order=new_order: place_order_function(new_order))
# the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import sys from pyexchange.bittrex import BittrexApi from pymaker import Wad bittrex = BittrexApi('https://bittrex.com', sys.argv[1], sys.argv[2], 9.5) # print(bittrex.get_markets()) # print(bittrex.get_pair('ETH-DAI')) # print(bittrex.get_all_trades('ETH-DAI')) # print(f"Balance: {bittrex.get_balances()}") # order = bittrex.place_order('ETH-DAI', True, Wad.from_number(0.1), Wad.from_number(50)) # order = bittrex.place_order('ETH-DAI', False, Wad.from_number(0.00001), Wad.from_number(50)) # print(f"Placed order: {order}") # print(f"Balance: {bittrex.get_balances()}") print(bittrex.get_trades('ETH-DAI')) # print(bittrex.get_all_trades('ETH-DAI')) #print(bittrex.cancel_order("16bb9e73-92b6-4e1f-8f59-8d34397eff47")) #print(bittrex.get_orders('ETH-DAI')) #print(f"Balance: {bittrex.get_balances()}")