예제 #1
0
    def test_order_side_parser_given_invalid_value_raises_value_error(self):
        # Arrange, Act, Assert
        with pytest.raises(ValueError):
            OrderSideParser.to_str_py(0)

        with pytest.raises(ValueError):
            OrderSideParser.from_str_py("")
예제 #2
0
    def test_order_side_to_str(self, enum, expected):
        # Arrange
        # Act
        result = OrderSideParser.to_str_py(enum)

        # Assert
        assert expected == result
예제 #3
0
    async def _submit_trailing_stop_market_order(
            self, order: TrailingStopMarketOrder) -> None:
        if order.trigger_type in (TriggerType.DEFAULT, TriggerType.LAST):
            working_type = "CONTRACT_PRICE"
        elif order.trigger_type == TriggerType.MARK:
            working_type = "MARK_PRICE"
        else:
            self._log.error(
                f"Cannot submit order: invalid `order.trigger_type`, was "
                f"{TriggerTypeParser.to_str_py(order.trigger_price)}. {order}",
            )
            return

        if order.offset_type not in (TrailingOffsetType.DEFAULT,
                                     TrailingOffsetType.BASIS_POINTS):
            self._log.error(
                f"Cannot submit order: invalid `order.offset_type`, was "
                f"{TrailingOffsetTypeParser.to_str_py(order.offset_type)} (use `BASIS_POINTS`). "
                f"{order}", )
            return

        await self._http_account.new_order(
            symbol=format_symbol(order.instrument_id.symbol.value),
            side=OrderSideParser.to_str_py(order.side),
            type=binance_order_type(order),
            time_in_force=TimeInForceParser.to_str_py(order.time_in_force),
            quantity=str(order.quantity),
            activation_price=str(order.trigger_price),
            callback_rate=str(order.trailing_offset / 100),
            working_type=working_type,
            reduce_only=order.
            is_reduce_only,  # Cannot be sent with Hedge-Mode or closePosition
            new_client_order_id=order.client_order_id.value,
            recv_window=5000,
        )
예제 #4
0
    def test_order_side_from_str(self, string, expected):
        # Arrange
        # Act
        result = OrderSideParser.from_str_py(string)

        # Assert
        assert expected == result
예제 #5
0
 async def _submit_stop_limit_order(
     self,
     order: StopLimitOrder,
     position: Optional[Position],
 ) -> None:
     order_type = "stop"
     if position is not None:
         if order.is_buy and order.trigger_price < position.avg_px_open:
             order_type = "take_profit"
         elif order.is_sell and order.trigger_price > position.avg_px_open:
             order_type = "take_profit"
     response = await self._http_client.place_trigger_order(
         market=order.instrument_id.symbol.value,
         side=OrderSideParser.to_str_py(order.side).lower(),
         size=str(order.quantity),
         order_type=order_type,
         client_id=order.client_order_id.value,
         price=str(order.price),
         trigger_price=str(order.trigger_price),
         reduce_only=order.is_reduce_only,
     )
     self.generate_order_accepted(
         strategy_id=order.strategy_id,
         instrument_id=order.instrument_id,
         client_order_id=order.client_order_id,
         venue_order_id=VenueOrderId(str(response["id"])),
         ts_event=self._clock.timestamp_ns(),
     )
예제 #6
0
 async def _submit_market_order(self, order: MarketOrder) -> None:
     await self._http_account.new_order(
         symbol=format_symbol(order.instrument_id.symbol.value),
         side=OrderSideParser.to_str_py(order.side),
         type="MARKET",
         quantity=str(order.quantity),
         new_client_order_id=order.client_order_id.value,
         recv_window=5000,
     )
예제 #7
0
 async def _submit_market_order(self, order: MarketOrder) -> None:
     await self._http_client.place_order(
         market=order.instrument_id.symbol.value,
         side=OrderSideParser.to_str_py(order.side).lower(),
         size=str(order.quantity),
         order_type="market",
         client_id=order.client_order_id.value,
         ioc=order.time_in_force == TimeInForce.IOC,
         reduce_only=order.is_reduce_only,
     )
 def test_make_order_market_on_close(self, side, liability):
     order = BetfairTestStubs.market_order(
         time_in_force=TimeInForce.OC,
         side=OrderSideParser.from_str_py(side))
     result = make_order(order)
     expected = {
         "marketOnCloseOrder": {
             "liability": liability
         },
         "orderType": "MARKET_ON_CLOSE",
     }
     assert result == expected
예제 #9
0
 async def _submit_stop_limit_order(self, order: StopLimitOrder) -> None:
     await self._http_account.new_order(
         symbol=format_symbol(order.instrument_id.symbol.value),
         side=OrderSideParser.to_str_py(order.side),
         type=binance_order_type(order),
         time_in_force=TimeInForceParser.to_str_py(order.time_in_force),
         quantity=str(order.quantity),
         price=str(order.price),
         stop_price=str(order.trigger_price),
         iceberg_qty=str(order.display_qty) if order.display_qty is not None else None,
         new_client_order_id=order.client_order_id.value,
         recv_window=5000,
     )
