lfest 0.138.3

A leveraged perpetual futures exchange for simulated trading and backtesting
Documentation
use std::num::NonZeroU16;

use crate::{
    mock_exchange_linear,
    prelude::*,
    test_fee_taker,
};

#[test]
#[tracing_test::traced_test]
fn submit_market_buy_order_reject() {
    let mut exchange = mock_exchange_linear();
    assert_eq!(
        exchange
            .update_state(&Bba {
                bid: QuoteCurrency::new(99, 0),
                ask: QuoteCurrency::new(100, 0),
                timestamp_exchange_ns: 0.into()
            })
            .unwrap(),
        &Vec::new()
    );

    let order = MarketOrder::new(Side::Buy, BaseCurrency::new(10, 0)).unwrap();
    assert_eq!(
        exchange.submit_market_order(order),
        Err(NotEnoughAvailableBalance.into())
    );
}
#[test]
#[tracing_test::traced_test]
fn submit_market_buy_order_no_position() {
    let mut exchange = mock_exchange_linear();
    assert!(
        exchange
            .update_state(&Bba {
                bid: QuoteCurrency::new(100, 0),
                ask: QuoteCurrency::new(101, 0),
                timestamp_exchange_ns: 0.into()
            })
            .unwrap()
            .is_empty()
    );

    let qty = BaseCurrency::new(5, 0);
    let order = MarketOrder::new(Side::Buy, qty).unwrap();
    exchange.submit_market_order(order).unwrap();

    // make sure its executed immediately
    let entry_price = QuoteCurrency::new(101, 0);
    let notional = QuoteCurrency::convert_from(qty, entry_price);
    let fee = notional * *test_fee_taker().as_ref();
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(qty, entry_price).unwrap()
    );
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(QuoteCurrency::new(1_000, 0) - fee)
            .total_fees_paid(fee)
            .build()
    );
    assert_eq!(
        exchange.account().position_margin(),
        QuoteCurrency::new(505, 0)
    );
    assert_eq!(exchange.account().order_margin(), Zero::zero());
    assert_eq!(
        exchange.account().available_balance(),
        QuoteCurrency::new(495, 0) - fee
    );
}

#[test]
#[tracing_test::traced_test]
fn submit_market_buy_order_with_long_position() {
    let mut exchange = mock_exchange_linear();
    assert_eq!(
        exchange
            .update_state(&Bba {
                bid: QuoteCurrency::new(99, 0),
                ask: QuoteCurrency::new(100, 0),
                timestamp_exchange_ns: 0.into()
            })
            .unwrap(),
        &Vec::new()
    );

    // First enter a long position
    let qty = BaseCurrency::new(5, 0);
    let order = MarketOrder::new(Side::Buy, qty).unwrap();
    exchange.submit_market_order(order).unwrap();
    let entry_price = QuoteCurrency::new(100, 0);
    let fee0 = QuoteCurrency::new(3, 1);
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(qty, entry_price).unwrap()
    );
    assert!(exchange.account().active_limit_orders().is_empty());
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(QuoteCurrency::new(1_000, 0) - fee0)
            .total_fees_paid(fee0)
            .build()
    );
    assert_eq!(
        exchange.account().position_margin(),
        QuoteCurrency::new(500, 0)
    );
    assert!(exchange.account().order_margin().is_zero());
    assert_eq!(
        exchange.account().available_balance(),
        QuoteCurrency::new(500, 0) - fee0
    );

    // Buy again
    let order = MarketOrder::new(Side::Buy, BaseCurrency::new(4, 0)).unwrap();
    exchange.submit_market_order(order).unwrap();
    let fee1 = QuoteCurrency::new(24, 2);
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(QuoteCurrency::new(1_000, 0) - fee0 - fee1)
            .total_fees_paid(fee0 + fee1)
            .build()
    );
    assert_eq!(
        exchange.account().position_margin(),
        QuoteCurrency::new(900, 0)
    );
    assert!(exchange.account().order_margin().is_zero());
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(BaseCurrency::new(9, 0), QuoteCurrency::new(100, 0)).unwrap()
    );
    assert_eq!(
        exchange.account().available_balance(),
        QuoteCurrency::new(100, 0) - fee0 - fee1
    );
    assert!(exchange.account().active_limit_orders().is_empty());
}

