def test_process(self): # Arrange ethusdt = TestInstrumentProvider.ethusdt_binance() wrangler = TradeTickDataWrangler(instrument=ethusdt) provider = TestDataProvider() # Act ticks = wrangler.process( provider.read_csv_ticks("binance-ethusdt-trades.csv")[:100]) # Assert assert len(ticks) == 100 assert ticks[0].price == Price.from_str("423.760") assert ticks[0].size == Quantity.from_str("2.67900") assert ticks[0].aggressor_side == AggressorSide.SELL assert ticks[0].trade_id == "148568980" assert ticks[0].ts_event == 1597399200223000064 assert ticks[0].ts_init == 1597399200223000064
def test_build_ticks(self): # Arrange tick_data = TestDataProvider.tardis_trades() self.tick_builder = TradeTickDataWrangler( instrument=TestInstrumentProvider.btcusdt_binance(), data=tick_data, ) # Act self.tick_builder.pre_process(0) ticks = self.tick_builder.build_ticks() # Assert self.assertEqual(9999, len(ticks)) self.assertEqual(Price.from_str("9682.00"), ticks[0].price) self.assertEqual(Quantity.from_str("0.132000"), ticks[0].size) self.assertEqual(AggressorSide.BUY, ticks[0].aggressor_side) self.assertEqual("42377944", ticks[0].match_id) self.assertEqual(1582329602418379008, ticks[0].ts_recv_ns)
def test_update_order(self): # Arrange instrument = IBTestStubs.instrument("AAPL") contract_details = IBTestStubs.contract_details("AAPL") contract = contract_details.contract order = IBTestStubs.create_order() self.instrument_setup(instrument=instrument, contract_details=contract_details) self.exec_client._ib_insync_orders[ TestIdStubs.client_order_id()] = Trade(contract=contract, order=order) # Act command = TestCommandStubs.modify_order_command( instrument_id=instrument.id, price=Price.from_int(10), quantity=Quantity.from_str("100"), ) with patch.object(self.exec_client._client, "placeOrder") as mock: self.exec_client.modify_order(command=command) # Assert expected = { "contract": Contract( secType="STK", conId=265598, symbol="AAPL", exchange="SMART", primaryExchange="NASDAQ", currency="USD", localSymbol="AAPL", tradingClass="NMS", ), "order": LimitOrder(action="BUY", totalQuantity=100, lmtPrice=10.0), } name, args, kwargs = mock.mock_calls[0] # Can't directly compare kwargs for some reason? assert kwargs["contract"] == expected["contract"] assert kwargs["order"].action == expected["order"].action assert kwargs["order"].totalQuantity == expected["order"].totalQuantity assert kwargs["order"].lmtPrice == expected["order"].lmtPrice
def test_build_ticks(self): # Arrange tick_data = TestDataProvider.ethusdt_trades() self.tick_builder = TradeTickDataWrangler( instrument=TestInstrumentProvider.ethusdt_binance(), data=tick_data, ) # Act self.tick_builder.pre_process(0) ticks = self.tick_builder.build_ticks() # Assert self.assertEqual(69806, len(ticks)) self.assertEqual(Price.from_str("423.760"), ticks[0].price) self.assertEqual(Quantity.from_str("2.67900"), ticks[0].size) self.assertEqual(AggressorSide.SELL, ticks[0].aggressor_side) self.assertEqual("148568980", ticks[0].match_id) self.assertEqual(1597399200223000064, ticks[0].ts_recv_ns)
def test_execute_command(self): order = self.strategy.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("1.00000000"), ) command = SubmitOrder( self.trader_id, self.strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) def execute_command(): self.exec_engine.execute(command) self.benchmark.pedantic(execute_command, iterations=10_000, rounds=1)
def _on_trade_ticker_update(self, ticker: Ticker): instrument_id = self._instrument_provider.contract_id_to_instrument_id[ ticker.contract.conId] for tick in ticker.ticks: price = str(tick.price) size = str(tick.size) ts_event = dt_to_unix_nanos(tick.time) update = TradeTick( instrument_id=instrument_id, price=Price.from_str(price), size=Quantity.from_str(size), aggressor_side=AggressorSide.UNKNOWN, trade_id=generate_trade_id(symbol=instrument_id.value, ts_event=ts_event, price=price, size=size), ts_event=ts_event, ts_init=self._clock.timestamp_ns(), ) self._handle_data(update)
def parse_historic_trade_ticks(historic_ticks: List[HistoricalTickLast], instrument_id: InstrumentId) -> List[TradeTick]: trades = [] for tick in historic_ticks: ts_init = dt_to_unix_nanos(tick.time) trade_tick = TradeTick( instrument_id=instrument_id, price=Price.from_str(str(tick.price)), size=Quantity.from_str(str(tick.size)), aggressor_side=AggressorSide.UNKNOWN, trade_id=generate_trade_id( symbol=instrument_id.symbol.value, ts_event=ts_init, price=tick.price, size=tick.size, ), ts_init=ts_init, ts_event=ts_init, ) trades.append(trade_tick) return trades
def test_aggressive_partial_fill(self): # Arrange: Prepare market self.cache.add_instrument(USDJPY_SIM) quote = QuoteTick( instrument_id=USDJPY_SIM.id, bid=Price.from_str("110.000"), ask=Price.from_str("110.010"), bid_size=Quantity.from_int(1500000), ask_size=Quantity.from_int(1500000), ts_event=0, ts_init=0, ) self.data_engine.process(quote) snapshot = TestDataStubs.order_book_snapshot( instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000, ) self.data_engine.process(snapshot) self.exchange.process_order_book(snapshot) # Act order = self.strategy.order_factory.limit( instrument_id=USDJPY_SIM.id, order_side=OrderSide.BUY, quantity=Quantity.from_int(7000), price=Price.from_int(20), post_only=False, ) self.strategy.submit_order(order) self.exchange.process(0) # Assert assert order.status == OrderStatus.PARTIALLY_FILLED assert order.filled_qty == Quantity.from_str("6000.0") # No slippage assert order.avg_px == Decimal("15.93333333333333333333333333") assert self.exchange.get_account().balance_total(USD) == Money( 999999.88, USD)
def parse_trade_report_http( account_id: AccountId, instrument_id: InstrumentId, data: Dict[str, Any], report_id: UUID4, ts_init: int, ) -> TradeReport: return TradeReport( account_id=account_id, instrument_id=instrument_id, venue_order_id=VenueOrderId(str(data["orderId"])), trade_id=TradeId(str(data["id"])), order_side=OrderSide.BUY if data["isBuyer"] else OrderSide.SELL, last_qty=Quantity.from_str(data["qty"]), last_px=Price.from_str(data["price"]), commission=Money(data["commission"], Currency.from_str(data["commissionAsset"])), liquidity_side=LiquiditySide.MAKER if data["isMaker"] else LiquiditySide.TAKER, report_id=report_id, ts_event=millis_to_nanos(data["time"]), ts_init=ts_init, )
def test_order_filled(self): # Arrange uuid = uuid4() event = OrderFilled( account_id=AccountId("SIM", "000"), client_order_id=ClientOrderId("O-2020872378423"), venue_order_id=VenueOrderId("123456"), execution_id=ExecutionId("1"), position_id=PositionId("2"), strategy_id=StrategyId("SCALPER-001"), instrument_id=InstrumentId(Symbol("BTC/USDT"), Venue("BINANCE")), order_side=OrderSide.BUY, last_qty=Quantity.from_str("0.561000"), last_px=Price.from_str("15600.12445"), currency=USDT, commission=Money(12.20000000, USDT), liquidity_side=LiquiditySide.MAKER, execution_ns=0, event_id=uuid, timestamp_ns=0, ) print(event) # Act assert ( f"OrderFilled(account_id=SIM-000, client_order_id=O-2020872378423, " f"venue_order_id=123456, position_id=2, strategy_id=SCALPER-001, " f"instrument_id=BTC/USDT.BINANCE, side=BUY-MAKER, last_qty=0.561000, " f"last_px=15600.12445, " f"commission=12.20000000 USDT, event_id={uuid})" == str(event)) assert ( f"OrderFilled(account_id=SIM-000, client_order_id=O-2020872378423, " f"venue_order_id=123456, position_id=2, strategy_id=SCALPER-001, " f"instrument_id=BTC/USDT.BINANCE, side=BUY-MAKER, last_qty=0.561000, " f"last_px=15600.12445, " f"commission=12.20000000 USDT, event_id={uuid})" == repr(event))
def test_order_initialized(self): # Arrange uuid = uuid4() event = OrderInitialized( client_order_id=ClientOrderId("O-2020872378423"), strategy_id=StrategyId("SCALPER-001"), instrument_id=InstrumentId(Symbol("BTC/USDT"), Venue("BINANCE")), order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=Quantity.from_str("0.561000"), time_in_force=TimeInForce.DAY, event_id=uuid, timestamp_ns=0, options={"Price": "15200.10"}, ) # Act # Assert assert ( f"OrderInitialized(client_order_id=O-2020872378423, strategy_id=SCALPER-001, event_id={uuid})" == str(event)) assert ( f"OrderInitialized(client_order_id=O-2020872378423, strategy_id=SCALPER-001, event_id={uuid})" == repr(event))
def test_calculate_unrealized_pnl_for_short(self): # Arrange order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("5.912000"), ) fill = TestStubs.event_order_filled( order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("10505.60"), ) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) pnl = position.unrealized_pnl(Price.from_str("10407.15")) # Assert self.assertEqual(Money(582.03640000, USDT), pnl) self.assertEqual(Money(-62.10910720, USDT), position.realized_pnl) self.assertEqual([Money(62.10910720, USDT)], position.commissions())
def test_order_filled_event_to_from_dict_and_str_repr(self): # Arrange uuid = UUID4() event = OrderFilled( trader_id=TraderId("TRADER-001"), strategy_id=StrategyId("SCALPER-001"), account_id=AccountId("SIM", "000"), instrument_id=InstrumentId(Symbol("BTC/USDT"), Venue("BINANCE")), client_order_id=ClientOrderId("O-2020872378423"), venue_order_id=VenueOrderId("123456"), execution_id=ExecutionId("1"), position_id=PositionId("2"), order_side=OrderSide.BUY, order_type=OrderType.LIMIT, last_qty=Quantity.from_str("0.561000"), last_px=Price.from_str("15600.12445"), currency=USDT, commission=Money(12.20000000, USDT), liquidity_side=LiquiditySide.MAKER, ts_event=0, event_id=uuid, ts_init=0, ) # Act, Assert assert event.is_buy assert not event.is_sell assert OrderFilled.from_dict(OrderFilled.to_dict(event)) == event assert ( str(event) == "OrderFilled(account_id=SIM-000, instrument_id=BTC/USDT.BINANCE, client_order_id=O-2020872378423, venue_order_id=123456, execution_id=1, position_id=2, order_side=BUY, order_type=LIMIT, last_qty=0.561000, last_px=15600.12445 USDT, commission=12.20000000 USDT, liquidity_side=MAKER, ts_event=0)" # noqa ) assert ( repr(event) == f"OrderFilled(trader_id=TRADER-001, strategy_id=SCALPER-001, account_id=SIM-000, instrument_id=BTC/USDT.BINANCE, client_order_id=O-2020872378423, venue_order_id=123456, execution_id=1, position_id=2, order_side=BUY, order_type=LIMIT, last_qty=0.561000, last_px=15600.12445 USDT, commission=12.20000000 USDT, liquidity_side=MAKER, event_id={uuid}, ts_event=0, ts_init=0)" # noqa )
def test_opening_positions_with_multi_asset_account(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) last_ethusd = QuoteTick( ETHUSD_BITMEX.id, Price.from_str("376.05"), Price.from_str("377.10"), Quantity.from_str("16"), Quantity.from_str("25"), 0, 0, ) last_btcusd = QuoteTick( BTCUSD_BITMEX.id, Price.from_str("10500.05"), Price.from_str("10501.51"), Quantity.from_str("2.54"), Quantity.from_str("0.91"), 0, 0, ) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_btcusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_btcusd) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(10000), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {ETH: Money(26.59220848, ETH)}, self.portfolio.net_exposures(BITMEX), ) self.assertEqual( {ETH: Money(0.20608962, ETH)}, self.portfolio.maint_margins(BITMEX), ) self.assertEqual( Money(26.59220848, ETH), self.portfolio.net_exposure(ETHUSD_BITMEX.id), ) self.assertEqual( Money(0.00000000, ETH), self.portfolio.unrealized_pnl(ETHUSD_BITMEX.id), )
def test_opening_one_short_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("0.515"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("15000.00"), ) last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("15510.15"), Price.from_str("15510.25"), Quantity.from_str("12.62"), Quantity.from_str("3.1"), 0, 0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {USDT: Money(7987.77875000, USDT)}, self.portfolio.net_exposures(BINANCE), ) self.assertEqual( {USDT: Money(-262.77875000, USDT)}, self.portfolio.unrealized_pnls(BINANCE), ) self.assertEqual( {USDT: Money(7.98777875, USDT)}, self.portfolio.maint_margins(BINANCE), ) self.assertEqual( Money(7987.77875000, USDT), self.portfolio.net_exposure(BTCUSDT_BINANCE.id), ) self.assertEqual( Money(-262.77875000, USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id), ) self.assertEqual( Decimal("-0.515"), self.portfolio.net_position(order.instrument_id), ) self.assertFalse(self.portfolio.is_net_long(order.instrument_id)) self.assertTrue(self.portfolio.is_net_short(order.instrument_id)) self.assertFalse(self.portfolio.is_flat(order.instrument_id)) self.assertFalse(self.portfolio.is_completely_flat())
def test_handle_quote_tick_when_value_beyond_threshold_sends_bar_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(20000), ask_size=Quantity.from_int(20000), ts_event_ns=0, ts_recv_ns=0, ) tick2 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(60000), ask_size=Quantity.from_int(20000), ts_event_ns=0, ts_recv_ns=0, ) tick3 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(30500), ask_size=Quantity.from_int(20000), ts_event_ns=0, ts_recv_ns=0, ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert self.assertEqual(1, len(bar_store.get_store())) self.assertEqual(Price.from_str("1.00001"), bar_store.get_store()[0].open) self.assertEqual(Price.from_str("1.00002"), bar_store.get_store()[0].high) self.assertEqual(Price.from_str("1.00000"), bar_store.get_store()[0].low) self.assertEqual(Price.from_str("1.00000"), bar_store.get_store()[0].close) self.assertEqual(Quantity.from_str("99999"), bar_store.get_store()[0].volume) self.assertEqual(Decimal("10501.400"), aggregator.get_cumulative_value())
def test_opening_one_short_position_updates_portfolio(self): # Arrange AccountFactory.register_calculated_account("BINANCE") account_id = AccountId("BINANCE", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), AccountBalance( USDT, Money(100000.00000000, USDT), Money(0.00000000, USDT), Money(100000.00000000, USDT), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("0.515"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S-001"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("15000.00"), ) last = QuoteTick( instrument_id=BTCUSDT_BINANCE.id, bid=Price.from_str("15510.15"), ask=Price.from_str("15510.25"), bid_size=Quantity.from_str("12.62"), ask_size=Quantity.from_str("3.1"), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position, OMSType.HEDGING) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert assert self.portfolio.net_exposures(BINANCE) == { USDT: Money(7987.77875000, USDT) } assert self.portfolio.unrealized_pnls(BINANCE) == { USDT: Money(-262.77875000, USDT) } assert self.portfolio.margins_maint(BINANCE) == { BTCUSDT_BINANCE.id: Money(7.72500000, USDT) } assert self.portfolio.net_exposure(BTCUSDT_BINANCE.id) == Money( 7987.77875000, USDT) assert self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id) == Money( -262.77875000, USDT) assert self.portfolio.net_position( order.instrument_id) == Decimal("-0.515") assert not self.portfolio.is_net_long(order.instrument_id) assert self.portfolio.is_net_short(order.instrument_id) assert not self.portfolio.is_flat(order.instrument_id) assert not self.portfolio.is_completely_flat()
def test_market_value_when_insufficient_data_for_xrate_returns_none(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) last_ethusd = QuoteTick( ETHUSD_BITMEX.id, Price.from_str("376.05"), Price.from_str("377.10"), Quantity.from_str("16"), Quantity.from_str("25"), 0, 0, ) last_xbtusd = QuoteTick( BTCUSD_BITMEX.id, Price.from_str("50000.00"), Price.from_str("50000.00"), Quantity.from_str("1"), Quantity.from_str("1"), 0, 0, ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) self.cache.add_position(position) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_xbtusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_xbtusd) # Act result = self.portfolio.net_exposures(BITMEX) # Assert self.assertEqual({BTC: Money(0.00200000, BTC)}, result)
def parse_perpetual_instrument_http( symbol_info: BinanceFuturesSymbolInfo, ts_event: int, ts_init: int, ) -> CryptoPerpetual: # Create base asset base_currency = Currency( code=symbol_info.baseAsset, precision=symbol_info.baseAssetPrecision, iso4217=0, # Currently undetermined for crypto assets name=symbol_info.baseAsset, currency_type=CurrencyType.CRYPTO, ) # Create quote asset quote_currency = Currency( code=symbol_info.quoteAsset, precision=symbol_info.quotePrecision, iso4217=0, # Currently undetermined for crypto assets name=symbol_info.quoteAsset, currency_type=CurrencyType.CRYPTO, ) symbol = Symbol(symbol_info.symbol + "-PERP") instrument_id = InstrumentId(symbol=symbol, venue=BINANCE_VENUE) # Parse instrument filters filters: Dict[BinanceSymbolFilterType, BinanceSymbolFilter] = { f.filterType: f for f in symbol_info.filters } price_filter: BinanceSymbolFilter = filters.get( BinanceSymbolFilterType.PRICE_FILTER) lot_size_filter: BinanceSymbolFilter = filters.get( BinanceSymbolFilterType.LOT_SIZE) min_notional_filter: BinanceSymbolFilter = filters.get( BinanceSymbolFilterType.MIN_NOTIONAL) tick_size = price_filter.tickSize.rstrip("0") step_size = lot_size_filter.stepSize.rstrip("0") price_precision = precision_from_str(tick_size) size_precision = precision_from_str(step_size) price_increment = Price.from_str(tick_size) size_increment = Quantity.from_str(step_size) max_quantity = Quantity(float(lot_size_filter.maxQty), precision=size_precision) min_quantity = Quantity(float(lot_size_filter.minQty), precision=size_precision) min_notional = None if filters.get(BinanceSymbolFilterType.MIN_NOTIONAL): min_notional = Money(min_notional_filter.minNotional, currency=quote_currency) max_price = Price(float(price_filter.maxPrice), precision=price_precision) min_price = Price(float(price_filter.minPrice), precision=price_precision) # Futures commissions maker_fee = Decimal("0.0002") # TODO taker_fee = Decimal("0.0004") # TODO assert symbol_info.marginAsset == symbol_info.quoteAsset # Create instrument return CryptoPerpetual( instrument_id=instrument_id, native_symbol=Symbol(symbol_info.symbol), base_currency=base_currency, quote_currency=quote_currency, settlement_currency=quote_currency, is_inverse=False, # No inverse instruments trade on Binance price_precision=price_precision, size_precision=size_precision, price_increment=price_increment, size_increment=size_increment, max_quantity=max_quantity, min_quantity=min_quantity, max_notional=None, min_notional=min_notional, max_price=max_price, min_price=min_price, margin_init=Decimal(0), margin_maint=Decimal(0), maker_fee=maker_fee, taker_fee=taker_fee, ts_event=ts_event, ts_init=ts_init, info=orjson.loads(msgspec.json.encode(symbol_info)), )
def test_simulate_order_fills_single(asks): fills = asks.simulate_order_fills( order=Order(price=15, size=10, side=OrderSide.BUY, id="1")) assert fills == [(Price.from_str("15.0000"), Quantity.from_str("10.0000"))]
def test_opening_positions_with_multi_asset_account(self): # Arrange AccountFactory.register_calculated_account("BITMEX") account_id = AccountId("BITMEX", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) last_ethusd = QuoteTick( instrument_id=ETHUSD_BITMEX.id, bid=Price.from_str("376.05"), ask=Price.from_str("377.10"), bid_size=Quantity.from_str("16"), ask_size=Quantity.from_str("25"), ts_event=0, ts_init=0, ) last_btcusd = QuoteTick( instrument_id=BTCUSD_BITMEX.id, bid=Price.from_str("10500.05"), ask=Price.from_str("10501.51"), bid_size=Quantity.from_str("2.54"), ask_size=Quantity.from_str("0.91"), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_btcusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_btcusd) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(10000), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, strategy_id=StrategyId("S-001"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("376.05"), ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) # Act self.cache.add_position(position, OMSType.HEDGING) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert assert self.portfolio.net_exposures(BITMEX) == { ETH: Money(26.59220848, ETH) } assert self.portfolio.margins_maint(BITMEX) == { ETHUSD_BITMEX.id: Money(0.20608962, ETH) } assert self.portfolio.net_exposure(ETHUSD_BITMEX.id) == Money( 26.59220848, ETH) assert self.portfolio.unrealized_pnl(ETHUSD_BITMEX.id) == Money( 0.00000000, ETH)
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, )
def test_calculate_pnls_for_multi_currency_cash_account_btcusdt(self): # Arrange event = AccountState( account_id=AccountId("SIM", "001"), account_type=AccountType.CASH, base_currency=None, # Multi-currency reported=True, balances=[ AccountBalance( Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], margins=[], info={}, # No default currency set event_id=UUID4(), ts_event=0, ts_init=0, ) account = CashAccount(event) order1 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("0.50000000"), ) fill1 = TestEventStubs.order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("45500.00"), ) position = Position(BTCUSDT_BINANCE, fill1) # Act result1 = account.calculate_pnls( instrument=BTCUSDT_BINANCE, position=position, fill=fill1, ) order2 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("0.50000000"), ) fill2 = TestEventStubs.order_filled( order2, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("45500.00"), ) position.apply(fill2) result2 = account.calculate_pnls( instrument=BTCUSDT_BINANCE, position=position, fill=fill2, ) # Assert assert result1 == [ Money(-0.50000000, BTC), Money(22727.25000000, USDT) ] assert result2 == [ Money(0.50000000, BTC), Money(-22772.75000000, USDT) ]
def test_market_value_when_insufficient_data_for_xrate_returns_none(self): # Arrange AccountFactory.register_calculated_account("BITMEX") account_id = AccountId("BITMEX", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("376.05"), ) last_ethusd = QuoteTick( instrument_id=ETHUSD_BITMEX.id, bid=Price.from_str("376.05"), ask=Price.from_str("377.10"), bid_size=Quantity.from_str("16"), ask_size=Quantity.from_str("25"), ts_event=0, ts_init=0, ) last_xbtusd = QuoteTick( instrument_id=BTCUSD_BITMEX.id, bid=Price.from_str("50000.00"), ask=Price.from_str("50000.00"), bid_size=Quantity.from_str("1"), ask_size=Quantity.from_str("1"), ts_event=0, ts_init=0, ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) self.cache.add_position(position, OMSType.HEDGING) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_xbtusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_xbtusd) # Act result = self.portfolio.net_exposures(BITMEX) # Assert assert result == {BTC: Money(0.00200000, BTC)}
async def load_all_async(self) -> None: """ Load the latest Binance instruments into the provider asynchronously. """ # Set async loading flag self._loading = True # Get current commission rates try: fee_res: List[Dict[str, str]] = await self._wallet.trade_fee() fees: Dict[str, Dict[str, str]] = {s["symbol"]: s for s in fee_res} except BinanceClientError: self._log.error( "Cannot load instruments: API key authentication failed " "(this is needed to fetch the applicable account fee tier).", ) return # Get exchange info for all assets assets_res: Dict[str, Any] = await self._spot_market.exchange_info() server_time_ns: int = millis_to_nanos(assets_res["serverTime"]) for info in assets_res["symbols"]: local_symbol = Symbol(info["symbol"]) # Create base asset base_asset: str = info["baseAsset"] base_currency = Currency( code=base_asset, precision=info["baseAssetPrecision"], iso4217=0, # Currently undetermined for crypto assets name=base_asset, currency_type=CurrencyType.CRYPTO, ) # Create quote asset quote_asset: str = info["quoteAsset"] quote_currency = Currency( code=quote_asset, precision=info["quoteAssetPrecision"], iso4217=0, # Currently undetermined for crypto assets name=quote_asset, currency_type=CurrencyType.CRYPTO, ) # symbol = Symbol(base_currency.code + "/" + quote_currency.code) instrument_id = InstrumentId(symbol=local_symbol, venue=BINANCE_VENUE) # Parse instrument filters symbol_filters = {f["filterType"]: f for f in info["filters"]} price_filter = symbol_filters.get("PRICE_FILTER") lot_size_filter = symbol_filters.get("LOT_SIZE") min_notional_filter = symbol_filters.get("MIN_NOTIONAL") # market_lot_size_filter = symbol_filters.get("MARKET_LOT_SIZE") tick_size = price_filter["tickSize"].rstrip("0") step_size = lot_size_filter["stepSize"].rstrip("0") price_precision = precision_from_str(tick_size) size_precision = precision_from_str(step_size) price_increment = Price.from_str(tick_size) size_increment = Quantity.from_str(step_size) lot_size = Quantity.from_str(step_size) max_quantity = Quantity(float(lot_size_filter["maxQty"]), precision=size_precision) min_quantity = Quantity(float(lot_size_filter["minQty"]), precision=size_precision) min_notional = None if min_notional_filter is not None: min_notional = Money(min_notional_filter["minNotional"], currency=quote_currency) max_price = Price(float(price_filter["maxPrice"]), precision=price_precision) min_price = Price(float(price_filter["minPrice"]), precision=price_precision) pair_fees = fees.get(local_symbol.value) maker_fee: Decimal = Decimal(0) taker_fee: Decimal = Decimal(0) if pair_fees: maker_fee = Decimal(pair_fees["makerCommission"]) taker_fee = Decimal(pair_fees["takerCommission"]) # Create instrument instrument = CurrencySpot( instrument_id=instrument_id, local_symbol=local_symbol, base_currency=base_currency, quote_currency=quote_currency, price_precision=price_precision, size_precision=size_precision, price_increment=price_increment, size_increment=size_increment, lot_size=lot_size, max_quantity=max_quantity, min_quantity=min_quantity, max_notional=None, min_notional=min_notional, max_price=max_price, min_price=min_price, margin_init=Decimal(0), margin_maint=Decimal(0), maker_fee=maker_fee, taker_fee=taker_fee, ts_event=server_time_ns, ts_init=time.time_ns(), info=info, ) self.add_currency(currency=base_currency) self.add_currency(currency=quote_currency) self.add(instrument=instrument) # Set async loading flags self._loading = False self._loaded = True
def default_fx_ccy(symbol: str, venue: Venue = None) -> CurrencySpot: """ Return a default FX currency pair instrument from the given instrument_id. Parameters ---------- symbol : str The currency pair symbol. venue : Venue The currency pair venue. Returns ------- CurrencySpot Raises ------ ValueError If `symbol` length is not in range [6, 7]. """ if venue is None: venue = Venue("SIM") PyCondition.valid_string(symbol, "symbol") PyCondition.in_range_int(len(symbol), 6, 7, "len(symbol)") instrument_id = InstrumentId( symbol=Symbol(symbol), venue=venue, ) base_currency = symbol[:3] quote_currency = symbol[-3:] # Check tick precision of quote currency if quote_currency == "JPY": price_precision = 3 else: price_precision = 5 return CurrencySpot( instrument_id=instrument_id, local_symbol=Symbol(symbol), base_currency=Currency.from_str(base_currency), quote_currency=Currency.from_str(quote_currency), price_precision=price_precision, size_precision=0, price_increment=Price(1 / 10**price_precision, price_precision), size_increment=Quantity.from_int(1), lot_size=Quantity.from_str("1000"), max_quantity=Quantity.from_str("1e7"), min_quantity=Quantity.from_str("1000"), max_price=None, min_price=None, max_notional=Money(50000000.00, USD), min_notional=Money(1000.00, USD), margin_init=Decimal("0.03"), margin_maint=Decimal("0.03"), maker_fee=Decimal("0.00002"), taker_fee=Decimal("0.00002"), ts_event=0, ts_init=0, )
def test_handle_trade_tick_when_volume_beyond_threshold_sends_bars_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.LAST) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("20.00001"), size=Quantity.from_str("3000.00"), aggressor_side=AggressorSide.BUY, match_id="123456", ts_event_ns=0, ts_recv_ns=0, ) tick2 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("20.00002"), size=Quantity.from_str("4000.00"), aggressor_side=AggressorSide.BUY, match_id="123457", ts_event_ns=0, ts_recv_ns=0, ) tick3 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("20.00000"), size=Quantity.from_str("5000.00"), aggressor_side=AggressorSide.BUY, match_id="123458", ts_event_ns=0, ts_recv_ns=0, ) # Act aggregator.handle_trade_tick(tick1) aggregator.handle_trade_tick(tick2) aggregator.handle_trade_tick(tick3) # Assert self.assertEqual(2, len(bar_store.get_store())) self.assertEqual(Price.from_str("20.00001"), bar_store.get_store()[0].open) self.assertEqual(Price.from_str("20.00002"), bar_store.get_store()[0].high) self.assertEqual(Price.from_str("20.00001"), bar_store.get_store()[0].low) self.assertEqual(Price.from_str("20.00002"), bar_store.get_store()[0].close) self.assertEqual(Quantity.from_str("5000.00"), bar_store.get_store()[0].volume) self.assertEqual(Price.from_str("20.00002"), bar_store.get_store()[1].open) self.assertEqual(Price.from_str("20.00002"), bar_store.get_store()[1].high) self.assertEqual(Price.from_str("20.00000"), bar_store.get_store()[1].low) self.assertEqual(Price.from_str("20.00000"), bar_store.get_store()[1].close) self.assertEqual(Quantity.from_str("5000.00"), bar_store.get_store()[1].volume) self.assertEqual( Decimal("40000.11000"), aggregator.get_cumulative_value()) # TODO: WIP - Should be 40000
def _handle_order_trade_update(self, msg: BinanceFuturesOrderUpdateMsg): data: BinanceFuturesOrderData = msg.o instrument_id: InstrumentId = self._get_cached_instrument_id(data.s) client_order_id = ClientOrderId(data.c) if data.c != "" else None venue_order_id = VenueOrderId(str(data.i)) ts_event = millis_to_nanos(msg.T) # Fetch strategy ID strategy_id: StrategyId = self._cache.strategy_id_for_order( client_order_id) if strategy_id is None: if strategy_id is None: self._generate_external_order_status( instrument_id, client_order_id, venue_order_id, msg.o, ts_event, ) return if data.x == BinanceFuturesExecutionType.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 data.x == BinanceFuturesExecutionType.TRADE: instrument: Instrument = self._instrument_provider.find( instrument_id=instrument_id) # Determine commission if data.N is not None: commission = Money.from_str(f"{data.n} {data.N}") else: # Commission in margin collateral currency commission = Money(0, instrument.quote_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=PositionId( f"{instrument_id}-{data.ps.value}"), trade_id=TradeId(str(data.t)), order_side=OrderSide.BUY if data.S == BinanceOrderSide.BUY else OrderSide.SELL, 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 data.x == BinanceFuturesExecutionType.CANCELED: 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, ) elif data.x == BinanceFuturesExecutionType.EXPIRED: self.generate_order_expired( strategy_id=strategy_id, instrument_id=instrument_id, client_order_id=client_order_id, venue_order_id=venue_order_id, ts_event=ts_event, )
def test_str_and_to_str(self, value, expected): # Arrange # Act # Assert assert Quantity.from_str(value).to_str() == expected
def test_update_positions(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) # Create a closed position order1 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.50000000"), ) order2 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("10.50000000"), ) self.exec_engine.cache.add_order(order1, PositionId.null()) self.exec_engine.cache.add_order(order2, PositionId.null()) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.exec_engine.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.exec_engine.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) fill2 = TestStubs.event_order_filled( order2, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) position1 = Position(instrument=BTCUSDT_BINANCE, fill=fill1) position1.apply(fill2) order3 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.00000000"), ) fill3 = TestStubs.event_order_filled( order3, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-2"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) position2 = Position(instrument=BTCUSDT_BINANCE, fill=fill3) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("25001.00"), Price.from_str("25002.00"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) # Act self.cache.add_position(position1) self.cache.add_position(position2) self.portfolio.initialize_positions() self.portfolio.update_tick(last) # Assert self.assertTrue(self.portfolio.is_net_long(BTCUSDT_BINANCE.id))