package fba import ( "testing" "github.com/edwardtay/injective-booklab/book" "github.com/edwardtay/injective-booklab/dec" ) var D = dec.MustParse func lv(p, q string) book.Level { return book.Level{Price: D(p), Qty: D(q)} } // Fixture: the "Market Order Matching" worked example in the Injective API docs. func TestClearMarketDocsExample(t *testing.T) { start := &book.Book{ Asks: []book.Level{lv("64360", "0.5"), lv("64370", "0.2"), lv("64390", "0.3")}, Bids: []book.Level{lv("64210", "0.1"), lv("64205", "0.4"), lv("64200", "0.2")}, } buys := ClearMarket(start, book.Buy, []Order{ {ID: "mb1", Price: D("64360"), Qty: D("0.2")}, {ID: "mb2", Price: D("66000"), Qty: D("0.4")}, }) if buys.Matched.String() != "0.5" || buys.ClearingPrice.String() != "64360" { t.Fatalf("buys matched %s at %s, docs say 0.5 at 64360", buys.Matched, buys.ClearingPrice) } // Highest worst-price first: mb2 fills 0.4, mb1 gets 0.1 and 0.1 is cancelled. if buys.Orders[0].ID != "mb2" || buys.Orders[1].Cancelled.String() != "0.1" { t.Errorf("order priority wrong: %+v", buys.Orders) } sells := ClearMarket(start, book.Sell, []Order{ {ID: "ms1", Price: D("60000"), Qty: D("0.1")}, {ID: "ms2", Price: D("61000"), Qty: D("0.2")}, {ID: "ms3", Price: D("69000"), Qty: D("0.3")}, }) if sells.Matched.String() != "0.3" { t.Fatalf("sells matched %s, docs say 0.3", sells.Matched) } // (64210*0.1 + 64205*0.2) / 0.3 = 64206.666... if got := sells.ClearingPrice.Round(2).String(); got != "64206.67" { t.Errorf("sell clearing price %s, docs say 64206.67", got) } if sells.Orders[2].ID != "ms3" || sells.Orders[2].Cancelled.String() != "0.3" { t.Errorf("ms3 should be cancelled in full: %+v", sells.Orders[2]) } } // Fixture: the "Limit Order Matching" worked example in the same docs. func TestClearLimitDocsExample(t *testing.T) { rest := &book.Book{ Asks: []book.Level{lv("64250", "0.5"), lv("64370", "0.2"), lv("64390", "0.3")}, Bids: []book.Level{lv("64210", "0.1"), lv("64205", "0.3"), lv("64200", "0.2")}, } orders := []Order{ {ID: "ls1", Side: book.Sell, Price: D("64220"), Qty: D("0.4")}, {ID: "ls2", Side: book.Sell, Price: D("64180"), Qty: D("0.2")}, {ID: "lb1", Side: book.Buy, Price: D("64370"), Qty: D("0.4")}, {ID: "lb2", Side: book.Buy, Price: D("64360"), Qty: D("0.2")}, } r := ClearLimit(rest, orders, dec.Zero()) if r.LastSell.String() != "64220" || r.LastBuy.String() != "64360" { t.Fatalf("range [%s, %s], docs say [64220, 64360]", r.LastSell, r.LastBuy) } if r.ClearingPrice.String() != "64230" { t.Errorf("clearing %s, docs say resting mid 64230", r.ClearingPrice) } if err := r.After.Validate(); err != nil { t.Errorf("book after the auction is invalid: %v", err) } // Mark price outside the range clamps to the nearer bound. if p := ClearLimit(rest, orders, D("64500")).ClearingPrice.String(); p != "64360" { t.Errorf("mark above range should clear at last buy, got %s", p) } if p := ClearLimit(rest, orders, D("64300")).ClearingPrice.String(); p != "64300" { t.Errorf("mark inside range should be used, got %s", p) } } func TestNoCrossNoMatch(t *testing.T) { rest := &book.Book{Asks: []book.Level{lv("10", "1")}, Bids: []book.Level{lv("9", "1")}} r := ClearLimit(rest, []Order{{ID: "b", Side: book.Buy, Price: D("9.5"), Qty: D("2")}}, dec.Zero()) if !r.Matched.IsZero() || r.Orders[0].Rests.String() != "2" { t.Fatalf("non-crossing order should rest: %+v", r) } }