Exemple #1
0
    def instruments(self) -> List[Instrument]:
        """construct a list of instruments from the coinbase-returned json list of instruments"""
        ret = []

        # This will fetch a list of pairs
        products = self._products()

        for product in products:
            # separate pair into base and quote
            first = product["base_currency"]
            second = product["quote_currency"]

            # for each pair, construct both underlying currencies as well
            # as the pair object
            ret.append(
                Instrument(
                    name="{}-{}".format(first, second),
                    type=InstrumentType.PAIR,
                    exchange=self.exchange,
                    brokerId=product["id"],
                    leg1=self.currency(first),
                    leg2=self.currency(second),
                    leg1_side=Side.BUY,
                    leg2_side=Side.SELL,
                ))
        return ret
Exemple #2
0
    async def onStart(self, event: Event) -> None:
        # Create an instrument
        inst = Instrument(name=self._symbol, type=InstrumentType(self._symbol_type))

        # Subscribe
        await self.subscribe(inst)
        print("Subscribing to {}".format(inst))
Exemple #3
0
    def accounts(self):
        '''fetch a list of coinbase accounts. These store quantities of InstrumentType.CURRENCY'''
        ret = []

        # fetch all accounts
        accounts = self._accounts()

        # if unauthorized or invalid api key, raise
        if accounts == {
                'message': 'Unauthorized.'
        } or accounts == {
                'message': 'Invalid API Key'
        }:
            raise Exception('Coinbase auth failed')

        # for each account
        for account in accounts:
            # grab the id to lookup info
            acc_data = self._account(account['id'])

            # if tradeable and positive balance
            if acc_data['trading_enabled'] and float(acc_data['balance']) > 0:
                # construct a position representing the balance

                # acc = Account(account['id'], exchange, [
                pos = Position(
                    float(acc_data['balance']), 0., datetime.now(),
                    Instrument(acc_data['currency'],
                               InstrumentType.CURRENCY,
                               exchange=self.exchange), self.exchange, [])
                ret.append(pos)
                # ]
                # )
                # ret.append(acc)
        return ret
Exemple #4
0
 async def onStart(self, event: Event) -> None:
     # Get available instruments from exchange
     await self.subscribe(
         Instrument(
             name=self._symbol,
             type=InstrumentType.EQUITY,
             exchange=ExchangeType("iex"),
         ))
Exemple #5
0
    async def onStart(self, event: Event) -> None:
        # Create an instrument
        inst = Instrument(
            name=self._symbol, type=InstrumentType.EQUITY, exchange=ExchangeType("iex")
        )

        # Subscribe
        await self.subscribe(inst)
        print("Subscribing to {}".format(inst))
Exemple #6
0
    async def onStart(self, event: Event) -> None:
        # Create an instrument
        inst = Instrument(name=self._symbol, type=InstrumentType.EQUITY)

        # Check that its available
        if inst not in self.instruments():
            raise Exception("Not available on exchange: {}".format(
                self._symbol))

        # Subscribe
        await self.subscribe(inst)
        print("Subscribing to {}".format(inst))
Exemple #7
0
    async def onStart(self, event: Event) -> None:
        # Get available instruments from exchange
        for name in ('MSFT', 'AAPL'):
            # Create an instrument
            inst = Instrument(name=name, type=InstrumentType.EQUITY)
            insts = await self.lookup(inst)

            # Check that its available
            if inst not in insts:
                raise Exception('Not available on exchange: {}'.format(name))

            # Subscribe
            self.subscribe(inst)
            print('Subscribing to {}'.format(inst))
Exemple #8
0
    async def onStart(self, event: Event) -> None:
        # Get available instruments from exchange
        insts = self.instruments()
        print('Available Instruments:\n{}'.format(insts))

        for name in ('FB', 'AMZN', 'NFLX', 'GOOG', 'MSFT', 'AAPL', 'NVDA'):
            # Create an instrument
            inst = Instrument(name=name, type=InstrumentType.EQUITY)

            # Check that its available
            if inst not in insts:
                raise Exception('Not available on exchange: {}'.format(name))

            # Subscribe
            self.subscribe(inst)
            print('Subscribing to {}'.format(inst))
Exemple #9
0
def _get_instruments(public_client, exchange):
    ret = []

    products = public_client.get_products()

    for product in products:
        first = product['base_currency']
        second = product['quote_currency']

        ret.append(
            Instrument(name='{}/{}'.format(first, second),
                       type=InstrumentType.PAIR,
                       exchange=exchange,
                       leg1=_get_currency(first),
                       leg2=_get_currency(second),
                       leg1_side=Side.BUY,
                       leg2_side=Side.SELL))
    return ret
