package orderbook

import (
	"errors"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/gofrs/uuid"
	"github.com/thrasher-corp/gocryptotrader/currency"
	"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)

var id = uuid.Must(uuid.NewV4())

func TestGetLength(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	err := d.Invalidate(nil)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}
	_, err = d.GetAskLength()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.LoadSnapshot([]Item{{Price: 1337}}, nil, 0, time.Time{}, true)

	askLen, err := d.GetAskLength()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if askLen != 0 {
		t.Errorf("expected len %v, but received %v", 0, askLen)
	}

	d.asks.load([]Item{{Price: 1337}}, d.stack)

	askLen, err = d.GetAskLength()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if askLen != 1 {
		t.Errorf("expected len %v, but received %v", 1, askLen)
	}

	d = NewDepth(id)
	err = d.Invalidate(nil)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}
	_, err = d.GetBidLength()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.LoadSnapshot(nil, []Item{{Price: 1337}}, 0, time.Time{}, true)

	bidLen, err := d.GetBidLength()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if bidLen != 0 {
		t.Errorf("expected len %v, but received %v", 0, bidLen)
	}

	d.bids.load([]Item{{Price: 1337}}, d.stack)

	bidLen, err = d.GetBidLength()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if bidLen != 1 {
		t.Errorf("expected len %v, but received %v", 1, bidLen)
	}
}

func TestRetrieve(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.asks.load([]Item{{Price: 1337}}, d.stack)
	d.bids.load([]Item{{Price: 1337}}, d.stack)
	d.options = options{
		exchange:         "THE BIG ONE!!!!!!",
		pair:             currency.NewPair(currency.THETA, currency.USD),
		asset:            asset.DownsideProfitContract,
		lastUpdated:      time.Now(),
		lastUpdateID:     1337,
		priceDuplication: true,
		isFundingRate:    true,
		VerifyOrderbook:  true,
		restSnapshot:     true,
		idAligned:        true,
	}

	// If we add anymore options to the options struct later this will complain
	// generally want to return a full carbon copy
	mirrored := reflect.Indirect(reflect.ValueOf(d.options))
	for n := 0; n < mirrored.NumField(); n++ {
		structVal := mirrored.Field(n)
		if structVal.IsZero() {
			t.Fatalf("struct value options not set for field %v",
				mirrored.Type().Field(n).Name)
		}
	}

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if len(ob.Asks) != 1 {
		t.Errorf("expected len %v, but received %v", 1, len(ob.Bids))
	}

	if len(ob.Bids) != 1 {
		t.Errorf("expected len %v, but received %v", 1, len(ob.Bids))
	}
}

func TestTotalAmounts(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)

	err := d.Invalidate(nil)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}
	_, _, err = d.TotalBidAmounts()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.validationError = nil
	liquidity, value, err := d.TotalBidAmounts()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if liquidity != 0 || value != 0 {
		t.Fatalf("liquidity expected %f received %f value expected %f received %f",
			0.,
			liquidity,
			0.,
			value)
	}

	err = d.Invalidate(nil)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	_, _, err = d.TotalAskAmounts()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.validationError = nil

	liquidity, value, err = d.TotalAskAmounts()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if liquidity != 0 || value != 0 {
		t.Fatalf("liquidity expected %f received %f value expected %f received %f",
			0.,
			liquidity,
			0.,
			value)
	}

	d.asks.load([]Item{{Price: 1337, Amount: 1}}, d.stack)
	d.bids.load([]Item{{Price: 1337, Amount: 10}}, d.stack)

	liquidity, value, err = d.TotalBidAmounts()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if liquidity != 10 || value != 13370 {
		t.Fatalf("liquidity expected %f received %f value expected %f received %f",
			10.,
			liquidity,
			13370.,
			value)
	}

	liquidity, value, err = d.TotalAskAmounts()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if liquidity != 1 || value != 1337 {
		t.Fatalf("liquidity expected %f received %f value expected %f received %f",
			1.,
			liquidity,
			1337.,
			value)
	}
}

