Ejemplo n.º 1
0
    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='bitso-market-maker-keeper')

        parser.add_argument(
            "--bitso-api-server",
            type=str,
            default="https://api.bitso.com",
            help=
            "Address of the bitso API server (default: 'https://api.bitso.com')"
        )

        parser.add_argument("--bitso-api-key",
                            type=str,
                            required=True,
                            help="API key for the Bitso API")

        parser.add_argument(
            "--bitso-secret-key",
            type=str,
            required=True,
            help="RSA Private Key for signing requests to the BitsoX API")

        parser.add_argument(
            "--bitso-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Bitso 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.bitso_api = BitsoApi(api_server=self.arguments.bitso_api_server,
                                  api_key=self.arguments.bitso_api_key,
                                  secret_key=self.arguments.bitso_secret_key,
                                  timeout=self.arguments.bitso_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.bitso_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.bitso_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.bitso_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()
Ejemplo n.º 2
0
class BitsoMarketMakerKeeper:
    """Keeper acting as a market maker on Bitso."""

    logger = logging.getLogger()

    def __init__(self, args: list):
        parser = argparse.ArgumentParser(prog='bitso-market-maker-keeper')

        parser.add_argument(
            "--bitso-api-server",
            type=str,
            default="https://api.bitso.com",
            help=
            "Address of the bitso API server (default: 'https://api.bitso.com')"
        )

        parser.add_argument("--bitso-api-key",
                            type=str,
                            required=True,
                            help="API key for the Bitso API")

        parser.add_argument(
            "--bitso-secret-key",
            type=str,
            required=True,
            help="RSA Private Key for signing requests to the BitsoX API")

        parser.add_argument(
            "--bitso-timeout",
            type=float,
            default=9.5,
            help=
            "Timeout for accessing the Bitso 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.bitso_api = BitsoApi(api_server=self.arguments.bitso_api_server,
                                  api_key=self.arguments.bitso_api_key,
                                  secret_key=self.arguments.bitso_secret_key,
                                  timeout=self.arguments.bitso_timeout)

        self.order_book_manager = OrderBookManager(
            refresh_frequency=self.arguments.refresh_frequency)
        self.order_book_manager.get_orders_with(
            lambda: self.bitso_api.get_orders(self.pair()))
        self.order_book_manager.get_balances_with(
            lambda: self.bitso_api.get_balances())
        self.order_book_manager.cancel_orders_with(
            lambda order: self.bitso_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):
        return self.arguments.pair.lower()

    def token_sell(self) -> str:
        return self.arguments.pair.split('_')[0].lower()

    def token_buy(self) -> str:
        return self.arguments.pair.split('_')[1].lower()

    def our_available_balance(self, our_balances: list, token: str) -> Wad:
        balance = list(filter(lambda x: x['currency'] == token,
                              our_balances))[0]['total']
        return Wad.from_number(balance)

    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
        self.place_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])

    def place_orders(self, new_orders):
        def place_order_function(new_order_to_be_placed):
            amount = new_order_to_be_placed.pay_amount if new_order_to_be_placed.is_sell else new_order_to_be_placed.buy_amount

            # Convert wad to float as Bitso limits amount decimal places to 8, and price to 2
            float_price = round(Wad.__float__(new_order_to_be_placed.price), 2)
            float_amount = round(Wad.__float__(amount), 8)

            side = "sell" if new_order_to_be_placed.is_sell == True else "buy"
            order_id = self.bitso_api.place_order(book=self.pair(),
                                                  side=side,
                                                  price=float_price,
                                                  amount=float_amount)

            timestamp = datetime.now(tz=timezone.utc).isoformat()

            return Order(str(order_id), timestamp, self.pair(),
                         new_order_to_be_placed.is_sell,
                         new_order_to_be_placed.price, amount)

        for new_order in new_orders:
            self.order_book_manager.place_order(
                lambda new_order=new_order: place_order_function(new_order))
Ejemplo n.º 3
0
class TestBitso:
    def setup_method(self):
        self.bitso = BitsoApi(api_server="localhost",
                              api_key="00000000-0000-0000-0000-000000000000",
                              secret_key="bitsosecretkey",
                              timeout=15.5)

    def test_convert_iso_to_unix(self):
        iso_timestamp = datetime.now(tz=timezone.utc).isoformat()
        assert (isinstance(iso_timestamp, str))

        unix_timestamp = iso8601_to_unix(iso_timestamp)
        assert (isinstance(unix_timestamp, int))

    def test_get_markets(self, mocker):
        mocker.patch("requests.request",
                     side_effect=BitsoMockServer.handle_request)
        response = self.bitso.get_markets()
        assert (len(response) > 0)
        assert (any(x["book"] == "eth_mxn" for x in response))

    def test_order(self):
        price = Wad.from_number(4.8765)
        amount = Wad.from_number(0.222)
        remaining_amount = Wad.from_number(0.153)
        order = Order(order_id="153153",
                      timestamp=iso8601_to_unix(
                          datetime.now(tz=timezone.utc).isoformat()),
                      pair="eth_mxn",
                      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=BitsoMockServer.handle_request)
        response = self.bitso.get_balances()
        assert (len(response) > 0)
        for balance in response:
            if "eth" in balance["currency"]:
                assert (float(balance["total"]) > 0)

    @staticmethod
    def check_orders(orders):
        by_oid = {}
        duplicate_count = 0
        duplicate_first_found = -1
        current_time = iso8601_to_unix(
            datetime.now(tz=timezone.utc).isoformat())
        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):
        instrument_id = "eth_mxn"
        mocker.patch("requests.request",
                     side_effect=BitsoMockServer.handle_request)
        response = self.bitso.get_orders(instrument_id)
        assert (len(response) > 0)
        for order in response:
            assert (isinstance(order.is_sell, bool))
            assert (Wad(order.price) > Wad(0))
        TestBitso.check_orders(response)

    def test_order_placement_and_cancellation(self, mocker):
        instrument_id = "eth_mxn"
        side = "sell"
        mocker.patch("requests.request",
                     side_effect=BitsoMockServer.handle_request)
        order_id = self.bitso.place_order(instrument_id, side, 4400.000, .01)
        assert (isinstance(order_id, str))
        assert (order_id is not None)
        cancel_result = self.bitso.cancel_order(order_id)
        assert (cancel_result is 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):
        instrument_id = "eth_mxn"
        mocker.patch("requests.request",
                     side_effect=BitsoMockServer.handle_request)
        response = self.bitso.get_trades(instrument_id)
        assert (len(response) > 0)
        TestBitso.check_trades(response)
Ejemplo n.º 4
0
 def setup_method(self):
     self.bitso = BitsoApi(api_server="localhost",
                           api_key="00000000-0000-0000-0000-000000000000",
                           secret_key="bitsosecretkey",
                           timeout=15.5)
Ejemplo n.º 5
0
# (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.bitso import BitsoApi
from pymaker.numeric import Wad

bitso = BitsoApi('https://api.bitso.com', sys.argv[1], sys.argv[2], 9.5)
print("Starting BitsoApi with the following parameters: ", sys.argv)

# GET "/v3/balance/"
# print(bitso.get_balances())

#print(bitso.get_markets())
# print(bitso.get_pair('ETH/USDC'))

# GET "/api/v1/orders"
# print(bitso.get_orders('eth_mxn'))

# POST /api/v1/orders
# print(bitso.place_order('eth_mxn', 'buy', 5200.000, .01))

# DELETE /api/v1/orders/{order_id}