SPB Git

Repository Public

test_margin.py 8.4 KB 142 lines python

1"""spq.margin must give the numbers of packages/derivatives/src/margin.ts on the same inputs.23Every expected value below is copied from packages/derivatives/test/margin.test.ts (the hand computations are in4that file's comments); `s` is the canonical decimal string of the TypeScript `D.str`."""5from __future__ import annotations67from decimal import Decimal, Inexact89import pytest1011from spq.margin import (Position, apply_order_to_position, dec, div, dstr, effective_leverage, exact,12                        free_collateral_after_order, initial_margin, liquidation_price_at_leverage,13                        liquidation_price_cross, liquidation_price_isolated, maintenance_margin, notional,14                        quantize_down, signed_size, unrealized_pnl)1516d, s = dec, dstr171819def test_notional_and_margins():20    assert s(notional(d("0.5"), d("60000"))) == "30000"21    assert s(notional(d("-0.5"), d("60000"))) == "30000"22    assert s(initial_margin(d("30000"), d("20"))) == "1500"23    assert s(initial_margin(d("100"), d("3"))) == "33.333333333333333334"       # rounded UP at 18 decimals24    assert s(maintenance_margin(d("30000"), d("0.005"))) == "150"25    with pytest.raises(ValueError):26        initial_margin(d("100"), d("0"))27    with pytest.raises(ValueError):28        maintenance_margin(d("-1"), d("0.01"))293031def test_unrealized_pnl():32    assert s(unrealized_pnl("long", d("0.5"), d("60000"), d("61000"))) == "500"33    assert s(unrealized_pnl("short", d("0.5"), d("60000"), d("61000"))) == "-500"34    assert s(unrealized_pnl("short", d("3"), d("2000"), d("1950.5"))) == "148.5"35    assert s(unrealized_pnl("long", d("3"), d("2000"), d("2000"))) == "0"363738def test_effective_leverage():39    assert s(effective_leverage(d("13000"), d("65000"))) == "5"40    assert s(effective_leverage(d("30000"), d("65000"))) == "2.166666666666666667"41    assert effective_leverage(d("0"), d("65000")) is None and effective_leverage(d("-5"), d("65000")) is None42    assert s(effective_leverage(d("100"), d("0"))) == "0"434445def test_isolated_liquidation_price_long():46    # long 10 @ 100 with 100 of margin, maintenance 6.25 %: p = 900 / 9.375 = 9647    assert s(liquidation_price_isolated("long", d("10"), d("100"), d("100"), d("0.0625"))) == "96"48    # maintenance 5 % + closing fee 1.25 % gives the same k49    assert s(liquidation_price_isolated("long", d("10"), d("100"), d("100"), d("0.05"), d("0.0125"))) == "96"50    # (60 000 - 6 000) / 0.995 rounded UP at 18 decimals51    assert s(liquidation_price_isolated("long", d("1"), d("60000"), d("6000"), d("0.005"))) == "54271.35678391959798995"52    assert liquidation_price_isolated("long", d("10"), d("100"), d("1000"), d("0.05")) is None53    with pytest.raises(ValueError):54        liquidation_price_isolated("long", d("1"), d("100"), d("10"), d("1"))555657def test_isolated_liquidation_price_short():58    # short 10 @ 100 with 275 of margin, maintenance 6.25 %: p = 1 275 / 10.625 = 12059    assert s(liquidation_price_isolated("short", d("10"), d("100"), d("275"), d("0.0625"))) == "120"60    # (1 000 + 100) / 10.5 rounded DOWN at 18 decimals61    assert s(liquidation_price_isolated("short", d("10"), d("100"), d("100"), d("0.05"))) == "104.761904761904761904"626364def test_liquidation_price_from_a_leverage():65    assert s(liquidation_price_at_leverage("long", d("100"), d("10"), d("0.0625"))) == "96"66    assert s(liquidation_price_at_leverage("short", d("100"), d("4"), d("0"))) == "125"67    assert liquidation_price_at_leverage("long", d("100"), d("1"), d("0.01")) is None686970def test_cross_liquidation_price_single_position_approximation():71    assert s(liquidation_price_cross("long", d("10"), d("100"), d("500"), d("0.0625"))) == "53.333333333333333334"72    assert s(liquidation_price_cross("long", d("10"), d("100"), d("500"), d("0.0625"), d("-100"), d("300"))) == "96"73    assert s(liquidation_price_cross("short", d("10"), d("100"), d("275"), d("0.0625"))) == "120"74    # the estimate shown by the TypeScript paper account in paper.test.ts (long 9 @ 100, collateral 99.1, 5 %)75    assert s(liquidation_price_cross("long", d("9"), d("100"), d("99.1"), d("0.05"))) == "93.672514619883040936"767778def test_effect_of_an_order_on_a_position():79    e = apply_order_to_position(None, "sell", d("2"), d("100"))80    assert (e.kind, e.position.side, s(e.position.size), s(e.position.entry_price), s(e.realized_pnl)) == \81        ("open", "short", "2", "100", "0")82    long2 = Position("long", d("2"), d("100"))83    inc = apply_order_to_position(long2, "buy", d("1"), d("130"))84    assert (inc.kind, s(inc.position.size), s(inc.position.entry_price), s(inc.realized_pnl), s(inc.opened_size)) == \85        ("increase", "3", "110", "0", "1")86    red = apply_order_to_position(inc.position, "sell", d("1"), d("120"))87    assert (red.kind, s(red.position.size), s(red.position.entry_price), s(red.realized_pnl), s(red.closed_size)) == \88        ("reduce", "2", "110", "10", "1")89    close = apply_order_to_position(red.position, "sell", d("2"), d("105"))90    assert (close.kind, close.position, s(close.realized_pnl)) == ("close", None, "-10")91    flip = apply_order_to_position(long2, "sell", d("5"), d("90"))92    assert (flip.kind, flip.position.side, s(flip.position.size), s(flip.position.entry_price), s(flip.realized_pnl),93            s(flip.closed_size), s(flip.opened_size)) == ("flip", "short", "3", "90", "-20", "2", "3")94    sred = apply_order_to_position(Position("short", d("4"), d("50")), "buy", d("1"), d("40"))95    assert (sred.kind, s(sred.realized_pnl), s(sred.position.size)) == ("reduce", "10", "3")96    avg = apply_order_to_position(Position("long", d("1"), d("100")), "buy", d("2"), d("101"))97    assert s(avg.position.entry_price) == "100.666666666666666667"             # half-even at 18 decimals98    with pytest.raises(ValueError):99        apply_order_to_position(None, "buy", d("0"), d("1"))100101102def test_free_collateral_after_a_hypothetical_order():103    base = dict(equity=d("1000"), margin_used=d("200"), price=d("100"), leverage=d("5"))104    assert s(free_collateral_after_order(side="buy", size=d("1"), **base)) == "780"105    assert s(free_collateral_after_order(side="buy", size=d("1"), fee_rate=d("0.001"), **base)) == "779.9"106    long2 = Position("long", d("2"), d("90"))107    assert s(free_collateral_after_order(side="sell", size=d("1"), position=long2, **base)) == "800"108    assert s(free_collateral_after_order(side="sell", size=d("3"), position=long2, **base)) == "800"109    assert s(free_collateral_after_order(side="sell", size=d("6"), position=long2, **base)) == "760"110    assert s(free_collateral_after_order(side="buy", size=d("3"), position=long2, **base)) == "740"111    assert s(signed_size(Position("long", d("2"), d("1")))) == "2"112    assert s(signed_size(Position("short", d("2"), d("1")))) == "-2" and s(signed_size(None)) == "0"113114115def test_at_the_liquidation_price_equity_equals_maintenance():116    """Property of the formula (margin.test.ts checks it on random inputs): equity - maintenance is zero at p,117    up to the rounding of p at 18 decimals."""118    for side, size, entry, margin, mmf in [("long", "3.7", "2011.5", "900", "0.02"), ("short", "0.42", "61234", "4100", "0.0125"),119                                           ("long", "150", "83.4", "2000", "0.05"), ("short", "9", "100", "99.1", "0.05")]:120        p = liquidation_price_isolated(side, d(size), d(entry), d(margin), d(mmf))121        with exact():122            equity = d(margin) + unrealized_pnl(side, d(size), d(entry), p)123            maint = d(size) * p * d(mmf)124        assert abs(equity - maint) < Decimal("1e-14")125        assert (p < d(entry)) if side == "long" else (p > d(entry))126127128def test_exact_context_and_helpers():129    assert dstr(dec(0.1)) == "0.1" and dstr(dec("1.2300")) == "1.23" and dstr(dec(5)) == "5" and dstr(dec("0.000")) == "0"130    assert dstr(div(d("1"), d("3"), 4, "down")) == "0.3333" and dstr(div(d("1"), d("3"), 4, "up")) == "0.3334"131    assert dstr(div(d("-1"), d("3"), 4, "up")) == "-0.3334" and dstr(div(d("1"), d("8"), 2, "half_even")) == "0.12"132    assert dstr(div(d("3"), d("8"), 2, "half_even")) == "0.38"133    assert dstr(quantize_down(d("1.23456789"), 4)) == "1.2345" and dstr(quantize_down(d("-1.23456789"), 4)) == "-1.2345"134    with exact():135        assert d("0.1") + d("0.2") == d("0.3")              # exact, unlike floats136        with pytest.raises(Inexact):137            d("1") / d("3")                                 # a division that would round is an error, not a value138    with pytest.raises(TypeError):139        dec(True)140    with pytest.raises(ValueError):141        dec(float("nan"))142
Files main
Menu

Theme