func TestLoadSnapshot(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false)

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if ob.Asks[0].Price != 1337 || ob.Bids[0].Price != 1337 {
		t.Fatalf("not set")
	}
}

func TestInvalidate(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.exchange = "testexchange"
	d.pair = currency.NewPair(currency.BTC, currency.WABI)
	d.asset = asset.Spot
	d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false)

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if ob == nil {
		t.Fatalf("unexpected value")
	}

	err = d.Invalidate(errors.New("random reason"))
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	_, err = d.Retrieve()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	if err.Error() != "testexchange BTCWABI spot orderbook data integrity compromised Reason: [random reason]" {
		t.Fatal("unexpected string return")
	}

	d.validationError = nil

	ob, err = d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if len(ob.Asks) != 0 || len(ob.Bids) != 0 {
		t.Fatalf("not flushed")
	}
}

func TestUpdateBidAskByPrice(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	// empty
	d.UpdateBidAskByPrice(&Update{})

	updates := &Update{
		Bids:     Items{{Price: 1337, Amount: 2, ID: 1}},
		Asks:     Items{{Price: 1337, Amount: 2, ID: 2}},
		UpdateID: 1,
	}
	d.UpdateBidAskByPrice(updates)

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if ob.Asks[0].Amount != 2 || ob.Bids[0].Amount != 2 {
		t.Fatalf("orderbook amounts not updated correctly")
	}

	updates = &Update{
		Bids:     Items{{Price: 1337, Amount: 0, ID: 1}},
		Asks:     Items{{Price: 1337, Amount: 0, ID: 2}},
		UpdateID: 2,
	}
	d.UpdateBidAskByPrice(updates)

	askLen, err := d.GetAskLength()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	bidLen, err := d.GetBidLength()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if askLen != 0 || bidLen != 0 {
		t.Fatalf("orderbook amounts not updated correctly")
	}
}

func TestDeleteBidAskByID(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	updates := &Update{
		Bids: Items{{Price: 1337, Amount: 2, ID: 1}},
		Asks: Items{{Price: 1337, Amount: 2, ID: 2}},
	}
	err := d.DeleteBidAskByID(updates, false)
	if err != nil {
		t.Fatal(err)
	}

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if len(ob.Asks) != 0 || len(ob.Bids) != 0 {
		t.Fatalf("items not deleted")
	}

	updates = &Update{
		Bids: Items{{Price: 1337, Amount: 2, ID: 1}},
	}
	err = d.DeleteBidAskByID(updates, false)
	if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) {
		t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
	}

	updates = &Update{
		Asks: Items{{Price: 1337, Amount: 2, ID: 2}},
	}
	err = d.DeleteBidAskByID(updates, false)
	if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) {
		t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
	}

	updates = &Update{
		Asks: Items{{Price: 1337, Amount: 2, ID: 2}},
	}
	err = d.DeleteBidAskByID(updates, true)
	if !errors.Is(err, nil) {
		t.Fatalf("error expected %v received %v", nil, err)
	}
}

func TestUpdateBidAskByID(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	updates := &Update{
		Bids: Items{{Price: 1337, Amount: 2, ID: 1}},
		Asks: Items{{Price: 1337, Amount: 2, ID: 2}},
	}
	err := d.UpdateBidAskByID(updates)
	if err != nil {
		t.Fatal(err)
	}

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if ob.Asks[0].Amount != 2 || ob.Bids[0].Amount != 2 {
		t.Fatalf("orderbook amounts not updated correctly")
	}

	updates = &Update{
		Bids: Items{{Price: 1337, Amount: 2, ID: 666}},
	}
	// random unmatching IDs
	err = d.UpdateBidAskByID(updates)
	if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) {
		t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
	}

	updates = &Update{
		Asks: Items{{Price: 1337, Amount: 2, ID: 69}},
	}
	err = d.UpdateBidAskByID(updates)
	if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) {
		t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err)
	}
}