Exemple #10
0
    async def accounts(self) -> List[Position]:
        """fetch a list of coinbase accounts. These store quantities of InstrumentType.CURRENCY"""
        ret = []

        # fetch all accounts
        accounts = self._accounts()

        # if unauthorized or invalid api key, raise
        if accounts == {
                "message": "Unauthorized."
        } or accounts == {
                "message": "Invalid API Key"
        }:
            raise Exception("Coinbase auth failed")

        # for each account
        for account in accounts:
            # grab the id to lookup info
            acc_data = self._account(account["id"])

            # if tradeable and positive balance
            if acc_data["trading_enabled"] and float(acc_data["balance"]) > 0:
                # construct a position representing the balance

                # acc = Account(account['id'], exchange, [
                pos = Position(
                    float(acc_data["balance"]),
                    0.0,
                    datetime.now(),
                    Instrument(
                        acc_data["currency"],
                        InstrumentType.CURRENCY,
                        exchange=self.exchange,
                    ),
                    self.exchange,
                    [],
                )
                ret.append(pos)
                # ]
                # )
                # ret.append(acc)
        return ret
Exemple #11
0
 async def onStart(self, event: Event):
     # Get available instruments from exchange
     self.subscribe(
         Instrument(name=self._symbol, type=InstrumentType.EQUITY))
