depth: Add methods to derive liquidity allowances on orderbooks by volume and slippage (#962)

* depth: methods to derive liquidity impact details

* depth: fix comments on link list methods

* depth: fix whoopsie

* Update exchanges/orderbook/linked_list_test.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* orderbook: standardise methods to GCT math package

* averagePrice: implementation (WIP)

* ll: hmmmmmm

* continued

* orderbook: reworked functions

* WIP

* orderbook: add tests link up with RPC

* orderbook: refined calculations, add tests (WIP)

* orderbook: add tests finalise/verify remove state until next PR if needed

* rpcserver/orderbook: remove redundant type and change wording

* linter: fix

* Update exchanges/orderbook/linked_list.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* gctcli: noobed it up

* orderbook: work work work (yesterday)

* depth: WIP and testing

* orderbook: improve calculations for bids traversal

* orderbook: adjust tests

* orderbook: linters/nits

* orderbook: fix error returns and add asset to whalebomb

* orderbook: drop error when full book is potentially consumed

* Update cmd/gctcli/orderbook.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* orderbook: Add tests and nits

* glorious: nits

* backtester: handle new errors

* grpc: linter

* orderbook: remove pesky t.Log()s

* glorious: nits (yesterday)

* depth/gctcli: Add in purchase requirements into orderbook movement, will also standardize in next commit after tests are fixed.

* orderbook: standardize and overhaul

* orderbook: update comments, update tests

* rpcserver: add average ordercost and amounts

* depth: add spread and imbalance methods

* linter: fix

* glorious: nits

* orderbook: don't purge price, rn test.

* glorious: codes nits

* linter: fix

* Update exchanges/orderbook/linked_list.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/orderbook/linked_list.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/orderbook/linked_list.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/orderbook/linked_list.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/orderbook/linked_list.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* thrasher: nits

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2022-10-14 16:43:37 +11:00
committed by GitHub
parent 74d0cc323d
commit 9acbdbf203
21 changed files with 9150 additions and 3037 deletions

View File

@@ -32,6 +32,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
"github.com/thrasher-corp/gocryptotrader/gctrpc"
@@ -3202,3 +3203,312 @@ func TestGetAllManagedPositions(t *testing.T) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
}
func TestGetOrderbookMovement(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
if err != nil {
t.Fatal(err)
}
exch.SetDefaults()
b := exch.GetBase()
b.Name = fakeExchangeName
b.Enabled = true
cp, err := currency.NewPairFromString("btc-metal")
if err != nil {
t.Fatal(err)
}
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
b.CurrencyPairs.Pairs[asset.Spot] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.PairFormat{Delimiter: "/"},
RequestFormat: &currency.PairFormat{Delimiter: "/"},
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
fakeExchange := fExchange{
IBotExchange: exch,
}
em.Add(fakeExchange)
s := RPCServer{Engine: &Engine{ExchangeManager: em}}
req := &gctrpc.GetOrderbookMovementRequest{}
_, err = s.GetOrderbookMovement(context.Background(), req)
if !errors.Is(err, ErrExchangeNameIsEmpty) {
t.Fatalf("received: '%+v' but expected: '%v'", err, ErrExchangeNameIsEmpty)
}
req.Exchange = "fake"
_, err = s.GetOrderbookMovement(context.Background(), req)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%+v' but expected: '%v'", err, asset.ErrNotSupported)
}
req.Asset = asset.Spot.String()
req.Pair = &gctrpc.CurrencyPair{}
_, err = s.GetOrderbookMovement(context.Background(), req)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%+v' but expected: '%v'", err, currency.ErrCurrencyPairEmpty)
}
req.Pair = &gctrpc.CurrencyPair{
Base: currency.BTC.String(),
Quote: currency.METAL.String(),
}
_, err = s.GetOrderbookMovement(context.Background(), req)
if !strings.Contains(err.Error(), "cannot find orderbook") {
t.Fatalf("received: '%+v' but expected: '%v'", err, "cannot find orderbook")
}
depth, err := orderbook.DeployDepth(req.Exchange, currency.NewPair(currency.BTC, currency.METAL), asset.Spot)
if err != nil {
t.Fatal(err)
}
bid := []orderbook.Item{
{Price: 10, Amount: 1},
{Price: 9, Amount: 1},
{Price: 8, Amount: 1},
{Price: 7, Amount: 1},
}
ask := []orderbook.Item{
{Price: 11, Amount: 1},
{Price: 12, Amount: 1},
{Price: 13, Amount: 1},
{Price: 14, Amount: 1},
}
depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
_, err = s.GetOrderbookMovement(context.Background(), req)
if err.Error() != "quote amount invalid" {
t.Fatalf("received: '%+v' but expected: '%v'", err, "quote amount invalid")
}
req.Amount = 11
move, err := s.GetOrderbookMovement(context.Background(), req)
if !errors.Is(err, nil) {
t.Fatalf("received: '%+v' but expected: '%v'", err, nil)
}
if move.Bought != 1 {
t.Fatalf("received: '%v' but expected: '%v'", move.Bought, 1)
}
req.Sell = true
req.Amount = 1
move, err = s.GetOrderbookMovement(context.Background(), req)
if !errors.Is(err, nil) {
t.Fatalf("received: '%+v' but expected: '%v'", err, nil)
}
if move.Bought != 10 {
t.Fatalf("received: '%v' but expected: '%v'", move.Bought, 10)
}
}
func TestGetOrderbookAmountByNominal(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
if err != nil {
t.Fatal(err)
}
exch.SetDefaults()
b := exch.GetBase()
b.Name = fakeExchangeName
b.Enabled = true
cp, err := currency.NewPairFromString("btc-meme")
if err != nil {
t.Fatal(err)
}
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
b.CurrencyPairs.Pairs[asset.Spot] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.PairFormat{Delimiter: "/"},
RequestFormat: &currency.PairFormat{Delimiter: "/"},
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
fakeExchange := fExchange{
IBotExchange: exch,
}
em.Add(fakeExchange)
s := RPCServer{Engine: &Engine{ExchangeManager: em}}
req := &gctrpc.GetOrderbookAmountByNominalRequest{}
_, err = s.GetOrderbookAmountByNominal(context.Background(), req)
if !errors.Is(err, ErrExchangeNameIsEmpty) {
t.Fatalf("received: '%+v' but expected: '%v'", err, ErrExchangeNameIsEmpty)
}
req.Exchange = "fake"
_, err = s.GetOrderbookAmountByNominal(context.Background(), req)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%+v' but expected: '%v'", err, asset.ErrNotSupported)
}
req.Asset = asset.Spot.String()
req.Pair = &gctrpc.CurrencyPair{}
_, err = s.GetOrderbookAmountByNominal(context.Background(), req)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%+v' but expected: '%v'", err, currency.ErrCurrencyPairEmpty)
}
req.Pair = &gctrpc.CurrencyPair{
Base: currency.BTC.String(),
Quote: currency.MEME.String(),
}
_, err = s.GetOrderbookAmountByNominal(context.Background(), req)
if !strings.Contains(err.Error(), "cannot find orderbook") {
t.Fatalf("received: '%+v' but expected: '%v'", err, "cannot find orderbook")
}
depth, err := orderbook.DeployDepth(req.Exchange, currency.NewPair(currency.BTC, currency.MEME), asset.Spot)
if err != nil {
t.Fatal(err)
}
bid := []orderbook.Item{
{Price: 10, Amount: 1},
{Price: 9, Amount: 1},
{Price: 8, Amount: 1},
{Price: 7, Amount: 1},
}
ask := []orderbook.Item{
{Price: 11, Amount: 1},
{Price: 12, Amount: 1},
{Price: 13, Amount: 1},
{Price: 14, Amount: 1},
}
depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
nominal, err := s.GetOrderbookAmountByNominal(context.Background(), req)
if !errors.Is(err, nil) {
t.Fatalf("received: '%+v' but expected: '%v'", err, nil)
}
if nominal.AmountRequired != 11 {
t.Fatalf("received: '%v' but expected: '%v'", nominal.AmountRequired, 11)
}
req.Sell = true
nominal, err = s.GetOrderbookAmountByNominal(context.Background(), req)
if !errors.Is(err, nil) {
t.Fatalf("received: '%+v' but expected: '%v'", err, nil)
}
if nominal.AmountRequired != 1 {
t.Fatalf("received: '%v' but expected: '%v'", nominal.AmountRequired, 1)
}
}
func TestGetOrderbookAmountByImpact(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName("ftx")
if err != nil {
t.Fatal(err)
}
exch.SetDefaults()
b := exch.GetBase()
b.Name = fakeExchangeName
b.Enabled = true
cp, err := currency.NewPairFromString("btc-mad")
if err != nil {
t.Fatal(err)
}
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
b.CurrencyPairs.Pairs[asset.Spot] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.PairFormat{Delimiter: "/"},
RequestFormat: &currency.PairFormat{Delimiter: "/"},
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
fakeExchange := fExchange{
IBotExchange: exch,
}
em.Add(fakeExchange)
s := RPCServer{Engine: &Engine{ExchangeManager: em}}
req := &gctrpc.GetOrderbookAmountByImpactRequest{}
_, err = s.GetOrderbookAmountByImpact(context.Background(), req)
if !errors.Is(err, ErrExchangeNameIsEmpty) {
t.Fatalf("received: '%+v' but expected: '%v'", err, ErrExchangeNameIsEmpty)
}
req.Exchange = "fake"
_, err = s.GetOrderbookAmountByImpact(context.Background(), req)
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%+v' but expected: '%v'", err, asset.ErrNotSupported)
}
req.Asset = asset.Spot.String()
req.Pair = &gctrpc.CurrencyPair{}
_, err = s.GetOrderbookAmountByImpact(context.Background(), req)
if !errors.Is(err, currency.ErrCurrencyPairEmpty) {
t.Fatalf("received: '%+v' but expected: '%v'", err, currency.ErrCurrencyPairEmpty)
}
req.Pair = &gctrpc.CurrencyPair{
Base: currency.BTC.String(),
Quote: currency.MAD.String(),
}
_, err = s.GetOrderbookAmountByImpact(context.Background(), req)
if !strings.Contains(err.Error(), "cannot find orderbook") {
t.Fatalf("received: '%+v' but expected: '%v'", err, "cannot find orderbook")
}
depth, err := orderbook.DeployDepth(req.Exchange, currency.NewPair(currency.BTC, currency.MAD), asset.Spot)
if err != nil {
t.Fatal(err)
}
bid := []orderbook.Item{
{Price: 10, Amount: 1},
{Price: 9, Amount: 1},
{Price: 8, Amount: 1},
{Price: 7, Amount: 1},
}
ask := []orderbook.Item{
{Price: 11, Amount: 1},
{Price: 12, Amount: 1},
{Price: 13, Amount: 1},
{Price: 14, Amount: 1},
}
depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
req.ImpactPercentage = 9.090909090909092
impact, err := s.GetOrderbookAmountByImpact(context.Background(), req)
if !errors.Is(err, nil) {
t.Fatalf("received: '%+v' but expected: '%v'", err, nil)
}
if impact.AmountRequired != 11 {
t.Fatalf("received: '%v' but expected: '%v'", impact.AmountRequired, 11)
}
req.Sell = true
req.ImpactPercentage = 10
impact, err = s.GetOrderbookAmountByImpact(context.Background(), req)
if !errors.Is(err, nil) {
t.Fatalf("received: '%+v' but expected: '%v'", err, nil)
}
if impact.AmountRequired != 1 {
t.Fatalf("received: '%v' but expected: '%v'", impact.AmountRequired, 1)
}
}