mirror of
https://github.com/c9s/bbgo.git
synced 2024-09-20 08:11:08 +00:00
Merge pull request #461 from c9s/c9s/refactor-ftx
REFACTOR: re-implement ftxapi with requestgen and support query order status API
This commit is contained in:
commit
83cc2d96e5
6
Makefile
6
Makefile
|
@ -131,7 +131,11 @@ pkg/version/dev.go: .FORCE
|
|||
dev-version: pkg/version/dev.go
|
||||
git commit $< -m "update dev build version"
|
||||
|
||||
version: pkg/version/version.go pkg/version/dev.go migrations
|
||||
cmd-doc: .FORCE
|
||||
go run ./cmd/update-doc
|
||||
git add -v doc/commands
|
||||
|
||||
version: pkg/version/version.go pkg/version/dev.go migrations cmd-doc
|
||||
git commit $< $(word 2,$^) -m "bump version to $(VERSION)" || true
|
||||
[[ -e doc/release/$(VERSION).md ]] || (echo "file doc/release/$(VERSION).md does not exist" ; exit 1)
|
||||
git add -v doc/release/$(VERSION).md && git commit doc/release/$(VERSION).md -m "add $(VERSION) release note" || true
|
||||
|
|
|
@ -39,6 +39,7 @@ bbgo [flags]
|
|||
* [bbgo cancel-order](bbgo_cancel-order.md) - cancel orders
|
||||
* [bbgo deposits](bbgo_deposits.md) - A testing utility that will query deposition history in last 7 days
|
||||
* [bbgo execute-order](bbgo_execute-order.md) - execute buy/sell on the balance/position you have on specific symbol
|
||||
* [bbgo get-order](bbgo_get-order.md) - Get order status
|
||||
* [bbgo kline](bbgo_kline.md) - connect to the kline market data streaming service of an exchange
|
||||
* [bbgo list-orders](bbgo_list-orders.md) - list user's open orders in exchange of a specific trading pair
|
||||
* [bbgo market](bbgo_market.md) - List the symbols that the are available to be traded in the exchange
|
||||
|
@ -46,7 +47,7 @@ bbgo [flags]
|
|||
* [bbgo orderupdate](bbgo_orderupdate.md) - Listen to order update events
|
||||
* [bbgo pnl](bbgo_pnl.md) - pnl calculator
|
||||
* [bbgo run](bbgo_run.md) - run strategies from config file
|
||||
* [bbgo submit-order](bbgo_submit-order.md) - submit limit order to the exchange
|
||||
* [bbgo submit-order](bbgo_submit-order.md) - place limit order to the exchange
|
||||
* [bbgo sync](bbgo_sync.md) - sync trades and orders history
|
||||
* [bbgo trades](bbgo_trades.md) - Query trading history
|
||||
* [bbgo tradeupdate](bbgo_tradeupdate.md) - Listen to trade update events
|
||||
|
@ -54,4 +55,4 @@ bbgo [flags]
|
|||
* [bbgo userdatastream](bbgo_userdatastream.md) - Listen to session events (orderUpdate, tradeUpdate, balanceUpdate, balanceSnapshot)
|
||||
* [bbgo version](bbgo_version.md) - show version name
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo account [--session=[exchange_name]] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -48,4 +48,4 @@ bbgo backtest [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Show user account balances
|
||||
|
||||
```
|
||||
bbgo balances [flags]
|
||||
bbgo balances --session SESSION [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
@ -40,4 +40,4 @@ bbgo balances [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -39,4 +39,4 @@ bbgo build [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -49,4 +49,4 @@ bbgo cancel-order [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo deposits [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
execute buy/sell on the balance/position you have on specific symbol
|
||||
|
||||
```
|
||||
bbgo execute-order [flags]
|
||||
bbgo execute-order --session SESSION --symbol SYMBOL --side SIDE --target-quantity TOTAL_QUANTITY --slice-quantity SLICE_QUANTITY [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
@ -48,4 +48,4 @@ bbgo execute-order [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
45
doc/commands/bbgo_get-order.md
Normal file
45
doc/commands/bbgo_get-order.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
## bbgo get-order
|
||||
|
||||
Get order status
|
||||
|
||||
```
|
||||
bbgo get-order --session SESSION --order-id ORDER_ID [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for get-order
|
||||
--order-id string order id
|
||||
--session string the exchange session name for sync
|
||||
--symbol string the trading pair, like btcusdt
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--binance-api-key string binance api key
|
||||
--binance-api-secret string binance api secret
|
||||
--config string config file (default "bbgo.yaml")
|
||||
--debug debug mode
|
||||
--dotenv string the dotenv file you want to load (default ".env.local")
|
||||
--ftx-api-key string ftx api key
|
||||
--ftx-api-secret string ftx api secret
|
||||
--ftx-subaccount string subaccount name. Specify it if the credential is for subaccount.
|
||||
--max-api-key string max api key
|
||||
--max-api-secret string max api secret
|
||||
--metrics enable prometheus metrics
|
||||
--metrics-port string prometheus http server port (default "9090")
|
||||
--no-dotenv disable built-in dotenv
|
||||
--slack-channel string slack trading channel (default "dev-bbgo")
|
||||
--slack-error-channel string slack error channel (default "bbgo-error")
|
||||
--slack-token string slack token
|
||||
--telegram-bot-auth-token string telegram auth token
|
||||
--telegram-bot-token string telegram bot token from bot father
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
|
@ -42,4 +42,4 @@ bbgo kline [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
list user's open orders in exchange of a specific trading pair
|
||||
|
||||
```
|
||||
bbgo list-orders [status] [flags]
|
||||
bbgo list-orders open|closed --session SESSION --symbol SYMBOL [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
@ -41,4 +41,4 @@ bbgo list-orders [status] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo market [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo orderbook --session=[exchange_name] --symbol=[pair_name] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo orderupdate [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -43,4 +43,4 @@ bbgo pnl [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -49,4 +49,4 @@ bbgo run [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
## bbgo submit-order
|
||||
|
||||
submit limit order to the exchange
|
||||
place limit order to the exchange
|
||||
|
||||
```
|
||||
bbgo submit-order --session SESSION --symbol SYMBOL --side SIDE --quantity QUANTITY [--price PRICE] [flags]
|
||||
|
@ -44,4 +44,4 @@ bbgo submit-order --session SESSION --symbol SYMBOL --side SIDE --quantity QUANT
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo sync --session=[exchange_name] --symbol=[pair_name] [--since=yyyy/mm/dd] [f
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo trades --session=[exchange_name] --symbol=[pair_name] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo tradeupdate --session=[exchange_name] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo transfer-history [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo userdatastream [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -39,4 +39,4 @@ bbgo version [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 22-Feb-2022
|
||||
###### Auto generated by spf13/cobra on 3-Mar-2022
|
||||
|
|
|
@ -8,7 +8,7 @@ You should send multiple small pull request to implement them.
|
|||
|
||||
## Checklist
|
||||
|
||||
Exchange Interface - minimum requirement for trading
|
||||
Exchange Interface - the minimum requirement for spot trading
|
||||
|
||||
- [ ] QueryMarkets
|
||||
- [ ] QueryTickers
|
||||
|
@ -17,11 +17,15 @@ Exchange Interface - minimum requirement for trading
|
|||
- [ ] CancelOrders
|
||||
- [ ] NewStream
|
||||
|
||||
Trading History Service Interface - used for syncing user trading data
|
||||
Trading History Service Interface - (optional) used for syncing user trading data
|
||||
|
||||
- [ ] QueryClosedOrders
|
||||
- [ ] QueryTrades
|
||||
|
||||
Order Query Service Interface - (optional) used for querying order status
|
||||
|
||||
- [ ] QueryOrder
|
||||
|
||||
Back-testing service - kline data is used for back-testing
|
||||
|
||||
- [ ] QueryKLines
|
||||
|
@ -100,33 +104,58 @@ func NewExchangeStandard(n types.ExchangeName, key, secret, passphrase, subAccou
|
|||
}
|
||||
```
|
||||
|
||||
## Testing order book stream
|
||||
## Test Market Data Stream
|
||||
|
||||
### Test order book stream
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo orderbook --config config/bbgo.yaml --session kucoin --symbol BTCUSDT
|
||||
```
|
||||
|
||||
## Testing user data stream
|
||||
## Test User Data Stream
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo --config config/bbgo.yaml userdatastream --session kucoin
|
||||
```
|
||||
|
||||
### Testing order submit
|
||||
|
||||
## Test Restful Endpoints
|
||||
|
||||
You can choose the session name to set-up for testing:
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo submit-order --session=kucoin --symbol=BTCUSDT --side=buy --price=18000 --quantity=0.001
|
||||
export BBGO_SESSION=ftx
|
||||
export BBGO_SESSION=kucoin
|
||||
export BBGO_SESSION=binance
|
||||
```
|
||||
|
||||
### Testing open orders query
|
||||
### Test user account balance
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session kucoin --symbol=BTCUSDT open
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session kucoin --symbol=BTCUSDT closed
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo balances --session $BBGO_SESSION
|
||||
```
|
||||
|
||||
### Testing order cancel
|
||||
### Test order submit
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo cancel-order --session kucoin --order-uuid 61c745c44592c200014abdcf
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo submit-order --session $BBGO_SESSION --symbol=BTCUSDT --side=buy --price=18000 --quantity=0.001
|
||||
```
|
||||
|
||||
### Test open orders query
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session $BBGO_SESSION --symbol=BTCUSDT open
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session $BBGO_SESSION --symbol=BTCUSDT closed
|
||||
```
|
||||
|
||||
### Test order status
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo get-order --session $BBGO_SESSION --order-id ORDER_ID
|
||||
```
|
||||
|
||||
### Test order cancel
|
||||
|
||||
```shell
|
||||
godotenv -f .env.local -- go run ./cmd/bbgo cancel-order --session $BBGO_SESSION --order-uuid 61c745c44592c200014abdcf
|
||||
```
|
||||
|
|
|
@ -3,9 +3,7 @@ package cmd
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -19,41 +17,19 @@ func init() {
|
|||
|
||||
// go run ./cmd/bbgo balances --session=ftx
|
||||
var balancesCmd = &cobra.Command{
|
||||
Use: "balances",
|
||||
Use: "balances --session SESSION",
|
||||
Short: "Show user account balances",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(configFile) == 0 {
|
||||
return errors.New("--config option is required")
|
||||
}
|
||||
|
||||
sessionName, err := cmd.Flags().GetString("session")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if config file exists, use the config loaded from the config file.
|
||||
// otherwise, use a empty config object
|
||||
var userConfig *bbgo.Config
|
||||
if _, err := os.Stat(configFile); err == nil {
|
||||
// load successfully
|
||||
userConfig, err = bbgo.Load(configFile, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// config file doesn't exist
|
||||
userConfig = &bbgo.Config{}
|
||||
} else {
|
||||
// other error
|
||||
return err
|
||||
if userConfig == nil {
|
||||
return fmt.Errorf("user config is not loaded")
|
||||
}
|
||||
|
||||
environ := bbgo.NewEnvironment()
|
||||
|
|
|
@ -19,11 +19,60 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
var getOrderCmd = &cobra.Command{
|
||||
Use: "get-order --session SESSION --order-id ORDER_ID",
|
||||
Short: "Get order status",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
if userConfig == nil {
|
||||
return errors.New("config file is required")
|
||||
}
|
||||
|
||||
environ := bbgo.NewEnvironment()
|
||||
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sessionName, err := cmd.Flags().GetString("session")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session, ok := environ.Session(sessionName)
|
||||
if !ok {
|
||||
return fmt.Errorf("session %s not found", sessionName)
|
||||
}
|
||||
|
||||
orderID, err := cmd.Flags().GetString("order-id")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get the symbol from flags: %w", err)
|
||||
}
|
||||
|
||||
service, ok := session.Exchange.(types.ExchangeOrderQueryService)
|
||||
if !ok {
|
||||
return fmt.Errorf("query order status is not supported for exchange %T, interface types.ExchangeOrderQueryService is not implemented", session.Exchange)
|
||||
}
|
||||
|
||||
order, err := service.QueryOrder(ctx, types.OrderQuery{
|
||||
OrderID: orderID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("%+v", order)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// go run ./cmd/bbgo list-orders [open|closed] --session=ftx --symbol=BTCUSDT
|
||||
var listOrdersCmd = &cobra.Command{
|
||||
Use: "list-orders [status]",
|
||||
Use: "list-orders open|closed --session SESSION --symbol SYMBOL",
|
||||
Short: "list user's open orders in exchange of a specific trading pair",
|
||||
Args: cobra.OnlyValidArgs,
|
||||
Args: cobra.OnlyValidArgs,
|
||||
// default is open which means we query open orders if you haven't provided args.
|
||||
ValidArgs: []string{"", "open", "closed"},
|
||||
SilenceUsage: true,
|
||||
|
@ -39,22 +88,10 @@ var listOrdersCmd = &cobra.Command{
|
|||
return fmt.Errorf("--config option is required")
|
||||
}
|
||||
|
||||
// if config file exists, use the config loaded from the config file.
|
||||
// otherwise, use a empty config object
|
||||
var userConfig *bbgo.Config
|
||||
if _, err := os.Stat(configFile); err == nil {
|
||||
// load successfully
|
||||
userConfig, err = bbgo.Load(configFile, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// config file doesn't exist
|
||||
userConfig = &bbgo.Config{}
|
||||
} else {
|
||||
// other error
|
||||
return err
|
||||
if userConfig == nil {
|
||||
return errors.New("config file is required")
|
||||
}
|
||||
|
||||
environ := bbgo.NewEnvironment()
|
||||
|
||||
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
|
||||
|
@ -116,7 +153,7 @@ var listOrdersCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
var executeOrderCmd = &cobra.Command{
|
||||
Use: "execute-order",
|
||||
Use: "execute-order --session SESSION --symbol SYMBOL --side SIDE --target-quantity TOTAL_QUANTITY --slice-quantity SLICE_QUANTITY",
|
||||
Short: "execute buy/sell on the balance/position you have on specific symbol",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -265,7 +302,7 @@ var executeOrderCmd = &cobra.Command{
|
|||
// go run ./cmd/bbgo submit-order --session=ftx --symbol=BTCUSDT --side=buy --price=18000 --quantity=0.001
|
||||
var submitOrderCmd = &cobra.Command{
|
||||
Use: "submit-order --session SESSION --symbol SYMBOL --side SIDE --quantity QUANTITY [--price PRICE]",
|
||||
Short: "submit limit order to the exchange",
|
||||
Short: "place limit order to the exchange",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
@ -345,6 +382,10 @@ func init() {
|
|||
listOrdersCmd.Flags().String("session", "", "the exchange session name for sync")
|
||||
listOrdersCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
|
||||
|
||||
getOrderCmd.Flags().String("session", "", "the exchange session name for sync")
|
||||
getOrderCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
|
||||
getOrderCmd.Flags().String("order-id", "", "order id")
|
||||
|
||||
submitOrderCmd.Flags().String("session", "", "the exchange session name for sync")
|
||||
submitOrderCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
|
||||
submitOrderCmd.Flags().String("side", "", "the trading side: buy or sell")
|
||||
|
@ -362,6 +403,7 @@ func init() {
|
|||
executeOrderCmd.Flags().Int("price-ticks", 0, "the number of price tick for the jump spread, default to 0")
|
||||
|
||||
RootCmd.AddCommand(listOrdersCmd)
|
||||
RootCmd.AddCommand(getOrderCmd)
|
||||
RootCmd.AddCommand(submitOrderCmd)
|
||||
RootCmd.AddCommand(executeOrderCmd)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -36,6 +37,67 @@ func TrimLowerString(original string) string {
|
|||
|
||||
var errUnsupportedOrderStatus = fmt.Errorf("unsupported order status")
|
||||
|
||||
func toGlobalOrderNew(r ftxapi.Order) (types.Order, error) {
|
||||
// In exchange/max/convert.go, it only parses these fields.
|
||||
timeInForce := types.TimeInForceGTC
|
||||
if r.Ioc {
|
||||
timeInForce = types.TimeInForceIOC
|
||||
}
|
||||
|
||||
// order type definition: https://github.com/ftexchange/ftx/blob/master/rest/client.py#L122
|
||||
orderType := types.OrderType(TrimUpperString(string(r.Type)))
|
||||
if orderType == types.OrderTypeLimit && r.PostOnly {
|
||||
orderType = types.OrderTypeLimitMaker
|
||||
}
|
||||
|
||||
o := types.Order{
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
ClientOrderID: r.ClientId,
|
||||
Symbol: toGlobalSymbol(r.Market),
|
||||
Side: types.SideType(TrimUpperString(string(r.Side))),
|
||||
Type: orderType,
|
||||
Quantity: r.Size,
|
||||
Price: r.Price,
|
||||
TimeInForce: timeInForce,
|
||||
},
|
||||
Exchange: types.ExchangeFTX,
|
||||
IsWorking: r.Status == ftxapi.OrderStatusOpen || r.Status == ftxapi.OrderStatusNew,
|
||||
OrderID: uint64(r.Id),
|
||||
Status: "",
|
||||
ExecutedQuantity: r.FilledSize,
|
||||
CreationTime: types.Time(r.CreatedAt),
|
||||
UpdateTime: types.Time(r.CreatedAt),
|
||||
}
|
||||
|
||||
s, err := toGlobalOrderStatus(r, r.Status)
|
||||
o.Status = s
|
||||
return o, err
|
||||
}
|
||||
|
||||
func toGlobalOrderStatus(o ftxapi.Order, s ftxapi.OrderStatus) (types.OrderStatus, error) {
|
||||
switch s {
|
||||
case ftxapi.OrderStatusNew:
|
||||
return types.OrderStatusNew, nil
|
||||
|
||||
case ftxapi.OrderStatusOpen:
|
||||
if !o.FilledSize.IsZero() {
|
||||
return types.OrderStatusPartiallyFilled, nil
|
||||
} else {
|
||||
return types.OrderStatusNew, nil
|
||||
}
|
||||
case ftxapi.OrderStatusClosed:
|
||||
// filled or canceled
|
||||
if o.FilledSize == o.Size {
|
||||
return types.OrderStatusFilled, nil
|
||||
} else {
|
||||
// can't distinguish it's canceled or rejected from order response, so always set to canceled
|
||||
return types.OrderStatusCanceled, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unsupported ftx order status %s: %w", s, errUnsupportedOrderStatus)
|
||||
}
|
||||
|
||||
func toGlobalOrder(r order) (types.Order, error) {
|
||||
// In exchange/max/convert.go, it only parses these fields.
|
||||
timeInForce := types.TimeInForceGTC
|
||||
|
@ -54,10 +116,10 @@ func toGlobalOrder(r order) (types.Order, error) {
|
|||
ClientOrderID: r.ClientId,
|
||||
Symbol: toGlobalSymbol(r.Market),
|
||||
Side: types.SideType(TrimUpperString(r.Side)),
|
||||
Type: orderType,
|
||||
Quantity: r.Size,
|
||||
Price: r.Price,
|
||||
TimeInForce: timeInForce,
|
||||
Type: orderType,
|
||||
Quantity: r.Size,
|
||||
Price: r.Price,
|
||||
TimeInForce: timeInForce,
|
||||
},
|
||||
Exchange: types.ExchangeFTX,
|
||||
IsWorking: r.Status == "open",
|
||||
|
@ -125,24 +187,24 @@ func toGlobalDepositStatus(input string) (types.DepositStatus, error) {
|
|||
return "", fmt.Errorf("unsupported status %s", input)
|
||||
}
|
||||
|
||||
func toGlobalTrade(f fill) (types.Trade, error) {
|
||||
func toGlobalTrade(f ftxapi.Fill) (types.Trade, error) {
|
||||
return types.Trade{
|
||||
ID: f.TradeId,
|
||||
GID: 0,
|
||||
OrderID: f.OrderId,
|
||||
Exchange: types.ExchangeFTX,
|
||||
Price: f.Price,
|
||||
Quantity: f.Size,
|
||||
QuoteQuantity: f.Price.Mul(f.Size),
|
||||
Symbol: toGlobalSymbol(f.Market),
|
||||
Side: f.Side,
|
||||
IsBuyer: f.Side == types.SideTypeBuy,
|
||||
IsMaker: f.Liquidity == "maker",
|
||||
Time: types.Time(f.Time.Time),
|
||||
Side: types.SideType(strings.ToUpper(string(f.Side))),
|
||||
IsBuyer: f.Side == ftxapi.SideBuy,
|
||||
IsMaker: f.Liquidity == ftxapi.LiquidityMaker,
|
||||
Time: types.Time(f.Time),
|
||||
Fee: f.Fee,
|
||||
FeeCurrency: f.FeeCurrency,
|
||||
IsMargin: false,
|
||||
IsIsolated: false,
|
||||
IsFutures: f.Future != "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -169,17 +231,17 @@ const (
|
|||
OrderTypeMarket OrderType = "market"
|
||||
)
|
||||
|
||||
func toLocalOrderType(orderType types.OrderType) (OrderType, error) {
|
||||
func toLocalOrderType(orderType types.OrderType) (ftxapi.OrderType, error) {
|
||||
switch orderType {
|
||||
|
||||
case types.OrderTypeLimitMaker:
|
||||
return OrderTypeLimit, nil
|
||||
return ftxapi.OrderTypeLimit, nil
|
||||
|
||||
case types.OrderTypeLimit:
|
||||
return OrderTypeLimit, nil
|
||||
return ftxapi.OrderTypeLimit, nil
|
||||
|
||||
case types.OrderTypeMarket:
|
||||
return OrderTypeMarket, nil
|
||||
return ftxapi.OrderTypeMarket, nil
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -104,18 +105,18 @@ func Test_toGlobalSymbol(t *testing.T) {
|
|||
func Test_toLocalOrderTypeWithLimitMaker(t *testing.T) {
|
||||
orderType, err := toLocalOrderType(types.OrderTypeLimitMaker)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orderType, OrderTypeLimit)
|
||||
assert.Equal(t, ftxapi.OrderTypeLimit, orderType)
|
||||
}
|
||||
|
||||
func Test_toLocalOrderTypeWithLimit(t *testing.T) {
|
||||
orderType, err := toLocalOrderType(types.OrderTypeLimit)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orderType, OrderTypeLimit)
|
||||
assert.Equal(t, ftxapi.OrderTypeLimit, orderType)
|
||||
}
|
||||
|
||||
func Test_toLocalOrderTypeWithMarket(t *testing.T) {
|
||||
orderType, err := toLocalOrderType(types.OrderTypeMarket)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, orderType, OrderTypeMarket)
|
||||
assert.Equal(t, ftxapi.OrderTypeMarket, orderType)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -31,6 +33,8 @@ var requestLimit = rate.NewLimiter(rate.Every(220*time.Millisecond), 2)
|
|||
//go:generate go run generate_symbol_map.go
|
||||
|
||||
type Exchange struct {
|
||||
client *ftxapi.RestClient
|
||||
|
||||
key, secret string
|
||||
subAccount string
|
||||
restEndpoint *url.URL
|
||||
|
@ -78,7 +82,10 @@ func NewExchange(key, secret string, subAccount string) *Exchange {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
client := ftxapi.NewClient()
|
||||
client.Auth(key, secret, subAccount)
|
||||
return &Exchange{
|
||||
client: client,
|
||||
restEndpoint: u,
|
||||
key: key,
|
||||
secret: secret,
|
||||
|
@ -119,16 +126,14 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
|||
}
|
||||
|
||||
func (e *Exchange) _queryMarkets(ctx context.Context) (MarketMap, error) {
|
||||
resp, err := e.newRest().Markets(ctx)
|
||||
req := e.client.NewGetMarketsRequest()
|
||||
ftxMarkets, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Success {
|
||||
return nil, fmt.Errorf("ftx returns querying markets failure")
|
||||
}
|
||||
|
||||
markets := MarketMap{}
|
||||
for _, m := range resp.Result {
|
||||
for _, m := range ftxMarkets {
|
||||
symbol := toGlobalSymbol(m.Name)
|
||||
symbolMap[symbol] = m.Name
|
||||
|
||||
|
@ -164,41 +169,40 @@ func (e *Exchange) _queryMarkets(ctx context.Context) (MarketMap, error) {
|
|||
}
|
||||
|
||||
func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {
|
||||
resp, err := e.newRest().Account(ctx)
|
||||
|
||||
req := e.client.NewGetAccountRequest()
|
||||
ftxAccount, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Success {
|
||||
return nil, fmt.Errorf("ftx returns querying balances failure")
|
||||
}
|
||||
|
||||
a := &types.Account{
|
||||
MakerCommission: resp.Result.MakerFee,
|
||||
TakerCommission: resp.Result.TakerFee,
|
||||
TotalAccountValue: resp.Result.TotalAccountValue,
|
||||
MakerCommission: ftxAccount.MakerFee,
|
||||
TakerCommission: ftxAccount.TakerFee,
|
||||
TotalAccountValue: ftxAccount.TotalAccountValue,
|
||||
}
|
||||
|
||||
balances, err := e.QueryAccountBalances(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.UpdateBalances(balances)
|
||||
|
||||
a.UpdateBalances(balances)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) {
|
||||
resp, err := e.newRest().Balances(ctx)
|
||||
balanceReq := e.client.NewGetBalancesRequest()
|
||||
ftxBalances, err := balanceReq.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Success {
|
||||
return nil, fmt.Errorf("ftx returns querying balances failure")
|
||||
}
|
||||
|
||||
var balances = make(types.BalanceMap)
|
||||
for _, r := range resp.Result {
|
||||
balances[toGlobalCurrency(r.Coin)] = types.Balance{
|
||||
Currency: toGlobalCurrency(r.Coin),
|
||||
for _, r := range ftxBalances {
|
||||
currency := toGlobalCurrency(r.Coin)
|
||||
balances[currency] = types.Balance{
|
||||
Currency: currency,
|
||||
Available: r.Free,
|
||||
Locked: r.Total.Sub(r.Free),
|
||||
}
|
||||
|
@ -340,74 +344,55 @@ func isIntervalSupportedInKLine(interval types.Interval) bool {
|
|||
}
|
||||
|
||||
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) ([]types.Trade, error) {
|
||||
var since, until time.Time
|
||||
if options.StartTime != nil {
|
||||
since = *options.StartTime
|
||||
}
|
||||
if options.EndTime != nil {
|
||||
until = *options.EndTime
|
||||
} else {
|
||||
until = time.Now()
|
||||
}
|
||||
|
||||
if since.After(until) {
|
||||
return nil, fmt.Errorf("invalid query trades time range, since: %+v, until: %+v", since, until)
|
||||
}
|
||||
|
||||
if options.Limit == 1 {
|
||||
// FTX doesn't provide pagination api, so we have to split the since/until time range into small slices, and paginate ourselves.
|
||||
// If the limit is 1, we always get the same data from FTX.
|
||||
return nil, fmt.Errorf("limit can't be 1 which can't be used in pagination")
|
||||
}
|
||||
limit := options.Limit
|
||||
if limit == 0 {
|
||||
limit = 200
|
||||
}
|
||||
|
||||
tradeIDs := make(map[uint64]struct{})
|
||||
|
||||
lastTradeID := options.LastTradeID
|
||||
|
||||
req := e.client.NewGetFillsRequest()
|
||||
req.Market(toLocalSymbol(symbol))
|
||||
|
||||
if options.StartTime != nil {
|
||||
req.StartTime(*options.StartTime)
|
||||
} else if options.EndTime != nil {
|
||||
req.EndTime(*options.EndTime)
|
||||
}
|
||||
|
||||
req.Order("asc")
|
||||
fills, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Slice(fills, func(i, j int) bool {
|
||||
return fills[i].Time.Before(fills[j].Time)
|
||||
})
|
||||
|
||||
var trades []types.Trade
|
||||
symbol = strings.ToUpper(symbol)
|
||||
for _, fill := range fills {
|
||||
if _, ok := tradeIDs[fill.TradeId]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for since.Before(until) {
|
||||
// DO not set limit to `1` since you will always get the same response.
|
||||
resp, err := e.newRest().Fills(ctx, toLocalSymbol(symbol), since, until, limit, true)
|
||||
if options.StartTime != nil && fill.Time.Before(*options.StartTime) {
|
||||
continue
|
||||
}
|
||||
|
||||
if options.EndTime != nil && fill.Time.After(*options.EndTime) {
|
||||
continue
|
||||
}
|
||||
|
||||
if fill.TradeId <= lastTradeID {
|
||||
continue
|
||||
}
|
||||
|
||||
tradeIDs[fill.TradeId] = struct{}{}
|
||||
lastTradeID = fill.TradeId
|
||||
|
||||
t, err := toGlobalTrade(fill)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Success {
|
||||
return nil, fmt.Errorf("ftx returns failure")
|
||||
}
|
||||
|
||||
sort.Slice(resp.Result, func(i, j int) bool {
|
||||
return resp.Result[i].TradeId < resp.Result[j].TradeId
|
||||
})
|
||||
|
||||
for _, r := range resp.Result {
|
||||
// always update since to avoid infinite loop
|
||||
since = r.Time.Time
|
||||
|
||||
if _, ok := tradeIDs[r.TradeId]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.TradeId <= lastTradeID || r.Time.Before(since) || r.Time.After(until) || r.Market != toLocalSymbol(symbol) {
|
||||
continue
|
||||
}
|
||||
tradeIDs[r.TradeId] = struct{}{}
|
||||
lastTradeID = r.TradeId
|
||||
|
||||
t, err := toGlobalTrade(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trades = append(trades, t)
|
||||
}
|
||||
|
||||
if int64(len(resp.Result)) < limit {
|
||||
return trades, nil
|
||||
}
|
||||
trades = append(trades, t)
|
||||
}
|
||||
|
||||
return trades, nil
|
||||
|
@ -458,27 +443,34 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
|
|||
logrus.WithError(err).Error("type error")
|
||||
}
|
||||
|
||||
or, err := e.newRest().PlaceOrder(ctx, PlaceOrderPayload{
|
||||
Market: toLocalSymbol(TrimUpperString(so.Symbol)),
|
||||
Side: TrimLowerString(string(so.Side)),
|
||||
Price: so.Price,
|
||||
Type: string(orderType),
|
||||
Size: so.Quantity,
|
||||
ReduceOnly: false,
|
||||
IOC: so.TimeInForce == types.TimeInForceIOC,
|
||||
PostOnly: so.Type == types.OrderTypeLimitMaker,
|
||||
ClientID: newSpotClientOrderID(so.ClientOrderID),
|
||||
})
|
||||
req := e.client.NewPlaceOrderRequest()
|
||||
req.Market(toLocalSymbol(TrimUpperString(so.Symbol)))
|
||||
req.OrderType(orderType)
|
||||
req.Side(ftxapi.Side(TrimLowerString(string(so.Side))))
|
||||
req.Size(so.Quantity)
|
||||
|
||||
switch so.Type {
|
||||
case types.OrderTypeLimit, types.OrderTypeLimitMaker:
|
||||
req.Price(so.Price)
|
||||
|
||||
}
|
||||
|
||||
if so.Type == types.OrderTypeLimitMaker {
|
||||
req.PostOnly(true)
|
||||
}
|
||||
|
||||
if so.TimeInForce == types.TimeInForceIOC {
|
||||
req.Ioc(true)
|
||||
}
|
||||
|
||||
req.ClientID(newSpotClientOrderID(so.ClientOrderID))
|
||||
|
||||
or, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return createdOrders, fmt.Errorf("failed to place order %+v: %w", so, err)
|
||||
}
|
||||
|
||||
if !or.Success {
|
||||
return createdOrders, fmt.Errorf("ftx returns placing order failure")
|
||||
}
|
||||
|
||||
globalOrder, err := toGlobalOrder(or.Result)
|
||||
globalOrder, err := toGlobalOrderNew(*or)
|
||||
if err != nil {
|
||||
return createdOrders, fmt.Errorf("failed to convert response to global order")
|
||||
}
|
||||
|
@ -488,20 +480,37 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
|
|||
return createdOrders, nil
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||
// TODO: invoke open trigger orders
|
||||
resp, err := e.newRest().OpenOrders(ctx, toLocalSymbol(symbol))
|
||||
func (e *Exchange) QueryOrder(ctx context.Context, q types.OrderQuery) (*types.Order, error) {
|
||||
orderID, err := strconv.ParseInt(q.OrderID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Success {
|
||||
return nil, fmt.Errorf("ftx returns querying open orders failure")
|
||||
|
||||
req := e.client.NewGetOrderStatusRequest(uint64(orderID))
|
||||
ftxOrder, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range resp.Result {
|
||||
o, err := toGlobalOrder(r)
|
||||
|
||||
order, err := toGlobalOrderNew(*ftxOrder)
|
||||
return &order, err
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||
// TODO: invoke open trigger orders
|
||||
|
||||
req := e.client.NewGetOpenOrdersRequest(toLocalSymbol(symbol))
|
||||
ftxOrders, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ftxOrder := range ftxOrders {
|
||||
o, err := toGlobalOrderNew(ftxOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return orders, err
|
||||
}
|
||||
|
||||
orders = append(orders, o)
|
||||
}
|
||||
return orders, nil
|
||||
|
@ -510,73 +519,59 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
|||
// symbol, since and until are all optional. FTX can only query by order created time, not updated time.
|
||||
// FTX doesn't support lastOrderID, so we will query by the time range first, and filter by the lastOrderID.
|
||||
func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []types.Order, err error) {
|
||||
if until == (time.Time{}) {
|
||||
until = time.Now()
|
||||
}
|
||||
if since.After(until) {
|
||||
return nil, fmt.Errorf("invalid query closed orders time range, since: %+v, until: %+v", since, until)
|
||||
}
|
||||
|
||||
symbol = TrimUpperString(symbol)
|
||||
limit := int64(100)
|
||||
hasMoreData := true
|
||||
s := since
|
||||
var lastOrder order
|
||||
for hasMoreData {
|
||||
|
||||
if err := requestLimit.Wait(ctx); err != nil {
|
||||
logrus.WithError(err).Error("rate limit error")
|
||||
req := e.client.NewGetOrderHistoryRequest(toLocalSymbol(symbol))
|
||||
|
||||
if since != (time.Time{}) {
|
||||
req.StartTime(since)
|
||||
} else if until != (time.Time{}) {
|
||||
req.EndTime(until)
|
||||
}
|
||||
|
||||
ftxOrders, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Slice(ftxOrders, func(i, j int) bool {
|
||||
return ftxOrders[i].CreatedAt.Before(ftxOrders[j].CreatedAt)
|
||||
})
|
||||
|
||||
for _, ftxOrder := range ftxOrders {
|
||||
switch ftxOrder.Status {
|
||||
case ftxapi.OrderStatusOpen, ftxapi.OrderStatusNew:
|
||||
continue
|
||||
}
|
||||
|
||||
resp, err := e.newRest().OrdersHistory(ctx, toLocalSymbol(symbol), s, until, limit)
|
||||
o, err := toGlobalOrderNew(ftxOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.Success {
|
||||
return nil, fmt.Errorf("ftx returns querying orders history failure")
|
||||
return orders, err
|
||||
}
|
||||
|
||||
sortByCreatedASC(resp.Result)
|
||||
|
||||
for _, r := range resp.Result {
|
||||
// There may be more than one orders at the same time, so also have to check the ID
|
||||
if r.CreatedAt.Before(lastOrder.CreatedAt.Time) || r.ID == lastOrder.ID || r.Status != "closed" || r.ID < int64(lastOrderID) {
|
||||
continue
|
||||
}
|
||||
lastOrder = r
|
||||
o, err := toGlobalOrder(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orders = append(orders, o)
|
||||
}
|
||||
hasMoreData = resp.HasMoreData
|
||||
// the start_time and end_time precision is second. There might be more than one orders within one second.
|
||||
s = lastOrder.CreatedAt.Add(-1 * time.Second)
|
||||
orders = append(orders, o)
|
||||
}
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
func sortByCreatedASC(orders []order) {
|
||||
sort.Slice(orders, func(i, j int) bool {
|
||||
return orders[i].CreatedAt.Before(orders[j].CreatedAt.Time)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {
|
||||
for _, o := range orders {
|
||||
rest := e.newRest()
|
||||
if err := requestLimit.Wait(ctx); err != nil {
|
||||
logrus.WithError(err).Error("rate limit error")
|
||||
}
|
||||
|
||||
if len(o.ClientOrderID) > 0 {
|
||||
if _, err := rest.CancelOrderByClientID(ctx, o.ClientOrderID); err != nil {
|
||||
req := e.client.NewCancelOrderByClientOrderIdRequest(o.ClientOrderID)
|
||||
_, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
req := e.client.NewCancelOrderRequest(strconv.FormatUint(o.OrderID, 10))
|
||||
_, err := req.Do(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := rest.CancelOrderByOrderID(ctx, o.OrderID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -86,10 +86,11 @@ func TestExchange_QueryAccountBalances(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
resp, err := ex.QueryAccountBalances(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -99,10 +100,6 @@ func TestExchange_QueryAccountBalances(t *testing.T) {
|
|||
expectedAvailable := fixedpoint.Must(fixedpoint.NewFromString("19.48085209"))
|
||||
assert.Equal(t, expectedAvailable, b.Available)
|
||||
assert.Equal(t, fixedpoint.Must(fixedpoint.NewFromString("1094.66405065")).Sub(expectedAvailable), b.Locked)
|
||||
|
||||
resp, err = ex.QueryAccountBalances(context.Background())
|
||||
assert.EqualError(t, err, "ftx returns querying balances failure")
|
||||
assert.Nil(t, resp)
|
||||
}
|
||||
|
||||
func TestExchange_QueryOpenOrders(t *testing.T) {
|
||||
|
@ -136,10 +133,12 @@ func TestExchange_QueryOpenOrders(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
resp, err := ex.QueryOpenOrders(context.Background(), "XRP-PREP")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, resp, 1)
|
||||
|
@ -155,10 +154,11 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -196,10 +196,11 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, resp, 1)
|
||||
|
@ -240,10 +241,11 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, resp, 3)
|
||||
|
@ -253,76 +255,6 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
assert.Equal(t, expectedOrderID[i], o.OrderID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("receive duplicated orders", func(t *testing.T) {
|
||||
successRespOne := `
|
||||
{
|
||||
"success": true,
|
||||
"result": [
|
||||
{
|
||||
"status": "closed",
|
||||
"createdAt": "2020-09-01T15:24:03.101197+00:00",
|
||||
"id": 123
|
||||
}
|
||||
],
|
||||
"hasMoreData": true
|
||||
}
|
||||
`
|
||||
|
||||
successRespTwo := `
|
||||
{
|
||||
"success": true,
|
||||
"result": [
|
||||
{
|
||||
"clientId": "ignored-by-created-at",
|
||||
"status": "closed",
|
||||
"createdAt": "1999-09-01T15:24:03.101197+00:00",
|
||||
"id": 999
|
||||
},
|
||||
{
|
||||
"clientId": "ignored-by-duplicated-id",
|
||||
"status": "closed",
|
||||
"createdAt": "2020-09-02T15:24:03.101197+00:00",
|
||||
"id": 123
|
||||
},
|
||||
{
|
||||
"clientId": "ignored-duplicated-entry",
|
||||
"status": "closed",
|
||||
"createdAt": "2020-09-01T15:24:03.101197+00:00",
|
||||
"id": 123
|
||||
},
|
||||
{
|
||||
"status": "closed",
|
||||
"createdAt": "2020-09-02T15:24:03.101197+00:00",
|
||||
"id": 456
|
||||
}
|
||||
],
|
||||
"hasMoreData": false
|
||||
}
|
||||
`
|
||||
i := 0
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if i == 0 {
|
||||
i++
|
||||
fmt.Fprintln(w, successRespOne)
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(w, successRespTwo)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, resp, 2)
|
||||
expectedOrderID := []uint64{123, 456}
|
||||
for i, o := range resp {
|
||||
assert.Equal(t, expectedOrderID[i], o.OrderID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExchange_QueryAccount(t *testing.T) {
|
||||
|
@ -391,10 +323,11 @@ func TestExchange_QueryAccount(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
resp, err := ex.QueryAccount(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -447,10 +380,12 @@ func TestExchange_QueryMarkets(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.client.BaseURL = serverURL
|
||||
ex.restEndpoint = serverURL
|
||||
|
||||
resp, err := ex.QueryMarkets(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -494,9 +429,10 @@ func TestExchange_QueryDepositHistory(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.client.BaseURL = serverURL
|
||||
ex.restEndpoint = serverURL
|
||||
|
||||
ctx := context.Background()
|
||||
|
@ -543,10 +479,10 @@ func TestExchange_QueryTrades(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
ctx := context.Background()
|
||||
actualConfirmedTime, err := parseDatetime("2021-02-23T09:29:08.534000+00:00")
|
||||
|
@ -619,10 +555,10 @@ func TestExchange_QueryTrades(t *testing.T) {
|
|||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
ex := NewExchange("test-key", "test-secret", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
ex.client.BaseURL = serverURL
|
||||
|
||||
ctx := context.Background()
|
||||
actualConfirmedTime, err := parseDatetime("2021-02-23T09:29:08.534000+00:00")
|
||||
|
|
88
pkg/exchange/ftx/ftxapi/account.go
Normal file
88
pkg/exchange/ftx/ftxapi/account.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package ftxapi
|
||||
|
||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command DeleteRequest requestgen -method DELETE -responseType .APIResponse -responseDataField Result
|
||||
|
||||
import (
|
||||
"github.com/c9s/requestgen"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
type Position struct {
|
||||
Cost fixedpoint.Value `json:"cost"`
|
||||
EntryPrice fixedpoint.Value `json:"entryPrice"`
|
||||
Future string `json:"future"`
|
||||
InitialMarginRequirement fixedpoint.Value `json:"initialMarginRequirement"`
|
||||
LongOrderSize fixedpoint.Value `json:"longOrderSize"`
|
||||
MaintenanceMarginRequirement fixedpoint.Value `json:"maintenanceMarginRequirement"`
|
||||
NetSize fixedpoint.Value `json:"netSize"`
|
||||
OpenSize fixedpoint.Value `json:"openSize"`
|
||||
ShortOrderSize fixedpoint.Value `json:"shortOrderSize"`
|
||||
Side string `json:"side"`
|
||||
Size fixedpoint.Value `json:"size"`
|
||||
RealizedPnl fixedpoint.Value `json:"realizedPnl"`
|
||||
UnrealizedPnl fixedpoint.Value `json:"unrealizedPnl"`
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
BackstopProvider bool `json:"backstopProvider"`
|
||||
Collateral fixedpoint.Value `json:"collateral"`
|
||||
FreeCollateral fixedpoint.Value `json:"freeCollateral"`
|
||||
Leverage fixedpoint.Value `json:"leverage"`
|
||||
InitialMarginRequirement fixedpoint.Value `json:"initialMarginRequirement"`
|
||||
MaintenanceMarginRequirement fixedpoint.Value `json:"maintenanceMarginRequirement"`
|
||||
Liquidating bool `json:"liquidating"`
|
||||
MakerFee fixedpoint.Value `json:"makerFee"`
|
||||
MarginFraction fixedpoint.Value `json:"marginFraction"`
|
||||
OpenMarginFraction fixedpoint.Value `json:"openMarginFraction"`
|
||||
TakerFee fixedpoint.Value `json:"takerFee"`
|
||||
TotalAccountValue fixedpoint.Value `json:"totalAccountValue"`
|
||||
TotalPositionSize fixedpoint.Value `json:"totalPositionSize"`
|
||||
Username string `json:"username"`
|
||||
Positions []Position `json:"positions"`
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/account" -type GetAccountRequest -responseDataType .Account
|
||||
type GetAccountRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetAccountRequest() *GetAccountRequest {
|
||||
return &GetAccountRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/positions" -type GetPositionsRequest -responseDataType []Position
|
||||
type GetPositionsRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetPositionsRequest() *GetPositionsRequest {
|
||||
return &GetPositionsRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type Balance struct {
|
||||
Coin string `json:"coin"`
|
||||
Free fixedpoint.Value `json:"free"`
|
||||
SpotBorrow fixedpoint.Value `json:"spotBorrow"`
|
||||
Total fixedpoint.Value `json:"total"`
|
||||
UsdValue fixedpoint.Value `json:"usdValue"`
|
||||
AvailableWithoutBorrow fixedpoint.Value `json:"availableWithoutBorrow"`
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/wallet/balances" -type GetBalancesRequest -responseDataType []Balance
|
||||
type GetBalancesRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetBalancesRequest() *GetBalancesRequest {
|
||||
return &GetBalancesRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
126
pkg/exchange/ftx/ftxapi/cancel_all_order_request_requestgen.go
Normal file
126
pkg/exchange/ftx/ftxapi/cancel_all_order_request_requestgen.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Code generated by "requestgen -method DELETE -url /api/orders -type CancelAllOrderRequest -responseType .APIResponse"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (c *CancelAllOrderRequest) Market(market string) *CancelAllOrderRequest {
|
||||
c.market = &market
|
||||
return c
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (c *CancelAllOrderRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (c *CancelAllOrderRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check market field -> json key market
|
||||
if c.market != nil {
|
||||
market := *c.market
|
||||
|
||||
// assign parameter of market
|
||||
params["market"] = market
|
||||
} else {
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (c *CancelAllOrderRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := c.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (c *CancelAllOrderRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := c.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (c *CancelAllOrderRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (c *CancelAllOrderRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (c *CancelAllOrderRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := c.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (c *CancelAllOrderRequest) Do(ctx context.Context) (*APIResponse, error) {
|
||||
|
||||
params, err := c.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/orders"
|
||||
|
||||
req, err := c.client.NewAuthenticatedRequest(ctx, "DELETE", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := c.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiResponse, nil
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
// Code generated by "requestgen -method DELETE -url /api/orders/by_client_id/:clientOrderId -type CancelOrderByClientOrderIdRequest -responseType .APIResponse"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (c *CancelOrderByClientOrderIdRequest) ClientOrderId(clientOrderId string) *CancelOrderByClientOrderIdRequest {
|
||||
c.clientOrderId = clientOrderId
|
||||
return c
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (c *CancelOrderByClientOrderIdRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (c *CancelOrderByClientOrderIdRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (c *CancelOrderByClientOrderIdRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := c.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (c *CancelOrderByClientOrderIdRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := c.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (c *CancelOrderByClientOrderIdRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check clientOrderId field -> json key clientOrderId
|
||||
clientOrderId := c.clientOrderId
|
||||
|
||||
// TEMPLATE check-required
|
||||
if len(clientOrderId) == 0 {
|
||||
return params, fmt.Errorf("clientOrderId is required, empty string given")
|
||||
}
|
||||
// END TEMPLATE check-required
|
||||
|
||||
// assign parameter of clientOrderId
|
||||
params["clientOrderId"] = clientOrderId
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (c *CancelOrderByClientOrderIdRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (c *CancelOrderByClientOrderIdRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := c.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (c *CancelOrderByClientOrderIdRequest) Do(ctx context.Context) (*APIResponse, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/orders/by_client_id/:clientOrderId"
|
||||
slugs, err := c.GetSlugsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL = c.applySlugsToUrl(apiURL, slugs)
|
||||
|
||||
req, err := c.client.NewAuthenticatedRequest(ctx, "DELETE", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := c.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiResponse, nil
|
||||
}
|
133
pkg/exchange/ftx/ftxapi/cancel_order_request_requestgen.go
Normal file
133
pkg/exchange/ftx/ftxapi/cancel_order_request_requestgen.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Code generated by "requestgen -method DELETE -url /api/orders/:orderID -type CancelOrderRequest -responseType .APIResponse"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (c *CancelOrderRequest) OrderID(orderID string) *CancelOrderRequest {
|
||||
c.orderID = orderID
|
||||
return c
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (c *CancelOrderRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (c *CancelOrderRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (c *CancelOrderRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := c.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (c *CancelOrderRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := c.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (c *CancelOrderRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check orderID field -> json key orderID
|
||||
orderID := c.orderID
|
||||
|
||||
// TEMPLATE check-required
|
||||
if len(orderID) == 0 {
|
||||
return params, fmt.Errorf("orderID is required, empty string given")
|
||||
}
|
||||
// END TEMPLATE check-required
|
||||
|
||||
// assign parameter of orderID
|
||||
params["orderID"] = orderID
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (c *CancelOrderRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (c *CancelOrderRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := c.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (c *CancelOrderRequest) Do(ctx context.Context) (*APIResponse, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/orders/:orderID"
|
||||
slugs, err := c.GetSlugsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL = c.applySlugsToUrl(apiURL, slugs)
|
||||
|
||||
req, err := c.client.NewAuthenticatedRequest(ctx, "DELETE", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := c.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiResponse, nil
|
||||
}
|
204
pkg/exchange/ftx/ftxapi/client.go
Normal file
204
pkg/exchange/ftx/ftxapi/client.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
package ftxapi
|
||||
|
||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command DeleteRequest requestgen -method DELETE -responseType .APIResponse -responseDataField Result
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/requestgen"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const defaultHTTPTimeout = time.Second * 15
|
||||
const RestBaseURL = "https://ftx.com/api"
|
||||
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Result json.RawMessage `json:"result,omitempty"`
|
||||
HasMoreData bool `json:"hasMoreData,omitempty"`
|
||||
}
|
||||
|
||||
type RestClient struct {
|
||||
BaseURL *url.URL
|
||||
|
||||
client *http.Client
|
||||
|
||||
Key, Secret, subAccount string
|
||||
|
||||
/*
|
||||
AccountService *AccountService
|
||||
MarketDataService *MarketDataService
|
||||
TradeService *TradeService
|
||||
BulletService *BulletService
|
||||
*/
|
||||
}
|
||||
|
||||
func NewClient() *RestClient {
|
||||
u, err := url.Parse(RestBaseURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client := &RestClient{
|
||||
BaseURL: u,
|
||||
client: &http.Client{
|
||||
Timeout: defaultHTTPTimeout,
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
client.AccountService = &AccountService{client: client}
|
||||
client.MarketDataService = &MarketDataService{client: client}
|
||||
client.TradeService = &TradeService{client: client}
|
||||
client.BulletService = &BulletService{client: client}
|
||||
*/
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *RestClient) Auth(key, secret, subAccount string) {
|
||||
c.Key = key
|
||||
c.Secret = secret
|
||||
c.subAccount = subAccount
|
||||
}
|
||||
|
||||
// NewRequest create new API request. Relative url can be provided in refURL.
|
||||
func (c *RestClient) NewRequest(ctx context.Context, method, refURL string, params url.Values, payload interface{}) (*http.Request, error) {
|
||||
rel, err := url.Parse(refURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if params != nil {
|
||||
rel.RawQuery = params.Encode()
|
||||
}
|
||||
|
||||
body, err := castPayload(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathURL := c.BaseURL.ResolveReference(rel)
|
||||
return http.NewRequestWithContext(ctx, method, pathURL.String(), bytes.NewReader(body))
|
||||
}
|
||||
|
||||
// sendRequest sends the request to the API server and handle the response
|
||||
func (c *RestClient) SendRequest(req *http.Request) (*requestgen.Response, error) {
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// newResponse reads the response body and return a new Response object
|
||||
response, err := requestgen.NewResponse(resp)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
// Check error, if there is an error, return the ErrorResponse struct type
|
||||
if response.IsError() {
|
||||
return response, errors.New(string(response.Body))
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// newAuthenticatedRequest creates new http request for authenticated routes.
|
||||
func (c *RestClient) NewAuthenticatedRequest(ctx context.Context, method, refURL string, params url.Values, payload interface{}) (*http.Request, error) {
|
||||
if len(c.Key) == 0 {
|
||||
return nil, errors.New("empty api key")
|
||||
}
|
||||
|
||||
if len(c.Secret) == 0 {
|
||||
return nil, errors.New("empty api secret")
|
||||
}
|
||||
|
||||
rel, err := url.Parse(refURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if params != nil {
|
||||
rel.RawQuery = params.Encode()
|
||||
}
|
||||
|
||||
// pathURL is for sending request
|
||||
pathURL := c.BaseURL.ResolveReference(rel)
|
||||
|
||||
// path here is used for auth header
|
||||
path := pathURL.Path
|
||||
if rel.RawQuery != "" {
|
||||
path += "?" + rel.RawQuery
|
||||
}
|
||||
|
||||
body, err := castPayload(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, pathURL.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
// Build authentication headers
|
||||
c.attachAuthHeaders(req, method, path, body)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) attachAuthHeaders(req *http.Request, method string, path string, body []byte) {
|
||||
millisecondTs := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
ts := strconv.FormatInt(millisecondTs, 10)
|
||||
p := ts + method + path + string(body)
|
||||
signature := sign(c.Secret, p)
|
||||
req.Header.Set("FTX-KEY", c.Key)
|
||||
req.Header.Set("FTX-SIGN", signature)
|
||||
req.Header.Set("FTX-TS", ts)
|
||||
if c.subAccount != "" {
|
||||
req.Header.Set("FTX-SUBACCOUNT", c.subAccount)
|
||||
}
|
||||
}
|
||||
|
||||
// sign uses sha256 to sign the payload with the given secret
|
||||
func sign(secret, payload string) string {
|
||||
var sig = hmac.New(sha256.New, []byte(secret))
|
||||
_, err := sig.Write([]byte(payload))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return hex.EncodeToString(sig.Sum(nil))
|
||||
}
|
||||
|
||||
func castPayload(payload interface{}) ([]byte, error) {
|
||||
if payload != nil {
|
||||
switch v := payload.(type) {
|
||||
case string:
|
||||
return []byte(v), nil
|
||||
|
||||
case []byte:
|
||||
return v, nil
|
||||
|
||||
default:
|
||||
body, err := json.Marshal(v)
|
||||
return body, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
99
pkg/exchange/ftx/ftxapi/client_test.go
Normal file
99
pkg/exchange/ftx/ftxapi/client_test.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
func maskSecret(s string) string {
|
||||
re := regexp.MustCompile(`\b(\w{4})\w+\b`)
|
||||
s = re.ReplaceAllString(s, "$1******")
|
||||
return s
|
||||
}
|
||||
|
||||
func integrationTestConfigured(t *testing.T) (key, secret string, ok bool) {
|
||||
var hasKey, hasSecret bool
|
||||
key, hasKey = os.LookupEnv("FTX_API_KEY")
|
||||
secret, hasSecret = os.LookupEnv("FTX_API_SECRET")
|
||||
ok = hasKey && hasSecret && os.Getenv("TEST_FTX") == "1"
|
||||
if ok {
|
||||
t.Logf("ftx api integration test enabled, key = %s, secret = %s", maskSecret(key), maskSecret(secret))
|
||||
}
|
||||
return key, secret, ok
|
||||
}
|
||||
|
||||
func TestClient_Requests(t *testing.T) {
|
||||
key, secret, ok := integrationTestConfigured(t)
|
||||
if !ok {
|
||||
t.SkipNow()
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 15 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
client := NewClient()
|
||||
client.Auth(key, secret, "")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
tt func(t *testing.T)
|
||||
} {
|
||||
{
|
||||
name: "GetAccountRequest",
|
||||
tt: func(t *testing.T) {
|
||||
req := client.NewGetAccountRequest()
|
||||
account ,err := req.Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, account)
|
||||
t.Logf("account: %+v", account)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PlaceOrderRequest",
|
||||
tt: func(t *testing.T) {
|
||||
req := client.NewPlaceOrderRequest()
|
||||
req.PostOnly(true).
|
||||
Size(fixedpoint.MustNewFromString("1.0")).
|
||||
Price(fixedpoint.MustNewFromString("10.0")).
|
||||
OrderType(OrderTypeLimit).
|
||||
Side(SideBuy).
|
||||
Market("LTC/USDT")
|
||||
|
||||
createdOrder,err := req.Do(ctx)
|
||||
if assert.NoError(t, err) {
|
||||
assert.NotNil(t, createdOrder)
|
||||
t.Logf("createdOrder: %+v", createdOrder)
|
||||
|
||||
req2 := client.NewCancelOrderRequest(strconv.FormatInt(createdOrder.Id, 10))
|
||||
ret, err := req2.Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
t.Logf("cancelOrder: %+v", ret)
|
||||
assert.True(t, ret.Success)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetFillsRequest",
|
||||
tt: func(t *testing.T) {
|
||||
req := client.NewGetFillsRequest()
|
||||
req.Market("CRO/USDT")
|
||||
fills, err := req.Do(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, fills)
|
||||
t.Logf("fills: %+v", fills)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, testCase.tt)
|
||||
}
|
||||
}
|
42
pkg/exchange/ftx/ftxapi/coin.go
Normal file
42
pkg/exchange/ftx/ftxapi/coin.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package ftxapi
|
||||
|
||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command DeleteRequest requestgen -method DELETE -responseType .APIResponse -responseDataField Result
|
||||
|
||||
import (
|
||||
"github.com/c9s/requestgen"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
type Coin struct {
|
||||
Bep2Asset *string `json:"bep2Asset"`
|
||||
CanConvert bool `json:"canConvert"`
|
||||
CanDeposit bool `json:"canDeposit"`
|
||||
CanWithdraw bool `json:"canWithdraw"`
|
||||
Collateral bool `json:"collateral"`
|
||||
CollateralWeight fixedpoint.Value `json:"collateralWeight"`
|
||||
CreditTo *string `json:"creditTo"`
|
||||
Erc20Contract string `json:"erc20Contract"`
|
||||
Fiat bool `json:"fiat"`
|
||||
HasTag bool `json:"hasTag"`
|
||||
Id string `json:"id"`
|
||||
IsToken bool `json:"isToken"`
|
||||
Methods []string `json:"methods"`
|
||||
Name string `json:"name"`
|
||||
SplMint string `json:"splMint"`
|
||||
Trc20Contract string `json:"trc20Contract"`
|
||||
UsdFungible bool `json:"usdFungible"`
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "api/coins" -type GetCoinsRequest -responseDataType []Coin
|
||||
type GetCoinsRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetCoinsRequest() *GetCoinsRequest {
|
||||
return &GetCoinsRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
115
pkg/exchange/ftx/ftxapi/get_account_request_requestgen.go
Normal file
115
pkg/exchange/ftx/ftxapi/get_account_request_requestgen.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /api/account -type GetAccountRequest -responseDataType .Account"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetAccountRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetAccountRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetAccountRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetAccountRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetAccountRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetAccountRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetAccountRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetAccountRequest) Do(ctx context.Context) (*Account, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/account"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data Account
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
115
pkg/exchange/ftx/ftxapi/get_balances_request_requestgen.go
Normal file
115
pkg/exchange/ftx/ftxapi/get_balances_request_requestgen.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /api/wallet/balances -type GetBalancesRequest -responseDataType []Balance"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetBalancesRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetBalancesRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetBalancesRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetBalancesRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetBalancesRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetBalancesRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetBalancesRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetBalancesRequest) Do(ctx context.Context) ([]Balance, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/wallet/balances"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []Balance
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
115
pkg/exchange/ftx/ftxapi/get_coins_request_requestgen.go
Normal file
115
pkg/exchange/ftx/ftxapi/get_coins_request_requestgen.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url api/coins -type GetCoinsRequest -responseDataType []Coin"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetCoinsRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetCoinsRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetCoinsRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetCoinsRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetCoinsRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetCoinsRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetCoinsRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetCoinsRequest) Do(ctx context.Context) ([]Coin, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "api/coins"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []Coin
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
187
pkg/exchange/ftx/ftxapi/get_fills_request_requestgen.go
Normal file
187
pkg/exchange/ftx/ftxapi/get_fills_request_requestgen.go
Normal file
|
@ -0,0 +1,187 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /api/fills -type GetFillsRequest -responseDataType []Fill"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (g *GetFillsRequest) Market(market string) *GetFillsRequest {
|
||||
g.market = &market
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetFillsRequest) StartTime(startTime time.Time) *GetFillsRequest {
|
||||
g.startTime = &startTime
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetFillsRequest) EndTime(endTime time.Time) *GetFillsRequest {
|
||||
g.endTime = &endTime
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetFillsRequest) OrderID(orderID int) *GetFillsRequest {
|
||||
g.orderID = &orderID
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetFillsRequest) Order(order string) *GetFillsRequest {
|
||||
g.order = &order
|
||||
return g
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetFillsRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check market field -> json key market
|
||||
if g.market != nil {
|
||||
market := *g.market
|
||||
|
||||
// assign parameter of market
|
||||
params["market"] = market
|
||||
} else {
|
||||
}
|
||||
// check startTime field -> json key start_time
|
||||
if g.startTime != nil {
|
||||
startTime := *g.startTime
|
||||
|
||||
// assign parameter of startTime
|
||||
// convert time.Time to seconds time stamp
|
||||
params["start_time"] = strconv.FormatInt(startTime.Unix(), 10)
|
||||
} else {
|
||||
}
|
||||
// check endTime field -> json key end_time
|
||||
if g.endTime != nil {
|
||||
endTime := *g.endTime
|
||||
|
||||
// assign parameter of endTime
|
||||
// convert time.Time to seconds time stamp
|
||||
params["end_time"] = strconv.FormatInt(endTime.Unix(), 10)
|
||||
} else {
|
||||
}
|
||||
// check orderID field -> json key orderId
|
||||
if g.orderID != nil {
|
||||
orderID := *g.orderID
|
||||
|
||||
// assign parameter of orderID
|
||||
params["orderId"] = orderID
|
||||
} else {
|
||||
}
|
||||
// check order field -> json key order
|
||||
if g.order != nil {
|
||||
order := *g.order
|
||||
|
||||
// assign parameter of order
|
||||
params["order"] = order
|
||||
} else {
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetFillsRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetFillsRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetFillsRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetFillsRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetFillsRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetFillsRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetFillsRequest) Do(ctx context.Context) ([]Fill, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query, err := g.GetQueryParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/api/fills"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []Fill
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
131
pkg/exchange/ftx/ftxapi/get_market_request_requestgen.go
Normal file
131
pkg/exchange/ftx/ftxapi/get_market_request_requestgen.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url api/markets/:market -type GetMarketRequest -responseDataType .Market"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (g *GetMarketRequest) Market(market string) *GetMarketRequest {
|
||||
g.market = market
|
||||
return g
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetMarketRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetMarketRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetMarketRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetMarketRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetMarketRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check market field -> json key market
|
||||
market := g.market
|
||||
|
||||
// assign parameter of market
|
||||
params["market"] = market
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetMarketRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetMarketRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetMarketRequest) Do(ctx context.Context) (*Market, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "api/markets/:market"
|
||||
slugs, err := g.GetSlugsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL = g.applySlugsToUrl(apiURL, slugs)
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data Market
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
115
pkg/exchange/ftx/ftxapi/get_markets_request_requestgen.go
Normal file
115
pkg/exchange/ftx/ftxapi/get_markets_request_requestgen.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url api/markets -type GetMarketsRequest -responseDataType []Market"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetMarketsRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetMarketsRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetMarketsRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetMarketsRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetMarketsRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetMarketsRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetMarketsRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetMarketsRequest) Do(ctx context.Context) ([]Market, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "api/markets"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []Market
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
128
pkg/exchange/ftx/ftxapi/get_open_orders_request_requestgen.go
Normal file
128
pkg/exchange/ftx/ftxapi/get_open_orders_request_requestgen.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /api/orders -type GetOpenOrdersRequest -responseDataType []Order"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (g *GetOpenOrdersRequest) Market(market string) *GetOpenOrdersRequest {
|
||||
g.market = market
|
||||
return g
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetOpenOrdersRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check market field -> json key market
|
||||
market := g.market
|
||||
|
||||
// assign parameter of market
|
||||
params["market"] = market
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetOpenOrdersRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetOpenOrdersRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetOpenOrdersRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetOpenOrdersRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetOpenOrdersRequest) Do(ctx context.Context) ([]Order, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query, err := g.GetQueryParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/api/orders"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []Order
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
158
pkg/exchange/ftx/ftxapi/get_order_history_request_requestgen.go
Normal file
158
pkg/exchange/ftx/ftxapi/get_order_history_request_requestgen.go
Normal file
|
@ -0,0 +1,158 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /api/orders/history -type GetOrderHistoryRequest -responseDataType []Order"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (g *GetOrderHistoryRequest) Market(market string) *GetOrderHistoryRequest {
|
||||
g.market = market
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOrderHistoryRequest) StartTime(startTime time.Time) *GetOrderHistoryRequest {
|
||||
g.startTime = &startTime
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GetOrderHistoryRequest) EndTime(endTime time.Time) *GetOrderHistoryRequest {
|
||||
g.endTime = &endTime
|
||||
return g
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetOrderHistoryRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check market field -> json key market
|
||||
market := g.market
|
||||
|
||||
// assign parameter of market
|
||||
params["market"] = market
|
||||
// check startTime field -> json key start_time
|
||||
if g.startTime != nil {
|
||||
startTime := *g.startTime
|
||||
|
||||
// assign parameter of startTime
|
||||
// convert time.Time to seconds time stamp
|
||||
params["start_time"] = strconv.FormatInt(startTime.Unix(), 10)
|
||||
} else {
|
||||
}
|
||||
// check endTime field -> json key end_time
|
||||
if g.endTime != nil {
|
||||
endTime := *g.endTime
|
||||
|
||||
// assign parameter of endTime
|
||||
// convert time.Time to seconds time stamp
|
||||
params["end_time"] = strconv.FormatInt(endTime.Unix(), 10)
|
||||
} else {
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetOrderHistoryRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetOrderHistoryRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetOrderHistoryRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetOrderHistoryRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetOrderHistoryRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetOrderHistoryRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetOrderHistoryRequest) Do(ctx context.Context) ([]Order, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query, err := g.GetQueryParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/api/orders/history"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []Order
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
131
pkg/exchange/ftx/ftxapi/get_order_status_request_requestgen.go
Normal file
131
pkg/exchange/ftx/ftxapi/get_order_status_request_requestgen.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /api/orders/:orderId -type GetOrderStatusRequest -responseDataType .Order"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (g *GetOrderStatusRequest) OrderID(orderID uint64) *GetOrderStatusRequest {
|
||||
g.orderID = orderID
|
||||
return g
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetOrderStatusRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetOrderStatusRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetOrderStatusRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetOrderStatusRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetOrderStatusRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check orderID field -> json key orderId
|
||||
orderID := g.orderID
|
||||
|
||||
// assign parameter of orderID
|
||||
params["orderId"] = orderID
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetOrderStatusRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetOrderStatusRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetOrderStatusRequest) Do(ctx context.Context) (*Order, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/orders/:orderId"
|
||||
slugs, err := g.GetSlugsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiURL = g.applySlugsToUrl(apiURL, slugs)
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data Order
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
115
pkg/exchange/ftx/ftxapi/get_positions_request_requestgen.go
Normal file
115
pkg/exchange/ftx/ftxapi/get_positions_request_requestgen.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /api/positions -type GetPositionsRequest -responseDataType []Position"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (g *GetPositionsRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (g *GetPositionsRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (g *GetPositionsRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (g *GetPositionsRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := g.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (g *GetPositionsRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (g *GetPositionsRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (g *GetPositionsRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := g.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (g *GetPositionsRequest) Do(ctx context.Context) ([]Position, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/positions"
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := g.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []Position
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
58
pkg/exchange/ftx/ftxapi/market.go
Normal file
58
pkg/exchange/ftx/ftxapi/market.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package ftxapi
|
||||
|
||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command DeleteRequest requestgen -method DELETE -responseType .APIResponse -responseDataField Result
|
||||
|
||||
import (
|
||||
"github.com/c9s/requestgen"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
type Market struct {
|
||||
Name string `json:"name"`
|
||||
BaseCurrency string `json:"baseCurrency"`
|
||||
QuoteCurrency string `json:"quoteCurrency"`
|
||||
QuoteVolume24H fixedpoint.Value `json:"quoteVolume24h"`
|
||||
Change1H fixedpoint.Value `json:"change1h"`
|
||||
Change24H fixedpoint.Value `json:"change24h"`
|
||||
ChangeBod fixedpoint.Value `json:"changeBod"`
|
||||
VolumeUsd24H fixedpoint.Value `json:"volumeUsd24h"`
|
||||
HighLeverageFeeExempt bool `json:"highLeverageFeeExempt"`
|
||||
MinProvideSize fixedpoint.Value `json:"minProvideSize"`
|
||||
Type string `json:"type"`
|
||||
Underlying string `json:"underlying"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Ask fixedpoint.Value `json:"ask"`
|
||||
Bid fixedpoint.Value `json:"bid"`
|
||||
Last fixedpoint.Value `json:"last"`
|
||||
PostOnly bool `json:"postOnly"`
|
||||
Price fixedpoint.Value `json:"price"`
|
||||
PriceIncrement fixedpoint.Value `json:"priceIncrement"`
|
||||
SizeIncrement fixedpoint.Value `json:"sizeIncrement"`
|
||||
Restricted bool `json:"restricted"`
|
||||
}
|
||||
//go:generate GetRequest -url "api/markets" -type GetMarketsRequest -responseDataType []Market
|
||||
type GetMarketsRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetMarketsRequest() *GetMarketsRequest {
|
||||
return &GetMarketsRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "api/markets/:market" -type GetMarketRequest -responseDataType .Market
|
||||
type GetMarketRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
market string `param:"market,slug"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetMarketRequest(market string) *GetMarketRequest {
|
||||
return &GetMarketRequest{
|
||||
client: c,
|
||||
market: market,
|
||||
}
|
||||
}
|
219
pkg/exchange/ftx/ftxapi/place_order_request_requestgen.go
Normal file
219
pkg/exchange/ftx/ftxapi/place_order_request_requestgen.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
// Code generated by "requestgen -method POST -responseType .APIResponse -responseDataField Result -url /api/orders -type PlaceOrderRequest -responseDataType .Order"; DO NOT EDIT.
|
||||
|
||||
package ftxapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func (p *PlaceOrderRequest) Market(market string) *PlaceOrderRequest {
|
||||
p.market = market
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) Side(side Side) *PlaceOrderRequest {
|
||||
p.side = side
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) Price(price fixedpoint.Value) *PlaceOrderRequest {
|
||||
p.price = price
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) Size(size fixedpoint.Value) *PlaceOrderRequest {
|
||||
p.size = size
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) OrderType(orderType OrderType) *PlaceOrderRequest {
|
||||
p.orderType = orderType
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) Ioc(ioc bool) *PlaceOrderRequest {
|
||||
p.ioc = &ioc
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) PostOnly(postOnly bool) *PlaceOrderRequest {
|
||||
p.postOnly = &postOnly
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) ClientID(clientID string) *PlaceOrderRequest {
|
||||
p.clientID = &clientID
|
||||
return p
|
||||
}
|
||||
|
||||
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||
func (p *PlaceOrderRequest) GetQueryParameters() (url.Values, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
query := url.Values{}
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParameters builds and checks the parameters and return the result in a map object
|
||||
func (p *PlaceOrderRequest) GetParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
// check market field -> json key market
|
||||
market := p.market
|
||||
|
||||
// TEMPLATE check-required
|
||||
if len(market) == 0 {
|
||||
return params, fmt.Errorf("market is required, empty string given")
|
||||
}
|
||||
// END TEMPLATE check-required
|
||||
|
||||
// assign parameter of market
|
||||
params["market"] = market
|
||||
// check side field -> json key side
|
||||
side := p.side
|
||||
|
||||
// TEMPLATE check-required
|
||||
if len(side) == 0 {
|
||||
return params, fmt.Errorf("side is required, empty string given")
|
||||
}
|
||||
// END TEMPLATE check-required
|
||||
|
||||
// assign parameter of side
|
||||
params["side"] = side
|
||||
// check price field -> json key price
|
||||
price := p.price
|
||||
|
||||
// assign parameter of price
|
||||
params["price"] = price
|
||||
// check size field -> json key size
|
||||
size := p.size
|
||||
|
||||
// assign parameter of size
|
||||
params["size"] = size
|
||||
// check orderType field -> json key type
|
||||
orderType := p.orderType
|
||||
|
||||
// assign parameter of orderType
|
||||
params["type"] = orderType
|
||||
// check ioc field -> json key ioc
|
||||
if p.ioc != nil {
|
||||
ioc := *p.ioc
|
||||
|
||||
// assign parameter of ioc
|
||||
params["ioc"] = ioc
|
||||
} else {
|
||||
}
|
||||
// check postOnly field -> json key postOnly
|
||||
if p.postOnly != nil {
|
||||
postOnly := *p.postOnly
|
||||
|
||||
// assign parameter of postOnly
|
||||
params["postOnly"] = postOnly
|
||||
} else {
|
||||
}
|
||||
// check clientID field -> json key clientId
|
||||
if p.clientID != nil {
|
||||
clientID := *p.clientID
|
||||
|
||||
// assign parameter of clientID
|
||||
params["clientId"] = clientID
|
||||
} else {
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||
func (p *PlaceOrderRequest) GetParametersQuery() (url.Values, error) {
|
||||
query := url.Values{}
|
||||
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
query.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||
func (p *PlaceOrderRequest) GetParametersJSON() ([]byte, error) {
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(params)
|
||||
}
|
||||
|
||||
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||
func (p *PlaceOrderRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||
var params = map[string]interface{}{}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||
for k, v := range slugs {
|
||||
needleRE := regexp.MustCompile(":" + k + "\\b")
|
||||
url = needleRE.ReplaceAllString(url, v)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) GetSlugsMap() (map[string]string, error) {
|
||||
slugs := map[string]string{}
|
||||
params, err := p.GetSlugParameters()
|
||||
if err != nil {
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
for k, v := range params {
|
||||
slugs[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
return slugs, nil
|
||||
}
|
||||
|
||||
func (p *PlaceOrderRequest) Do(ctx context.Context) (*Order, error) {
|
||||
|
||||
params, err := p.GetParameters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/orders"
|
||||
|
||||
req, err := p.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := p.client.SendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var apiResponse APIResponse
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data Order
|
||||
if err := json.Unmarshal(apiResponse.Result, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &data, nil
|
||||
}
|
172
pkg/exchange/ftx/ftxapi/trade.go
Normal file
172
pkg/exchange/ftx/ftxapi/trade.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package ftxapi
|
||||
|
||||
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||
//go:generate -command DeleteRequest requestgen -method DELETE -responseType .APIResponse -responseDataField Result
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/c9s/requestgen"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Future string `json:"future"`
|
||||
Id int64 `json:"id"`
|
||||
Market string `json:"market"`
|
||||
Price fixedpoint.Value `json:"price"`
|
||||
AvgFillPrice fixedpoint.Value `json:"avgFillPrice"`
|
||||
Size fixedpoint.Value `json:"size"`
|
||||
RemainingSize fixedpoint.Value `json:"remainingSize"`
|
||||
FilledSize fixedpoint.Value `json:"filledSize"`
|
||||
Side Side `json:"side"`
|
||||
Status OrderStatus `json:"status"`
|
||||
Type OrderType `json:"type"`
|
||||
ReduceOnly bool `json:"reduceOnly"`
|
||||
Ioc bool `json:"ioc"`
|
||||
PostOnly bool `json:"postOnly"`
|
||||
ClientId string `json:"clientId"`
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/orders" -type GetOpenOrdersRequest -responseDataType []Order
|
||||
type GetOpenOrdersRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
market string `param:"market,query"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetOpenOrdersRequest(market string) *GetOpenOrdersRequest {
|
||||
return &GetOpenOrdersRequest{
|
||||
client: c,
|
||||
market: market,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/orders/history" -type GetOrderHistoryRequest -responseDataType []Order
|
||||
type GetOrderHistoryRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
|
||||
market string `param:"market,query"`
|
||||
|
||||
startTime *time.Time `param:"start_time,seconds,query"`
|
||||
endTime *time.Time `param:"end_time,seconds,query"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetOrderHistoryRequest(market string) *GetOrderHistoryRequest {
|
||||
return &GetOrderHistoryRequest{
|
||||
client: c,
|
||||
market: market,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate PostRequest -url "/api/orders" -type PlaceOrderRequest -responseDataType .Order
|
||||
type PlaceOrderRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
|
||||
market string `param:"market,required"`
|
||||
side Side `param:"side,required"`
|
||||
price fixedpoint.Value `param:"price"`
|
||||
size fixedpoint.Value `param:"size"`
|
||||
orderType OrderType `param:"type"`
|
||||
ioc *bool `param:"ioc"`
|
||||
postOnly *bool `param:"postOnly"`
|
||||
clientID *string `param:"clientId,optional"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewPlaceOrderRequest() *PlaceOrderRequest {
|
||||
return &PlaceOrderRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate requestgen -method DELETE -url "/api/orders/:orderID" -type CancelOrderRequest -responseType .APIResponse
|
||||
type CancelOrderRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
orderID string `param:"orderID,required,slug"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewCancelOrderRequest(orderID string) *CancelOrderRequest {
|
||||
return &CancelOrderRequest{
|
||||
client: c,
|
||||
orderID: orderID,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate requestgen -method DELETE -url "/api/orders" -type CancelAllOrderRequest -responseType .APIResponse
|
||||
type CancelAllOrderRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
market *string `param:"market"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewCancelAllOrderRequest() *CancelAllOrderRequest {
|
||||
return &CancelAllOrderRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate requestgen -method DELETE -url "/api/orders/by_client_id/:clientOrderId" -type CancelOrderByClientOrderIdRequest -responseType .APIResponse
|
||||
type CancelOrderByClientOrderIdRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
clientOrderId string `param:"clientOrderId,required,slug"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewCancelOrderByClientOrderIdRequest(clientOrderId string) *CancelOrderByClientOrderIdRequest {
|
||||
return &CancelOrderByClientOrderIdRequest{
|
||||
client: c,
|
||||
clientOrderId: clientOrderId,
|
||||
}
|
||||
}
|
||||
|
||||
type Fill struct {
|
||||
// Id is fill ID
|
||||
Id uint64 `json:"id"`
|
||||
Future string `json:"future"`
|
||||
Liquidity Liquidity `json:"liquidity"`
|
||||
Market string `json:"market"`
|
||||
BaseCurrency string `json:"baseCurrency"`
|
||||
QuoteCurrency string `json:"quoteCurrency"`
|
||||
OrderId uint64 `json:"orderId"`
|
||||
TradeId uint64 `json:"tradeId"`
|
||||
Price fixedpoint.Value `json:"price"`
|
||||
Side Side `json:"side"`
|
||||
Size fixedpoint.Value `json:"size"`
|
||||
Time time.Time `json:"time"`
|
||||
Type string `json:"type"` // always = "order"
|
||||
Fee fixedpoint.Value `json:"fee"`
|
||||
FeeCurrency string `json:"feeCurrency"`
|
||||
FeeRate fixedpoint.Value `json:"feeRate"`
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/fills" -type GetFillsRequest -responseDataType []Fill
|
||||
type GetFillsRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
|
||||
market *string `param:"market,query"`
|
||||
startTime *time.Time `param:"start_time,seconds,query"`
|
||||
endTime *time.Time `param:"end_time,seconds,query"`
|
||||
orderID *int `param:"orderId,query"`
|
||||
|
||||
// order is the order of the returned records, asc or null
|
||||
order *string `param:"order,query"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetFillsRequest() *GetFillsRequest {
|
||||
return &GetFillsRequest{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate GetRequest -url "/api/orders/:orderId" -type GetOrderStatusRequest -responseDataType .Order
|
||||
type GetOrderStatusRequest struct {
|
||||
client requestgen.AuthenticatedAPIClient
|
||||
orderID uint64 `param:"orderId,slug"`
|
||||
}
|
||||
|
||||
func (c *RestClient) NewGetOrderStatusRequest(orderID uint64) *GetOrderStatusRequest {
|
||||
return &GetOrderStatusRequest{
|
||||
client: c,
|
||||
orderID: orderID,
|
||||
}
|
||||
}
|
35
pkg/exchange/ftx/ftxapi/types.go
Normal file
35
pkg/exchange/ftx/ftxapi/types.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package ftxapi
|
||||
|
||||
type Liquidity string
|
||||
|
||||
const (
|
||||
LiquidityTaker Liquidity = "taker"
|
||||
LiquidityMaker Liquidity = "maker"
|
||||
)
|
||||
|
||||
type Side string
|
||||
|
||||
const (
|
||||
SideBuy Side = "buy"
|
||||
SideSell Side = "sell"
|
||||
)
|
||||
|
||||
type OrderType string
|
||||
|
||||
const (
|
||||
OrderTypeLimit OrderType = "limit"
|
||||
OrderTypeMarket OrderType = "market"
|
||||
|
||||
// trigger order types
|
||||
OrderTypeStopLimit OrderType = "stop"
|
||||
OrderTypeTrailingStop OrderType = "trailingStop"
|
||||
OrderTypeTakeProfit OrderType = "takeProfit"
|
||||
)
|
||||
|
||||
type OrderStatus string
|
||||
|
||||
const (
|
||||
OrderStatusNew OrderStatus = "new"
|
||||
OrderStatusOpen OrderStatus = "open"
|
||||
OrderStatusClosed OrderStatus = "closed"
|
||||
)
|
|
@ -55,9 +55,7 @@ func (r *restRequest) Transfer(ctx context.Context, p TransferPayload) (transfer
|
|||
type restRequest struct {
|
||||
*walletRequest
|
||||
*orderRequest
|
||||
*accountRequest
|
||||
*marketRequest
|
||||
*fillsRequest
|
||||
*transferRequest
|
||||
|
||||
key, secret string
|
||||
|
@ -88,9 +86,7 @@ func newRestRequest(c *http.Client, baseURL *url.URL) *restRequest {
|
|||
p: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
r.fillsRequest = &fillsRequest{restRequest: r}
|
||||
r.marketRequest = &marketRequest{restRequest: r}
|
||||
r.accountRequest = &accountRequest{restRequest: r}
|
||||
r.walletRequest = &walletRequest{restRequest: r}
|
||||
r.orderRequest = &orderRequest{restRequest: r}
|
||||
return r
|
||||
|
@ -241,12 +237,12 @@ func (r *restRequest) sendRequest(req *http.Request) (*util.Response, error) {
|
|||
type ErrorResponse struct {
|
||||
*util.Response
|
||||
|
||||
IsSuccess bool `json:"Success"`
|
||||
IsSuccess bool `json:"success"`
|
||||
ErrorString string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%s %s %d, Success: %t, err: %s",
|
||||
return fmt.Sprintf("%s %s %d, success: %t, err: %s",
|
||||
r.Response.Request.Method,
|
||||
r.Response.Request.URL.String(),
|
||||
r.Response.StatusCode,
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
package ftx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type accountRequest struct {
|
||||
*restRequest
|
||||
}
|
||||
|
||||
func (r *accountRequest) Account(ctx context.Context) (accountResponse, error) {
|
||||
resp, err := r.
|
||||
Method("GET").
|
||||
ReferenceURL("api/account").
|
||||
DoAuthenticatedRequest(ctx)
|
||||
|
||||
if err != nil {
|
||||
return accountResponse{}, err
|
||||
}
|
||||
|
||||
var a accountResponse
|
||||
if err := json.Unmarshal(resp.Body, &a); err != nil {
|
||||
return accountResponse{}, fmt.Errorf("failed to unmarshal account response body to json: %w", err)
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (r *accountRequest) Positions(ctx context.Context) (positionsResponse, error) {
|
||||
resp, err := r.
|
||||
Method("GET").
|
||||
ReferenceURL("api/positions").
|
||||
DoAuthenticatedRequest(ctx)
|
||||
|
||||
if err != nil {
|
||||
return positionsResponse{}, err
|
||||
}
|
||||
|
||||
var p positionsResponse
|
||||
if err := json.Unmarshal(resp.Body, &p); err != nil {
|
||||
return positionsResponse{}, fmt.Errorf("failed to unmarshal position response body to json: %w", err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package ftx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fillsRequest struct {
|
||||
*restRequest
|
||||
}
|
||||
|
||||
func (r *fillsRequest) Fills(ctx context.Context, market string, since, until time.Time, limit int64, orderByASC bool) (fillsResponse, error) {
|
||||
q := make(map[string]string)
|
||||
if len(market) > 0 {
|
||||
q["market"] = market
|
||||
}
|
||||
if since != (time.Time{}) {
|
||||
q["start_time"] = strconv.FormatInt(since.Unix(), 10)
|
||||
}
|
||||
if until != (time.Time{}) {
|
||||
q["end_time"] = strconv.FormatInt(until.Unix(), 10)
|
||||
}
|
||||
if limit > 0 {
|
||||
q["limit"] = strconv.FormatInt(limit, 10)
|
||||
}
|
||||
// default is descending
|
||||
if orderByASC {
|
||||
q["order"] = "asc"
|
||||
}
|
||||
resp, err := r.
|
||||
Method("GET").
|
||||
ReferenceURL("api/fills").
|
||||
Query(q).
|
||||
DoAuthenticatedRequest(ctx)
|
||||
|
||||
if err != nil {
|
||||
return fillsResponse{}, err
|
||||
}
|
||||
|
||||
var f fillsResponse
|
||||
if err := json.Unmarshal(resp.Body, &f); err != nil {
|
||||
fmt.Println("??? => ", resp.Body)
|
||||
return fillsResponse{}, fmt.Errorf("failed to unmarshal fills response body to json: %w", err)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
|
@ -123,7 +123,7 @@ func (h *messageHandler) handlePrivateOrders(response websocketResponse) {
|
|||
return
|
||||
}
|
||||
|
||||
globalOrder, err := toGlobalOrder(r.Data)
|
||||
globalOrder, err := toGlobalOrderNew(r.Data)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to convert order update to global order")
|
||||
return
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func Test_messageHandler_handleMessage(t *testing.T) {
|
||||
|
@ -93,9 +93,8 @@ func Test_messageHandler_handleMessage(t *testing.T) {
|
|||
h.OnTradeUpdate(func(trade types.Trade) {
|
||||
i++
|
||||
assert.Equal(t, types.Trade{
|
||||
GID: 0,
|
||||
ID: 6276431,
|
||||
OrderID: 323789,
|
||||
ID: uint64(6276431),
|
||||
OrderID: uint64(323789),
|
||||
Exchange: types.ExchangeFTX,
|
||||
Price: fixedpoint.NewFromFloat(2.723),
|
||||
Quantity: fixedpoint.One,
|
||||
|
@ -109,6 +108,7 @@ func Test_messageHandler_handleMessage(t *testing.T) {
|
|||
FeeCurrency: "USD",
|
||||
IsMargin: false,
|
||||
IsIsolated: false,
|
||||
IsFutures: true,
|
||||
StrategyID: sql.NullString{},
|
||||
PnL: sql.NullFloat64{},
|
||||
}, trade)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -115,7 +116,7 @@ type optionalFields struct {
|
|||
type orderUpdateResponse struct {
|
||||
mandatoryFields
|
||||
|
||||
Data order `json:"data"`
|
||||
Data ftxapi.Order `json:"data"`
|
||||
}
|
||||
|
||||
func (r websocketResponse) toOrderUpdateResponse() (orderUpdateResponse, error) {
|
||||
|
@ -133,7 +134,7 @@ func (r websocketResponse) toOrderUpdateResponse() (orderUpdateResponse, error)
|
|||
type tradeUpdateResponse struct {
|
||||
mandatoryFields
|
||||
|
||||
Data fill `json:"data"`
|
||||
Data ftxapi.Fill `json:"data"`
|
||||
}
|
||||
|
||||
func (r websocketResponse) toTradeUpdateResponse() (tradeUpdateResponse, error) {
|
||||
|
@ -195,7 +196,7 @@ func (r websocketResponse) toErrResponse() errResponse {
|
|||
}
|
||||
}
|
||||
|
||||
//sample :{"bid": 49194.0, "ask": 49195.0, "bidSize": 0.0775, "askSize": 0.0247, "last": 49200.0, "time": 1640171788.9339821}
|
||||
// sample :{"bid": 49194.0, "ask": 49195.0, "bidSize": 0.0775, "askSize": 0.0247, "last": 49200.0, "time": 1640171788.9339821}
|
||||
func (r websocketResponse) toBookTickerResponse() (bookTickerResponse, error) {
|
||||
if r.Channel != bookTickerChannel {
|
||||
return bookTickerResponse{}, fmt.Errorf("type %s, channel %s: %w", r.Type, r.Channel, errUnsupportedConversion)
|
||||
|
@ -404,12 +405,12 @@ func toGlobalBookTicker(r bookTickerResponse) (types.BookTicker, error) {
|
|||
return types.BookTicker{
|
||||
// ex. BTC/USDT
|
||||
Symbol: toGlobalSymbol(strings.ToUpper(r.Market)),
|
||||
//Time: r.Timestamp,
|
||||
// Time: r.Timestamp,
|
||||
Buy: r.Bid,
|
||||
BuySize: r.BidSize,
|
||||
Sell: r.Ask,
|
||||
SellSize: r.AskSize,
|
||||
//Last: r.Last,
|
||||
// Last: r.Last,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -225,8 +226,8 @@ func Test_websocketResponse_toOrderUpdateResponse(t *testing.T) {
|
|||
Channel: privateOrdersChannel,
|
||||
Type: updateRespType,
|
||||
},
|
||||
Data: order{
|
||||
ID: 12345,
|
||||
Data: ftxapi.Order{
|
||||
Id: 12345,
|
||||
ClientId: "test-client-id",
|
||||
Market: "SOL/USD",
|
||||
Type: "limit",
|
||||
|
@ -237,11 +238,10 @@ func Test_websocketResponse_toOrderUpdateResponse(t *testing.T) {
|
|||
FilledSize: fixedpoint.Zero,
|
||||
RemainingSize: fixedpoint.Zero,
|
||||
ReduceOnly: false,
|
||||
Liquidation: false,
|
||||
AvgFillPrice: fixedpoint.Zero,
|
||||
PostOnly: false,
|
||||
Ioc: false,
|
||||
CreatedAt: datetime{Time: mustParseDatetime("2021-03-27T11:00:36.418674+00:00")},
|
||||
CreatedAt: mustParseDatetime("2021-03-27T11:00:36.418674+00:00"),
|
||||
Future: "",
|
||||
},
|
||||
}, r)
|
||||
|
|
Loading…
Reference in New Issue
Block a user