func TestInsertBidAskByID(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	updates := &Update{
		Asks: Items{{Price: 1337, Amount: 2, ID: 3}},
	}

	err := d.InsertBidAskByID(updates)
	if !strings.Contains(err.Error(), errCollisionDetected.Error()) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errCollisionDetected)
	}

	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	updates = &Update{
		Bids: Items{{Price: 1337, Amount: 2, ID: 3}},
	}

	err = d.InsertBidAskByID(updates)
	if !strings.Contains(err.Error(), errCollisionDetected.Error()) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errCollisionDetected)
	}

	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)
	updates = &Update{
		Bids: Items{{Price: 1338, Amount: 2, ID: 3}},
		Asks: Items{{Price: 1336, Amount: 2, ID: 4}},
	}
	err = d.InsertBidAskByID(updates)
	if err != nil {
		t.Fatal(err)
	}

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if len(ob.Asks) != 2 || len(ob.Bids) != 2 {
		t.Fatalf("items not added correctly")
	}
}

func TestUpdateInsertByID(t *testing.T) {
	t.Parallel()
	d := NewDepth(id)
	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	updates := &Update{
		Bids: Items{{Price: 1338, Amount: 0, ID: 3}},
		Asks: Items{{Price: 1336, Amount: 2, ID: 4}},
	}
	err := d.UpdateInsertByID(updates)
	if !strings.Contains(err.Error(), errAmountCannotBeLessOrEqualToZero.Error()) {
		t.Fatalf("expected: %v but received: %v", errAmountCannotBeLessOrEqualToZero, err)
	}

	// Above will invalidate the book
	_, err = d.Retrieve()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	updates = &Update{
		Bids: Items{{Price: 1338, Amount: 2, ID: 3}},
		Asks: Items{{Price: 1336, Amount: 0, ID: 4}},
	}
	err = d.UpdateInsertByID(updates)
	if !strings.Contains(err.Error(), errAmountCannotBeLessOrEqualToZero.Error()) {
		t.Fatalf("expected: %v but received: %v", errAmountCannotBeLessOrEqualToZero, err)
	}

	// Above will invalidate the book
	_, err = d.Retrieve()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false)

	updates = &Update{
		Bids: Items{{Price: 1338, Amount: 2, ID: 3}},
		Asks: Items{{Price: 1336, Amount: 2, ID: 4}},
	}
	err = d.UpdateInsertByID(updates)
	if err != nil {
		t.Fatal(err)
	}

	ob, err := d.Retrieve()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if len(ob.Asks) != 2 || len(ob.Bids) != 2 {
		t.Fatalf("items not added correctly")
	}
}

func TestAssignOptions(t *testing.T) {
	t.Parallel()
	d := Depth{}
	cp := currency.NewPair(currency.LINK, currency.BTC)
	tn := time.Now()
	d.AssignOptions(&Base{
		Exchange:         "test",
		Pair:             cp,
		Asset:            asset.Spot,
		LastUpdated:      tn,
		LastUpdateID:     1337,
		PriceDuplication: true,
		IsFundingRate:    true,
		VerifyOrderbook:  true,
		RestSnapshot:     true,
		IDAlignment:      true,
	})

	if d.exchange != "test" ||
		d.pair != cp ||
		d.asset != asset.Spot ||
		d.lastUpdated != tn ||
		d.lastUpdateID != 1337 ||
		!d.priceDuplication ||
		!d.isFundingRate ||
		!d.VerifyOrderbook ||
		!d.restSnapshot ||
		!d.idAligned {
		t.Fatalf("failed to set correctly")
	}
}

func TestGetName(t *testing.T) {
	t.Parallel()
	d := Depth{}
	d.exchange = "test"
	if d.GetName() != "test" {
		t.Fatalf("failed to get correct value")
	}
}

func TestIsRestSnapshot(t *testing.T) {
	t.Parallel()
	d := Depth{}
	d.restSnapshot = true
	err := d.Invalidate(nil)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}
	_, err = d.IsRESTSnapshot()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.validationError = nil
	b, err := d.IsRESTSnapshot()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !b {
		t.Fatalf("received: '%v' but expected: '%v'", b, true)
	}
}

