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

@@ -1297,7 +1297,11 @@ func (s *RPCServer) SimulateOrder(ctx context.Context, r *gctrpc.SimulateOrderRe
buy = false
}
result := o.SimulateOrder(r.Amount, buy)
result, err := o.SimulateOrder(r.Amount, buy)
if err != nil {
return nil, err
}
var resp gctrpc.SimulateOrderResponse
for x := range result.Orders {
resp.Orders = append(resp.Orders, &gctrpc.OrderbookItem{
@@ -1332,12 +1336,17 @@ func (s *RPCServer) WhaleBomb(ctx context.Context, r *gctrpc.WhaleBombRequest) (
return nil, err
}
err = checkParams(r.Exchange, exch, asset.Spot, p)
a, err := asset.New(r.AssetType)
if err != nil {
return nil, err
}
o, err := exch.FetchOrderbook(ctx, p, asset.Spot)
err = checkParams(r.Exchange, exch, a, p)
if err != nil {
return nil, err
}
o, err := exch.FetchOrderbook(ctx, p, a)
if err != nil {
return nil, err
}
@@ -5333,3 +5342,226 @@ func (s *RPCServer) GetMarginRatesHistory(ctx context.Context, r *gctrpc.GetMarg
return resp, nil
}
// GetOrderbookMovement using the requested amount simulates a buy or sell and
// returns the nominal/impact percentages and costings.
func (s *RPCServer) GetOrderbookMovement(ctx context.Context, r *gctrpc.GetOrderbookMovementRequest) (*gctrpc.GetOrderbookMovementResponse, error) {
exch, err := s.GetExchangeByName(r.Exchange)
if err != nil {
return nil, err
}
as, err := asset.New(r.Asset)
if err != nil {
return nil, err
}
pair, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
if pair.IsEmpty() {
return nil, currency.ErrCurrencyPairEmpty
}
err = checkParams(r.Exchange, exch, as, pair)
if err != nil {
return nil, err
}
depth, err := orderbook.GetDepth(exch.GetName(), pair, as)
if err != nil {
return nil, err
}
isRest, err := depth.IsRESTSnapshot()
if err != nil {
return nil, err
}
updateProtocol := "WEBSOCKET"
if isRest {
updateProtocol = "REST"
}
var move *orderbook.Movement
var bought, sold, side string
if r.Sell {
move, err = depth.HitTheBidsFromBest(r.Amount, r.Purchase)
bought = pair.Quote.Upper().String()
sold = pair.Base.Upper().String()
side = order.Bid.String()
} else {
move, err = depth.LiftTheAsksFromBest(r.Amount, r.Purchase)
bought = pair.Base.Upper().String()
sold = pair.Quote.Upper().String()
side = order.Ask.String()
}
if err != nil {
return nil, err
}
return &gctrpc.GetOrderbookMovementResponse{
NominalPercentage: move.NominalPercentage,
ImpactPercentage: move.ImpactPercentage,
SlippageCost: move.SlippageCost,
CurrencyBought: bought,
Bought: move.Purchased,
CurrencySold: sold,
Sold: move.Sold,
SideAffected: side,
UpdateProtocol: updateProtocol,
FullOrderbookSideConsumed: move.FullBookSideConsumed,
NoSlippageOccurred: move.ImpactPercentage == 0,
StartPrice: move.StartPrice,
EndPrice: move.EndPrice,
AverageOrderCost: move.AverageOrderCost,
}, nil
}
// GetOrderbookAmountByNominal using the requested nominal percentage requirement
// returns the amount on orderbook that can fit without exceeding that value.
func (s *RPCServer) GetOrderbookAmountByNominal(ctx context.Context, r *gctrpc.GetOrderbookAmountByNominalRequest) (*gctrpc.GetOrderbookAmountByNominalResponse, error) {
exch, err := s.GetExchangeByName(r.Exchange)
if err != nil {
return nil, err
}
as, err := asset.New(r.Asset)
if err != nil {
return nil, err
}
pair, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
if pair.IsEmpty() {
return nil, currency.ErrCurrencyPairEmpty
}
err = checkParams(r.Exchange, exch, as, pair)
if err != nil {
return nil, err
}
depth, err := orderbook.GetDepth(exch.GetName(), pair, as)
if err != nil {
return nil, err
}
isRest, err := depth.IsRESTSnapshot()
if err != nil {
return nil, err
}
updateProtocol := "WEBSOCKET"
if isRest {
updateProtocol = "REST"
}
var nominal *orderbook.Movement
var selling, buying, side string
if r.Sell {
nominal, err = depth.HitTheBidsByNominalSlippageFromBest(r.NominalPercentage)
selling = pair.Upper().Base.String()
buying = pair.Upper().Quote.String()
side = order.Bid.String()
} else {
nominal, err = depth.LiftTheAsksByNominalSlippageFromBest(r.NominalPercentage)
buying = pair.Upper().Base.String()
selling = pair.Upper().Quote.String()
side = order.Ask.String()
}
if err != nil {
return nil, err
}
return &gctrpc.GetOrderbookAmountByNominalResponse{
AmountRequired: nominal.Sold,
CurrencySelling: selling,
AmountReceived: nominal.Purchased,
CurrencyBuying: buying,
SideAffected: side,
ApproximateNominalSlippagePercentage: nominal.NominalPercentage,
UpdateProtocol: updateProtocol,
FullOrderbookSideConsumed: nominal.FullBookSideConsumed,
StartPrice: nominal.StartPrice,
EndPrice: nominal.EndPrice,
AverageOrderCost: nominal.AverageOrderCost,
}, nil
}
// GetOrderbookAmountByImpact using the requested impact percentage requirement
// determines the amount on orderbook that can fit that will slip the orderbook.
func (s *RPCServer) GetOrderbookAmountByImpact(ctx context.Context, r *gctrpc.GetOrderbookAmountByImpactRequest) (*gctrpc.GetOrderbookAmountByImpactResponse, error) {
exch, err := s.GetExchangeByName(r.Exchange)
if err != nil {
return nil, err
}
as, err := asset.New(r.Asset)
if err != nil {
return nil, err
}
pair, err := currency.NewPairFromStrings(r.Pair.Base, r.Pair.Quote)
if err != nil {
return nil, err
}
if pair.IsEmpty() {
return nil, currency.ErrCurrencyPairEmpty
}
err = checkParams(r.Exchange, exch, as, pair)
if err != nil {
return nil, err
}
depth, err := orderbook.GetDepth(exch.GetName(), pair, as)
if err != nil {
return nil, err
}
isRest, err := depth.IsRESTSnapshot()
if err != nil {
return nil, err
}
updateProtocol := "WEBSOCKET"
if isRest {
updateProtocol = "REST"
}
var impact *orderbook.Movement
var selling, buying, side string
if r.Sell {
impact, err = depth.HitTheBidsByImpactSlippageFromBest(r.ImpactPercentage)
selling = pair.Upper().Base.String()
buying = pair.Upper().Quote.String()
side = order.Bid.String()
} else {
impact, err = depth.LiftTheAsksByImpactSlippageFromBest(r.ImpactPercentage)
buying = pair.Upper().Base.String()
selling = pair.Upper().Quote.String()
side = order.Ask.String()
}
if err != nil {
return nil, err
}
return &gctrpc.GetOrderbookAmountByImpactResponse{
AmountRequired: impact.Sold,
CurrencySelling: selling,
AmountReceived: impact.Purchased,
CurrencyBuying: buying,
SideAffected: side,
ApproximateImpactSlippagePercentage: impact.ImpactPercentage,
UpdateProtocol: updateProtocol,
FullOrderbookSideConsumed: impact.FullBookSideConsumed,
StartPrice: impact.StartPrice,
EndPrice: impact.EndPrice,
AverageOrderCost: impact.AverageOrderCost,
}, nil
}