예제 #10
0
    def _handle_fills(self, instrument: Instrument, data: Dict[str,
                                                               Any]) -> None:
        if data["type"] != "order":
            self._log.error(f"Fill not for order, {data}")
            return

        # Parse identifiers
        venue_order_id = VenueOrderId(str(data["orderId"]))
        client_order_id = self._order_ids.get(venue_order_id)
        if client_order_id is None:
            client_order_id = ClientOrderId(str(uuid.uuid4()))
            # TODO(cs): WIP
            # triggers = await self._http_client.get_trigger_order_triggers(venue_order_id.value)
            #
            # for trigger in triggers:
            #     client_order_id = self._open_triggers.get(trigger)
            #     if client_order_id is not None:
            #         break
            # if client_order_id is None:
            #     client_order_id = ClientOrderId(str(uuid.uuid4()))

        # Fetch strategy ID
        strategy_id: StrategyId = self._cache.strategy_id_for_order(
            client_order_id)
        if strategy_id is None:
            self._generate_external_trade_report(instrument, data)
            return

        self.generate_order_filled(
            strategy_id=strategy_id,
            instrument_id=instrument.id,
            client_order_id=client_order_id,
            venue_order_id=venue_order_id,
            venue_position_id=None,  # NETTING accounts
            trade_id=TradeId(str(data["id"])),  # Trade ID
            order_side=OrderSideParser.from_str_py(data["side"].upper()),
            order_type=self._order_types[venue_order_id],
            last_qty=Quantity(data["size"], instrument.size_precision),
            last_px=Price(data["price"], instrument.price_precision),
            quote_currency=instrument.quote_currency,
            commission=Money(data["fee"],
                             Currency.from_str(data["feeCurrency"])),
            liquidity_side=LiquiditySide.MAKER
            if data["liquidity"] == "maker" else LiquiditySide.TAKER,
            ts_event=pd.to_datetime(data["time"], utc=True).to_datetime64(),
        )
        if not self._calculated_account:
            self._loop.create_task(self._update_account_state())
예제 #11
0
    async def _submit_limit_order(self, order: LimitOrder) -> None:
        time_in_force = TimeInForceParser.to_str_py(order.time_in_force)
        if order.is_post_only:
            time_in_force = "GTX"

        await self._http_account.new_order(
            symbol=format_symbol(order.instrument_id.symbol.value),
            side=OrderSideParser.to_str_py(order.side),
            type=binance_order_type(order),
            time_in_force=time_in_force,
            quantity=str(order.quantity),
            price=str(order.price),
            reduce_only=order.
            is_reduce_only,  # Cannot be sent with Hedge-Mode or closePosition
            new_client_order_id=order.client_order_id.value,
            recv_window=5000,
        )
예제 #12
0
 async def _submit_trailing_stop_market(
         self, order: TrailingStopMarketOrder) -> None:
     response = await self._http_client.place_trigger_order(
         market=order.instrument_id.symbol.value,
         side=OrderSideParser.to_str_py(order.side).lower(),
         size=str(order.quantity),
         order_type="trailing_stop",
         client_id=order.client_order_id.value,
         trigger_price=str(order.trigger_price),
         trail_value=str(order.trailing_offset)
         if order.is_buy else str(-order.trailing_offset),
         reduce_only=order.is_reduce_only,
     )
     self.generate_order_accepted(
         strategy_id=order.strategy_id,
         instrument_id=order.instrument_id,
         client_order_id=order.client_order_id,
         venue_order_id=VenueOrderId(str(response["id"])),
         ts_event=self._clock.timestamp_ns(),
     )
예제 #13
0
    def _handle_execution_report(self, data: Dict[str, Any]):
        execution_type: str = data["x"]

        instrument_id: InstrumentId = self._get_cached_instrument_id(data["s"])

        # Parse client order ID
        client_order_id_str: str = data["c"]
        if not client_order_id_str or not client_order_id_str.startswith("O"):
            client_order_id_str = data["C"]
        client_order_id = ClientOrderId(client_order_id_str)

        # Fetch strategy ID
        strategy_id: StrategyId = self._cache.strategy_id_for_order(client_order_id)
        if strategy_id is None:
            # TODO(cs): Implement external order handling
            self._log.error(
                f"Cannot handle trade report: strategy ID for {client_order_id} not found.",
            )
            return

        venue_order_id = VenueOrderId(str(data["i"]))
        ts_event: int = millis_to_nanos(data["E"])

        if execution_type == "NEW":
            self.generate_order_accepted(
                strategy_id=strategy_id,
                instrument_id=instrument_id,
                client_order_id=client_order_id,
                venue_order_id=venue_order_id,
                ts_event=ts_event,
            )
        elif execution_type in "TRADE":
            instrument: Instrument = self._instrument_provider.find(instrument_id=instrument_id)

            # Determine commission
            commission_asset: str = data["N"]
            commission_amount: str = data["n"]
            if commission_asset is not None:
                commission = Money.from_str(f"{commission_amount} {commission_asset}")
            else:
                # Binance typically charges commission as base asset or BNB
                commission = Money(0, instrument.base_currency)

            self.generate_order_filled(
                strategy_id=strategy_id,
                instrument_id=instrument_id,
                client_order_id=client_order_id,
                venue_order_id=venue_order_id,
                venue_position_id=None,  # NETTING accounts
                trade_id=TradeId(str(data["t"])),  # Trade ID
                order_side=OrderSideParser.from_str_py(data["S"]),
                order_type=parse_order_type(data["o"]),
                last_qty=Quantity.from_str(data["l"]),
                last_px=Price.from_str(data["L"]),
                quote_currency=instrument.quote_currency,
                commission=commission,
                liquidity_side=LiquiditySide.MAKER if data["m"] else LiquiditySide.TAKER,
                ts_event=ts_event,
            )
        elif execution_type == "CANCELED" or execution_type == "EXPIRED":
            self.generate_order_canceled(
                strategy_id=strategy_id,
                instrument_id=instrument_id,
                client_order_id=client_order_id,
                venue_order_id=venue_order_id,
                ts_event=ts_event,
            )