mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
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:
@@ -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] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.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] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.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] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{Delimiter: "/"},
|
||||
RequestFormat: ¤cy.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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user