func TestLastUpdateID(t *testing.T) {
	t.Parallel()
	d := Depth{}
	err := d.Invalidate(nil)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}
	_, err = d.LastUpdateID()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	d.validationError = nil
	d.lastUpdateID = 1337
	id, err := d.LastUpdateID()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if id != 1337 {
		t.Fatalf("received: '%v' but expected: '%v'", id, 1337)
	}
}

func TestIsFundingRate(t *testing.T) {
	t.Parallel()
	d := Depth{}
	d.isFundingRate = true
	if !d.IsFundingRate() {
		t.Fatalf("failed to get correct value")
	}
}

func TestPublish(t *testing.T) {
	t.Parallel()
	d := Depth{}
	if err := d.Invalidate(nil); !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}
	d.Publish()
	d.validationError = nil
	d.Publish()
}

func TestIsValid(t *testing.T) {
	t.Parallel()
	d := Depth{}
	if !d.IsValid() {
		t.Fatalf("received: '%v' but expected: '%v'", d.IsValid(), true)
	}
	if err := d.Invalidate(nil); !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}
	if d.IsValid() {
		t.Fatalf("received: '%v' but expected: '%v'", d.IsValid(), false)
	}
}

func TestHitTheBidsByNominalSlippage(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsByNominalSlippage(10, 1355.5)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.HitTheBidsByNominalSlippage(10, 1355.5)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First tranche
	amt, err := depth.HitTheBidsByNominalSlippage(0, 1336)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 1 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 2)
	}

	if amt.NominalPercentage != 0 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 0)
	}

	if amt.StartPrice != 1336 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1336)
	}

	if amt.EndPrice != 1336 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1336)
	}

	if amt.FullBookSideConsumed {
		t.Fatalf("received: '%+v' but expected: '%v'", amt.FullBookSideConsumed, false)
	}

	// First and second price
	amt, err = depth.HitTheBidsByNominalSlippage(0.037425149700598806, 1336)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 2 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 2)
	}

	if amt.NominalPercentage != 0.037425149700598806 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 0.037425149700598806)
	}

	if amt.StartPrice != 1336 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1336)
	}

	if amt.EndPrice != 1335 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1335)
	}

	if amt.FullBookSideConsumed {
		t.Fatalf("received: '%+v' but expected: '%v'", amt.FullBookSideConsumed, false)
	}

	// First and half of second tranche
	amt, err = depth.HitTheBidsByNominalSlippage(0.02495009980039353, 1336)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 1.4999999999998295 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1.4999999999998295)
	}

	if amt.NominalPercentage != 0.02495009980039353 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 0.02495009980039353)
	}

	if amt.StartPrice != 1336 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1336)
	}

	if amt.EndPrice != 1335 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1335)
	}

	if amt.FullBookSideConsumed {
		t.Fatalf("received: '%+v' but expected: '%v'", amt.FullBookSideConsumed, false)
	}

	// All the way up to the last price
	amt, err = depth.HitTheBidsByNominalSlippage(0.7110778443113772, 1336)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	// This exceeds the entire total base available - should be 20.
	if amt.Sold != 20 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 20.00721336370539)
	}

	if amt.NominalPercentage != 0.7110778443113772 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 0.7110778443113772)
	}

	if amt.StartPrice != 1336 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1336)
	}

	if amt.EndPrice != 1317 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 1317)
	}

	if !amt.FullBookSideConsumed {
		t.Fatalf("received: '%+v' but expected: '%v'", amt.FullBookSideConsumed, true)
	}
}

func TestHitTheBidsByNominalSlippageFromMid(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsByNominalSlippageFromMid(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.HitTheBidsByNominalSlippageFromMid(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	// First price from mid point
	amt, err := depth.HitTheBidsByNominalSlippageFromMid(0.03741114852226)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 1 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 1)
	}

	// All the way up to the last price from mid price
	amt, err = depth.HitTheBidsByNominalSlippageFromMid(0.74822297044519)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	// This exceeds the entire total base available
	if amt.Sold != 20 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 20)
	}
}

