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]
fn submit_market_sell_order_reject() {
    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()
    );

    let order = MarketOrder::new(Side::Sell, BaseCurrency::new(10, 0)).unwrap();
    assert_eq!(
        exchange.submit_market_order(order),
        Err(NotEnoughAvailableBalance.into())
    );
}

#[test]
fn submit_market_sell_order() {
    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()
    );

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

    exchange.submit_market_order(order).unwrap();
    // make sure its executed immediately
    let entry_price = QuoteCurrency::new(100, 0);
    let notional = QuoteCurrency::convert_from(qty, entry_price);
    let fees = 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) - fees)
            .total_fees_paid(fees)
            .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) - fees
    );
}

#[test]
fn submit_market_sell_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(5, 0)).unwrap();
    let fee0 = QuoteCurrency::convert_from(order.quantity(), exchange.market_state().bid())
        * *test_fee_taker().as_ref();
    exchange.submit_market_order(order).unwrap();
    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
    );
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(BaseCurrency::new(-5, 0), QuoteCurrency::new(100, 0),).unwrap()
    );
    assert_eq!(
        exchange.account().active_limit_orders(),
        &ActiveLimitOrders::with_capacity(NonZeroU16::new(10).unwrap())
    );

    // Sell again
    let order = MarketOrder::new(Side::Sell, 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().available_balance(),
        QuoteCurrency::new(100, 0) - fee0 - fee1
    );
    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())
    );
}

#[test]
fn submit_market_sell_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 order = MarketOrder::new(Side::Buy, BaseCurrency::new(9, 0)).unwrap();
    let fee_0 = QuoteCurrency::convert_from(order.quantity(), QuoteCurrency::new(100, 0))
        * *test_fee_taker().as_ref();
    exchange.submit_market_order(order).unwrap();

    // Now close the position with a sell order
    let order = MarketOrder::new(Side::Sell, BaseCurrency::new(9, 0)).unwrap();
    let fee_1 = QuoteCurrency::convert_from(order.quantity(), QuoteCurrency::new(99, 0))
        * *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) - QuoteCurrency::new(9, 0) - fee_0 - fee_1)
            .total_fees_paid(fee_0 + fee_1)
            .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_sell_order_turnaround_long() {
    let mut exchange = mock_exchange_linear();
    assert!(
        exchange
            .update_state(&Bba {
                bid: QuoteCurrency::new(99, 0),
                ask: QuoteCurrency::new(100, 0),
                timestamp_exchange_ns: 0.into()
            })
            .unwrap()
            .is_empty()
    );

    // First enter a long position
    let qty = BaseCurrency::new(9, 0);
    let order = MarketOrder::new(Side::Buy, qty).unwrap();
    exchange.submit_market_order(order).unwrap();
    let fee0 = QuoteCurrency::convert_from(qty, exchange.market_state().ask())
        * *test_fee_taker().as_ref();
    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(),
        &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 reverse the position
    let qty = BaseCurrency::new(18, 0);
    let order = MarketOrder::new(Side::Sell, qty).unwrap();
    exchange.submit_market_order(order).unwrap();
    let fee1 =
        QuoteCurrency::convert_from(qty, QuoteCurrency::new(99, 0)) * *test_fee_taker().as_ref();
    assert_eq!(
        exchange.account().balances(),
        &Balances::builder()
            .equity(QuoteCurrency::new(991, 0) - fee0 - fee1)
            .total_fees_paid(fee0 + fee1)
            .build()
    );
    assert_eq!(
        exchange.account().position_margin(),
        QuoteCurrency::new(891, 0)
    );
    assert!(exchange.account().order_margin().is_zero());
    assert_eq!(
        exchange.account().available_balance(),
        QuoteCurrency::new(100, 0) - fee0 - fee1
    );
    assert_eq!(
        exchange.account().position().clone(),
        Position::new(BaseCurrency::new(-9, 0), QuoteCurrency::new(99, 0),).unwrap()
    );
    assert_eq!(
        exchange.account().active_limit_orders(),
        &ActiveLimitOrders::with_capacity(NonZeroU16::new(10).unwrap())
    );
}