mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 15:10:12 +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:
@@ -85,7 +85,10 @@ func (e *Exchange) ExecuteOrder(o order.Event, data data.Handler, orderManager *
|
||||
return f, err
|
||||
}
|
||||
// calculate an estimated slippage rate
|
||||
price, amount = slippage.CalculateSlippageByOrderbook(ob, o.GetDirection(), allocatedFunds, f.ExchangeFee)
|
||||
price, amount, err = slippage.CalculateSlippageByOrderbook(ob, o.GetDirection(), allocatedFunds, f.ExchangeFee)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
f.Slippage = price.Sub(f.ClosePrice).Div(f.ClosePrice).Mul(decimal.NewFromInt(100))
|
||||
} else {
|
||||
slippageRate := slippage.EstimateSlippagePercentage(cs.MinimumSlippageRate, cs.MaximumSlippageRate)
|
||||
|
||||
@@ -31,8 +31,12 @@ func EstimateSlippagePercentage(maximumSlippageRate, minimumSlippageRate decimal
|
||||
|
||||
// CalculateSlippageByOrderbook will analyse a provided orderbook and return the result of attempting to
|
||||
// place the order on there
|
||||
func CalculateSlippageByOrderbook(ob *orderbook.Base, side gctorder.Side, allocatedFunds, feeRate decimal.Decimal) (price, amount decimal.Decimal) {
|
||||
result := ob.SimulateOrder(allocatedFunds.InexactFloat64(), side == gctorder.Buy)
|
||||
func CalculateSlippageByOrderbook(ob *orderbook.Base, side gctorder.Side, allocatedFunds, feeRate decimal.Decimal) (price, amount decimal.Decimal, err error) {
|
||||
var result *orderbook.WhaleBombResult
|
||||
result, err = ob.SimulateOrder(allocatedFunds.InexactFloat64(), side == gctorder.Buy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rate := (result.MinimumPrice - result.MaximumPrice) / result.MaximumPrice
|
||||
price = decimal.NewFromFloat(result.MinimumPrice * (rate + 1))
|
||||
amount = decimal.NewFromFloat(result.Amount * (1 - feeRate.InexactFloat64()))
|
||||
|
||||
@@ -30,7 +30,10 @@ func TestCalculateSlippageByOrderbook(t *testing.T) {
|
||||
}
|
||||
amountOfFunds := decimal.NewFromInt(1000)
|
||||
feeRate := decimal.NewFromFloat(0.03)
|
||||
price, amount := CalculateSlippageByOrderbook(ob, gctorder.Buy, amountOfFunds, feeRate)
|
||||
price, amount, err := CalculateSlippageByOrderbook(ob, gctorder.Buy, amountOfFunds, feeRate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if price.Mul(amount).Add(price.Mul(amount).Mul(feeRate)).GreaterThan(amountOfFunds) {
|
||||
t.Error("order size must be less than funds")
|
||||
}
|
||||
|
||||
@@ -585,118 +585,6 @@ func getTickers(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var getOrderbookCommand = &cli.Command{
|
||||
Name: "getorderbook",
|
||||
Usage: "gets the orderbook for a specific currency pair and exchange",
|
||||
ArgsUsage: "<exchange> <pair> <asset>",
|
||||
Action: getOrderbook,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to get the orderbook for",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "the currency pair to get the orderbook for",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type of the currency pair to get the orderbook for",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getOrderbook(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "getorderbook")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
var currencyPair string
|
||||
var assetType string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
currencyPair = c.String("pair")
|
||||
} else {
|
||||
currencyPair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(2)
|
||||
}
|
||||
|
||||
assetType = strings.ToLower(assetType)
|
||||
if !validAsset(assetType) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbook(c.Context,
|
||||
&gctrpc.GetOrderbookRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
AssetType: assetType,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var getOrderbooksCommand = &cli.Command{
|
||||
Name: "getorderbooks",
|
||||
Usage: "gets all orderbooks for all enabled exchanges and currency pairs",
|
||||
Action: getOrderbooks,
|
||||
}
|
||||
|
||||
func getOrderbooks(c *cli.Context) error {
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbooks(c.Context, &gctrpc.GetOrderbooksRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var getAccountInfoCommand = &cli.Command{
|
||||
Name: "getaccountinfo",
|
||||
Usage: "gets the exchange account balance info",
|
||||
@@ -1766,107 +1654,6 @@ func simulateOrder(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var whaleBombCommand = &cli.Command{
|
||||
Name: "whalebomb",
|
||||
Usage: "whale bomb finds the amount required to reach a price target",
|
||||
ArgsUsage: "<exchange> <pair> <side> <price>",
|
||||
Action: whaleBomb,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to whale bomb",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "the currency pair",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "side",
|
||||
Usage: "the order side to use (BUY OR SELL)",
|
||||
},
|
||||
&cli.Float64Flag{
|
||||
Name: "price",
|
||||
Usage: "the price target",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func whaleBomb(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "whalebomb")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
var currencyPair string
|
||||
var orderSide string
|
||||
var price float64
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
currencyPair = c.String("pair")
|
||||
} else {
|
||||
currencyPair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("side") {
|
||||
orderSide = c.String("side")
|
||||
} else {
|
||||
orderSide = c.Args().Get(2)
|
||||
}
|
||||
|
||||
if orderSide == "" {
|
||||
return errors.New("order side must be set")
|
||||
}
|
||||
|
||||
if c.IsSet("price") {
|
||||
price = c.Float64("price")
|
||||
} else if c.Args().Get(3) != "" {
|
||||
var err error
|
||||
price, err = strconv.ParseFloat(c.Args().Get(3), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.WhaleBomb(c.Context, &gctrpc.WhaleBombRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
Side: orderSide,
|
||||
PriceTarget: price,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cancelOrderCommand = &cli.Command{
|
||||
Name: "cancelorder",
|
||||
Usage: "cancel order cancels an exchange order",
|
||||
@@ -3354,212 +3141,6 @@ func setLoggerDetails(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var getOrderbookStreamCommand = &cli.Command{
|
||||
Name: "getorderbookstream",
|
||||
Usage: "gets the orderbook stream for a specific currency pair and exchange",
|
||||
ArgsUsage: "<exchange> <pair> <asset>",
|
||||
Action: getOrderbookStream,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to get the orderbook from",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "currency pair",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type of the currency pair",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getOrderbookStream(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "getorderbookstream")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
var pair string
|
||||
var assetType string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
pair = c.String("pair")
|
||||
} else {
|
||||
pair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(pair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(2)
|
||||
}
|
||||
|
||||
assetType = strings.ToLower(assetType)
|
||||
|
||||
if !validAsset(assetType) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbookStream(c.Context,
|
||||
&gctrpc.GetOrderbookStreamRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
Delimiter: p.Delimiter,
|
||||
},
|
||||
AssetType: assetType,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := result.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = clearScreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Orderbook stream for %s %s:\n\n", exchangeName, resp.Pair)
|
||||
if resp.Error != "" {
|
||||
fmt.Printf("%s\n", resp.Error)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("\t\tBids\t\t\t\tAsks")
|
||||
fmt.Println()
|
||||
|
||||
bidLen := len(resp.Bids) - 1
|
||||
askLen := len(resp.Asks) - 1
|
||||
|
||||
var maxLen int
|
||||
if bidLen >= askLen {
|
||||
maxLen = bidLen
|
||||
} else {
|
||||
maxLen = askLen
|
||||
}
|
||||
|
||||
for i := 0; i < maxLen; i++ {
|
||||
var bidAmount, bidPrice float64
|
||||
if i <= bidLen {
|
||||
bidAmount = resp.Bids[i].Amount
|
||||
bidPrice = resp.Bids[i].Price
|
||||
}
|
||||
|
||||
var askAmount, askPrice float64
|
||||
if i <= askLen {
|
||||
askAmount = resp.Asks[i].Amount
|
||||
askPrice = resp.Asks[i].Price
|
||||
}
|
||||
|
||||
fmt.Printf("%.8f %s @ %.8f %s\t\t%.8f %s @ %.8f %s\n",
|
||||
bidAmount,
|
||||
resp.Pair.Base,
|
||||
bidPrice,
|
||||
resp.Pair.Quote,
|
||||
askAmount,
|
||||
resp.Pair.Base,
|
||||
askPrice,
|
||||
resp.Pair.Quote)
|
||||
|
||||
if i >= 49 {
|
||||
// limits orderbook display output
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var getExchangeOrderbookStreamCommand = &cli.Command{
|
||||
Name: "getexchangeorderbookstream",
|
||||
Usage: "gets a stream for all orderbooks associated with an exchange",
|
||||
ArgsUsage: "<exchange>",
|
||||
Action: getExchangeOrderbookStream,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to get the orderbook from",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getExchangeOrderbookStream(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "getexchangeorderbookstream")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetExchangeOrderbookStream(c.Context,
|
||||
&gctrpc.GetExchangeOrderbookStreamRequest{
|
||||
Exchange: exchangeName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := result.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = clearScreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Orderbook streamed for %s %s", exchangeName, resp.Pair)
|
||||
if resp.Error != "" {
|
||||
fmt.Printf("%s\n", resp.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var getTickerStreamCommand = &cli.Command{
|
||||
Name: "gettickerstream",
|
||||
Usage: "gets the ticker stream for a specific currency pair and exchange",
|
||||
|
||||
@@ -162,8 +162,6 @@ func main() {
|
||||
getExchangeInfoCommand,
|
||||
getTickerCommand,
|
||||
getTickersCommand,
|
||||
getOrderbookCommand,
|
||||
getOrderbooksCommand,
|
||||
getAccountInfoCommand,
|
||||
getAccountInfoStreamCommand,
|
||||
updateAccountInfoCommand,
|
||||
@@ -179,7 +177,6 @@ func main() {
|
||||
getOrderCommand,
|
||||
submitOrderCommand,
|
||||
simulateOrderCommand,
|
||||
whaleBombCommand,
|
||||
cancelOrderCommand,
|
||||
cancelBatchOrdersCommand,
|
||||
cancelAllOrdersCommand,
|
||||
@@ -196,8 +193,6 @@ func main() {
|
||||
getLoggerDetailsCommand,
|
||||
setLoggerDetailsCommand,
|
||||
exchangePairManagerCommand,
|
||||
getOrderbookStreamCommand,
|
||||
getExchangeOrderbookStreamCommand,
|
||||
getTickerStreamCommand,
|
||||
getExchangeTickerStreamCommand,
|
||||
getAuditEventCommand,
|
||||
@@ -213,6 +208,7 @@ func main() {
|
||||
shutdownCommand,
|
||||
technicalAnalysisCommand,
|
||||
getMarginRatesHistoryCommand,
|
||||
orderbookCommand,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
768
cmd/gctcli/orderbook.go
Normal file
768
cmd/gctcli/orderbook.go
Normal file
@@ -0,0 +1,768 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/gctrpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var orderbookCommonFlags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to get the orderbook for",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "the currency pair to get the orderbook for",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type of the currency pair to get the orderbook for",
|
||||
},
|
||||
}
|
||||
|
||||
var orderbookCommand = &cli.Command{
|
||||
Name: "orderbook",
|
||||
Usage: "orderbook system simulations and analytics command",
|
||||
ArgsUsage: "<command> <args>",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "sell",
|
||||
Usage: "simulates sell to derive orderbook liquidity impact information",
|
||||
ArgsUsage: "<command> <args>",
|
||||
Subcommands: []*cli.Command{nominal, impact, base, quoteRequired},
|
||||
Flags: []cli.Flag{&cli.BoolFlag{Name: "sell", Hidden: true, Value: true}},
|
||||
},
|
||||
{
|
||||
Name: "buy",
|
||||
Usage: "simulates buy to derive orderbook liquidity impact information",
|
||||
ArgsUsage: "<command> <args>",
|
||||
Subcommands: []*cli.Command{nominal, impact, quote, baseRequired},
|
||||
},
|
||||
getOrderbookCommand,
|
||||
getOrderbooksCommand,
|
||||
getOrderbookStreamCommand,
|
||||
getExchangeOrderbookStreamCommand,
|
||||
whaleBombCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var nominal = &cli.Command{
|
||||
Name: "nominal",
|
||||
Usage: "simulates a buy or sell based off the percentage between the reference price and the average order cost",
|
||||
ArgsUsage: "<exchange> <pair> <asset> <percent>",
|
||||
Action: getNominal,
|
||||
Flags: append(orderbookCommonFlags, &cli.Float64Flag{
|
||||
Name: "percent",
|
||||
Usage: "the max percentage slip you wish to occur e.g. 1 = 1% and 100 = 100%. Note: If selling base/hitting the bids you can only have a max value of 100%",
|
||||
}),
|
||||
}
|
||||
|
||||
func getNominal(c *cli.Context) error {
|
||||
isSelling := c.Bool("sell")
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "nominal")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
var currencyPair string
|
||||
if c.IsSet("pair") {
|
||||
currencyPair = c.String("pair")
|
||||
} else {
|
||||
currencyPair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
var assetType string
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(2)
|
||||
}
|
||||
|
||||
assetType = strings.ToLower(assetType)
|
||||
if !validAsset(assetType) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var percentage float64
|
||||
if c.IsSet("asset") {
|
||||
percentage = c.Float64("percent")
|
||||
} else {
|
||||
percentage, _ = strconv.ParseFloat(c.Args().Get(3), 64)
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbookAmountByNominal(c.Context,
|
||||
&gctrpc.GetOrderbookAmountByNominalRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
Asset: assetType,
|
||||
Sell: isSelling,
|
||||
NominalPercentage: percentage,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var impact = &cli.Command{
|
||||
Name: "impact",
|
||||
Usage: "simulates a buy or sell based off the reference price and the orderbook impact slippage",
|
||||
ArgsUsage: "<exchange> <pair> <asset> <percent>",
|
||||
Action: getImpact,
|
||||
Flags: append(orderbookCommonFlags, &cli.Float64Flag{
|
||||
Name: "percent",
|
||||
Usage: "the max percentage slip you wish to occur e.g. 1 = 1% and 100 = 100%. Note: If selling base/hitting the bids you can only have a max value of 100%",
|
||||
}),
|
||||
}
|
||||
|
||||
func getImpact(c *cli.Context) error {
|
||||
isSelling := c.Bool("sell")
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "impact")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
var currencyPair string
|
||||
if c.IsSet("pair") {
|
||||
currencyPair = c.String("pair")
|
||||
} else {
|
||||
currencyPair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
var assetType string
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(2)
|
||||
}
|
||||
|
||||
assetType = strings.ToLower(assetType)
|
||||
if !validAsset(assetType) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var percentage float64
|
||||
if c.IsSet("asset") {
|
||||
percentage = c.Float64("percent")
|
||||
} else {
|
||||
percentage, _ = strconv.ParseFloat(c.Args().Get(3), 64)
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbookAmountByImpact(c.Context,
|
||||
&gctrpc.GetOrderbookAmountByImpactRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
Asset: assetType,
|
||||
Sell: isSelling,
|
||||
ImpactPercentage: percentage,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var purchase = &cli.BoolFlag{
|
||||
Name: "purchase",
|
||||
Hidden: true,
|
||||
Value: true,
|
||||
}
|
||||
|
||||
var quote = &cli.Command{
|
||||
Name: "quote",
|
||||
Usage: "simulates a buy using quotation amount",
|
||||
ArgsUsage: "<exchange> <pair> <asset> <amount>",
|
||||
Action: getMovement,
|
||||
Flags: append(orderbookCommonFlags, &cli.Float64Flag{
|
||||
Name: "amount",
|
||||
Usage: "the amount of quotation currency lifting the asks",
|
||||
}),
|
||||
}
|
||||
|
||||
var baseRequired = &cli.Command{
|
||||
Name: "baserequired",
|
||||
Usage: "simulates a buy with a required base amount to be purchased",
|
||||
ArgsUsage: "<exchange> <pair> <asset> <amount>",
|
||||
Action: getMovement,
|
||||
Flags: append(orderbookCommonFlags, &cli.Float64Flag{
|
||||
Name: "amount",
|
||||
Usage: "the amount of base currency required to be purchased when lifting the asks",
|
||||
}, purchase),
|
||||
}
|
||||
|
||||
var base = &cli.Command{
|
||||
Name: "base",
|
||||
Usage: "simulates a sell using base amount",
|
||||
ArgsUsage: "<exchange> <pair> <asset> <amount>",
|
||||
Action: getMovement,
|
||||
Flags: append(orderbookCommonFlags, &cli.Float64Flag{
|
||||
Name: "amount",
|
||||
Usage: "the amount of base currency hitting the bids",
|
||||
}),
|
||||
}
|
||||
|
||||
var quoteRequired = &cli.Command{
|
||||
Name: "quoterequired",
|
||||
Usage: "simulates a sell with a required quote amount to be purchased",
|
||||
ArgsUsage: "<exchange> <pair> <asset> <amount>",
|
||||
Action: getMovement,
|
||||
Flags: append(orderbookCommonFlags, &cli.Float64Flag{
|
||||
Name: "amount",
|
||||
Usage: "the amount of quotation currency required to be purchased when hitting the bids",
|
||||
}, purchase),
|
||||
}
|
||||
|
||||
func getMovement(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, c.Command.Name)
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
var currencyPair string
|
||||
if c.IsSet("pair") {
|
||||
currencyPair = c.String("pair")
|
||||
} else {
|
||||
currencyPair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
var assetType string
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(2)
|
||||
}
|
||||
|
||||
assetType = strings.ToLower(assetType)
|
||||
if !validAsset(assetType) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var amount float64
|
||||
if c.IsSet("amount") {
|
||||
amount = c.Float64("amount")
|
||||
} else {
|
||||
amount, _ = strconv.ParseFloat(c.Args().Get(3), 64)
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbookMovement(c.Context, &gctrpc.GetOrderbookMovementRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
Asset: assetType,
|
||||
Sell: c.Bool("sell"),
|
||||
Amount: amount,
|
||||
Purchase: c.Bool("purchase"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var getOrderbookCommand = &cli.Command{
|
||||
Name: "getorderbook",
|
||||
Usage: "gets the orderbook for a specific currency pair and exchange",
|
||||
ArgsUsage: "<exchange> <pair> <asset>",
|
||||
Action: getOrderbook,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to get the orderbook for",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "the currency pair to get the orderbook for",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type of the currency pair to get the orderbook for",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getOrderbook(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "getorderbook")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
var currencyPair string
|
||||
var assetType string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
currencyPair = c.String("pair")
|
||||
} else {
|
||||
currencyPair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(2)
|
||||
}
|
||||
|
||||
assetType = strings.ToLower(assetType)
|
||||
if !validAsset(assetType) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbook(c.Context,
|
||||
&gctrpc.GetOrderbookRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
AssetType: assetType,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var getOrderbooksCommand = &cli.Command{
|
||||
Name: "getorderbooks",
|
||||
Usage: "gets all orderbooks for all enabled exchanges and currency pairs",
|
||||
Action: getOrderbooks,
|
||||
}
|
||||
|
||||
func getOrderbooks(c *cli.Context) error {
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbooks(c.Context, &gctrpc.GetOrderbooksRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
var getOrderbookStreamCommand = &cli.Command{
|
||||
Name: "getorderbookstream",
|
||||
Usage: "gets the orderbook stream for a specific currency pair and exchange",
|
||||
ArgsUsage: "<exchange> <pair> <asset>",
|
||||
Action: getOrderbookStream,
|
||||
Flags: orderbookCommonFlags,
|
||||
}
|
||||
|
||||
func getOrderbookStream(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "getorderbookstream")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
var pair string
|
||||
var assetType string
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
pair = c.String("pair")
|
||||
} else {
|
||||
pair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(pair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(2)
|
||||
}
|
||||
|
||||
assetType = strings.ToLower(assetType)
|
||||
|
||||
if !validAsset(assetType) {
|
||||
return errInvalidAsset
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetOrderbookStream(c.Context,
|
||||
&gctrpc.GetOrderbookStreamRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
Delimiter: p.Delimiter,
|
||||
},
|
||||
AssetType: assetType,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := result.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = clearScreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Orderbook stream for %s %s:\n\n", exchangeName, resp.Pair)
|
||||
if resp.Error != "" {
|
||||
fmt.Printf("%s\n", resp.Error)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println("\t\tBids\t\t\t\tAsks")
|
||||
fmt.Println()
|
||||
|
||||
bidLen := len(resp.Bids) - 1
|
||||
askLen := len(resp.Asks) - 1
|
||||
|
||||
var maxLen int
|
||||
if bidLen >= askLen {
|
||||
maxLen = bidLen
|
||||
} else {
|
||||
maxLen = askLen
|
||||
}
|
||||
|
||||
for i := 0; i < maxLen; i++ {
|
||||
var bidAmount, bidPrice float64
|
||||
if i <= bidLen {
|
||||
bidAmount = resp.Bids[i].Amount
|
||||
bidPrice = resp.Bids[i].Price
|
||||
}
|
||||
|
||||
var askAmount, askPrice float64
|
||||
if i <= askLen {
|
||||
askAmount = resp.Asks[i].Amount
|
||||
askPrice = resp.Asks[i].Price
|
||||
}
|
||||
|
||||
fmt.Printf("%.8f %s @ %.8f %s\t\t%.8f %s @ %.8f %s\n",
|
||||
bidAmount,
|
||||
resp.Pair.Base,
|
||||
bidPrice,
|
||||
resp.Pair.Quote,
|
||||
askAmount,
|
||||
resp.Pair.Base,
|
||||
askPrice,
|
||||
resp.Pair.Quote)
|
||||
|
||||
if i >= 49 {
|
||||
// limits orderbook display output
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var getExchangeOrderbookStreamCommand = &cli.Command{
|
||||
Name: "getexchangeorderbookstream",
|
||||
Usage: "gets a stream for all orderbooks associated with an exchange",
|
||||
ArgsUsage: "<exchange>",
|
||||
Action: getExchangeOrderbookStream,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to get the orderbook from",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func getExchangeOrderbookStream(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "getexchangeorderbookstream")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.GetExchangeOrderbookStream(c.Context,
|
||||
&gctrpc.GetExchangeOrderbookStreamRequest{
|
||||
Exchange: exchangeName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := result.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = clearScreen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Orderbook streamed for %s %s", exchangeName, resp.Pair)
|
||||
if resp.Error != "" {
|
||||
fmt.Printf("%s\n", resp.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var whaleBombCommand = &cli.Command{
|
||||
Name: "whalebomb",
|
||||
Usage: "whale bomb finds the amount required to reach a price target",
|
||||
ArgsUsage: "<exchange> <pair> <side> <asset> <price>",
|
||||
Action: whaleBomb,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "exchange",
|
||||
Usage: "the exchange to whale bomb",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pair",
|
||||
Usage: "the currency pair",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "side",
|
||||
Usage: "the order side to use (BUY OR SELL)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "asset",
|
||||
Usage: "the asset type of the currency pair to get the orderbook for",
|
||||
},
|
||||
&cli.Float64Flag{
|
||||
Name: "price",
|
||||
Usage: "the price target",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func whaleBomb(c *cli.Context) error {
|
||||
if c.NArg() == 0 && c.NumFlags() == 0 {
|
||||
return cli.ShowCommandHelp(c, "whalebomb")
|
||||
}
|
||||
|
||||
var exchangeName string
|
||||
var currencyPair string
|
||||
var orderSide string
|
||||
var price float64
|
||||
|
||||
if c.IsSet("exchange") {
|
||||
exchangeName = c.String("exchange")
|
||||
} else {
|
||||
exchangeName = c.Args().First()
|
||||
}
|
||||
|
||||
if c.IsSet("pair") {
|
||||
currencyPair = c.String("pair")
|
||||
} else {
|
||||
currencyPair = c.Args().Get(1)
|
||||
}
|
||||
|
||||
if !validPair(currencyPair) {
|
||||
return errInvalidPair
|
||||
}
|
||||
|
||||
if c.IsSet("side") {
|
||||
orderSide = c.String("side")
|
||||
} else {
|
||||
orderSide = c.Args().Get(2)
|
||||
}
|
||||
|
||||
if orderSide == "" {
|
||||
return errors.New("order side must be set")
|
||||
}
|
||||
|
||||
var assetType string
|
||||
if c.IsSet("asset") {
|
||||
assetType = c.String("asset")
|
||||
} else {
|
||||
assetType = c.Args().Get(3)
|
||||
}
|
||||
|
||||
if c.IsSet("price") {
|
||||
price = c.Float64("price")
|
||||
} else if c.Args().Get(4) != "" {
|
||||
var err error
|
||||
price, err = strconv.ParseFloat(c.Args().Get(4), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, cancel, err := setupClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeConn(conn, cancel)
|
||||
|
||||
client := gctrpc.NewGoCryptoTraderServiceClient(conn)
|
||||
result, err := client.WhaleBomb(c.Context, &gctrpc.WhaleBombRequest{
|
||||
Exchange: exchangeName,
|
||||
Pair: &gctrpc.CurrencyPair{
|
||||
Delimiter: p.Delimiter,
|
||||
Base: p.Base.String(),
|
||||
Quote: p.Quote.String(),
|
||||
},
|
||||
Side: orderSide,
|
||||
PriceTarget: price,
|
||||
AssetType: assetType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonOutput(result)
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,16 @@ package orderbook
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
math "github.com/thrasher-corp/gocryptotrader/common/math"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
const fullLiquidityUsageWarning = "[WARNING]: Full liquidity exhausted."
|
||||
|
||||
var (
|
||||
errPriceTargetInvalid = errors.New("price target is invalid")
|
||||
errCannotShiftPrice = errors.New("cannot shift price")
|
||||
)
|
||||
|
||||
// WhaleBombResult returns the whale bomb result
|
||||
@@ -15,214 +21,240 @@ type WhaleBombResult struct {
|
||||
MinimumPrice float64
|
||||
MaximumPrice float64
|
||||
PercentageGainOrLoss float64
|
||||
Orders orderSummary
|
||||
Orders Items
|
||||
Status string
|
||||
}
|
||||
|
||||
// WhaleBomb finds the amount required to target a price
|
||||
func (b *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error) {
|
||||
if priceTarget < 0 {
|
||||
return nil, errors.New("price target is invalid")
|
||||
return nil, errPriceTargetInvalid
|
||||
}
|
||||
if buy {
|
||||
a, orders := b.findAmount(priceTarget, true)
|
||||
min, max := orders.MinimumPrice(false), orders.MaximumPrice(true)
|
||||
var err error
|
||||
if max < priceTarget {
|
||||
err = errors.New("unable to hit price target due to insufficient orderbook items")
|
||||
}
|
||||
status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.",
|
||||
a, b.Pair.Quote.String(), b.Pair.Base.String(), min, max,
|
||||
math.CalculatePercentageGainOrLoss(max, min), len(orders))
|
||||
return &WhaleBombResult{
|
||||
Amount: a,
|
||||
Orders: orders,
|
||||
MinimumPrice: min,
|
||||
MaximumPrice: max,
|
||||
Status: status,
|
||||
}, err
|
||||
action, err := b.findAmount(priceTarget, buy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a, orders := b.findAmount(priceTarget, false)
|
||||
min, max := orders.MinimumPrice(false), orders.MaximumPrice(true)
|
||||
var err error
|
||||
if min > priceTarget {
|
||||
err = errors.New("unable to hit price target due to insufficient orderbook items")
|
||||
var warning string
|
||||
if action.FullLiquidityUsed {
|
||||
warning = fullLiquidityUsageWarning
|
||||
}
|
||||
status := fmt.Sprintf("Selling %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.",
|
||||
a, b.Pair.Base.String(), b.Pair.Quote.String(), max, min,
|
||||
math.CalculatePercentageGainOrLoss(min, max), len(orders))
|
||||
|
||||
var status string
|
||||
var percent, min, max, amount float64
|
||||
if buy {
|
||||
min = action.ReferencePrice
|
||||
max = action.TranchePositionPrice
|
||||
amount = action.QuoteAmount
|
||||
percent = math.CalculatePercentageGainOrLoss(action.TranchePositionPrice, action.ReferencePrice)
|
||||
status = fmt.Sprintf("Buying using %.2f %s worth of %s will send the price from %v to %v [%.2f%%] and impact %d price tranche(s). %s",
|
||||
amount, b.Pair.Quote, b.Pair.Base, min, max,
|
||||
percent, len(action.Tranches), warning)
|
||||
} else {
|
||||
min = action.TranchePositionPrice
|
||||
max = action.ReferencePrice
|
||||
amount = action.BaseAmount
|
||||
percent = math.CalculatePercentageGainOrLoss(action.TranchePositionPrice, action.ReferencePrice)
|
||||
status = fmt.Sprintf("Selling using %.2f %s worth of %s will send the price from %v to %v [%.2f%%] and impact %d price tranche(s). %s",
|
||||
amount, b.Pair.Base, b.Pair.Quote, max, min,
|
||||
percent, len(action.Tranches), warning)
|
||||
}
|
||||
|
||||
return &WhaleBombResult{
|
||||
Amount: a,
|
||||
Orders: orders,
|
||||
MinimumPrice: min,
|
||||
MaximumPrice: max,
|
||||
Status: status,
|
||||
Amount: amount,
|
||||
Orders: action.Tranches,
|
||||
MinimumPrice: min,
|
||||
MaximumPrice: max,
|
||||
Status: status,
|
||||
PercentageGainOrLoss: percent,
|
||||
}, err
|
||||
}
|
||||
|
||||
// OrderSimulationResult returns the order simulation result
|
||||
type OrderSimulationResult WhaleBombResult
|
||||
|
||||
// SimulateOrder simulates an order
|
||||
func (b *Base) SimulateOrder(amount float64, buy bool) *OrderSimulationResult {
|
||||
func (b *Base) SimulateOrder(amount float64, buy bool) (*WhaleBombResult, error) {
|
||||
var direction string
|
||||
var action *DeploymentAction
|
||||
var soldAmount, boughtAmount, minimumPrice, maximumPrice float64
|
||||
var sold, bought currency.Code
|
||||
var err error
|
||||
if buy {
|
||||
orders, amt := b.buy(amount)
|
||||
min, max := orders.MinimumPrice(false), orders.MaximumPrice(true)
|
||||
pct := math.CalculatePercentageGainOrLoss(max, min)
|
||||
status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.",
|
||||
amount, b.Pair.Quote.String(), b.Pair.Base.String(), min, max,
|
||||
pct, len(orders))
|
||||
return &OrderSimulationResult{
|
||||
Orders: orders,
|
||||
Amount: amt,
|
||||
MinimumPrice: min,
|
||||
MaximumPrice: max,
|
||||
PercentageGainOrLoss: pct,
|
||||
Status: status,
|
||||
direction = "Buying"
|
||||
action, err = b.buy(amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
soldAmount = action.QuoteAmount
|
||||
boughtAmount = action.BaseAmount
|
||||
maximumPrice = action.TranchePositionPrice
|
||||
minimumPrice = action.ReferencePrice
|
||||
sold = b.Pair.Quote
|
||||
bought = b.Pair.Base
|
||||
} else {
|
||||
direction = "Selling"
|
||||
action, err = b.sell(amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
soldAmount = action.BaseAmount
|
||||
boughtAmount = action.QuoteAmount
|
||||
minimumPrice = action.TranchePositionPrice
|
||||
maximumPrice = action.ReferencePrice
|
||||
sold = b.Pair.Base
|
||||
bought = b.Pair.Quote
|
||||
}
|
||||
orders, amt := b.sell(amount)
|
||||
min, max := orders.MinimumPrice(false), orders.MaximumPrice(true)
|
||||
pct := math.CalculatePercentageGainOrLoss(min, max)
|
||||
status := fmt.Sprintf("Selling %f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.",
|
||||
amount, b.Pair.Base.String(), b.Pair.Quote.String(), max, min,
|
||||
pct, len(orders))
|
||||
return &OrderSimulationResult{
|
||||
Orders: orders,
|
||||
Amount: amt,
|
||||
MinimumPrice: min,
|
||||
MaximumPrice: max,
|
||||
|
||||
var warning string
|
||||
if action.FullLiquidityUsed {
|
||||
warning = fullLiquidityUsageWarning
|
||||
}
|
||||
|
||||
pct := math.CalculatePercentageGainOrLoss(action.TranchePositionPrice, action.ReferencePrice)
|
||||
status := fmt.Sprintf("%s using %f %v worth of %v will send the price from %v to %v [%.2f%%] and impact %v price tranche(s). %s",
|
||||
direction, soldAmount, sold, bought, action.ReferencePrice,
|
||||
action.TranchePositionPrice, pct, len(action.Tranches), warning)
|
||||
return &WhaleBombResult{
|
||||
Orders: action.Tranches,
|
||||
Amount: boughtAmount,
|
||||
MinimumPrice: minimumPrice,
|
||||
MaximumPrice: maximumPrice,
|
||||
PercentageGainOrLoss: pct,
|
||||
Status: status,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
type orderSummary []Item
|
||||
|
||||
func (o orderSummary) Print() {
|
||||
for x := range o {
|
||||
log.Debugf(log.OrderBook, "Order: Price: %f Amount: %f", o[x].Price, o[x].Amount)
|
||||
}
|
||||
}
|
||||
|
||||
func (o orderSummary) MinimumPrice(reverse bool) float64 {
|
||||
if len(o) != 0 {
|
||||
sortOrdersByPrice(&o, reverse)
|
||||
return o[0].Price
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (o orderSummary) MaximumPrice(reverse bool) float64 {
|
||||
if len(o) != 0 {
|
||||
sortOrdersByPrice(&o, reverse)
|
||||
return o[0].Price
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ByPrice used for sorting orders by order date
|
||||
type ByPrice orderSummary
|
||||
|
||||
func (b ByPrice) Len() int { return len(b) }
|
||||
func (b ByPrice) Less(i, j int) bool { return b[i].Price < b[j].Price }
|
||||
func (b ByPrice) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
|
||||
// sortOrdersByPrice the caller function to sort orders
|
||||
func sortOrdersByPrice(o *orderSummary, reverse bool) {
|
||||
if reverse {
|
||||
sort.Sort(sort.Reverse(ByPrice(*o)))
|
||||
} else {
|
||||
sort.Sort(ByPrice(*o))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Base) findAmount(price float64, buy bool) (float64, orderSummary) {
|
||||
orders := make(orderSummary, 0)
|
||||
var amt float64
|
||||
|
||||
func (b *Base) findAmount(priceTarget float64, buy bool) (*DeploymentAction, error) {
|
||||
action := DeploymentAction{}
|
||||
if buy {
|
||||
if len(b.Asks) == 0 {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
action.ReferencePrice = b.Asks[0].Price
|
||||
if action.ReferencePrice > priceTarget {
|
||||
return nil, fmt.Errorf("%w to %f as it's below ascending ask prices starting at %f",
|
||||
errCannotShiftPrice, priceTarget, action.ReferencePrice)
|
||||
}
|
||||
for x := range b.Asks {
|
||||
if b.Asks[x].Price >= price {
|
||||
amt += b.Asks[x].Price * b.Asks[x].Amount
|
||||
orders = append(orders, Item{
|
||||
Price: b.Asks[x].Price,
|
||||
Amount: b.Asks[x].Amount,
|
||||
})
|
||||
return amt, orders
|
||||
if b.Asks[x].Price >= priceTarget {
|
||||
action.TranchePositionPrice = b.Asks[x].Price
|
||||
return &action, nil
|
||||
}
|
||||
orders = append(orders, Item{
|
||||
Price: b.Asks[x].Price,
|
||||
Amount: b.Asks[x].Amount,
|
||||
})
|
||||
amt += b.Asks[x].Price * b.Asks[x].Amount
|
||||
action.Tranches = append(action.Tranches, b.Asks[x])
|
||||
action.QuoteAmount += b.Asks[x].Price * b.Asks[x].Amount
|
||||
action.BaseAmount += b.Asks[x].Amount
|
||||
}
|
||||
return amt, orders
|
||||
action.TranchePositionPrice = b.Asks[len(b.Asks)-1].Price
|
||||
action.FullLiquidityUsed = true
|
||||
return &action, nil
|
||||
}
|
||||
|
||||
for x := range b.Bids {
|
||||
if b.Bids[x].Price <= price {
|
||||
amt += b.Bids[x].Amount
|
||||
orders = append(orders, Item{
|
||||
Price: b.Bids[x].Price,
|
||||
Amount: b.Bids[x].Amount,
|
||||
})
|
||||
break
|
||||
}
|
||||
orders = append(orders, Item{
|
||||
Price: b.Bids[x].Price,
|
||||
Amount: b.Bids[x].Amount,
|
||||
})
|
||||
amt += b.Bids[x].Amount
|
||||
if len(b.Bids) == 0 {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
return amt, orders
|
||||
action.ReferencePrice = b.Bids[0].Price
|
||||
if action.ReferencePrice < priceTarget {
|
||||
return nil, fmt.Errorf("%w to %f as it's above descending bid prices starting at %f",
|
||||
errCannotShiftPrice, priceTarget, action.ReferencePrice)
|
||||
}
|
||||
for x := range b.Bids {
|
||||
if b.Bids[x].Price <= priceTarget {
|
||||
action.TranchePositionPrice = b.Bids[x].Price
|
||||
return &action, nil
|
||||
}
|
||||
action.Tranches = append(action.Tranches, b.Bids[x])
|
||||
action.QuoteAmount += b.Bids[x].Price * b.Bids[x].Amount
|
||||
action.BaseAmount += b.Bids[x].Amount
|
||||
}
|
||||
action.TranchePositionPrice = b.Bids[len(b.Bids)-1].Price
|
||||
action.FullLiquidityUsed = true
|
||||
return &action, nil
|
||||
}
|
||||
|
||||
func (b *Base) buy(amount float64) (orders orderSummary, baseAmount float64) {
|
||||
var processedAmt float64
|
||||
// DeploymentAction defines deployment information on a liquidity side.
|
||||
type DeploymentAction struct {
|
||||
ReferencePrice float64
|
||||
TranchePositionPrice float64
|
||||
BaseAmount float64
|
||||
QuoteAmount float64
|
||||
Tranches Items
|
||||
FullLiquidityUsed bool
|
||||
}
|
||||
|
||||
func (b *Base) buy(quote float64) (*DeploymentAction, error) {
|
||||
if quote <= 0 {
|
||||
return nil, errQuoteAmountInvalid
|
||||
}
|
||||
if len(b.Asks) == 0 {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
action := &DeploymentAction{ReferencePrice: b.Asks[0].Price}
|
||||
for x := range b.Asks {
|
||||
subtotal := b.Asks[x].Price * b.Asks[x].Amount
|
||||
if processedAmt+subtotal >= amount {
|
||||
diff := amount - processedAmt
|
||||
subAmt := diff / b.Asks[x].Price
|
||||
orders = append(orders, Item{
|
||||
action.TranchePositionPrice = b.Asks[x].Price
|
||||
trancheValue := b.Asks[x].Price * b.Asks[x].Amount
|
||||
action.QuoteAmount += trancheValue
|
||||
remaining := quote - trancheValue
|
||||
if remaining <= 0 {
|
||||
if remaining == 0 {
|
||||
if len(b.Asks)-1 > x {
|
||||
action.TranchePositionPrice = b.Asks[x+1].Price
|
||||
} else {
|
||||
action.FullLiquidityUsed = true
|
||||
}
|
||||
}
|
||||
subAmount := quote / b.Asks[x].Price
|
||||
action.Tranches = append(action.Tranches, Item{
|
||||
Price: b.Asks[x].Price,
|
||||
Amount: subAmt,
|
||||
Amount: subAmount,
|
||||
})
|
||||
baseAmount += subAmt
|
||||
break
|
||||
action.BaseAmount += subAmount
|
||||
return action, nil
|
||||
}
|
||||
processedAmt += subtotal
|
||||
baseAmount += b.Asks[x].Amount
|
||||
orders = append(orders, Item{
|
||||
Price: b.Asks[x].Price,
|
||||
Amount: b.Asks[x].Amount,
|
||||
})
|
||||
if len(b.Asks)-1 <= x {
|
||||
action.FullLiquidityUsed = true
|
||||
}
|
||||
quote = remaining
|
||||
action.BaseAmount += b.Asks[x].Amount
|
||||
action.Tranches = append(action.Tranches, b.Asks[x])
|
||||
}
|
||||
return
|
||||
|
||||
return action, nil
|
||||
}
|
||||
|
||||
func (b *Base) sell(amount float64) (orders orderSummary, quoteAmount float64) {
|
||||
var processedAmt float64
|
||||
for x := range b.Bids {
|
||||
if processedAmt+b.Bids[x].Amount >= amount {
|
||||
diff := amount - processedAmt
|
||||
orders = append(orders, Item{
|
||||
Price: b.Bids[x].Price,
|
||||
Amount: diff,
|
||||
})
|
||||
quoteAmount += diff * b.Bids[x].Price
|
||||
break
|
||||
}
|
||||
processedAmt += b.Bids[x].Amount
|
||||
quoteAmount += b.Bids[x].Amount * b.Bids[x].Price
|
||||
orders = append(orders, Item{
|
||||
Price: b.Bids[x].Price,
|
||||
Amount: b.Bids[x].Amount,
|
||||
})
|
||||
func (b *Base) sell(base float64) (*DeploymentAction, error) {
|
||||
if base <= 0 {
|
||||
return nil, errBaseAmountInvalid
|
||||
}
|
||||
return
|
||||
if len(b.Bids) == 0 {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
action := &DeploymentAction{ReferencePrice: b.Bids[0].Price}
|
||||
for x := range b.Bids {
|
||||
action.TranchePositionPrice = b.Bids[x].Price
|
||||
remaining := base - b.Bids[x].Amount
|
||||
if remaining <= 0 {
|
||||
if remaining == 0 {
|
||||
if len(b.Bids)-1 > x {
|
||||
action.TranchePositionPrice = b.Bids[x+1].Price
|
||||
} else {
|
||||
action.FullLiquidityUsed = true
|
||||
}
|
||||
}
|
||||
action.Tranches = append(action.Tranches, Item{
|
||||
Price: b.Bids[x].Price,
|
||||
Amount: base,
|
||||
})
|
||||
action.BaseAmount += base
|
||||
action.QuoteAmount += base * b.Bids[x].Price
|
||||
return action, nil
|
||||
}
|
||||
if len(b.Bids)-1 <= x {
|
||||
action.FullLiquidityUsed = true
|
||||
}
|
||||
base = remaining
|
||||
action.BaseAmount += b.Bids[x].Amount
|
||||
action.QuoteAmount += b.Bids[x].Amount * b.Bids[x].Price
|
||||
action.Tranches = append(action.Tranches, b.Bids[x])
|
||||
}
|
||||
return action, nil
|
||||
}
|
||||
|
||||
// GetAveragePrice finds the average buy or sell price of a specified amount.
|
||||
@@ -254,10 +286,9 @@ func (elem Items) FindNominalAmount(amount float64) (aggNominalAmount, remaining
|
||||
aggNominalAmount += elem[x].Price * remainingAmount
|
||||
remainingAmount = 0
|
||||
break
|
||||
} else {
|
||||
aggNominalAmount += elem[x].Price * elem[x].Amount
|
||||
remainingAmount -= elem[x].Amount
|
||||
}
|
||||
aggNominalAmount += elem[x].Price * elem[x].Amount
|
||||
remainingAmount -= elem[x].Amount
|
||||
}
|
||||
return aggNominalAmount, remainingAmount
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package orderbook
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
@@ -27,69 +28,475 @@ func TestWhaleBomb(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := testSetup()
|
||||
|
||||
// invalid price amount
|
||||
_, err := b.WhaleBomb(-1, true)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
if !errors.Is(err, errPriceTargetInvalid) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errPriceTargetInvalid)
|
||||
}
|
||||
|
||||
// valid
|
||||
_, err = b.WhaleBomb(7001, true)
|
||||
result, err := b.WhaleBomb(7001, true) // <- This price should not be wiped out on the book.
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 7000)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 7001 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7001)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.PercentageGainOrLoss != 0.014285714285714287 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.PercentageGainOrLoss, 0.014285714285714287)
|
||||
}
|
||||
|
||||
result, err = b.WhaleBomb(7000.5, true) // <- Slot between prices will lift to next ask tranche
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
// invalid
|
||||
_, err = b.WhaleBomb(7002, true)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
|
||||
if result.Amount != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 7000)
|
||||
}
|
||||
|
||||
// valid
|
||||
_, err = b.WhaleBomb(6998, false)
|
||||
if result.MaximumPrice != 7001 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7001)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.PercentageGainOrLoss != 0.014285714285714287 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.PercentageGainOrLoss, 0.014285714285714287)
|
||||
}
|
||||
|
||||
result, err = b.WhaleBomb(7002, true) // <- exceed available quotations
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if !strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatal("expected status to contain liquidity warning")
|
||||
}
|
||||
|
||||
result, err = b.WhaleBomb(7000, true) // <- Book should not move
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 0 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 0)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.PercentageGainOrLoss != 0 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.PercentageGainOrLoss, 0)
|
||||
}
|
||||
|
||||
_, err = b.WhaleBomb(6000, true)
|
||||
if !errors.Is(err, errCannotShiftPrice) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errCannotShiftPrice)
|
||||
}
|
||||
|
||||
_, err = b.WhaleBomb(-1, false)
|
||||
if !errors.Is(err, errPriceTargetInvalid) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errPriceTargetInvalid)
|
||||
}
|
||||
|
||||
result, err = b.WhaleBomb(6998, false) // <- This price should not be wiped out on the book.
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 1)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6998 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6998)
|
||||
}
|
||||
|
||||
if result.PercentageGainOrLoss != -0.014287755393627661 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.PercentageGainOrLoss, -0.014287755393627661)
|
||||
}
|
||||
|
||||
result, err = b.WhaleBomb(6998.5, false) // <- Slot between prices will drop to next bid tranche
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
// invalid
|
||||
_, err = b.WhaleBomb(6997, false)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
|
||||
if result.Amount != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 1)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6998 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6998)
|
||||
}
|
||||
|
||||
if result.PercentageGainOrLoss != -0.014287755393627661 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.PercentageGainOrLoss, -0.014287755393627661)
|
||||
}
|
||||
|
||||
result, err = b.WhaleBomb(6997, false) // <- exceed available quotations
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if !strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatal("expected status to contain liquidity warning")
|
||||
}
|
||||
|
||||
result, err = b.WhaleBomb(6999, false) // <- Book should not move
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 0 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 0)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.PercentageGainOrLoss != 0 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.PercentageGainOrLoss, 0)
|
||||
}
|
||||
|
||||
_, err = b.WhaleBomb(7500, false)
|
||||
if !errors.Is(err, errCannotShiftPrice) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errCannotShiftPrice)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimulateOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := testSetup()
|
||||
b.SimulateOrder(8000, true)
|
||||
b.SimulateOrder(1.5, false)
|
||||
}
|
||||
|
||||
func TestOrderSummary(t *testing.T) {
|
||||
var o orderSummary
|
||||
if p := o.MaximumPrice(false); p != 0 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MinimumPrice(false); p != 0 {
|
||||
t.Error("unexpected result")
|
||||
// Invalid
|
||||
_, err := b.SimulateOrder(-8000, true)
|
||||
if !errors.Is(err, errQuoteAmountInvalid) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errQuoteAmountInvalid)
|
||||
}
|
||||
|
||||
o = orderSummary{
|
||||
{Price: 1337, Amount: 1},
|
||||
{Price: 9001, Amount: 1},
|
||||
}
|
||||
if p := o.MaximumPrice(false); p != 1337 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MaximumPrice(true); p != 9001 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MinimumPrice(false); p != 1337 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MinimumPrice(true); p != 9001 {
|
||||
t.Error("unexpected result")
|
||||
_, err = (&Base{}).SimulateOrder(1337, true)
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
o.Print()
|
||||
// Full liquidity used
|
||||
result, err := b.SimulateOrder(21002, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 3 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 3)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 7001 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7001)
|
||||
}
|
||||
|
||||
if !strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, fullLiquidityUsageWarning)
|
||||
}
|
||||
|
||||
if len(result.Orders) != 2 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 2)
|
||||
}
|
||||
|
||||
// Exceed full liquidity used
|
||||
result, err = b.SimulateOrder(21003, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 3 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 3)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 7001 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7001)
|
||||
}
|
||||
|
||||
if !strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, fullLiquidityUsageWarning)
|
||||
}
|
||||
|
||||
if len(result.Orders) != 2 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 2)
|
||||
}
|
||||
|
||||
// First tranche
|
||||
result, err = b.SimulateOrder(7000, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 1)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 7001 { // A full tranche is wiped out and this one should be preserved.
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7001)
|
||||
}
|
||||
|
||||
if strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, "NO WARNING")
|
||||
}
|
||||
|
||||
if len(result.Orders) != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 1)
|
||||
}
|
||||
|
||||
// Half of first tranch
|
||||
result, err = b.SimulateOrder(3500, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != .5 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, .5)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7000)
|
||||
}
|
||||
|
||||
if strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, "NO WARNING")
|
||||
}
|
||||
|
||||
if len(result.Orders) != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 1)
|
||||
}
|
||||
|
||||
if result.Orders[0].Amount != 0.5 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Orders[0].Amount, 0.5)
|
||||
}
|
||||
|
||||
// Half of second tranche
|
||||
result, err = b.SimulateOrder(14001, true)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 2 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 2)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 7000 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 7000)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 7001 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 7001)
|
||||
}
|
||||
|
||||
if strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, "NO WARNING")
|
||||
}
|
||||
|
||||
if len(result.Orders) != 2 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 1)
|
||||
}
|
||||
|
||||
if result.Orders[1].Amount != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Orders[1].Amount, 1)
|
||||
}
|
||||
|
||||
// Hitting bids
|
||||
|
||||
// Invalid
|
||||
|
||||
_, err = (&Base{}).SimulateOrder(-1, false)
|
||||
if !errors.Is(err, errBaseAmountInvalid) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errBaseAmountInvalid)
|
||||
}
|
||||
|
||||
_, err = (&Base{}).SimulateOrder(2, false)
|
||||
if !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
|
||||
// Full liquidity used
|
||||
result, err = b.SimulateOrder(3, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 20995 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 20995)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6998 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6998)
|
||||
}
|
||||
|
||||
if !strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, fullLiquidityUsageWarning)
|
||||
}
|
||||
|
||||
if len(result.Orders) != 2 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 2)
|
||||
}
|
||||
|
||||
// Exceed full liquidity used
|
||||
result, err = b.SimulateOrder(3.1, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 20995 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 20995)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6998 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6998)
|
||||
}
|
||||
|
||||
if !strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, fullLiquidityUsageWarning)
|
||||
}
|
||||
|
||||
if len(result.Orders) != 2 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 2)
|
||||
}
|
||||
|
||||
// First tranche
|
||||
result, err = b.SimulateOrder(1, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 6999)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6998 { // A full tranche is wiped out and this one should be preserved.
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6998)
|
||||
}
|
||||
|
||||
if strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, "NO WARNING")
|
||||
}
|
||||
|
||||
if len(result.Orders) != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 1)
|
||||
}
|
||||
|
||||
// Half of first tranch
|
||||
result, err = b.SimulateOrder(.5, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 3499.5 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 3499.5)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, "NO WARNING")
|
||||
}
|
||||
|
||||
if len(result.Orders) != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 1)
|
||||
}
|
||||
|
||||
if result.Orders[0].Amount != 0.5 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Orders[0].Amount, 0.5)
|
||||
}
|
||||
|
||||
// Half of second tranche
|
||||
result, err = b.SimulateOrder(2, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if result.Amount != 13997 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Amount, 13997)
|
||||
}
|
||||
|
||||
if result.MaximumPrice != 6999 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MaximumPrice, 6999)
|
||||
}
|
||||
|
||||
if result.MinimumPrice != 6998 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.MinimumPrice, 6998)
|
||||
}
|
||||
|
||||
if strings.Contains(result.Status, fullLiquidityUsageWarning) {
|
||||
t.Fatalf("received: '%v' but expected string to contain: '%v'", result.Status, "NO WARNING")
|
||||
}
|
||||
|
||||
if len(result.Orders) != 2 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(result.Orders), 2)
|
||||
}
|
||||
|
||||
if result.Orders[1].Amount != 1 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", result.Orders[1].Amount, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAveragePrice(t *testing.T) {
|
||||
|
||||
@@ -64,26 +64,6 @@ func (d *Depth) Publish() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetAskLength returns length of asks
|
||||
func (d *Depth) GetAskLength() (int, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
return d.asks.length, nil
|
||||
}
|
||||
|
||||
// GetBidLength returns length of bids
|
||||
func (d *Depth) GetBidLength() (int, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
return d.bids.length, nil
|
||||
}
|
||||
|
||||
// Retrieve returns the orderbook base a copy of the underlying linked list
|
||||
// spread
|
||||
func (d *Depth) Retrieve() (*Base, error) {
|
||||
@@ -106,30 +86,6 @@ func (d *Depth) Retrieve() (*Base, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TotalBidAmounts returns the total amount of bids and the total orderbook
|
||||
// bids value
|
||||
func (d *Depth) TotalBidAmounts() (liquidity, value float64, err error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, 0, d.validationError
|
||||
}
|
||||
liquidity, value = d.bids.amount()
|
||||
return liquidity, value, nil
|
||||
}
|
||||
|
||||
// TotalAskAmounts returns the total amount of asks and the total orderbook
|
||||
// asks value
|
||||
func (d *Depth) TotalAskAmounts() (liquidity, value float64, err error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, 0, d.validationError
|
||||
}
|
||||
liquidity, value = d.asks.amount()
|
||||
return liquidity, value, nil
|
||||
}
|
||||
|
||||
// LoadSnapshot flushes the bids and asks with a snapshot
|
||||
func (d *Depth) LoadSnapshot(bids, asks []Item, lastUpdateID int64, lastUpdated time.Time, updateByREST bool) {
|
||||
d.m.Lock()
|
||||
@@ -323,6 +279,50 @@ func (d *Depth) IsFundingRate() bool {
|
||||
return d.isFundingRate
|
||||
}
|
||||
|
||||
// GetAskLength returns length of asks
|
||||
func (d *Depth) GetAskLength() (int, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
return d.asks.length, nil
|
||||
}
|
||||
|
||||
// GetBidLength returns length of bids
|
||||
func (d *Depth) GetBidLength() (int, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
return d.bids.length, nil
|
||||
}
|
||||
|
||||
// TotalBidAmounts returns the total amount of bids and the total orderbook
|
||||
// bids value
|
||||
func (d *Depth) TotalBidAmounts() (liquidity, value float64, err error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, 0, d.validationError
|
||||
}
|
||||
liquidity, value = d.bids.amount()
|
||||
return liquidity, value, nil
|
||||
}
|
||||
|
||||
// TotalAskAmounts returns the total amount of asks and the total orderbook
|
||||
// asks value
|
||||
func (d *Depth) TotalAskAmounts() (liquidity, value float64, err error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, 0, d.validationError
|
||||
}
|
||||
liquidity, value = d.asks.amount()
|
||||
return liquidity, value, nil
|
||||
}
|
||||
|
||||
// updateAndAlert updates the last updated ID and when it was updated to the
|
||||
// recent update. Then alerts all pending routines. NOTE: This requires locking.
|
||||
func (d *Depth) updateAndAlert(update *Update) {
|
||||
@@ -330,3 +330,382 @@ func (d *Depth) updateAndAlert(update *Update) {
|
||||
d.lastUpdated = update.UpdateTime
|
||||
d.Alert()
|
||||
}
|
||||
|
||||
// HitTheBidsByNominalSlippage hits the bids by the required nominal slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details for the bid side.
|
||||
func (d *Depth) HitTheBidsByNominalSlippage(maxSlippage, refPrice float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
return d.bids.hitBidsByNominalSlippage(maxSlippage, refPrice)
|
||||
}
|
||||
|
||||
// HitTheBidsByNominalSlippageFromMid hits the bids by the required nominal
|
||||
// slippage percentage, calculated from the mid price and returns orderbook
|
||||
// movement details for the bid side.
|
||||
func (d *Depth) HitTheBidsByNominalSlippageFromMid(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
mid, err := d.getMidPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.bids.hitBidsByNominalSlippage(maxSlippage, mid)
|
||||
}
|
||||
|
||||
// HitTheBidsByNominalSlippageFromBest hits the bids by the required nominal
|
||||
// slippage percentage, calculated from the best bid price and returns orderbook
|
||||
// movement details for the bid side.
|
||||
func (d *Depth) HitTheBidsByNominalSlippageFromBest(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
head, err := d.bids.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.bids.hitBidsByNominalSlippage(maxSlippage, head)
|
||||
}
|
||||
|
||||
// LiftTheAsksByNominalSlippage lifts the asks by the required nominal slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details for the ask side.
|
||||
func (d *Depth) LiftTheAsksByNominalSlippage(maxSlippage, refPrice float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
return d.asks.liftAsksByNominalSlippage(maxSlippage, refPrice)
|
||||
}
|
||||
|
||||
// LiftTheAsksByNominalSlippageFromMid lifts the asks by the required nominal
|
||||
// slippage percentage, calculated from the mid price and returns orderbook
|
||||
// movement details for the ask side.
|
||||
func (d *Depth) LiftTheAsksByNominalSlippageFromMid(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
mid, err := d.getMidPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.asks.liftAsksByNominalSlippage(maxSlippage, mid)
|
||||
}
|
||||
|
||||
// LiftTheAsksByNominalSlippageFromBest lifts the asks by the required nominal
|
||||
// slippage percentage, calculated from the best ask price and returns orderbook
|
||||
// movement details for the ask side.
|
||||
func (d *Depth) LiftTheAsksByNominalSlippageFromBest(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
head, err := d.asks.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.asks.liftAsksByNominalSlippage(maxSlippage, head)
|
||||
}
|
||||
|
||||
// HitTheBidsByImpactSlippage hits the bids by the required impact slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details for the bid side.
|
||||
func (d *Depth) HitTheBidsByImpactSlippage(maxSlippage, refPrice float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
return d.bids.hitBidsByImpactSlippage(maxSlippage, refPrice)
|
||||
}
|
||||
|
||||
// HitTheBidsByImpactSlippageFromMid hits the bids by the required impact
|
||||
// slippage percentage, calculated from the mid price and returns orderbook
|
||||
// movement details for the bid side.
|
||||
func (d *Depth) HitTheBidsByImpactSlippageFromMid(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
mid, err := d.getMidPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.bids.hitBidsByImpactSlippage(maxSlippage, mid)
|
||||
}
|
||||
|
||||
// HitTheBidsByImpactSlippageFromBest hits the bids by the required impact
|
||||
// slippage percentage, calculated from the best bid price and returns orderbook
|
||||
// movement details for the bid side.
|
||||
func (d *Depth) HitTheBidsByImpactSlippageFromBest(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
head, err := d.bids.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.bids.hitBidsByImpactSlippage(maxSlippage, head)
|
||||
}
|
||||
|
||||
// LiftTheAsksByImpactSlippage lifts the asks by the required impact slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details for the ask side.
|
||||
func (d *Depth) LiftTheAsksByImpactSlippage(maxSlippage, refPrice float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
return d.asks.liftAsksByImpactSlippage(maxSlippage, refPrice)
|
||||
}
|
||||
|
||||
// LiftTheAsksByImpactSlippageFromMid lifts the asks by the required impact
|
||||
// slippage percentage, calculated from the mid price and returns orderbook
|
||||
// movement details for the ask side.
|
||||
func (d *Depth) LiftTheAsksByImpactSlippageFromMid(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
mid, err := d.getMidPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.asks.liftAsksByImpactSlippage(maxSlippage, mid)
|
||||
}
|
||||
|
||||
// LiftTheAsksByImpactSlippageFromBest lifts the asks by the required impact
|
||||
// slippage percentage, calculated from the best ask price and returns orderbook
|
||||
// movement details for the ask side.
|
||||
func (d *Depth) LiftTheAsksByImpactSlippageFromBest(maxSlippage float64) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
head, err := d.asks.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.asks.liftAsksByImpactSlippage(maxSlippage, head)
|
||||
}
|
||||
|
||||
// HitTheBids derives full orderbook slippage information from reference price
|
||||
// using an amount. Purchase refers to how much quote currency is desired else
|
||||
// the amount would refer to base currency deployed to orderbook bid side.
|
||||
func (d *Depth) HitTheBids(amount, refPrice float64, purchase bool) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
if purchase {
|
||||
return d.bids.getMovementByQuotation(amount, refPrice, false)
|
||||
}
|
||||
return d.bids.getMovementByBase(amount, refPrice, false)
|
||||
}
|
||||
|
||||
// HitTheBidsFromMid derives full orderbook slippage information from mid price
|
||||
// using an amount. Purchase refers to how much quote currency is desired else
|
||||
// the amount would refer to base currency deployed to orderbook bid side.
|
||||
func (d *Depth) HitTheBidsFromMid(amount float64, purchase bool) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
mid, err := d.getMidPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if purchase {
|
||||
return d.bids.getMovementByQuotation(amount, mid, false)
|
||||
}
|
||||
return d.bids.getMovementByBase(amount, mid, false)
|
||||
}
|
||||
|
||||
// HitTheBidsFromBest derives full orderbook slippage information from best bid
|
||||
// price using an amount. Purchase refers to how much quote currency is desired
|
||||
// else the amount would refer to base currency deployed to orderbook bid side.
|
||||
func (d *Depth) HitTheBidsFromBest(amount float64, purchase bool) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
head, err := d.bids.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if purchase {
|
||||
return d.bids.getMovementByQuotation(amount, head, false)
|
||||
}
|
||||
return d.bids.getMovementByBase(amount, head, false)
|
||||
}
|
||||
|
||||
// LiftTheAsks derives full orderbook slippage information from reference price
|
||||
// using an amount. Purchase refers to how much base currency is desired else
|
||||
// the amount would refer to quote currency deployed to orderbook ask side.
|
||||
func (d *Depth) LiftTheAsks(amount, refPrice float64, purchase bool) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
if purchase {
|
||||
return d.asks.getMovementByBase(amount, refPrice, true)
|
||||
}
|
||||
return d.asks.getMovementByQuotation(amount, refPrice, true)
|
||||
}
|
||||
|
||||
// LiftTheAsksFromMid derives full orderbook slippage information from mid price
|
||||
// using an amount. Purchase refers to how much base currency is desired else
|
||||
// the amount would refer to quote currency deployed to orderbook ask side.
|
||||
func (d *Depth) LiftTheAsksFromMid(amount float64, purchase bool) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
mid, err := d.getMidPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if purchase {
|
||||
return d.asks.getMovementByBase(amount, mid, true)
|
||||
}
|
||||
return d.asks.getMovementByQuotation(amount, mid, true)
|
||||
}
|
||||
|
||||
// LiftTheAsksFromBest derives full orderbook slippage information from best ask
|
||||
// price using an amount. Purchase refers to how much base currency is desired
|
||||
// else the amount would refer to quote currency deployed to orderbook ask side.
|
||||
func (d *Depth) LiftTheAsksFromBest(amount float64, purchase bool) (*Movement, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return nil, d.validationError
|
||||
}
|
||||
head, err := d.asks.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if purchase {
|
||||
return d.asks.getMovementByBase(amount, head, true)
|
||||
}
|
||||
return d.asks.getMovementByQuotation(amount, head, true)
|
||||
}
|
||||
|
||||
// GetMidPrice returns the mid price between the ask and bid spread
|
||||
func (d *Depth) GetMidPrice() (float64, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
return d.getMidPriceNoLock()
|
||||
}
|
||||
|
||||
// getMidPriceNoLock is an unprotected helper that gets mid price
|
||||
func (d *Depth) getMidPriceNoLock() (float64, error) {
|
||||
bidHead, err := d.bids.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
askHead, err := d.asks.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return (bidHead + askHead) / 2, nil
|
||||
}
|
||||
|
||||
// GetBestBid returns the best bid price
|
||||
func (d *Depth) GetBestBid() (float64, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
return d.bids.getHeadPriceNoLock()
|
||||
}
|
||||
|
||||
// GetBestAsk returns the best ask price
|
||||
func (d *Depth) GetBestAsk() (float64, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
return d.asks.getHeadPriceNoLock()
|
||||
}
|
||||
|
||||
// GetSpreadAmount returns the spread as a quotation amount
|
||||
func (d *Depth) GetSpreadAmount() (float64, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
askHead, err := d.asks.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
bidHead, err := d.bids.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return askHead - bidHead, nil
|
||||
}
|
||||
|
||||
// GetSpreadPercentage returns the spread as a percentage
|
||||
func (d *Depth) GetSpreadPercentage() (float64, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
askHead, err := d.asks.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
bidHead, err := d.bids.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return (askHead - bidHead) / askHead * 100, nil
|
||||
}
|
||||
|
||||
// GetImbalance returns top orderbook imbalance
|
||||
func (d *Depth) GetImbalance() (float64, error) {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.validationError != nil {
|
||||
return 0, d.validationError
|
||||
}
|
||||
askVolume, err := d.asks.getHeadVolumeNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
bidVolume, err := d.bids.getHeadVolumeNoLock()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return (bidVolume - askVolume) / (bidVolume + askVolume), nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,28 @@ package orderbook
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/math"
|
||||
)
|
||||
|
||||
var errIDCannotBeMatched = errors.New("cannot match ID on linked list")
|
||||
var errCollisionDetected = errors.New("cannot insert update collision detected")
|
||||
var errAmountCannotBeLessOrEqualToZero = errors.New("amount cannot be less or equal to zero")
|
||||
// FullLiquidityExhaustedPercentage defines when a book has been completely
|
||||
// wiped out of potential liquidity.
|
||||
const FullLiquidityExhaustedPercentage = -100
|
||||
|
||||
var (
|
||||
errIDCannotBeMatched = errors.New("cannot match ID on linked list")
|
||||
errCollisionDetected = errors.New("cannot insert update, collision detected")
|
||||
errAmountCannotBeLessOrEqualToZero = errors.New("amount cannot be less than or equal to zero")
|
||||
errInvalidNominalSlippage = errors.New("invalid slippage amount, its value must be greater than or equal to zero")
|
||||
errInvalidImpactSlippage = errors.New("invalid slippage amount, its value must be greater than zero")
|
||||
errInvalidSlippageCannotExceed100 = errors.New("invalid slippage amount, its value cannot exceed 100%")
|
||||
errBaseAmountInvalid = errors.New("invalid base amount")
|
||||
errInvalidReferencePrice = errors.New("invalid reference price")
|
||||
errQuoteAmountInvalid = errors.New("quote amount invalid")
|
||||
errInvalidCost = errors.New("invalid cost amount")
|
||||
errInvalidAmount = errors.New("invalid amount")
|
||||
errInvalidHeadPrice = errors.New("invalid head price")
|
||||
)
|
||||
|
||||
// linkedList defines a linked list for a depth level, reutilisation of nodes
|
||||
// to and from a stack.
|
||||
@@ -340,6 +357,115 @@ func (ll *linkedList) insertUpdates(updts Items, stack *stack, comp comparison)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getHeadPriceNoLock gets best/head price
|
||||
func (ll *linkedList) getHeadPriceNoLock() (float64, error) {
|
||||
if ll.head == nil {
|
||||
return 0, errNoLiquidity
|
||||
}
|
||||
return ll.head.Value.Price, nil
|
||||
}
|
||||
|
||||
// getHeadVolumeNoLock gets best/head volume
|
||||
func (ll *linkedList) getHeadVolumeNoLock() (float64, error) {
|
||||
if ll.head == nil {
|
||||
return 0, errNoLiquidity
|
||||
}
|
||||
return ll.head.Value.Amount, nil
|
||||
}
|
||||
|
||||
// getMovementByQuotation traverses through orderbook liquidity using quotation
|
||||
// currency as a limiter and returns orderbook movement details. Swap boolean
|
||||
// allows the swap of sold and purchased to reduce code so it doesn't need to be
|
||||
// specific to bid or ask.
|
||||
func (ll *linkedList) getMovementByQuotation(quote, refPrice float64, swap bool) (*Movement, error) {
|
||||
if quote <= 0 {
|
||||
return nil, errQuoteAmountInvalid
|
||||
}
|
||||
|
||||
if refPrice <= 0 {
|
||||
return nil, errInvalidReferencePrice
|
||||
}
|
||||
|
||||
head, err := ll.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := Movement{StartPrice: refPrice}
|
||||
for tip := ll.head; tip != nil; tip = tip.Next {
|
||||
trancheValue := tip.Value.Amount * tip.Value.Price
|
||||
leftover := quote - trancheValue
|
||||
if leftover < 0 {
|
||||
m.Purchased += quote
|
||||
m.Sold += quote / trancheValue * tip.Value.Amount
|
||||
// This tranche is not consumed so the book shifts to this price.
|
||||
m.EndPrice = tip.Value.Price
|
||||
quote = 0
|
||||
break
|
||||
}
|
||||
// Full tranche consumed
|
||||
m.Purchased += tip.Value.Price * tip.Value.Amount
|
||||
m.Sold += tip.Value.Amount
|
||||
quote = leftover
|
||||
if leftover == 0 {
|
||||
// Price no longer exists on the book so use next full price tranche
|
||||
// to calculate book impact. If available.
|
||||
if tip.Next != nil {
|
||||
m.EndPrice = tip.Next.Value.Price
|
||||
} else {
|
||||
m.FullBookSideConsumed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.finalizeFields(m.Purchased, m.Sold, head, quote, swap)
|
||||
}
|
||||
|
||||
// getMovementByBase traverses through orderbook liquidity using base currency
|
||||
// as a limiter and returns orderbook movement details. Swap boolean allows the
|
||||
// swap of sold and purchased to reduce code so it doesn't need to be specific
|
||||
// to bid or ask.
|
||||
func (ll *linkedList) getMovementByBase(base, refPrice float64, swap bool) (*Movement, error) {
|
||||
if base <= 0 {
|
||||
return nil, errBaseAmountInvalid
|
||||
}
|
||||
|
||||
if refPrice <= 0 {
|
||||
return nil, errInvalidReferencePrice
|
||||
}
|
||||
|
||||
head, err := ll.getHeadPriceNoLock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := Movement{StartPrice: refPrice}
|
||||
for tip := ll.head; tip != nil; tip = tip.Next {
|
||||
leftover := base - tip.Value.Amount
|
||||
if leftover < 0 {
|
||||
m.Purchased += tip.Value.Price * base
|
||||
m.Sold += base
|
||||
// This tranche is not consumed so the book shifts to this price.
|
||||
m.EndPrice = tip.Value.Price
|
||||
base = 0
|
||||
break
|
||||
}
|
||||
// Full tranche consumed
|
||||
m.Purchased += tip.Value.Price * tip.Value.Amount
|
||||
m.Sold += tip.Value.Amount
|
||||
base = leftover
|
||||
if leftover == 0 {
|
||||
// Price no longer exists on the book so use next full price tranche
|
||||
// to calculate book impact.
|
||||
if tip.Next != nil {
|
||||
m.EndPrice = tip.Next.Value.Price
|
||||
} else {
|
||||
m.FullBookSideConsumed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.finalizeFields(m.Purchased, m.Sold, head, base, swap)
|
||||
}
|
||||
|
||||
// bids embed a linked list to attach methods for bid depth specific
|
||||
// functionality
|
||||
type bids struct {
|
||||
@@ -367,6 +493,115 @@ func (ll *bids) insertUpdates(updts Items, stack *stack) error {
|
||||
return ll.linkedList.insertUpdates(updts, stack, bidCompare)
|
||||
}
|
||||
|
||||
// hitBidsByNominalSlippage hits the bids by the required nominal slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details.
|
||||
func (ll *bids) hitBidsByNominalSlippage(slippage, refPrice float64) (*Movement, error) {
|
||||
if slippage < 0 {
|
||||
return nil, errInvalidNominalSlippage
|
||||
}
|
||||
|
||||
if slippage > 100 {
|
||||
return nil, errInvalidSlippageCannotExceed100
|
||||
}
|
||||
|
||||
if refPrice <= 0 {
|
||||
return nil, errInvalidReferencePrice
|
||||
}
|
||||
|
||||
if ll.head == nil {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
|
||||
nominal := &Movement{StartPrice: refPrice, EndPrice: refPrice}
|
||||
var cumulativeValue, cumulativeAmounts float64
|
||||
for tip := ll.head; tip != nil; tip = tip.Next {
|
||||
totalTrancheValue := tip.Value.Price * tip.Value.Amount
|
||||
currentFullValue := totalTrancheValue + cumulativeValue
|
||||
currentTotalAmounts := cumulativeAmounts + tip.Value.Amount
|
||||
|
||||
nominal.AverageOrderCost = currentFullValue / currentTotalAmounts
|
||||
percent := math.CalculatePercentageGainOrLoss(nominal.AverageOrderCost, refPrice)
|
||||
if percent != 0 {
|
||||
percent *= -1
|
||||
}
|
||||
|
||||
if slippage < percent {
|
||||
targetCost := (1 - slippage/100) * refPrice
|
||||
if targetCost == refPrice {
|
||||
nominal.AverageOrderCost = cumulativeValue / cumulativeAmounts
|
||||
// Rounding issue on requested nominal percentage
|
||||
return nominal, nil
|
||||
}
|
||||
comparative := targetCost * cumulativeAmounts
|
||||
comparativeDiff := comparative - cumulativeValue
|
||||
trancheTargetPriceDiff := tip.Value.Price - targetCost
|
||||
trancheAmountExpectation := comparativeDiff / trancheTargetPriceDiff
|
||||
nominal.NominalPercentage = slippage
|
||||
nominal.Sold = cumulativeAmounts + trancheAmountExpectation
|
||||
nominal.Purchased += trancheAmountExpectation * tip.Value.Price
|
||||
nominal.AverageOrderCost = nominal.Purchased / nominal.Sold
|
||||
nominal.EndPrice = tip.Value.Price
|
||||
return nominal, nil
|
||||
}
|
||||
|
||||
nominal.EndPrice = tip.Value.Price
|
||||
cumulativeValue = currentFullValue
|
||||
nominal.NominalPercentage = percent
|
||||
nominal.Sold += tip.Value.Amount
|
||||
nominal.Purchased += totalTrancheValue
|
||||
cumulativeAmounts = currentTotalAmounts
|
||||
if slippage == percent {
|
||||
nominal.FullBookSideConsumed = tip.Next == nil
|
||||
return nominal, nil
|
||||
}
|
||||
}
|
||||
nominal.FullBookSideConsumed = true
|
||||
return nominal, nil
|
||||
}
|
||||
|
||||
// hitBidsByImpactSlippage hits the bids by the required impact slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details.
|
||||
func (ll *bids) hitBidsByImpactSlippage(slippage, refPrice float64) (*Movement, error) {
|
||||
if slippage <= 0 {
|
||||
return nil, errInvalidImpactSlippage
|
||||
}
|
||||
|
||||
if slippage > 100 {
|
||||
return nil, errInvalidSlippageCannotExceed100
|
||||
}
|
||||
|
||||
if refPrice <= 0 {
|
||||
return nil, errInvalidReferencePrice
|
||||
}
|
||||
|
||||
if ll.head == nil {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
|
||||
impact := &Movement{StartPrice: refPrice, EndPrice: refPrice}
|
||||
for tip := ll.head; tip != nil; tip = tip.Next {
|
||||
percent := math.CalculatePercentageGainOrLoss(tip.Value.Price, refPrice)
|
||||
if percent != 0 {
|
||||
percent *= -1
|
||||
}
|
||||
impact.EndPrice = tip.Value.Price
|
||||
impact.ImpactPercentage = percent
|
||||
if slippage <= percent {
|
||||
// Don't include this tranche amount as this consumes the tranche
|
||||
// book price, thus obtaining a higher percentage impact.
|
||||
return impact, nil
|
||||
}
|
||||
impact.Sold += tip.Value.Amount
|
||||
impact.Purchased += tip.Value.Amount * tip.Value.Price
|
||||
impact.AverageOrderCost = impact.Purchased / impact.Sold
|
||||
}
|
||||
impact.FullBookSideConsumed = true
|
||||
impact.ImpactPercentage = FullLiquidityExhaustedPercentage
|
||||
return impact, nil
|
||||
}
|
||||
|
||||
// asks embed a linked list to attach methods for ask depth specific
|
||||
// functionality
|
||||
type asks struct {
|
||||
@@ -394,6 +629,100 @@ func (ll *asks) insertUpdates(updts Items, stack *stack) error {
|
||||
return ll.linkedList.insertUpdates(updts, stack, askCompare)
|
||||
}
|
||||
|
||||
// liftAsksByNominalSlippage lifts the asks by the required nominal slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details.
|
||||
func (ll *asks) liftAsksByNominalSlippage(slippage, refPrice float64) (*Movement, error) {
|
||||
if slippage < 0 {
|
||||
return nil, errInvalidNominalSlippage
|
||||
}
|
||||
|
||||
if refPrice <= 0 {
|
||||
return nil, errInvalidReferencePrice
|
||||
}
|
||||
|
||||
if ll.head == nil {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
|
||||
nominal := &Movement{StartPrice: refPrice, EndPrice: refPrice}
|
||||
var cumulativeAmounts float64
|
||||
for tip := ll.head; tip != nil; tip = tip.Next {
|
||||
totalTrancheValue := tip.Value.Price * tip.Value.Amount
|
||||
currentValue := totalTrancheValue + nominal.Sold
|
||||
currentAmounts := cumulativeAmounts + tip.Value.Amount
|
||||
|
||||
nominal.AverageOrderCost = currentValue / currentAmounts
|
||||
percent := math.CalculatePercentageGainOrLoss(nominal.AverageOrderCost, refPrice)
|
||||
|
||||
if slippage < percent {
|
||||
targetCost := (1 + slippage/100) * refPrice
|
||||
if targetCost == refPrice {
|
||||
nominal.AverageOrderCost = nominal.Sold / nominal.Purchased
|
||||
// Rounding issue on requested nominal percentage
|
||||
return nominal, nil
|
||||
}
|
||||
|
||||
comparative := targetCost * cumulativeAmounts
|
||||
comparativeDiff := comparative - nominal.Sold
|
||||
trancheTargetPriceDiff := tip.Value.Price - targetCost
|
||||
trancheAmountExpectation := comparativeDiff / trancheTargetPriceDiff
|
||||
nominal.NominalPercentage = slippage
|
||||
nominal.Sold += trancheAmountExpectation * tip.Value.Price
|
||||
nominal.Purchased += trancheAmountExpectation
|
||||
nominal.AverageOrderCost = nominal.Sold / nominal.Purchased
|
||||
nominal.EndPrice = tip.Value.Price
|
||||
return nominal, nil
|
||||
}
|
||||
|
||||
nominal.EndPrice = tip.Value.Price
|
||||
nominal.Sold = currentValue
|
||||
nominal.Purchased += tip.Value.Amount
|
||||
nominal.NominalPercentage = percent
|
||||
if slippage == percent {
|
||||
return nominal, nil
|
||||
}
|
||||
cumulativeAmounts = currentAmounts
|
||||
}
|
||||
nominal.FullBookSideConsumed = true
|
||||
return nominal, nil
|
||||
}
|
||||
|
||||
// liftAsksByImpactSlippage lifts the asks by the required impact slippage
|
||||
// percentage, calculated from the reference price and returns orderbook
|
||||
// movement details.
|
||||
func (ll *asks) liftAsksByImpactSlippage(slippage, refPrice float64) (*Movement, error) {
|
||||
if slippage <= 0 {
|
||||
return nil, errInvalidImpactSlippage
|
||||
}
|
||||
|
||||
if refPrice <= 0 {
|
||||
return nil, errInvalidReferencePrice
|
||||
}
|
||||
|
||||
if ll.head == nil {
|
||||
return nil, errNoLiquidity
|
||||
}
|
||||
|
||||
impact := &Movement{StartPrice: refPrice, EndPrice: refPrice}
|
||||
for tip := ll.head; tip != nil; tip = tip.Next {
|
||||
percent := math.CalculatePercentageGainOrLoss(tip.Value.Price, refPrice)
|
||||
impact.ImpactPercentage = percent
|
||||
impact.EndPrice = tip.Value.Price
|
||||
if slippage <= percent {
|
||||
// Don't include this tranche amount as this consumes the tranche
|
||||
// book price, thus obtaining a higher percentage impact.
|
||||
return impact, nil
|
||||
}
|
||||
impact.Sold += tip.Value.Amount * tip.Value.Price
|
||||
impact.Purchased += tip.Value.Amount
|
||||
impact.AverageOrderCost = impact.Sold / impact.Purchased
|
||||
}
|
||||
impact.FullBookSideConsumed = true
|
||||
impact.ImpactPercentage = FullLiquidityExhaustedPercentage
|
||||
return impact, nil
|
||||
}
|
||||
|
||||
// move moves a node from a point in a node chain to another node position,
|
||||
// this left justified towards head as element zero is the top of the depth
|
||||
// side. (can inline)
|
||||
@@ -526,3 +855,67 @@ func shiftBookmark(tip *Node, bookmark, head **Node, updt Item) bool {
|
||||
(*bookmark).Next = nil
|
||||
return true
|
||||
}
|
||||
|
||||
// finalizeFields sets average order costing, percentages, slippage cost and
|
||||
// preserves existing fields.
|
||||
func (m *Movement) finalizeFields(cost, amount, headPrice, leftover float64, swap bool) (*Movement, error) {
|
||||
if cost <= 0 {
|
||||
return nil, errInvalidCost
|
||||
}
|
||||
|
||||
if amount <= 0 {
|
||||
return nil, errInvalidAmount
|
||||
}
|
||||
|
||||
if headPrice <= 0 {
|
||||
return nil, errInvalidHeadPrice
|
||||
}
|
||||
|
||||
if m.StartPrice != m.EndPrice {
|
||||
// Average order cost defines the actual cost price as capital is
|
||||
// deployed through the orderbook liquidity.
|
||||
m.AverageOrderCost = cost / amount
|
||||
} else {
|
||||
// Edge case rounding issue for float64 with small numbers.
|
||||
m.AverageOrderCost = m.StartPrice
|
||||
}
|
||||
|
||||
// Nominal percentage is the difference from the reference price to average
|
||||
// order cost.
|
||||
m.NominalPercentage = math.CalculatePercentageGainOrLoss(m.AverageOrderCost, m.StartPrice)
|
||||
if m.NominalPercentage < 0 {
|
||||
m.NominalPercentage *= -1
|
||||
}
|
||||
|
||||
if !m.FullBookSideConsumed && leftover == 0 {
|
||||
// Impact percentage is how much the orderbook slips from the reference
|
||||
// price to the remaining tranche price.
|
||||
|
||||
m.ImpactPercentage = math.CalculatePercentageGainOrLoss(m.EndPrice, m.StartPrice)
|
||||
if m.ImpactPercentage < 0 {
|
||||
m.ImpactPercentage *= -1
|
||||
}
|
||||
} else {
|
||||
// Full liquidity exhausted by request amount
|
||||
m.ImpactPercentage = FullLiquidityExhaustedPercentage
|
||||
m.FullBookSideConsumed = true
|
||||
}
|
||||
|
||||
// Slippage cost is the difference in quotation terms between the actual
|
||||
// cost and the amounts at head price e.g.
|
||||
// Let P(n)=Price A(n)=Amount and iterate through a descending bid order example;
|
||||
// Cost: $270 (P1:100 x A1:1 + P2:90 x A2:1 + P3:80 x A3:1)
|
||||
// No slippage cost: $300 (P1:100 x A1:1 + P1:100 x A2:1 + P1:100 x A3:1)
|
||||
// $300 - $270 = $30 of slippage.
|
||||
m.SlippageCost = cost - (headPrice * amount)
|
||||
if m.SlippageCost < 0 {
|
||||
m.SlippageCost *= -1
|
||||
}
|
||||
|
||||
// Swap saves on code duplication for difference in ask or bid amounts.
|
||||
if swap {
|
||||
m.Sold, m.Purchased = m.Purchased, m.Sold
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
@@ -30,6 +30,29 @@ var ask = Items{
|
||||
Item{Price: 1356, Amount: 1},
|
||||
}
|
||||
|
||||
var bid = Items{
|
||||
Item{Price: 1336, Amount: 1},
|
||||
Item{Price: 1335, Amount: 1},
|
||||
Item{Price: 1334, Amount: 1},
|
||||
Item{Price: 1333, Amount: 1},
|
||||
Item{Price: 1332, Amount: 1},
|
||||
Item{Price: 1331, Amount: 1},
|
||||
Item{Price: 1330, Amount: 1},
|
||||
Item{Price: 1329, Amount: 1},
|
||||
Item{Price: 1328, Amount: 1},
|
||||
Item{Price: 1327, Amount: 1},
|
||||
Item{Price: 1326, Amount: 1},
|
||||
Item{Price: 1325, Amount: 1},
|
||||
Item{Price: 1324, Amount: 1},
|
||||
Item{Price: 1323, Amount: 1},
|
||||
Item{Price: 1322, Amount: 1},
|
||||
Item{Price: 1321, Amount: 1},
|
||||
Item{Price: 1320, Amount: 1},
|
||||
Item{Price: 1319, Amount: 1},
|
||||
Item{Price: 1318, Amount: 1},
|
||||
Item{Price: 1317, Amount: 1},
|
||||
}
|
||||
|
||||
// Display displays depth content for tests
|
||||
func (ll *linkedList) display() {
|
||||
for tip := ll.head; tip != nil; tip = tip.Next {
|
||||
@@ -1447,3 +1470,674 @@ func TestShiftBookmark(t *testing.T) {
|
||||
t.Fatal("unexpected pointer variable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMovementByBaseAmount(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
Name string
|
||||
BaseAmount float64
|
||||
ReferencePrice float64
|
||||
BidLiquidity Items
|
||||
ExpectedNominal float64
|
||||
ExpectedImpact float64
|
||||
ExpectedCost float64
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "no amount",
|
||||
ExpectedError: errBaseAmountInvalid,
|
||||
},
|
||||
{
|
||||
Name: "no reference price",
|
||||
BaseAmount: 1,
|
||||
ExpectedError: errInvalidReferencePrice,
|
||||
},
|
||||
{
|
||||
Name: "not enough liquidity to service quote amount",
|
||||
BaseAmount: 1,
|
||||
ReferencePrice: 1000,
|
||||
ExpectedError: errNoLiquidity,
|
||||
},
|
||||
{
|
||||
Name: "thrasher test",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
BaseAmount: 10,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 0.8999999999999999,
|
||||
ExpectedImpact: 2,
|
||||
ExpectedCost: 900,
|
||||
},
|
||||
{
|
||||
Name: "consume first tranche",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
BaseAmount: 2,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 0,
|
||||
ExpectedImpact: 1,
|
||||
ExpectedCost: 0,
|
||||
},
|
||||
{
|
||||
Name: "consume most of first tranche",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
BaseAmount: 1.5,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 0,
|
||||
ExpectedImpact: 0,
|
||||
ExpectedCost: 0,
|
||||
},
|
||||
{
|
||||
Name: "consume full liquidity",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
BaseAmount: 12,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 1.0833333333333395,
|
||||
ExpectedImpact: FullLiquidityExhaustedPercentage,
|
||||
ExpectedCost: 1300,
|
||||
},
|
||||
}
|
||||
|
||||
for x := range cases {
|
||||
tt := cases[x]
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
depth := NewDepth(id)
|
||||
depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true)
|
||||
movement, err := depth.bids.getMovementByBase(tt.BaseAmount, tt.ReferencePrice, false)
|
||||
if !errors.Is(err, tt.ExpectedError) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError)
|
||||
}
|
||||
|
||||
if movement == nil {
|
||||
return
|
||||
}
|
||||
if movement.NominalPercentage != tt.ExpectedNominal {
|
||||
t.Fatalf("nominal received: '%v' but expected: '%v'",
|
||||
movement.NominalPercentage, tt.ExpectedNominal)
|
||||
}
|
||||
|
||||
if movement.ImpactPercentage != tt.ExpectedImpact {
|
||||
t.Fatalf("impact received: '%v' but expected: '%v'",
|
||||
movement.ImpactPercentage, tt.ExpectedImpact)
|
||||
}
|
||||
|
||||
if movement.SlippageCost != tt.ExpectedCost {
|
||||
t.Fatalf("cost received: '%v' but expected: '%v'",
|
||||
movement.SlippageCost, tt.ExpectedCost)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBaseAmountFromNominalSlippage(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
Name string
|
||||
NominalSlippage float64
|
||||
ReferencePrice float64
|
||||
BidLiquidity Items
|
||||
ExpectedShift *Movement
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "invalid slippage",
|
||||
NominalSlippage: -1,
|
||||
ExpectedError: errInvalidNominalSlippage,
|
||||
},
|
||||
{
|
||||
Name: "invalid slippage - larger than 100%",
|
||||
NominalSlippage: 101,
|
||||
ExpectedError: errInvalidSlippageCannotExceed100,
|
||||
},
|
||||
{
|
||||
Name: "no reference price",
|
||||
NominalSlippage: 1,
|
||||
ExpectedError: errInvalidReferencePrice,
|
||||
},
|
||||
{
|
||||
Name: "no liquidity to service quote amount",
|
||||
NominalSlippage: 1,
|
||||
ReferencePrice: 1000,
|
||||
ExpectedError: errNoLiquidity,
|
||||
},
|
||||
{
|
||||
Name: "thrasher test",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
NominalSlippage: 1,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 11,
|
||||
Purchased: 108900,
|
||||
AverageOrderCost: 9900,
|
||||
NominalPercentage: 1,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 9800,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "consume first tranche - take one amount out of second",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
NominalSlippage: 0.33333333333334,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 3.0000000000000275, // <- expected rounding issue
|
||||
Purchased: 29900.00000000027,
|
||||
AverageOrderCost: 9966.666666666664,
|
||||
NominalPercentage: 0.33333333333334,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 9900,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "consume full liquidity",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
NominalSlippage: 10,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 12,
|
||||
Purchased: 118700,
|
||||
AverageOrderCost: 9891.666666666666,
|
||||
NominalPercentage: 1.0833333333333395,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 9800,
|
||||
FullBookSideConsumed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "scotts lovely slippery slippage requirements",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
NominalSlippage: 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000001,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 2,
|
||||
Purchased: 20000,
|
||||
AverageOrderCost: 10000,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for x := range cases {
|
||||
tt := cases[x]
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
depth := NewDepth(id)
|
||||
depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true)
|
||||
base, err := depth.bids.hitBidsByNominalSlippage(tt.NominalSlippage, tt.ReferencePrice)
|
||||
if !errors.Is(err, tt.ExpectedError) {
|
||||
t.Fatalf("%s received: '%v' but expected: '%v'",
|
||||
tt.Name, err, tt.ExpectedError)
|
||||
}
|
||||
if !base.IsEqual(tt.ExpectedShift) {
|
||||
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
||||
tt.Name, base, tt.ExpectedShift)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IsEqual is a tester function for comparison.
|
||||
func (m *Movement) IsEqual(that *Movement) bool {
|
||||
if m == nil || that == nil {
|
||||
return m == nil && that == nil
|
||||
}
|
||||
return m.FullBookSideConsumed == that.FullBookSideConsumed &&
|
||||
m.Sold == that.Sold &&
|
||||
m.Purchased == that.Purchased &&
|
||||
m.NominalPercentage == that.NominalPercentage &&
|
||||
m.ImpactPercentage == that.ImpactPercentage &&
|
||||
m.EndPrice == that.EndPrice &&
|
||||
m.StartPrice == that.StartPrice &&
|
||||
m.AverageOrderCost == that.AverageOrderCost
|
||||
}
|
||||
|
||||
func TestGetBaseAmountFromImpact(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
Name string
|
||||
ImpactSlippage float64
|
||||
ReferencePrice float64
|
||||
BidLiquidity Items
|
||||
ExpectedShift *Movement
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "invalid slippage",
|
||||
ExpectedError: errInvalidImpactSlippage,
|
||||
},
|
||||
{
|
||||
Name: "invalid slippage - exceed 100%",
|
||||
ImpactSlippage: 101,
|
||||
ExpectedError: errInvalidSlippageCannotExceed100,
|
||||
},
|
||||
{
|
||||
Name: "no reference price",
|
||||
ImpactSlippage: 1,
|
||||
ExpectedError: errInvalidReferencePrice,
|
||||
},
|
||||
{
|
||||
Name: "no liquidity",
|
||||
ImpactSlippage: 1,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedError: errNoLiquidity,
|
||||
},
|
||||
{
|
||||
Name: "thrasher test",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
ImpactSlippage: 1,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 2,
|
||||
Purchased: 20000,
|
||||
ImpactPercentage: 1,
|
||||
AverageOrderCost: 10000,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 9900,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "consume first tranche and second tranche",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
ImpactSlippage: 2,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 9,
|
||||
Purchased: 89300,
|
||||
AverageOrderCost: 9922.222222222223,
|
||||
ImpactPercentage: 2,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 9800,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "consume full liquidity",
|
||||
BidLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 9900, Amount: 7}, {Price: 9800, Amount: 3}},
|
||||
ImpactSlippage: 10,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 12,
|
||||
Purchased: 118700,
|
||||
ImpactPercentage: FullLiquidityExhaustedPercentage,
|
||||
AverageOrderCost: 9891.666666666666,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 9800,
|
||||
FullBookSideConsumed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for x := range cases {
|
||||
tt := cases[x]
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
depth := NewDepth(id)
|
||||
depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true)
|
||||
base, err := depth.bids.hitBidsByImpactSlippage(tt.ImpactSlippage, tt.ReferencePrice)
|
||||
if !errors.Is(err, tt.ExpectedError) {
|
||||
t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError)
|
||||
}
|
||||
if !base.IsEqual(tt.ExpectedShift) {
|
||||
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
||||
tt.Name, base, tt.ExpectedShift)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMovementByQuoteAmount(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
Name string
|
||||
QuoteAmount float64
|
||||
ReferencePrice float64
|
||||
AskLiquidity Items
|
||||
ExpectedNominal float64
|
||||
ExpectedImpact float64
|
||||
ExpectedCost float64
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "no amount",
|
||||
ExpectedError: errQuoteAmountInvalid,
|
||||
},
|
||||
{
|
||||
Name: "no reference price",
|
||||
QuoteAmount: 1,
|
||||
ExpectedError: errInvalidReferencePrice,
|
||||
},
|
||||
{
|
||||
Name: "not enough liquidity to service quote amount",
|
||||
QuoteAmount: 1,
|
||||
ReferencePrice: 1000,
|
||||
ExpectedError: errNoLiquidity,
|
||||
},
|
||||
{
|
||||
Name: "thrasher test",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
QuoteAmount: 100900,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 0.8999999999999999,
|
||||
ExpectedImpact: 2,
|
||||
ExpectedCost: 900,
|
||||
},
|
||||
{
|
||||
Name: "consume first tranche",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
QuoteAmount: 20000,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 0,
|
||||
ExpectedImpact: 1,
|
||||
ExpectedCost: 0,
|
||||
},
|
||||
{
|
||||
Name: "consume most of first tranche",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
QuoteAmount: 15000,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 0,
|
||||
ExpectedImpact: 0,
|
||||
ExpectedCost: 0,
|
||||
},
|
||||
{
|
||||
Name: "consume full liquidity",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
QuoteAmount: 121300,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedNominal: 1.0833333333333395,
|
||||
ExpectedImpact: FullLiquidityExhaustedPercentage,
|
||||
ExpectedCost: 1300,
|
||||
},
|
||||
}
|
||||
|
||||
for x := range cases {
|
||||
tt := cases[x]
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
depth := NewDepth(id)
|
||||
depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true)
|
||||
movement, err := depth.asks.getMovementByQuotation(tt.QuoteAmount, tt.ReferencePrice, false)
|
||||
if !errors.Is(err, tt.ExpectedError) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError)
|
||||
}
|
||||
|
||||
if movement == nil {
|
||||
return
|
||||
}
|
||||
if movement.NominalPercentage != tt.ExpectedNominal {
|
||||
t.Fatalf("nominal received: '%v' but expected: '%v'",
|
||||
movement.NominalPercentage, tt.ExpectedNominal)
|
||||
}
|
||||
|
||||
if movement.ImpactPercentage != tt.ExpectedImpact {
|
||||
t.Fatalf("impact received: '%v' but expected: '%v'",
|
||||
movement.ImpactPercentage, tt.ExpectedImpact)
|
||||
}
|
||||
|
||||
if movement.SlippageCost != tt.ExpectedCost {
|
||||
t.Fatalf("cost received: '%v' but expected: '%v'",
|
||||
movement.SlippageCost, tt.ExpectedCost)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetQuoteAmountFromNominalSlippage(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
Name string
|
||||
NominalSlippage float64
|
||||
ReferencePrice float64
|
||||
AskLiquidity Items
|
||||
ExpectedShift *Movement
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "invalid slippage",
|
||||
NominalSlippage: -1,
|
||||
ExpectedError: errInvalidNominalSlippage,
|
||||
},
|
||||
{
|
||||
Name: "no reference price",
|
||||
NominalSlippage: 1,
|
||||
ExpectedError: errInvalidReferencePrice,
|
||||
},
|
||||
{
|
||||
Name: "no liquidity",
|
||||
NominalSlippage: 1,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedError: errNoLiquidity,
|
||||
},
|
||||
{
|
||||
Name: "consume first tranche - one amount on second tranche",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
NominalSlippage: 0.33333333333334,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 30100.000000000276, // <- expected rounding issue
|
||||
Purchased: 3.0000000000000275,
|
||||
AverageOrderCost: 10033.333333333333333333333333333,
|
||||
NominalPercentage: 0.33333333333334,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10100,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "last tranche total agg meeting 1 percent nominally",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
NominalSlippage: 1,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 111100,
|
||||
Purchased: 11,
|
||||
AverageOrderCost: 10100,
|
||||
NominalPercentage: 1,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10200,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "take full second tranche",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
NominalSlippage: 0.7777777777777738,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 90700,
|
||||
Purchased: 9,
|
||||
AverageOrderCost: 10077.777777777777,
|
||||
NominalPercentage: 0.7777777777777738,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10100,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "consume full liquidity",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
NominalSlippage: 10,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 121300,
|
||||
Purchased: 12,
|
||||
AverageOrderCost: 10108.333333333334,
|
||||
NominalPercentage: 1.0833333333333395,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10200,
|
||||
FullBookSideConsumed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "scotts lovely slippery slippage requirements",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
NominalSlippage: 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000001,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 20000,
|
||||
Purchased: 2,
|
||||
AverageOrderCost: 10000,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for x := range cases {
|
||||
tt := cases[x]
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
depth := NewDepth(id)
|
||||
depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true)
|
||||
quote, err := depth.asks.liftAsksByNominalSlippage(tt.NominalSlippage, tt.ReferencePrice)
|
||||
if !errors.Is(err, tt.ExpectedError) {
|
||||
t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError)
|
||||
}
|
||||
if !quote.IsEqual(tt.ExpectedShift) {
|
||||
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
||||
tt.Name, quote, tt.ExpectedShift)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetQuoteAmountFromImpact(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
Name string
|
||||
ImpactSlippage float64
|
||||
ReferencePrice float64
|
||||
AskLiquidity Items
|
||||
ExpectedShift *Movement
|
||||
ExpectedError error
|
||||
}{
|
||||
{
|
||||
Name: "invalid slippage",
|
||||
ImpactSlippage: -1,
|
||||
ExpectedError: errInvalidImpactSlippage,
|
||||
},
|
||||
{
|
||||
Name: "no reference price",
|
||||
ImpactSlippage: 1,
|
||||
ExpectedError: errInvalidReferencePrice,
|
||||
},
|
||||
{
|
||||
Name: "no liquidity",
|
||||
ImpactSlippage: 1,
|
||||
ReferencePrice: 1000,
|
||||
ExpectedError: errNoLiquidity,
|
||||
},
|
||||
{
|
||||
Name: "thrasher test",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
ImpactSlippage: 1,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 20000,
|
||||
Purchased: 2,
|
||||
AverageOrderCost: 10000,
|
||||
ImpactPercentage: 1,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10100,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "consume first tranche and second tranche",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
ImpactSlippage: 2,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 90700,
|
||||
Purchased: 9,
|
||||
AverageOrderCost: 10077.777777777777777777777777778,
|
||||
ImpactPercentage: 2,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10200,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "consume full liquidity",
|
||||
AskLiquidity: Items{{Price: 10000, Amount: 2}, {Price: 10100, Amount: 7}, {Price: 10200, Amount: 3}},
|
||||
ImpactSlippage: 10,
|
||||
ReferencePrice: 10000,
|
||||
ExpectedShift: &Movement{
|
||||
Sold: 121300,
|
||||
Purchased: 12,
|
||||
AverageOrderCost: 10108.333333333333333333333333333,
|
||||
ImpactPercentage: FullLiquidityExhaustedPercentage,
|
||||
StartPrice: 10000,
|
||||
EndPrice: 10200,
|
||||
FullBookSideConsumed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for x := range cases {
|
||||
tt := cases[x]
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
depth := NewDepth(id)
|
||||
depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true)
|
||||
quote, err := depth.asks.liftAsksByImpactSlippage(tt.ImpactSlippage, tt.ReferencePrice)
|
||||
if !errors.Is(err, tt.ExpectedError) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError)
|
||||
}
|
||||
if !quote.IsEqual(tt.ExpectedShift) {
|
||||
t.Fatalf("%s quote received: '%+v' but expected: '%+v'",
|
||||
tt.Name, quote, tt.ExpectedShift)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHeadPrice(t *testing.T) {
|
||||
t.Parallel()
|
||||
depth := NewDepth(id)
|
||||
if _, err := depth.bids.getHeadPriceNoLock(); !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
if _, err := depth.asks.getHeadPriceNoLock(); !errors.Is(err, errNoLiquidity) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity)
|
||||
}
|
||||
depth.LoadSnapshot(bid, ask, 0, time.Time{}, true)
|
||||
|
||||
val, err := depth.bids.getHeadPriceNoLock()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if val != 1336 {
|
||||
t.Fatal("unexpected value")
|
||||
}
|
||||
|
||||
val, err = depth.asks.getHeadPriceNoLock()
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if val != 1337 {
|
||||
t.Fatal("unexpected value", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalizeFields(t *testing.T) {
|
||||
m := &Movement{}
|
||||
_, err := m.finalizeFields(0, 0, 0, 0, false)
|
||||
if !errors.Is(err, errInvalidCost) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidCost)
|
||||
}
|
||||
_, err = m.finalizeFields(1, 0, 0, 0, false)
|
||||
if !errors.Is(err, errInvalidAmount) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidAmount)
|
||||
}
|
||||
_, err = m.finalizeFields(1, 1, 0, 0, false)
|
||||
if !errors.Is(err, errInvalidHeadPrice) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidHeadPrice)
|
||||
}
|
||||
|
||||
// Test slippage as per https://en.wikipedia.org/wiki/Slippage_(finance)
|
||||
mov, err := m.finalizeFields(20000*151.11585, 20000, 151.08, 0, false)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if mov.SlippageCost != 716.9999999995343 {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", mov.SlippageCost, 716.9999999995343)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,3 +148,40 @@ type Update struct {
|
||||
// only exchange utilising this field.
|
||||
MaxDepth int
|
||||
}
|
||||
|
||||
// Movement defines orderbook traversal details from either hitting the bids or
|
||||
// lifting the asks.
|
||||
type Movement struct {
|
||||
// NominalPercentage (real-world) defines how far in percentage terms is
|
||||
// your average order price away from the reference price.
|
||||
NominalPercentage float64
|
||||
// ImpactPercentage defines how far the price has moved on the order book
|
||||
// from the reference price.
|
||||
ImpactPercentage float64
|
||||
// SlippageCost is the cost of the slippage. This is priced in quotation.
|
||||
SlippageCost float64
|
||||
// StartPrice defines the reference price or the head of the orderbook side.
|
||||
StartPrice float64
|
||||
// EndPrice defines where the price has ended on the orderbook side.
|
||||
EndPrice float64
|
||||
// Sold defines the amount of currency sold.
|
||||
Sold float64
|
||||
// Purchases defines the amount of currency purchased.
|
||||
Purchased float64
|
||||
// AverageOrderCost defines the average order cost of position as it slips
|
||||
// through the orderbook tranches.
|
||||
AverageOrderCost float64
|
||||
// FullBookSideConsumed defines if the orderbook liquidty has been consumed
|
||||
// by the requested amount. This might not represent the actual book on the
|
||||
// exchange as they might restrict the amount of information being passed
|
||||
// back from either a REST request or websocket stream.
|
||||
FullBookSideConsumed bool
|
||||
}
|
||||
|
||||
// SideAmounts define the amounts total for the tranches, total value in
|
||||
// quotation and the cumulative base amounts.
|
||||
type SideAmounts struct {
|
||||
Tranches int64
|
||||
QuoteValue float64
|
||||
BaseAmount float64
|
||||
}
|
||||
|
||||
@@ -12,14 +12,18 @@ import (
|
||||
func TestSimulate(t *testing.T) {
|
||||
b := bitstamp.Bitstamp{}
|
||||
b.SetDefaults()
|
||||
b.Verbose = false
|
||||
o, err := b.FetchOrderbook(context.Background(),
|
||||
currency.NewPair(currency.BTC, currency.USD), asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
r := o.SimulateOrder(10000000, true)
|
||||
t.Log(r.Status)
|
||||
r = o.SimulateOrder(2171, false)
|
||||
t.Log(r.Status)
|
||||
_, err = o.SimulateOrder(10000000, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = o.SimulateOrder(2171, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
5629
gctrpc/rpc.pb.go
5629
gctrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@@ -3293,6 +3293,114 @@ func local_request_GoCryptoTraderService_GetFundingRates_0(ctx context.Context,
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_GoCryptoTraderService_GetOrderbookMovement_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_GoCryptoTraderService_GetOrderbookMovement_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetOrderbookMovementRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTraderService_GetOrderbookMovement_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.GetOrderbookMovement(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_GoCryptoTraderService_GetOrderbookMovement_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetOrderbookMovementRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTraderService_GetOrderbookMovement_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.GetOrderbookMovement(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_GoCryptoTraderService_GetOrderbookAmountByNominal_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetOrderbookAmountByNominalRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTraderService_GetOrderbookAmountByNominal_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.GetOrderbookAmountByNominal(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetOrderbookAmountByNominalRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTraderService_GetOrderbookAmountByNominal_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.GetOrderbookAmountByNominal(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_GoCryptoTraderService_GetOrderbookAmountByImpact_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetOrderbookAmountByImpactRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTraderService_GetOrderbookAmountByImpact_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.GetOrderbookAmountByImpact(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx context.Context, marshaler runtime.Marshaler, server GoCryptoTraderServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq GetOrderbookAmountByImpactRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTraderService_GetOrderbookAmountByImpact_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.GetOrderbookAmountByImpact(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterGoCryptoTraderServiceHandlerServer registers the http handlers for service GoCryptoTraderService to "mux".
|
||||
// UnaryRPC :call GoCryptoTraderServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
@@ -5645,6 +5753,78 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_GoCryptoTraderService_GetOrderbookMovement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookMovement", runtime.WithHTTPPathPattern("/v1/getorderbookmovement"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_GoCryptoTraderService_GetOrderbookMovement_0(ctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookMovement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_GoCryptoTraderService_GetOrderbookAmountByNominal_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal", runtime.WithHTTPPathPattern("/v1/getorderbookamountbynominal"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_GoCryptoTraderService_GetOrderbookAmountByImpact_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact", runtime.WithHTTPPathPattern("/v1/getorderbookamountbyimpact"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7828,6 +8008,69 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_GoCryptoTraderService_GetOrderbookMovement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookMovement", runtime.WithHTTPPathPattern("/v1/getorderbookmovement"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_GoCryptoTraderService_GetOrderbookMovement_0(ctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookMovement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_GoCryptoTraderService_GetOrderbookAmountByNominal_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal", runtime.WithHTTPPathPattern("/v1/getorderbookamountbynominal"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_GoCryptoTraderService_GetOrderbookAmountByImpact_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
var err error
|
||||
ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact", runtime.WithHTTPPathPattern("/v1/getorderbookamountbyimpact"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8035,6 +8278,12 @@ var (
|
||||
pattern_GoCryptoTraderService_GetAllManagedPositions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getallmanagedpositions"}, ""))
|
||||
|
||||
pattern_GoCryptoTraderService_GetFundingRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getfundingrates"}, ""))
|
||||
|
||||
pattern_GoCryptoTraderService_GetOrderbookMovement_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbookmovement"}, ""))
|
||||
|
||||
pattern_GoCryptoTraderService_GetOrderbookAmountByNominal_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbookamountbynominal"}, ""))
|
||||
|
||||
pattern_GoCryptoTraderService_GetOrderbookAmountByImpact_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getorderbookamountbyimpact"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -8241,4 +8490,10 @@ var (
|
||||
forward_GoCryptoTraderService_GetAllManagedPositions_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_GoCryptoTraderService_GetFundingRates_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookMovement_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookAmountByNominal_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_GoCryptoTraderService_GetOrderbookAmountByImpact_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
|
||||
@@ -382,6 +382,7 @@ message WhaleBombRequest {
|
||||
CurrencyPair pair = 2;
|
||||
double price_target = 3;
|
||||
string side = 4;
|
||||
string asset_type = 5;
|
||||
}
|
||||
|
||||
message CancelOrderRequest {
|
||||
@@ -1279,6 +1280,79 @@ message GetMarginRatesHistoryResponse {
|
||||
string taker_fee_rate = 9;
|
||||
}
|
||||
|
||||
message GetOrderbookMovementRequest {
|
||||
string exchange = 1;
|
||||
string asset = 2;
|
||||
CurrencyPair pair = 3;
|
||||
double amount = 6;
|
||||
bool sell = 7;
|
||||
bool requires_rest_orderbook = 8;
|
||||
bool purchase = 9;
|
||||
}
|
||||
|
||||
message GetOrderbookMovementResponse {
|
||||
double nominal_percentage = 1;
|
||||
double impact_percentage = 2;
|
||||
double slippage_cost = 3;
|
||||
string currency_bought = 4;
|
||||
double bought = 5;
|
||||
string currency_sold = 6;
|
||||
double sold = 7;
|
||||
string side_affected = 8;
|
||||
string update_protocol = 9;
|
||||
bool full_orderbook_side_consumed = 10;
|
||||
double start_price = 11;
|
||||
double end_price = 12;
|
||||
bool no_slippage_occurred = 13;
|
||||
double average_order_cost = 14;
|
||||
}
|
||||
|
||||
message GetOrderbookAmountByNominalRequest {
|
||||
string exchange = 1;
|
||||
string asset = 2;
|
||||
CurrencyPair pair = 3;
|
||||
double nominal_percentage = 6;
|
||||
bool sell = 7;
|
||||
bool requires_rest_orderbook = 8;
|
||||
}
|
||||
|
||||
message GetOrderbookAmountByNominalResponse {
|
||||
double amount_required = 1;
|
||||
string currency_selling = 2;
|
||||
double amount_received = 3;
|
||||
string currency_buying = 4;
|
||||
double start_price = 5;
|
||||
double end_price = 6;
|
||||
double average_order_cost = 7;
|
||||
string side_affected = 8;
|
||||
double approximate_nominal_slippage_percentage = 9;
|
||||
string update_protocol = 10;
|
||||
bool full_orderbook_side_consumed = 11;
|
||||
}
|
||||
|
||||
message GetOrderbookAmountByImpactRequest {
|
||||
string exchange = 1;
|
||||
string asset = 2;
|
||||
CurrencyPair pair = 3;
|
||||
double impact_percentage = 6;
|
||||
bool sell = 7;
|
||||
bool requires_rest_orderbook = 8;
|
||||
}
|
||||
|
||||
message GetOrderbookAmountByImpactResponse {
|
||||
double amount_required = 1;
|
||||
string currency_selling = 2;
|
||||
double amount_received = 3;
|
||||
string currency_buying = 4;
|
||||
double start_price = 5;
|
||||
double end_price = 6;
|
||||
double average_order_cost = 7;
|
||||
string side_affected = 8;
|
||||
double approximate_impact_slippage_percentage = 9;
|
||||
string update_protocol = 10;
|
||||
bool full_orderbook_side_consumed = 11;
|
||||
}
|
||||
|
||||
service GoCryptoTraderService {
|
||||
rpc GetInfo(GetInfoRequest) returns (GetInfoResponse) {
|
||||
option (google.api.http) = {
|
||||
@@ -1907,4 +1981,19 @@ service GoCryptoTraderService {
|
||||
get: "/v1/getfundingrates"
|
||||
};
|
||||
}
|
||||
rpc GetOrderbookMovement(GetOrderbookMovementRequest) returns (GetOrderbookMovementResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/getorderbookmovement"
|
||||
};
|
||||
}
|
||||
rpc GetOrderbookAmountByNominal(GetOrderbookAmountByNominalRequest) returns (GetOrderbookAmountByNominalResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/getorderbookamountbynominal"
|
||||
};
|
||||
}
|
||||
rpc GetOrderbookAmountByImpact(GetOrderbookAmountByImpactRequest) returns (GetOrderbookAmountByImpactResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/getorderbookamountbyimpact"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2556,6 +2556,231 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/getorderbookamountbyimpact": {
|
||||
"get": {
|
||||
"operationId": "GoCryptoTraderService_GetOrderbookAmountByImpact",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gctrpcGetOrderbookAmountByImpactResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "exchange",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "asset",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.delimiter",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.base",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.quote",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "impactPercentage",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
{
|
||||
"name": "sell",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "requiresRestOrderbook",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"GoCryptoTraderService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/getorderbookamountbynominal": {
|
||||
"get": {
|
||||
"operationId": "GoCryptoTraderService_GetOrderbookAmountByNominal",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gctrpcGetOrderbookAmountByNominalResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "exchange",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "asset",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.delimiter",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.base",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.quote",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "nominalPercentage",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
{
|
||||
"name": "sell",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "requiresRestOrderbook",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"GoCryptoTraderService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/getorderbookmovement": {
|
||||
"get": {
|
||||
"operationId": "GoCryptoTraderService_GetOrderbookMovement",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gctrpcGetOrderbookMovementResponse"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "exchange",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "asset",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.delimiter",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.base",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "pair.quote",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
{
|
||||
"name": "sell",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "requiresRestOrderbook",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "purchase",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"GoCryptoTraderService"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v1/getorderbooks": {
|
||||
"get": {
|
||||
"operationId": "GoCryptoTraderService_GetOrderbooks",
|
||||
@@ -5527,6 +5752,149 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"gctrpcGetOrderbookAmountByImpactResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amountRequired": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"currencySelling": {
|
||||
"type": "string"
|
||||
},
|
||||
"amountReceived": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"currencyBuying": {
|
||||
"type": "string"
|
||||
},
|
||||
"startPrice": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"endPrice": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"averageOrderCost": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"sideAffected": {
|
||||
"type": "string"
|
||||
},
|
||||
"approximateImpactSlippagePercentage": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"updateProtocol": {
|
||||
"type": "string"
|
||||
},
|
||||
"fullOrderbookSideConsumed": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gctrpcGetOrderbookAmountByNominalResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amountRequired": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"currencySelling": {
|
||||
"type": "string"
|
||||
},
|
||||
"amountReceived": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"currencyBuying": {
|
||||
"type": "string"
|
||||
},
|
||||
"startPrice": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"endPrice": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"averageOrderCost": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"sideAffected": {
|
||||
"type": "string"
|
||||
},
|
||||
"approximateNominalSlippagePercentage": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"updateProtocol": {
|
||||
"type": "string"
|
||||
},
|
||||
"fullOrderbookSideConsumed": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gctrpcGetOrderbookMovementResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nominalPercentage": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"impactPercentage": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"slippageCost": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"currencyBought": {
|
||||
"type": "string"
|
||||
},
|
||||
"bought": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"currencySold": {
|
||||
"type": "string"
|
||||
},
|
||||
"sold": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"sideAffected": {
|
||||
"type": "string"
|
||||
},
|
||||
"updateProtocol": {
|
||||
"type": "string"
|
||||
},
|
||||
"fullOrderbookSideConsumed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"startPrice": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"endPrice": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"noSlippageOccurred": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"averageOrderCost": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gctrpcGetOrderbookRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6468,6 +6836,9 @@
|
||||
},
|
||||
"side": {
|
||||
"type": "string"
|
||||
},
|
||||
"assetType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -124,6 +124,9 @@ type GoCryptoTraderServiceClient interface {
|
||||
GetManagedPosition(ctx context.Context, in *GetManagedPositionRequest, opts ...grpc.CallOption) (*GetManagedPositionsResponse, error)
|
||||
GetAllManagedPositions(ctx context.Context, in *GetAllManagedPositionsRequest, opts ...grpc.CallOption) (*GetManagedPositionsResponse, error)
|
||||
GetFundingRates(ctx context.Context, in *GetFundingRatesRequest, opts ...grpc.CallOption) (*GetFundingRatesResponse, error)
|
||||
GetOrderbookMovement(ctx context.Context, in *GetOrderbookMovementRequest, opts ...grpc.CallOption) (*GetOrderbookMovementResponse, error)
|
||||
GetOrderbookAmountByNominal(ctx context.Context, in *GetOrderbookAmountByNominalRequest, opts ...grpc.CallOption) (*GetOrderbookAmountByNominalResponse, error)
|
||||
GetOrderbookAmountByImpact(ctx context.Context, in *GetOrderbookAmountByImpactRequest, opts ...grpc.CallOption) (*GetOrderbookAmountByImpactResponse, error)
|
||||
}
|
||||
|
||||
type goCryptoTraderServiceClient struct {
|
||||
@@ -1190,6 +1193,33 @@ func (c *goCryptoTraderServiceClient) GetFundingRates(ctx context.Context, in *G
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *goCryptoTraderServiceClient) GetOrderbookMovement(ctx context.Context, in *GetOrderbookMovementRequest, opts ...grpc.CallOption) (*GetOrderbookMovementResponse, error) {
|
||||
out := new(GetOrderbookMovementResponse)
|
||||
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTraderService/GetOrderbookMovement", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *goCryptoTraderServiceClient) GetOrderbookAmountByNominal(ctx context.Context, in *GetOrderbookAmountByNominalRequest, opts ...grpc.CallOption) (*GetOrderbookAmountByNominalResponse, error) {
|
||||
out := new(GetOrderbookAmountByNominalResponse)
|
||||
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *goCryptoTraderServiceClient) GetOrderbookAmountByImpact(ctx context.Context, in *GetOrderbookAmountByImpactRequest, opts ...grpc.CallOption) (*GetOrderbookAmountByImpactResponse, error) {
|
||||
out := new(GetOrderbookAmountByImpactResponse)
|
||||
err := c.cc.Invoke(ctx, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// GoCryptoTraderServiceServer is the server API for GoCryptoTraderService service.
|
||||
// All implementations must embed UnimplementedGoCryptoTraderServiceServer
|
||||
// for forward compatibility
|
||||
@@ -1296,6 +1326,9 @@ type GoCryptoTraderServiceServer interface {
|
||||
GetManagedPosition(context.Context, *GetManagedPositionRequest) (*GetManagedPositionsResponse, error)
|
||||
GetAllManagedPositions(context.Context, *GetAllManagedPositionsRequest) (*GetManagedPositionsResponse, error)
|
||||
GetFundingRates(context.Context, *GetFundingRatesRequest) (*GetFundingRatesResponse, error)
|
||||
GetOrderbookMovement(context.Context, *GetOrderbookMovementRequest) (*GetOrderbookMovementResponse, error)
|
||||
GetOrderbookAmountByNominal(context.Context, *GetOrderbookAmountByNominalRequest) (*GetOrderbookAmountByNominalResponse, error)
|
||||
GetOrderbookAmountByImpact(context.Context, *GetOrderbookAmountByImpactRequest) (*GetOrderbookAmountByImpactResponse, error)
|
||||
mustEmbedUnimplementedGoCryptoTraderServiceServer()
|
||||
}
|
||||
|
||||
@@ -1609,6 +1642,15 @@ func (UnimplementedGoCryptoTraderServiceServer) GetAllManagedPositions(context.C
|
||||
func (UnimplementedGoCryptoTraderServiceServer) GetFundingRates(context.Context, *GetFundingRatesRequest) (*GetFundingRatesResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetFundingRates not implemented")
|
||||
}
|
||||
func (UnimplementedGoCryptoTraderServiceServer) GetOrderbookMovement(context.Context, *GetOrderbookMovementRequest) (*GetOrderbookMovementResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetOrderbookMovement not implemented")
|
||||
}
|
||||
func (UnimplementedGoCryptoTraderServiceServer) GetOrderbookAmountByNominal(context.Context, *GetOrderbookAmountByNominalRequest) (*GetOrderbookAmountByNominalResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetOrderbookAmountByNominal not implemented")
|
||||
}
|
||||
func (UnimplementedGoCryptoTraderServiceServer) GetOrderbookAmountByImpact(context.Context, *GetOrderbookAmountByImpactRequest) (*GetOrderbookAmountByImpactResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetOrderbookAmountByImpact not implemented")
|
||||
}
|
||||
func (UnimplementedGoCryptoTraderServiceServer) mustEmbedUnimplementedGoCryptoTraderServiceServer() {}
|
||||
|
||||
// UnsafeGoCryptoTraderServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
@@ -3476,6 +3518,60 @@ func _GoCryptoTraderService_GetFundingRates_Handler(srv interface{}, ctx context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _GoCryptoTraderService_GetOrderbookMovement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetOrderbookMovementRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(GoCryptoTraderServiceServer).GetOrderbookMovement(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gctrpc.GoCryptoTraderService/GetOrderbookMovement",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(GoCryptoTraderServiceServer).GetOrderbookMovement(ctx, req.(*GetOrderbookMovementRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _GoCryptoTraderService_GetOrderbookAmountByNominal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetOrderbookAmountByNominalRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(GoCryptoTraderServiceServer).GetOrderbookAmountByNominal(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(GoCryptoTraderServiceServer).GetOrderbookAmountByNominal(ctx, req.(*GetOrderbookAmountByNominalRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _GoCryptoTraderService_GetOrderbookAmountByImpact_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetOrderbookAmountByImpactRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(GoCryptoTraderServiceServer).GetOrderbookAmountByImpact(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(GoCryptoTraderServiceServer).GetOrderbookAmountByImpact(ctx, req.(*GetOrderbookAmountByImpactRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// GoCryptoTraderService_ServiceDesc is the grpc.ServiceDesc for GoCryptoTraderService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -3867,6 +3963,18 @@ var GoCryptoTraderService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetFundingRates",
|
||||
Handler: _GoCryptoTraderService_GetFundingRates_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetOrderbookMovement",
|
||||
Handler: _GoCryptoTraderService_GetOrderbookMovement_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetOrderbookAmountByNominal",
|
||||
Handler: _GoCryptoTraderService_GetOrderbookAmountByNominal_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetOrderbookAmountByImpact",
|
||||
Handler: _GoCryptoTraderService_GetOrderbookAmountByImpact_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user