func TestHitTheBidsByNominalSlippageFromBest(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsByNominalSlippageFromBest(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.HitTheBidsByNominalSlippageFromBest(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	// First and second price from best bid
	amt, err := depth.HitTheBidsByNominalSlippageFromBest(0.037425149700599)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 2 {
		t.Fatalf("received: '%+v' but expected: '%+v'", amt, 2)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.HitTheBidsByNominalSlippageFromBest(0.71107784431138)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	// This exceeds the entire total base available
	if amt.Sold != 20 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 20)
	}
}

func TestLiftTheAsksByNominalSlippage(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksByNominalSlippage(10, 1355.5)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.LiftTheAsksByNominalSlippage(10, 1355.5)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First and second price
	amt, err := depth.LiftTheAsksByNominalSlippage(0.037397157816006, 1337)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 2675 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 2675)
	}

	// All the way up to the last price
	amt, err = depth.LiftTheAsksByNominalSlippage(0.71054599850411, 1337)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 26930 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 26930)
	}
}

func TestLiftTheAsksByNominalSlippageFromMid(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksByNominalSlippageFromMid(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.LiftTheAsksByNominalSlippageFromMid(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	// First price from mid point
	amt, err := depth.LiftTheAsksByNominalSlippageFromMid(0.074822297044519)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 2675 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 2675)
	}

	// All the way up to the last price from mid price
	amt, err = depth.LiftTheAsksByNominalSlippageFromMid(0.74822297044519)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	// This does not match the entire total quote available
	if amt.Sold != 26930 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 26930)
	}
}

func TestLiftTheAsksByNominalSlippageFromBest(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksByNominalSlippageFromBest(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.LiftTheAsksByNominalSlippageFromBest(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	// First and second price from best bid
	amt, err := depth.LiftTheAsksByNominalSlippageFromBest(0.037397157816006)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 2675 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 2675)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.LiftTheAsksByNominalSlippageFromBest(0.71054599850411)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	// This does not match the entire total quote available
	if amt.Sold != 26930 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 26930)
	}
}

func TestHitTheBidsByImpactSlippage(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsByImpactSlippage(0.7485029940119761, 1336)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First and second price from best bid - price level target 1326 (which should be kept)
	amt, err := depth.HitTheBidsByImpactSlippage(0.7485029940119761, 1336)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 10 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 10)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.HitTheBidsByImpactSlippage(1.4221556886227544, 1336)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	// This does not match the entire total quote available - should be 26930.
	if amt.Sold != 19 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 19)
	}
}

func TestHitTheBidsByImpactSlippageFromMid(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsByImpactSlippageFromMid(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.HitTheBidsByImpactSlippageFromMid(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First and second price from mid - price level target 1326 (which should be kept)
	amt, err := depth.HitTheBidsByImpactSlippageFromMid(0.7485029940119761)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 10 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 10)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.HitTheBidsByImpactSlippageFromMid(1.4221556886227544)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 19 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 19)
	}
}

func TestHitTheBidsByImpactSlippageFromBest(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsByImpactSlippageFromBest(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.HitTheBidsByImpactSlippageFromBest(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First and second price from mid - price level target 1326 (which should be kept)
	amt, err := depth.HitTheBidsByImpactSlippageFromBest(0.7485029940119761)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 10 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 10)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.HitTheBidsByImpactSlippageFromBest(1.4221556886227544)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 19 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 19)
	}
}

func TestLiftTheAsksByImpactSlippage(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksByImpactSlippage(0.7479431563201197, 1337)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First and second price from best bid - price level target 1326 (which should be kept)
	amt, err := depth.LiftTheAsksByImpactSlippage(0.7479431563201197, 1337)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 13415 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 13415)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.LiftTheAsksByImpactSlippage(1.4210919970082274, 1337)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 25574 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 25574)
	}
}

