gctcli: Add colourful exchange-style rendering to orderbook fetching commands (optional) (#1348)

* fancybook

* fix bug

* oopsie-doodle

* now I remember why we don't use required
This commit is contained in:
Scott
2023-09-14 10:10:22 +10:00
committed by GitHub
parent 3218982b3a
commit bcabf44b8c
3 changed files with 171 additions and 60 deletions

View File

@@ -10,6 +10,15 @@ import (
"google.golang.org/grpc"
)
var (
// use these to change text colours in CMD output
redText = "\033[38;5;203m"
greenText = "\033[38;5;157m"
whiteText = "\033[38;5;255m"
grayText = "\033[38;5;243m"
defaultText = "\u001b[0m"
)
func clearScreen() error {
switch runtime.GOOS {
case "windows":

View File

@@ -5,7 +5,9 @@ import (
"fmt"
"strconv"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/gctrpc"
"github.com/urfave/cli/v2"
@@ -352,22 +354,17 @@ func getMovement(c *cli.Context) error {
var getOrderbookCommand = &cli.Command{
Name: "getorderbook",
Usage: "gets the orderbook for a specific currency pair and exchange",
ArgsUsage: "<exchange> <pair> <asset>",
ArgsUsage: "<exchange> <pair> <asset> <exchangestyle> <depthlimit>",
Action: getOrderbook,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "exchange",
Usage: "the exchange to get the orderbook for",
Flags: append(orderbookCommonFlags,
&cli.BoolFlag{
Name: "exchangestyle",
Usage: "optional - renders the books like on an exchange website",
},
&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",
},
},
&cli.Int64Flag{
Name: "depthlimit",
Usage: "optional - limit how deep the book rendering is, max 100 - only works if exchangestyle is true",
}),
}
func getOrderbook(c *cli.Context) error {
@@ -375,9 +372,12 @@ func getOrderbook(c *cli.Context) error {
return cli.ShowSubcommandHelp(c)
}
var exchangeName string
var currencyPair string
var assetType string
var (
exchangeName, pair, assetType string
depthLimit int64
exchangeStyle bool
err error
)
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
@@ -386,12 +386,12 @@ func getOrderbook(c *cli.Context) error {
}
if c.IsSet("pair") {
currencyPair = c.String("pair")
pair = c.String("pair")
} else {
currencyPair = c.Args().Get(1)
pair = c.Args().Get(1)
}
if !validPair(currencyPair) {
if !validPair(pair) {
return errInvalidPair
}
@@ -401,12 +401,30 @@ func getOrderbook(c *cli.Context) error {
assetType = c.Args().Get(2)
}
if c.IsSet("exchangestyle") {
exchangeStyle = c.Bool("exchangestyle")
} else if c.Args().Get(3) != "" {
exchangeStyle, err = strconv.ParseBool(c.Args().Get(3))
if err != nil {
return err
}
}
if c.IsSet("depthlimit") {
depthLimit = c.Int64("depthlimit")
} else if c.Args().Get(4) != "" {
depthLimit, err = strconv.ParseInt(c.Args().Get(4), 10, 64)
if err != nil {
return err
}
}
assetType = strings.ToLower(assetType)
if !validAsset(assetType) {
return errInvalidAsset
}
p, err := currency.NewPairDelimiter(currencyPair, pairDelimiter)
p, err := currency.NewPairDelimiter(pair, pairDelimiter)
if err != nil {
return err
}
@@ -434,7 +452,25 @@ func getOrderbook(c *cli.Context) error {
return err
}
jsonOutput(result)
if exchangeStyle {
var maxLen, bidLen, askLen int64
bidLen = int64(len(result.Bids) - 1)
askLen = int64(len(result.Asks) - 1)
if bidLen >= askLen {
maxLen = bidLen
} else {
maxLen = askLen
}
if depthLimit > 0 && depthLimit < maxLen {
maxLen = depthLimit
}
if maxLen > 100 {
maxLen = 100
}
renderOrderbookExchangeStyle(result, exchangeName, assetType, maxLen, askLen, bidLen)
} else {
jsonOutput(result)
}
return nil
}
@@ -464,9 +500,17 @@ func getOrderbooks(c *cli.Context) error {
var getOrderbookStreamCommand = &cli.Command{
Name: "getorderbookstream",
Usage: "gets the orderbook stream for a specific currency pair and exchange",
ArgsUsage: "<exchange> <pair> <asset>",
ArgsUsage: "<exchange> <pair> <asset> <exchangestyle> <depthlimit>",
Action: getOrderbookStream,
Flags: orderbookCommonFlags,
Flags: append(orderbookCommonFlags,
&cli.BoolFlag{
Name: "exchangestyle",
Usage: "optional - renders the books like on an exchange website",
},
&cli.Int64Flag{
Name: "depthlimit",
Usage: "optional - limit how deep the book rendering is, max 50",
}),
}
func getOrderbookStream(c *cli.Context) error {
@@ -474,9 +518,12 @@ func getOrderbookStream(c *cli.Context) error {
return cli.ShowSubcommandHelp(c)
}
var exchangeName string
var pair string
var assetType string
var (
exchangeName, pair, assetType string
depthLimit int64
exchangeStyle bool
err error
)
if c.IsSet("exchange") {
exchangeName = c.String("exchange")
@@ -500,6 +547,24 @@ func getOrderbookStream(c *cli.Context) error {
assetType = c.Args().Get(2)
}
if c.IsSet("exchangestyle") {
exchangeStyle = c.Bool("exchangestyle")
} else if c.Args().Get(3) != "" {
exchangeStyle, err = strconv.ParseBool(c.Args().Get(3))
if err != nil {
return err
}
}
if c.IsSet("depthlimit") {
depthLimit = c.Int64("depthlimit")
} else if c.Args().Get(4) != "" {
depthLimit, err = strconv.ParseInt(c.Args().Get(4), 10, 64)
if err != nil {
return err
}
}
assetType = strings.ToLower(assetType)
if !validAsset(assetType) {
@@ -545,56 +610,91 @@ func getOrderbookStream(c *cli.Context) error {
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 := int64(len(resp.Bids) - 1)
askLen := int64(len(resp.Asks) - 1)
bidLen := len(resp.Bids) - 1
askLen := len(resp.Asks) - 1
var maxLen int
var maxLen int64
if bidLen >= askLen {
maxLen = bidLen
} else {
maxLen = askLen
}
if depthLimit > 0 && depthLimit < maxLen {
maxLen = depthLimit
}
if maxLen > 50 {
maxLen = 50
}
for i := 0; i < maxLen; i++ {
var bidAmount, bidPrice float64
if i <= bidLen {
bidAmount = resp.Bids[i].Amount
bidPrice = resp.Bids[i].Price
}
if exchangeStyle {
renderOrderbookExchangeStyle(resp, exchangeName, assetType, maxLen, askLen, bidLen)
} else {
fmt.Printf("Orderbook stream for %s %s:\n\n", exchangeName, resp.Pair)
fmt.Println("\t\tBids\t\t\t\tAsks")
fmt.Println()
var askAmount, askPrice float64
if i <= askLen {
askAmount = resp.Asks[i].Amount
askPrice = resp.Asks[i].Price
}
for i := int64(0); i < maxLen; i++ {
var bidAmount, bidPrice float64
if i <= bidLen {
bidAmount = resp.Bids[i].Amount
bidPrice = resp.Bids[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)
var askAmount, askPrice float64
if i <= askLen {
askAmount = resp.Asks[i].Amount
askPrice = resp.Asks[i].Price
}
if i >= 49 {
// limits orderbook display output
break
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)
}
}
}
}
func renderOrderbookExchangeStyle(resp *gctrpc.OrderbookResponse, exchangeName, assetType string, maxLen, askLen, bidLen int64) {
maxLen-- // ensure we get the 0 index at the correct max length
upperBase := strings.ToUpper(resp.Pair.Base)
upperQuote := strings.ToUpper(resp.Pair.Quote)
printFmt := "%s%.8f\t\t%.8f\n"
fmt.Printf("%sOrderbook stream for %v %v %v - Last updated %v\n",
whiteText, strings.ToUpper(exchangeName), assetType, upperBase+"-"+upperQuote, time.UnixMicro(resp.LastUpdated).Format(common.SimpleTimeFormatWithTimezone))
fmt.Printf("%sPrice(%v)\t\tAmount(%s)\n",
grayText, upperQuote, upperBase)
for i := maxLen; i >= 0; i-- {
var askAmount, askPrice float64
if i <= askLen {
askAmount = resp.Asks[i].Amount
askPrice = resp.Asks[i].Price
}
fmt.Printf(printFmt, redText, askPrice, askAmount)
}
fmt.Println()
for i := int64(0); i <= maxLen; i++ {
var bidAmount, bidPrice float64
if i <= bidLen {
bidAmount = resp.Bids[i].Amount
bidPrice = resp.Bids[i].Price
}
fmt.Printf(printFmt, greenText, bidPrice, bidAmount)
}
fmt.Println(defaultText)
}
var getExchangeOrderbookStreamCommand = &cli.Command{
Name: "getexchangeorderbookstream",
Usage: "gets a stream for all orderbooks associated with an exchange",
@@ -647,7 +747,7 @@ func getExchangeOrderbookStream(c *cli.Context) error {
return err
}
fmt.Printf("Orderbook streamed for %s %s", exchangeName, resp.Pair)
fmt.Printf("Orderbook streamed for %s %s at %s", exchangeName, resp.Pair, time.UnixMicro(resp.LastUpdated).Format(common.SimpleTimeFormatWithTimezone))
if resp.Error != "" {
fmt.Printf("%s\n", resp.Error)
}

View File

@@ -2141,8 +2141,9 @@ func (s *RPCServer) GetOrderbookStream(r *gctrpc.GetOrderbookStreamRequest, stre
base, err := depth.Retrieve()
if err != nil {
resp.Error = err.Error()
resp.LastUpdated = time.Now().Unix()
resp.LastUpdated = time.Now().UnixMicro()
} else {
resp.LastUpdated = base.LastUpdated.UnixMicro()
resp.Bids = make([]*gctrpc.OrderbookItem, len(base.Bids))
for i := range base.Bids {
resp.Bids[i] = &gctrpc.OrderbookItem{
@@ -2204,8 +2205,9 @@ func (s *RPCServer) GetExchangeOrderbookStream(r *gctrpc.GetExchangeOrderbookStr
ob, err := d.Retrieve()
if err != nil {
resp.Error = err.Error()
resp.LastUpdated = time.Now().Unix()
resp.LastUpdated = time.Now().UnixMicro()
} else {
resp.LastUpdated = ob.LastUpdated.UnixMicro()
resp.Pair = &gctrpc.CurrencyPair{
Base: ob.Pair.Base.String(),
Quote: ob.Pair.Quote.String(),