Exemple #12
0
    async def websocket(self, subscriptions: List[Instrument]):  # type: ignore
        # copy the base subscription template
        subscription = _SUBSCRIPTION.copy()

        # for each subcription, add symbol to product_ids
        for sub in subscriptions:
            subscription["product_ids"].append(sub.brokerId)  # type: ignore

        # sign the message in a similar way to the rest api, but
        # using the message of GET/users/self/verify
        timestamp = str(time.time())
        message = timestamp + "GET/users/self/verify"
        hmac_key = base64.b64decode(self.secret_key)
        signature = hmac.new(hmac_key, message.encode(), hashlib.sha256)
        signature_b64 = base64.b64encode(signature.digest()).decode()

        # update the subscription message with the signing info
        subscription.update({
            "signature": signature_b64,
            "timestamp": timestamp,
            "key": self.api_key,
            "passphrase": self.passphrase,
        })

        # construct a new websocket session
        session = aiohttp.ClientSession()

        # connect to the websocket
        async with session.ws_connect(self.ws_url) as ws:
            # send the subscription
            await ws.send_str(json.dumps(subscription))

            # for each message returned
            async for msg in ws:
                # only handle text messages
                if msg.type == aiohttp.WSMsgType.TEXT:
                    # load the data as json
                    x = json.loads(msg.data)

                    # skip earlier messages that our order book
                    # already reflects
                    if "sequence" in x:
                        inst = Instrument(x["product_id"], InstrumentType.PAIR,
                                          self.exchange)
                        if x.get("sequence", float("inf")) < self.seqnum.get(
                                inst, 0):
                            # if msg has a sequence number, and that number is < the last sequence number
                            # of the order book snapshot, ignore
                            continue

                    # ignore subscription  and heartbeat messages
                    if x["type"] in ("subscriptions", "heartbeat"):
                        # TODO yield heartbeats?
                        continue

                    elif x["type"] == "received":
                        # generate new Open events
                        # A valid order has been received and is now active.
                        # This message is emitted for every single valid order as
                        # soon as the matching engine receives it whether it fills
                        # immediately or not.
                        #
                        # The received message does not indicate a resting order on
                        # the order book. It simply indicates a new incoming order
                        # which as been accepted by the matching engine for processing.
                        # Received orders may cause match message to follow if they
                        # are able to begin being filled (taker behavior). Self-trade
                        # prevention may also trigger change messages to follow if the
                        # order size needs to be adjusted. Orders which are not fully
                        # filled or canceled due to self-trade prevention result in an
                        # open message and become resting orders on the order book.
                        #
                        # Market orders (indicated by the order_type field) may have
                        # an optional funds field which indicates how much quote currency
                        # will be used to buy or sell. For example, a funds field of
                        # 100.00 for the BTC-USD product would indicate a purchase of
                        # up to 100.00 USD worth of bitcoin.
                        #
                        # {
                        #     "type": "received",
                        #     "time": "2014-11-07T08:19:27.028459Z",
                        #     "product_id": "BTC-USD",
                        #     "sequence": 10,
                        #     "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
                        #     "size": "1.34",
                        #     "price": "502.1",
                        #     "side": "buy",
                        #     "order_type": "limit"
                        # }
                        # {
                        #     "type": "received",
                        #     "time": "2014-11-09T08:19:27.028459Z",
                        #     "product_id": "BTC-USD",
                        #     "sequence": 12,
                        #     "order_id": "dddec984-77a8-460a-b958-66f114b0de9b",
                        #     "funds": "3000.234",
                        #     "side": "buy",
                        #     "order_type": "market"
                        # }
                        id = x["order_id"]

                        # FIXME make sure we dont need this
                        # if id in self._order_map:
                        #     # yield a received event and get order from dict
                        #     o = self._order_map[id]
                        #     yield Event(type=EventType.RECEIVED, target=o)

                        if x["order_type"] == "market":
                            if "size" in x and float(x["size"]) <= 0:
                                # ignore zero size orders
                                # TODO why do we even get these?
                                continue
                            elif "size" not in x and "funds" in x:
                                print("TODO: funds")
                                # TODO can't handle these yet, no mapping from funds to size/price
                                continue

                            # create a market data order from the event data
                            # TODO set something for price? float('inf') ?
                            o = Order(
                                float(x["size"]),
                                0.0,
                                Side(x["side"].upper()),
                                Instrument(x["product_id"],
                                           InstrumentType.PAIR, self.exchange),
                                self.exchange,
                                id=id,
                            )

                        else:
                            # create limit order from the event data
                            o = Order(
                                float(x["size"]),
                                float(x["price"]),
                                Side(x["side"].upper()),
                                Instrument(x["product_id"],
                                           InstrumentType.PAIR, self.exchange),
                                self.exchange,
                            )

                        # yield an open event for the new order
                        e = Event(type=EventType.OPEN, target=o)
                        yield e

                    elif x["type"] == "done":
                        # The order is no longer on the order book. Sent for
                        # all orders for which there was a received message.
                        # This message can result from an order being canceled
                        # or filled. There will be no more messages for this
                        # order_id after a done message. remaining_size indicates
                        # how much of the order went unfilled; this will
                        # be 0 for filled orders.
                        #
                        # market orders will not have a remaining_size or price
                        # field as they are never on the open order book at a
                        # given price.
                        #
                        # {
                        #     "type": "done",
                        #     "time": "2014-11-07T08:19:27.028459Z",
                        #     "product_id": "BTC-USD",
                        #     "sequence": 10,
                        #     "price": "200.2",
                        #     "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
                        #     "reason": "filled", // or "canceled"
                        #     "side": "sell",
                        #     "remaining_size": "0"
                        # }
                        if x["reason"] == "canceled":
                            id = x["order_id"]
                            # if cancelled
                            if "price" not in x:
                                # cancel this event if we have a full local order book
                                # where we can determine the original order
                                print("TODO: noprice")
                                continue

                            # FIXME don't use remaining_size, lookup original size in order book
                            o = Order(
                                float(x["remaining_size"]),
                                float(x["price"]),
                                Side(x["side"].upper()),
                                Instrument(
                                    x["product_id"],
                                    InstrumentType.PAIR,
                                    self.exchange,
                                ),
                                self.exchange,
                                id=id,
                            )

                            e = Event(type=EventType.CANCEL, target=o)
                            yield e

                        elif x["reason"] == "filled":
                            # Will have a match event
                            # TODO route these to full local order book
                            continue

                        else:
                            # TODO unhandled
                            # this should never print
                            print("TODO: unhandled", x)

                    elif x["type"] == "match":
                        # A trade occurred between two orders. The aggressor
                        # or taker order is the one executing immediately
                        # after being received and the maker order is a
                        # resting order on the book. The side field indicates
                        # the maker order side. If the side is sell this
                        # indicates the maker was a sell order and the match
                        # is considered an up-tick. A buy side match is a down-tick.
                        #
                        # If authenticated, and you were the taker, the message
                        # would also have the following fields:
                        # taker_user_id: "5844eceecf7e803e259d0365",
                        # user_id: "5844eceecf7e803e259d0365",
                        # taker_profile_id: "765d1549-9660-4be2-97d4-fa2d65fa3352",
                        # profile_id: "765d1549-9660-4be2-97d4-fa2d65fa3352",
                        # taker_fee_rate: "0.005"
                        #
                        # Similarly, if you were the maker, the message would have the following:
                        # maker_user_id: "5f8a07f17b7a102330be40a3",
                        # user_id: "5f8a07f17b7a102330be40a3",
                        # maker_profile_id: "7aa6b75c-0ff1-11eb-adc1-0242ac120002",
                        # profile_id: "7aa6b75c-0ff1-11eb-adc1-0242ac120002",
                        # maker_fee_rate: "0.001"
                        # {
                        #     "type": "match",
                        #     "trade_id": 10,
                        #     "sequence": 50,
                        #     "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
                        #     "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1",
                        #     "time": "2014-11-07T08:19:27.028459Z",
                        #     "product_id": "BTC-USD",
                        #     "size": "5.23512",
                        #     "price": "400.23",
                        #     "side": "sell"
                        # }

                        # Generate a trade event
                        # First, create an order from the event
                        if str(x.get("taker_order_id", "")) in self._order_map:
                            o = self._order_map[str(x.get("taker_order_id"))]

                            o.filled = float(x["size"])

                            # my order
                            mine = True

                        elif str(x.get("maker_order_id",
                                       "")) in self._order_map:
                            o = self._order_map[str(x.get("maker_order_id"))]
                            # TODO filled?

                            # my order
                            mine = True

                        else:
                            o = Order(
                                float(x["size"]),
                                float(x["price"]),
                                Side(x["side"].upper()),
                                Instrument(x["product_id"],
                                           InstrumentType.PAIR, self.exchange),
                                self.exchange,
                            )

                            # set filled to volume so we see it as "done"
                            o.filled = o.volume

                            # not my order
                            mine = False

                        # create a trader with this order as the taker
                        # makers would be accumulated via the
                        # `elif x['reason'] == 'filled'` block above
                        t = Trade(
                            float(x["size"]),
                            float(x["price"]),
                            taker_order=o,
                            maker_orders=[],
                        )

                        if mine:
                            t.my_order = o

                        e = Event(type=EventType.TRADE, target=t)
                        yield e

                    elif x["type"] == "open":
                        # The order is now open on the order book.
                        # This message will only be sent for orders
                        # which are not fully filled immediately.
                        # remaining_size will indicate how much of
                        # the order is unfilled and going on the book.
                        # {
                        #     "type": "open",
                        #     "time": "2014-11-07T08:19:27.028459Z",
                        #     "product_id": "BTC-USD",
                        #     "sequence": 10,
                        #     "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
                        #     "price": "200.2",
                        #     "remaining_size": "1.00",
                        #     "side": "sell"
                        # }
                        # TODO how are these differentiated from received?
                        o = Order(
                            float(x["remaining_size"]),
                            float(x["price"]),
                            Side(x["side"].upper()),
                            Instrument(x["product_id"], InstrumentType.PAIR,
                                       self.exchange),
                            self.exchange,
                        )

                        e = Event(type=EventType.OPEN, target=o)
                        yield e
                    elif x["type"] == "change":
                        # TODO
                        # An order has changed. This is the result
                        # of self-trade prevention adjusting the
                        # order size or available funds. Orders can
                        # only decrease in size or funds. change
                        # messages are sent anytime an order changes
                        # in size; this includes resting orders (open)
                        # as well as received but not yet open.
                        # change messages are also sent when a new
                        # market order goes through self trade prevention
                        # and the funds for the market order have changed.
                        # {
                        #     "type": "change",
                        #     "time": "2014-11-07T08:19:27.028459Z",
                        #     "sequence": 80,
                        #     "order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
                        #     "product_id": "BTC-USD",
                        #     "new_size": "5.23512",
                        #     "old_size": "12.234412",
                        #     "price": "400.23",
                        #     "side": "sell"
                        # }
                        # {
                        #     "type": "change",
                        #     "time": "2014-11-07T08:19:27.028459Z",
                        #     "sequence": 80,
                        #     "order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
                        #     "product_id": "BTC-USD",
                        #     "new_funds": "5.23512",
                        #     "old_funds": "12.234412",
                        #     "price": "400.23",
                        #     "side": "sell"
                        # }
                        print("TODO: change", x)
                    else:
                        # TODO unhandled
                        # this should never print
                        print("TODO: unhandled2", x)
Exemple #13
0
 def currency(self, symbol: str) -> Instrument:
     # construct a base currency from the symbol
     return Instrument(name=symbol, type=InstrumentType.CURRENCY)
Exemple #14
0
def _get_currency(symbol):
    return Instrument(name=symbol, type=InstrumentType.CURRENCY)
Exemple #15
0
    async def onStart(self, event: Event) -> None:
        pprint(self.instruments())
        pprint(self.positions())

        await self.subscribe(Instrument("BTC-USD", InstrumentType.PAIR))
Exemple #16
0
 def __init__(self, symbol: str, *args: Any, **kwargs: Any) -> None:
     super(ReadOnlyStrategy, self).__init__(*args, **kwargs)
     self._inst = Instrument(name=symbol,
                             type=InstrumentType.EQUITY,
                             exchange=ExchangeType("iex"))