func TestLiftTheAsksByImpactSlippageFromMid(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksByImpactSlippageFromMid(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.LiftTheAsksByImpactSlippageFromMid(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First and second price from mid - price level target 1326 (which should be kept)
	amt, err := depth.LiftTheAsksByImpactSlippageFromMid(0.7485029940119761)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 13415 {
		t.Fatalf("received: '%+v' but expected: '%v'", amt, 13415)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.LiftTheAsksByImpactSlippageFromMid(1.4221556886227544)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 25574 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 25574)
	}
}

func TestLiftTheAsksByImpactSlippageFromBest(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksByImpactSlippageFromBest(10)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.LiftTheAsksByImpactSlippageFromBest(10)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	// First and second price from mid - price level target 1326 (which should be kept)
	amt, err := depth.LiftTheAsksByImpactSlippageFromBest(0.7479431563201197)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if amt.Sold != 13415 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 13415)
	}

	// All the way up to the last price from best bid price
	amt, err = depth.LiftTheAsksByImpactSlippageFromBest(1.4210919970082274)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	// This goes to price 1356, it will not count that tranches' volume as it
	// is needed to sustain the slippage.
	if amt.Sold != 25574 {
		t.Fatalf("received: '%v' but expected: '%v'", amt, 25574)
	}
}

