def orderBook(self, subscriptions: List[Instrument]): '''fetch level 3 order book for each Instrument in our subscriptions''' for sub in subscriptions: # fetch the order book # order book is of form: # {'bids': [[price, volume, id]], # 'asks': [[price, volume, id]], # 'sequence': <some positive integer>} ob = self._orderBook(sub.brokerId) # set the last sequence number for when we # connect to websocket later self.seqnum[sub] = ob['sequence'] # type: ignore # generate an open limit order for each bid for (bid, qty, id) in ob['bids']: o = Order(float(qty), float(bid), Side.BUY, sub, self.exchange, order_type=OrderType.LIMIT) yield Event(type=EventType.OPEN, target=o) # generate an open limit order for each ask for (bid, qty, id) in ob['asks']: o = Order(float(qty), float(bid), Side.SELL, sub, self.exchange, order_type=OrderType.LIMIT) yield Event(type=EventType.OPEN, target=o)
async def onTrade(self, event: Event) -> None: '''Called whenever a `Trade` event is received''' trade: Trade = event.target # type: ignore # no current orders, no past trades if not self.orders(trade.instrument) and not self.trades( trade.instrument): req = Order(side=Side.BUY, price=trade.price, volume=math.ceil(1000 / trade.price), instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print('requesting buy : {}'.format(req)) await self.newOrder(req) else: # no current orders, 1 past trades, and stop set if not self.orders(trade.instrument) and len(self.trades(trade.instrument)) == 1 and \ trade.instrument in self._stop and \ (trade.price >= self._stop[trade.instrument][0] or trade.price <= self._stop[trade.instrument][1]): req = Order(side=Side.SELL, price=trade.price, volume=self._stop[trade.instrument][2], instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print('requesting sell : {}'.format(req)) await self.newOrder(req)
async def newOrder(self, order: Order) -> bool: """given an aat Order, construct a coinbase order json""" jsn: Dict[str, Union[str, int, float]] = {} jsn["product_id"] = order.instrument.name if order.order_type == OrderType.LIMIT: jsn["type"] = "limit" jsn["side"] = order.side.value.lower() jsn["price"] = order.price jsn["size"] = round(order.volume, 8) # From the coinbase docs if order.flag == OrderFlag.FILL_OR_KILL: jsn["time_in_force"] = "FOK" elif order.flag == OrderFlag.IMMEDIATE_OR_CANCEL: jsn["time_in_force"] = "IOC" else: jsn["time_in_force"] = "GTC" elif order.order_type == OrderType.MARKET: jsn["type"] = "market" jsn["side"] = order.side.value.lower() jsn["size"] = round(order.volume, 8) else: stop_order: Order = order.stop_target # type: ignore jsn["type"] = stop_order.side.value.lower() jsn["price"] = stop_order.price jsn["size"] = round(stop_order.volume, 8) if stop_order.side == Side.BUY: jsn["stop"] = "entry" else: jsn["stop"] = "loss" jsn["stop_price"] = order.price if stop_order.order_type == OrderType.LIMIT: jsn["type"] = "limit" if order.flag == OrderFlag.FILL_OR_KILL: jsn["time_in_force"] = "FOK" elif order.flag == OrderFlag.IMMEDIATE_OR_CANCEL: jsn["time_in_force"] = "IOC" else: jsn["time_in_force"] = "GTC" elif stop_order.order_type == OrderType.MARKET: jsn["type"] = "market" # submit the order json id = self._newOrder(jsn) if id != "": # successful, set id on the order and return true order.id = str(id) self._order_map[order.id] = order return True # otherwise return false indicating rejected return False
def newOrder(self, order: Order): '''given an aat Order, construct a coinbase order json''' jsn: Dict[str, Union[str, int, float]] = {} jsn['product_id'] = order.instrument.name if order.order_type == OrderType.LIMIT: jsn['type'] = 'limit' jsn['side'] = order.side.value.lower() jsn['price'] = order.price jsn['size'] = round(order.volume, 8) # From the coinbase docs if order.flag == OrderFlag.FILL_OR_KILL: jsn['time_in_force'] = 'FOK' elif order.flag == OrderFlag.IMMEDIATE_OR_CANCEL: jsn['time_in_force'] = 'IOC' else: jsn['time_in_force'] = 'GTC' elif order.order_type == OrderType.MARKET: jsn['type'] = 'market' jsn['side'] = order.side.value.lower() jsn['size'] = round(order.volume, 8) else: stop_order: Order = order.stop_target # type: ignore jsn['type'] = stop_order.side.value.lower() jsn['price'] = stop_order.price jsn['size'] = round(stop_order.volume, 8) if stop_order.side == Side.BUY: jsn['stop'] = 'entry' else: jsn['stop'] = 'loss' jsn['stop_price'] = order.price if stop_order.order_type == OrderType.LIMIT: jsn['type'] = 'limit' if order.flag == OrderFlag.FILL_OR_KILL: jsn['time_in_force'] = 'FOK' elif order.flag == OrderFlag.IMMEDIATE_OR_CANCEL: jsn['time_in_force'] = 'IOC' else: jsn['time_in_force'] = 'GTC' elif stop_order.order_type == OrderType.MARKET: jsn['type'] = 'market' # submit the order json id = self._newOrder(jsn) if id != "": # successful, set id on the order and return true order.id = id self._order_map[id] = order return True # otherwise return false indicating rejected return False
async def onTrade(self, event: Event) -> None: pprint(event) trade: Trade = event.target # type: ignore if self._trade and trade.my_order is None: await self.newOrder( Order( 1, trade.price, Side.BUY, trade.instrument, trade.exchange, )) self._trade = False
async def onTrade(self, event: Event) -> None: if self._count < 5: await self.newOrder( Order( 1, 10000000, Side.SELL, self.instruments()[0], order_type=OrderType.LIMIT, )) self._count += 1 assert len(self.orders()) == self._count else: await self.cancelAll() assert len(self.orders()) == 0
async def onTrade(self, event: Event) -> None: '''Called whenever a `Trade` event is received''' print('Trade:\n\t{}\n\tSlippage:{}\n\tTxnCost:{}'.format(event, event.target.slippage(), event.target.transactionCost())) # no past trades, no current orders if not self.orders(event.target.instrument) and not self.trades(event.target.instrument): # TODO await self.buy(...) ? req = Order(side=Side.BUY, price=event.target.price + 10, volume=1, instrument=event.target.instrument, order_type=Order.Types.MARKET, exchange=event.target.exchange) print("requesting buy : {}".format(req)) await self.newOrder(req)
async def onTrade(self, event: Event) -> None: '''Called whenever a `Trade` event is received''' trade: Trade = event.target # type: ignore # no past trades, no current orders if not self.orders(trade.instrument) and not self.trades(trade.instrument): req = Order(side=Side.BUY, price=trade.price, volume=5000 // trade.price, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print('requesting buy : {}'.format(req)) await self.newOrder(req)
async def onTrade(self, event: Event) -> None: '''Called whenever a `Trade` event is received''' trade: Trade = event.target # type: ignore if self.volume_ordered < self.target_volume: now = datetime.now() if now >= self.target_time and now < self.end_time: req = Order(side=Side.BUY, price=trade.price + self.price_limit, volume=self.step, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print("requesting : {}".format(req)) self.target_time += timedelta(seconds=self.delay) self.volume_ordered += self.step await self.newOrder(req) else: print("Waiting for time window") else: print("Orders launched, do nothing! vol: {}".format(self.volume_processed))
async def onTrade(self, event: Event) -> None: '''Called whenever a `Trade` event is received''' print('Trade:\n{}'.format(event)) trade: Trade = event.target # type: ignore # no past trades, no current orders if not self.orders(trade.instrument) and not self.trades(trade.instrument): req = Order(side=Side.BUY, price=trade.price + 10, volume=1, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print("requesting buy : {}".format(req)) await self.newOrder(req) else: print(self.positions()) print(self.risk())
async def onTrade(self, event: Event): '''Called whenever a `Trade` event is received''' trade: Trade = event.target # type: ignore # append prices self._long_ma_list.append(trade.price) self._short_ma_list.append(trade.price) # adding to list self._long_ma_list = self._long_ma_list[-self._long_ma:] self._short_ma_list = self._short_ma_list[-self._short_ma:] if len(self._long_ma_list) < self._long_ma: return # dont trade in first 15 minutes if self.now().hour == 9 and self.now().minute <= 45: return long_mvg_av = pd.Series(self._long_ma_list).rolling( self._long_ma, min_periods=self._long_ma).mean().iloc[-1] short_mvg_av = pd.Series(self._short_ma_list).rolling( self._short_ma, min_periods=self._short_ma).mean().iloc[-1] long_mvg_av = long_mvg_av + (long_mvg_av * .005) # States # # Not Entered, Not Triggered -> if long_ma > short_ma -> Not Entered, Triggered (Wait for short av to move above) # Not Entered, Not Triggered -> if long_ma <= short_ma -> Not Entered, Not Triggered (Not ready yet) # Not Entered, Triggered -> if long_ma > short_ma -> Not Entered, Triggered (Wait for short to move above) # Not Entered, Triggered -> if long_ma <= short_ma -> Entered, Triggered (BUY) # Entered, Triggered -> if long_ma > short_ma -> Not Entered, Triggered (SELL) # Entered, Triggered -> if long_ma <= short_ma -> Entered, Triggered (Wait for short to move below) if not self._entered: if not self._triggered: if long_mvg_av > short_mvg_av: # Not Entered, Not Triggered -> if long_ma > short_ma -> Not Entered, Triggered (Wait for short av to move above) self._triggered = True else: # Not Entered, Not Triggered -> if long_ma <= short_ma -> Not Entered, Not Triggered (Not ready yet) self._triggered = False else: if long_mvg_av > short_mvg_av: # Not Entered, Triggered -> if long_ma > sh`ort_ma -> Not Entered, Triggered (Wait for short to move above) pass else: # Not Entered, Triggered -> if long_ma <= short_ma -> Entered, Triggered (BUY) if not self.orders(trade.instrument): self._volume = math.ceil(1000 / trade.price) self._buy_order = Order(side=Side.BUY, price=trade.price, volume=self._volume, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print('submitting buy order: {}'.format( self._buy_order)) await self.newOrder(self._buy_order) else: if not self._triggered: raise Exception('Never in this state') else: if long_mvg_av > short_mvg_av: # Entered, Triggered -> if long_ma > short_ma -> Not Entered, Triggered (SELL) if not self._sell_order: self._sell_order = Order(side=Side.SELL, price=trade.price, volume=self._volume, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print('submitting sell order: {}'.format( self._sell_order)) await self.newOrder(self._sell_order) else: # Entered, Triggered -> if long_ma <= short_ma -> Entered, Triggered (Wait for short to move below) # exit if time to bail if not self._sell_order and self.now( ).hour == self._bail_hour and self.now( ).minute >= self._bail_minute: self._sell_order = Order(side=Side.SELL, price=trade.price, volume=self._volume, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange) print('submitting sell order: {}'.format( self._sell_order)) await self.newOrder(self._sell_order)
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)
async def onTrade(self, event: Event) -> None: """Called whenever a `Trade` event is received""" trade: Trade = event.target # type: ignore # set initial price to first trade if (self._initial_price is None or self._initial_price == 0.0 or self._initial_price_day != trade.timestamp.day): self._initial_price = trade.price self._initial_price_day = trade.timestamp.day # type: ignore # self last price to last traded price self._last_price = trade.price # determine if we trade cur_perf = ((self._last_price - self._initial_price) / self._initial_price # type: ignore if self._initial_price != 0.0 else None) if cur_perf is None: # don't handle 0 price return if self._enter_trade is not None and self._exit_order is None: # already traded, look for exit if cur_perf > self._profit: # close position with sell print("exiting with sell order") self._exit_order = Order( side=Side.SELL, price=trade.price, volume=self._quantity, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange, ) await self.newOrder(self._exit_order) elif cur_perf < self._stop: # close to stop print("exiting to stop loss") side = ( Side.SELL if self._enter_order.side == Side.BUY else Side.BUY # type: ignore ) self._exit_order = Order( side=side, price=trade.price, volume=self._quantity, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange, ) await self.newOrder(self._exit_order) else: # check if 15:45 or later if (trade.timestamp.hour >= self._exit_hour and trade.timestamp.minute >= self._exit_minute): # exit print("exiting at EOD") self._exit_order = Order( side=Side.SELL, price=trade.price, volume=self._quantity, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange, ) await self.newOrder(self._exit_order) elif self._enter_trade is not None and self._exit_order is not None: # already ordered, wait for execution return elif self._enter_trade is None and self._exit_order is not None: raise Exception("Inconsistent state machine") else: if (trade.timestamp.hour >= self._exit_hour and trade.timestamp.minute > self._exit_minute): # no more trading today return if (trade.timestamp.hour <= self._enter_hour and trade.timestamp.minute < self._enter_minute): # no trading yet return if self._enter_order is not None: # already trying to enter, wait for execution return if cur_perf > self._trigger: # buy print("entering with buy order") if self._notional is not None: self._quantity = max(self._notional // trade.price, 1) else: self._quantity = 1 # type: ignore self._enter_order = Order( side=Side.BUY, price=trade.price, volume=self._quantity, instrument=trade.instrument, order_type=Order.Types.MARKET, exchange=trade.exchange, ) await self.newOrder(self._enter_order)