-
Notifications
You must be signed in to change notification settings - Fork 902
gctcli: Improve Order Prompts & Struct Handling #1965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
… into gctcli_update
… added unit tests
… into gctcli_update
shazbert
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a suggestion for everybody.
cmd/gctcli/commands.go
Outdated
| Name: "amount", | ||
| Usage: "the amount for the order", | ||
| }, | ||
| &cli.StringFlag{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before I do a full review I have one suggestion which will need to be agreed by everyone. I am having a real hard time justiying the amount of toil when doing stuff like this and also tracking arg position it just adds in so much bloat. Suggestion we use structs with tags to define cli flags which will cut down time on implementation. cli tags can be updated with required and usage flags in future. @thrasher- @gbjk
var submitOrderCommand = &cli.Command{
Name: "submitorder",
Usage: "submit order submits an exchange order",
ArgsUsage: "<exchange> <pair> <side> <type> <amount> <price> <client_id>",
Action: submitOrder,
Flags: flagsFromStruct(submitOrderRequest),
}
type SubmitOrderParams struct {
Exchange string `cli:"exchange"`
Pair string `cli:"pair"`
Side string `cli:"side"`
Type string `cli:"type"`
Amount float64 `cli:"amount"`
Asset string `cli:"asset"`
Price float64 `cli:"price"`
Leverage float64 `cli:"leverage"`
ClientOrderID string `cli:"client_order_id"`
MarginType string `cli:"margin_type"`
TimeInForce string `cli:"time_in_force"`
QuoteAmount float64 `cli:"quote_amount"`
ClientID string `cli:"client_id"`
TriggerPrice float64 `cli:"trigger_price"`
TriggerLimitPrice float64 `cli:"trigger_limit_price"`
TriggerPriceType string `cli:"trigger_price_type"`
TpPrice float64 `cli:"tp_price"`
TpLimitPrice float64 `cli:"tp_limit_price"`
TpPriceType string `cli:"tp_price_type"`
SlPrice float64 `cli:"sl_price"`
SlLimitPrice float64 `cli:"sl_limit_price"`
SlPriceType string `cli:"sl_price_type"`
TrackingMode string `cli:"tracking_mode"`
TrackingValue float64 `cli:"tracking_value"`
Hidden bool `cli:"hidden"`
Iceberg bool `cli:"iceberg"`
AutoBorrow bool `cli:"auto_borrow"`
ReduceOnly bool `cli:"reduce_only"`
RetrieveFees bool `cli:"retrieve_fees"`
RetrieveFeeDelayMs int64 `cli:"retrieve_fee_delay_ms"`
}
var submitOrderRequest = &SubmitOrderParams{}
func flagsFromStruct(params any) []cli.Flag {
var flags []cli.Flag
val := reflect.ValueOf(params).Elem()
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
flagName := field.Tag.Get("cli")
if flagName == "" {
continue
}
switch field.Type.Kind() {
case reflect.String:
flags = append(flags, &cli.StringFlag{
Name: flagName,
Usage: "the " + flagName + " for the order",
})
case reflect.Float64:
flags = append(flags, &cli.Float64Flag{
Name: flagName,
Usage: "the " + flagName + " for the order",
})
case reflect.Bool:
flags = append(flags, &cli.BoolFlag{
Name: flagName,
Usage: "the " + flagName + " for the order",
})
case reflect.Int64:
flags = append(flags, &cli.Int64Flag{
Name: flagName,
Usage: "the " + flagName + " for the order",
})
}
}
return flags
}
func submitOrder(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
assetType := strings.ToLower(submitOrderRequest.Asset)
if !validAsset(assetType) {
return errInvalidAsset
}
marginType := strings.ToLower(submitOrderRequest.MarginType)
if !margin.IsValidString(marginType) {
return margin.ErrInvalidMarginType
}
p, err := currency.NewPairDelimiter(submitOrderRequest.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.SubmitOrder(c.Context, &gctrpc.SubmitOrderRequest{
Exchange: submitOrderRequest.Exchange,
Pair: &gctrpc.CurrencyPair{
Delimiter: p.Delimiter,
Base: p.Base.String(),
Quote: p.Quote.String(),
},
Amount: submitOrderRequest.Amount,
Price: submitOrderRequest.Price,
Leverage: submitOrderRequest.Leverage,
Side: submitOrderRequest.Side,
OrderType: submitOrderRequest.Type,
AssetType: assetType,
MarginType: marginType,
ClientId: submitOrderRequest.ClientID,
ClientOrderId: submitOrderRequest.ClientOrderID,
QuoteAmount: submitOrderRequest.QuoteAmount,
TimeInForce: submitOrderRequest.TimeInForce,
TriggerPrice: submitOrderRequest.TriggerPrice,
TriggerPriceType: submitOrderRequest.TriggerPriceType,
TriggerLimitPrice: submitOrderRequest.TriggerLimitPrice,
StopLoss: &gctrpc.RiskManagement{
Price: submitOrderRequest.SlPrice,
LimitPrice: submitOrderRequest.SlLimitPrice,
PriceType: submitOrderRequest.SlPriceType,
},
TakeProfit: &gctrpc.RiskManagement{
Price: submitOrderRequest.TpPrice,
LimitPrice: submitOrderRequest.TpLimitPrice,
PriceType: submitOrderRequest.TpPriceType,
},
Hidden: submitOrderRequest.Hidden,
Iceberg: submitOrderRequest.Iceberg,
ReduceOnly: submitOrderRequest.ReduceOnly,
AutoBorrow: submitOrderRequest.AutoBorrow,
RetrieveFees: submitOrderRequest.RetrieveFees,
RetrieveFeeDelayMs: submitOrderRequest.RetrieveFeeDelayMs,
TrackingMode: submitOrderRequest.TrackingMode,
TrackingValue: submitOrderRequest.TrackingValue,
})
if err != nil {
return err
}
jsonOutput(result)
return nil
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to set destination for flag can do something like this
Destination: reflect.ValueOf(params).Elem().FieldByName(field.Name).Addr().Interface().(*string), // For strings etcThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thrasher- @gbjk There seems to be no prior agreement on direction here and this was pushed through, when you get some time could you quickly review?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had applied this change with different additions. Could you please look at it and let me know your thoughts?
… into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
shazbert
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes, definately consolidates the code a bit, suggestions and requires a go mod tidy.
go.sum
Outdated
| github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= | ||
| github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= | ||
| github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZt8+kYaqUisuuIGTk= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to run go mod tidy
cmd/gctcli/commands.go
Outdated
| } | ||
|
|
||
| // UnmarshalCLIFields unmarshals field values from command line as *cli.Context to an interface. | ||
| func UnmarshalCLIFields(c *cli.Context, params any) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: If you set a global you don't need to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify what you mean by this comment?
We want to move away from globals, don't we?
cmd/gctcli/commands.go
Outdated
| } | ||
|
|
||
| // FlagsFromStruct returns list of cli flags from exported flags | ||
| func FlagsFromStruct(params any, usageInfo map[string]string) []cli.Flag { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: can use specific tags for field.Tag.Get as name,usage, required for field and set destination for value, if no usage can set up a global map for default values if no name default to using the field name and try to standardise field names.
… into gctcli_update
… into gctcli_update
cmd/gctcli/commands.go
Outdated
| return errInvalidAsset | ||
| in = &gctrpc.WithdrawalEventsByExchangeRequest{ | ||
| Exchange: arg.Exchange, | ||
| Limit: int32(arg.Limit), //nolint:gosec // TODO: SQL boiler's QueryMode limit only accepts the int type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧
- Pretty sure we mean
QueryMod - Confused why we say int type, but then give it int32. Especially since it accepts int.
- Can you check if this would work with int? Seems like we're going through the client first anyway...
🌴 Please make this change in similar comments below too
| Limit: int32(arg.Limit), //nolint:gosec // TODO: SQL boiler's QueryMode limit only accepts the int type | |
| Limit: int32(arg.Limit), //nolint:gosec // TODO: SQL boiler's QueryMod limit only accepts the int type |
cmd/gctcli/commands.go
Outdated
| } | ||
|
|
||
| // UnmarshalCLIFields unmarshals field values from command line as *cli.Context to an interface. | ||
| func UnmarshalCLIFields(c *cli.Context, params any) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify what you mean by this comment?
We want to move away from globals, don't we?
gbjk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for those changes
I've been considering the impact radius of our CLI processing here.
I've used viper to handle automatic flags and config files with merge-folding of defaults, user config, and test config from files, and then overlaying env vars and flags.
It doesn't need any scaffolding with tags, but it also doesn't handle requires or usage for you. OTOH, cli doesn't handle it for us either, so it's no worse off.
I guess the first question would be: Would the import list of viper be acceptable to even consider it. If it's not, then closed-case.
Sorry - If I was you I wouldn't want someone throwing this gasoline on my PR 🔥
| flagNames := strings.Split(field.Tag.Get("name"), ",") | ||
| if len(flagNames) == 0 || flagNames[0] == "" { | ||
| continue | ||
| } | ||
| flagName := flagNames[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧 Looking at the amount of code we have to support them, and and the amount of definitions, could we just drop the name entirely, use the field name?
This assumes we can strip - out of params coming in to match them, which I'm expecting we could.
It feels like that'd make all the multiple name and shortened name complexity go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If what we need is to have a single name that would make sense, and I support that. However, the current implementation allows multiple alias names, and using a structure that supports this—such as the name tag—is the most appropriate approach.
By the way, Not all field Names and cli tag name are similar.
cmd/gctcli/commands_types.go
Outdated
| RetrieveFeeDelayMs int64 `name:"retrieve_fee_delay_ms"` | ||
| } | ||
|
|
||
| // GetOrderParams defines command-line flags for exchange order retrieval and unmarshal their values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐒 🌴 This concept applies throughout... exchange XYZ sounds weird
| // GetOrderParams defines command-line flags for exchange order retrieval and unmarshal their values. | |
| // GetOrderParams defines command-line flags for retrieving exchange orders |
cmd/gctcli/futures.go
Outdated
| if !validPair(currencyPair) { | ||
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, currencyPair) | ||
| if !validPair(arg.Pair) { | ||
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) | |
| return fmt.Errorf("%w: %q", errInvalidPair, arg.Pair) |
cmd/gctcli/futures.go
Outdated
| if !validPair(currencyPair) { | ||
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, currencyPair) | ||
| if !validPair(arg.Pair) { | ||
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) | |
| return fmt.Errorf("%w: %q", errInvalidPair, arg.Pair) |
cmd/gctcli/futures.go
Outdated
| if !validPair(currencyPair) { | ||
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, currencyPair) | ||
| if !validPair(arg.Pair) { | ||
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) | |
| return fmt.Errorf("%w: %q", errInvalidPair, arg.Pair) |
cmd/gctcli/futures.go
Outdated
| if !margin.IsValidString(marginType) { | ||
| return fmt.Errorf("%w margintype:%v", margin.ErrInvalidMarginType, marginType) | ||
| if !margin.IsValidString(arg.MarginType) { | ||
| return fmt.Errorf("%w margintype:%v", margin.ErrInvalidMarginType, arg.MarginType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧
| return fmt.Errorf("%w margintype:%v", margin.ErrInvalidMarginType, arg.MarginType) | |
| return fmt.Errorf("%w: %q", margin.ErrInvalidMarginType, arg.MarginType) |
cmd/gctcli/futures.go
Outdated
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, currencyPair) | ||
| if arg.Pair != "" { | ||
| if !validPair(arg.Pair) { | ||
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚧
| return fmt.Errorf("%w currencypair:%v", errInvalidPair, arg.Pair) | |
| return fmt.Errorf("%w: %q", errInvalidPair, arg.Pair) |
… into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
This reverts commit 1de0f6b.
…ader; branch 'master' of http://github.com/thrasher-corp/gocryptotrader into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
… into gctcli_update
Shaloooooom 🕊️ ፐeam
This PR improves the
gctcliuser prompts for order submission, modification, and cancellation. Key updates include:Submit,Modify, andCancelstructs for improved efficiency.PriceTypeandTrackingMode.Fixes # (issue)
Type of change
Please delete options that are not relevant and add an
xin[]as item is complete.How has this been tested
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration and
also consider improving test coverage whilst working on a certain feature or package.
Checklist