func TestHitTheBids(t *testing.T) {
	t.Parallel()
	depth := NewDepth(id)
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.HitTheBids(20.1, 1336, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.HitTheBids(1, 1336, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07485029940119761 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07485029940119761)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.HitTheBids(19.5, 1336, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.692845079072617 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.692845079072617)
	}
	if mov.ImpactPercentage != 1.4221556886227544 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4221556886227544)
	}

	if mov.SlippageCost != 180.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 180.5)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.HitTheBids(20, 1336, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7110778443113772 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7110778443113772)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestHitTheBids_QuotationRequired(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBids(26531, 1336, true)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.HitTheBids(26531, 1336, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.HitTheBids(1336, 1336, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07485029940119761 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07485029940119761)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.HitTheBids(25871.5, 1336, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.692845079072617 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.692845079072617)
	}
	if mov.ImpactPercentage != 1.4221556886227544 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4221556886227544)
	}

	if mov.SlippageCost != 180.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 180.5)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.HitTheBids(26530, 1336, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7110778443113772 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7110778443113772)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestHitTheBidsFromMid(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsFromMid(10, false)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.HitTheBidsFromMid(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.HitTheBidsFromMid(20.1, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.HitTheBidsFromMid(1, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.03741114852225963 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.03741114852225963)
	}

	if mov.ImpactPercentage != 0.11223344556677892 { // mid price 1336.5 -> 1335
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.11223344556677892)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.HitTheBidsFromMid(19.5, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7299970262933156 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7299970262933156)
	}
	if mov.ImpactPercentage != 1.4590347923681257 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4590347923681257)
	}
	if mov.SlippageCost != 180.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 180.5)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.HitTheBidsFromMid(20, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7482229704451926 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7482229704451926)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestHitTheBidsFromMid_QuotationRequired(t *testing.T) {
	t.Parallel()
	depth := NewDepth(id)
	_, err := depth.HitTheBidsFromMid(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.HitTheBidsFromMid(26531, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.HitTheBidsFromMid(1336, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.03741114852225963 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.03741114852225963)
	}

	if mov.ImpactPercentage != 0.11223344556677892 { // mid price 1336.5 -> 1335
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.11223344556677892)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.HitTheBidsFromMid(25871.5, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7299970262933156 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7299970262933156)
	}
	if mov.ImpactPercentage != 1.4590347923681257 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4590347923681257)
	}
	if mov.SlippageCost != 180.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 180.5)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.HitTheBidsFromMid(26530, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7482229704451926 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7482229704451926)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestHitTheBidsFromBest(t *testing.T) {
	t.Parallel()
	depth := NewDepth(id)
	_, err := depth.HitTheBidsFromBest(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.HitTheBidsFromBest(20.1, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.HitTheBidsFromBest(1, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07485029940119761 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07485029940119761)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.HitTheBidsFromBest(19.5, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.692845079072617 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.692845079072617)
	}
	if mov.ImpactPercentage != 1.4221556886227544 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4221556886227544)
	}
	if mov.SlippageCost != 180.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 180.5)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.HitTheBidsFromBest(20, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7110778443113772 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7110778443113772)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestHitTheBidsFromBest_QuotationRequired(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().HitTheBidsFromBest(10, false)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.HitTheBidsFromBest(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.HitTheBidsFromBest(26531, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.HitTheBidsFromBest(1336, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07485029940119761 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07485029940119761)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.HitTheBidsFromBest(25871.5, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.692845079072617 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.692845079072617)
	}
	if mov.ImpactPercentage != 1.4221556886227544 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4221556886227544)
	}
	if mov.SlippageCost != 180.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 180.5)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.HitTheBidsFromBest(26530, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7110778443113772 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7110778443113772)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestLiftTheAsks(t *testing.T) {
	t.Parallel()
	depth := NewDepth(id)
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.LiftTheAsks(26931, 1337, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.LiftTheAsks(1337, 1337, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07479431563201197 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07479431563201197)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.LiftTheAsks(26900, 1337, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7097591258590459 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7097591258590459)
	}
	if mov.ImpactPercentage != 1.4210919970082274 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4210919970082274)
	}
	if mov.SlippageCost != 189.57964601770072 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 189.57964601770072)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.LiftTheAsks(26930, 1336, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7859281437125748 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7859281437125748)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestLiftTheAsks_BaseRequired(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsks(21, 1337, true)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.LiftTheAsks(21, 1337, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.LiftTheAsks(1, 1337, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07479431563201197 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07479431563201197)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.LiftTheAsks(19.97787610619469, 1337, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7097591258590288 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7097591258590288)
	}
	if mov.ImpactPercentage != 1.4210919970082274 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4210919970082274)
	}
	if mov.SlippageCost != 189.5796460176971 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 189.5796460176971)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.LiftTheAsks(20, 1336, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7859281437125748 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7859281437125748)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestLiftTheAsksFromMid(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksFromMid(10, false)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.LiftTheAsksFromMid(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.LiftTheAsksFromMid(26931, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.LiftTheAsksFromMid(1337, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.03741114852225963 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.03741114852225963)
	}
	if mov.ImpactPercentage != 0.11223344556677892 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.11223344556677892)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.LiftTheAsksFromMid(26900, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.747435803422031 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.747435803422031)
	}
	if mov.ImpactPercentage != 1.4590347923681257 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4590347923681257)
	}
	if mov.SlippageCost != 189.57964601770072 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 189.57964601770072)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.LiftTheAsksFromMid(26930, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7482229704451926 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7482229704451926)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestLiftTheAsksFromMid_BaseRequired(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksFromMid(10, false)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.LiftTheAsksFromMid(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.LiftTheAsksFromMid(21, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.LiftTheAsksFromMid(1, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.03741114852225963 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.03741114852225963)
	}
	if mov.ImpactPercentage != 0.11223344556677892 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.11223344556677892)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.LiftTheAsksFromMid(19.97787610619469, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7474358034220139 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7474358034220139)
	}
	if mov.ImpactPercentage != 1.4590347923681257 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4590347923681257)
	}
	if mov.SlippageCost != 189.5796460176971 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 189.5796460176971)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.LiftTheAsksFromMid(20, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7482229704451926 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7482229704451926)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestLiftTheAsksFromBest(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksFromBest(10, false)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.LiftTheAsksFromBest(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.LiftTheAsksFromBest(26931, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.LiftTheAsksFromBest(1337, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07479431563201197 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07479431563201197)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.LiftTheAsksFromBest(26900, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7097591258590459 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7097591258590459)
	}
	if mov.ImpactPercentage != 1.4210919970082274 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4210919970082274)
	}
	if mov.SlippageCost != 189.57964601770072 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 189.57964601770072)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.LiftTheAsksFromBest(26930, false)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7105459985041137 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7105459985041137)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestLiftTheAsksFromBest_BaseRequired(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().LiftTheAsksFromBest(10, false)
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.LiftTheAsksFromBest(10, false)
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mov, err := depth.LiftTheAsksFromBest(21, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if !mov.FullBookSideConsumed {
		t.Fatal("entire side should be consumed by this value")
	}

	mov, err = depth.LiftTheAsksFromBest(1, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0)
	}
	if mov.ImpactPercentage != 0.07479431563201197 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 0.07479431563201197)
	}
	if mov.SlippageCost != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 0)
	}

	mov, err = depth.LiftTheAsksFromBest(19.97787610619469, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}
	if mov.NominalPercentage != 0.7097591258590288 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7097591258590288)
	}
	if mov.ImpactPercentage != 1.4210919970082274 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, 1.4210919970082274)
	}
	if mov.SlippageCost != 189.5796460176971 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 189.5796460176971)
	}

	// All the way up to the last price from best bid price
	mov, err = depth.LiftTheAsksFromBest(20, true)
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mov.NominalPercentage != 0.7105459985041137 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.NominalPercentage, 0.7105459985041137)
	}
	if mov.ImpactPercentage != FullLiquidityExhaustedPercentage {
		t.Fatalf("received: '%v' but expected: '%v'", mov.ImpactPercentage, FullLiquidityExhaustedPercentage)
	}
	if mov.SlippageCost != 190 {
		t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 190)
	}
}