#[test]
#[tracing_test::traced_test]
fn submit_market_buy_order_with_short_position() {
    let mut exchange = mock_exchange_linear();
    assert_eq!(
        exchange
            .update_state(&Bba {
                bid: QuoteCurrency::new(100, 0),
                ask: QuoteCurrency::new(101, 0),
                timestamp_exchange_ns: 0.into()
            })
            .unwrap(),
        &Vec::new()
    );

    // First enter a short position
    let order = MarketOrder::new(Side::Sell, BaseCurrency::new(9, 0)).unwrap();
    exchange.submit_market_order(order).unwrap();
    let fee0 = QuoteCurrency::new(54, 2);
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(QuoteCurrency::new(1_000, 0) - fee0)
            .total_fees_paid(fee0)
            .build()
    );
    assert_eq!(
        exchange.account().position_margin(),
        QuoteCurrency::new(900, 0)
    );
    assert!(exchange.account().order_margin().is_zero());
    assert_eq!(
        exchange.account().available_balance(),
        QuoteCurrency::new(100, 0) - fee0
    );
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(BaseCurrency::new(-9, 0), QuoteCurrency::new(100, 0)).unwrap()
    );
    assert_eq!(
        exchange.account().active_limit_orders(),
        &ActiveLimitOrders::with_capacity(NonZeroU16::new(10).unwrap())
    );

    // Now close the position with a buy order
    let order = MarketOrder::new(Side::Buy, BaseCurrency::new(9, 0)).unwrap();
    let fee1 = QuoteCurrency::convert_from(order.quantity(), exchange.market_state().ask())
        * *test_fee_taker().as_ref();
    exchange.submit_market_order(order).unwrap();
    assert_eq!(exchange.account().position(), &Position::default());
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(QuoteCurrency::new(1000, 0) - fee0 - fee1 - QuoteCurrency::new(9, 0))
            .total_fees_paid(fee0 + fee1)
            .build()
    );
    assert_eq!(exchange.account().position_margin(), Zero::zero());
    assert_eq!(exchange.account().order_margin(), Zero::zero());
}

#[test]
#[tracing_test::traced_test]
fn submit_market_buy_order_turnaround_short() {
    let mut exchange = mock_exchange_linear();
    assert_eq!(
        exchange
            .update_state(&Bba {
                bid: QuoteCurrency::new(99, 0),
                ask: QuoteCurrency::new(100, 0),
                timestamp_exchange_ns: 0.into()
            })
            .unwrap(),
        &Vec::new()
    );

    // First enter a short position
    let qty = BaseCurrency::new(9, 0);
    let order = MarketOrder::new(Side::Sell, qty).unwrap();
    exchange.submit_market_order(order).unwrap();
    let entry_price = QuoteCurrency::new(99, 0);
    let notional = QuoteCurrency::convert_from(qty, entry_price);
    let fee_0 = notional * *test_fee_taker().as_ref();
    assert!(exchange.account().active_limit_orders().is_empty());
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(-qty, entry_price).unwrap()
    );
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(QuoteCurrency::new(1_000, 0) - fee_0)
            .total_fees_paid(fee_0)
            .build()
    );
    assert_eq!(
        exchange.account().position_margin(),
        QuoteCurrency::new(891, 0)
    );
    assert_eq!(
        exchange.account().available_balance(),
        QuoteCurrency::new(109, 0) - fee_0
    );
    assert!(exchange.account().order_margin().is_zero());

    // Close the entire position and buy some more
    let qty = BaseCurrency::new(18, 0);
    let order = MarketOrder::new(Side::Buy, qty).unwrap();
    exchange.submit_market_order(order).unwrap();
    let entry_price = QuoteCurrency::new(100, 0);
    let fee_1 = QuoteCurrency::convert_from(qty, entry_price) * *test_fee_taker().as_ref();
    assert!(exchange.account().active_limit_orders().is_empty());
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(BaseCurrency::new(9, 0), entry_price).unwrap()
    );
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(
                QuoteCurrency::new(1000, 0)
                    - QuoteCurrency::new(5346, 4)
                    - QuoteCurrency::new(108, 2)
                    - QuoteCurrency::new(9, 0)
            )
            .total_fees_paid(fee_0 + fee_1)
            .build()
    );
    assert_eq!(
        exchange.account().position_margin(),
        QuoteCurrency::new(900, 0)
    );
    assert_eq!(
        exchange.account().available_balance(),
        QuoteCurrency::new(100, 0)
            - QuoteCurrency::new(5346, 4)
            - QuoteCurrency::new(108, 2)
            - QuoteCurrency::new(9, 0)
    );
    assert!(exchange.account().order_margin().is_zero());
}