func TestGetMidPrice_Depth(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().GetMidPrice()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.GetMidPrice()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mid, err := depth.GetMidPrice()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mid != 1336.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mid, 1336.5)
	}
}

func TestGetMidPriceNoLock_Depth(t *testing.T) {
	t.Parallel()
	depth := NewDepth(id)
	_, err := depth.getMidPriceNoLock()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, nil, 0, time.Time{}, true)
	_, err = depth.getMidPriceNoLock()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mid, err := depth.getMidPriceNoLock()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if mid != 1336.5 {
		t.Fatalf("received: '%v' but expected: '%v'", mid, 1336.5)
	}
}

func TestGetBestBidASk_Depth(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().GetBestBid()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	_, err = getInvalidDepth().GetBestAsk()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)
	_, err = depth.GetBestBid()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	_, err = depth.GetBestAsk()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}
	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
	mid, err := depth.GetBestBid()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}
	if mid != 1336 {
		t.Fatalf("received: '%v' but expected: '%v'", mid, 1336)
	}
	mid, err = depth.GetBestAsk()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}
	if mid != 1337 {
		t.Fatalf("received: '%v' but expected: '%v'", mid, 1337)
	}
}

func TestGetSpreadAmount(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().GetSpreadAmount()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.GetSpreadAmount()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(nil, ask, 0, time.Time{}, true)

	_, err = depth.GetSpreadAmount()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	spread, err := depth.GetSpreadAmount()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if spread != 1 {
		t.Fatalf("received: '%v' but expected: '%v'", spread, 1)
	}
}

func TestGetSpreadPercentage(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().GetSpreadPercentage()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.GetSpreadPercentage()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(nil, ask, 0, time.Time{}, true)

	_, err = depth.GetSpreadPercentage()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	spread, err := depth.GetSpreadPercentage()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if spread != 0.07479431563201197 {
		t.Fatalf("received: '%v' but expected: '%v'", spread, 0.07479431563201197)
	}
}

func TestGetImbalance_Depth(t *testing.T) {
	t.Parallel()
	_, err := getInvalidDepth().GetImbalance()
	if !errors.Is(err, ErrOrderbookInvalid) {
		t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
	}

	depth := NewDepth(id)

	_, err = depth.GetImbalance()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(nil, ask, 0, time.Time{}, true)

	_, err = depth.GetImbalance()
	if !errors.Is(err, errNoLiquidity) {
		t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
	}

	depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)

	imbalance, err := depth.GetImbalance()
	if !errors.Is(err, nil) {
		t.Fatalf("received: '%v' but expected: '%v'", err, nil)
	}

	if imbalance != 0 {
		t.Fatalf("received: '%v' but expected: '%v'", imbalance, 0)
	}
}

func getInvalidDepth() *Depth {
	depth := NewDepth(id)
	_ = depth.Invalidate(errors.New("invalid reasoning"))
	return depth
}
