mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +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
|
dev-version: pkg/version/dev.go
|
||||||
git commit $< -m "update dev build version"
|
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
|
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)
|
[[ -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
|
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 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 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 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 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 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
|
* [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 orderupdate](bbgo_orderupdate.md) - Listen to order update events
|
||||||
* [bbgo pnl](bbgo_pnl.md) - pnl calculator
|
* [bbgo pnl](bbgo_pnl.md) - pnl calculator
|
||||||
* [bbgo run](bbgo_run.md) - run strategies from config file
|
* [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 sync](bbgo_sync.md) - sync trades and orders history
|
||||||
* [bbgo trades](bbgo_trades.md) - Query trading history
|
* [bbgo trades](bbgo_trades.md) - Query trading history
|
||||||
* [bbgo tradeupdate](bbgo_tradeupdate.md) - Listen to trade update events
|
* [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 userdatastream](bbgo_userdatastream.md) - Listen to session events (orderUpdate, tradeUpdate, balanceUpdate, balanceSnapshot)
|
||||||
* [bbgo version](bbgo_version.md) - show version name
|
* [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
|
* [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
|
* [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
|
Show user account balances
|
||||||
|
|
||||||
```
|
```
|
||||||
bbgo balances [flags]
|
bbgo balances --session SESSION [flags]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
@ -40,4 +40,4 @@ bbgo balances [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [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
|
* [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
|
* [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
|
* [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
|
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
|
### Options
|
||||||
|
@ -48,4 +48,4 @@ bbgo execute-order [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [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
|
* [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
|
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
|
### Options
|
||||||
|
@ -41,4 +41,4 @@ bbgo list-orders [status] [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [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
|
* [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
|
* [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
|
* [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
|
* [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
|
* [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
|
## 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]
|
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
|
* [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
|
* [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
|
* [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
|
* [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
|
* [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
|
* [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
|
* [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
|
## Checklist
|
||||||
|
|
||||||
Exchange Interface - minimum requirement for trading
|
Exchange Interface - the minimum requirement for spot trading
|
||||||
|
|
||||||
- [ ] QueryMarkets
|
- [ ] QueryMarkets
|
||||||
- [ ] QueryTickers
|
- [ ] QueryTickers
|
||||||
|
@ -17,11 +17,15 @@ Exchange Interface - minimum requirement for trading
|
||||||
- [ ] CancelOrders
|
- [ ] CancelOrders
|
||||||
- [ ] NewStream
|
- [ ] NewStream
|
||||||
|
|
||||||
Trading History Service Interface - used for syncing user trading data
|
Trading History Service Interface - (optional) used for syncing user trading data
|
||||||
|
|
||||||
- [ ] QueryClosedOrders
|
- [ ] QueryClosedOrders
|
||||||
- [ ] QueryTrades
|
- [ ] QueryTrades
|
||||||
|
|
||||||
|
Order Query Service Interface - (optional) used for querying order status
|
||||||
|
|
||||||
|
- [ ] QueryOrder
|
||||||
|
|
||||||
Back-testing service - kline data is used for back-testing
|
Back-testing service - kline data is used for back-testing
|
||||||
|
|
||||||
- [ ] QueryKLines
|
- [ ] 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
|
```shell
|
||||||
godotenv -f .env.local -- go run ./cmd/bbgo orderbook --config config/bbgo.yaml --session kucoin --symbol BTCUSDT
|
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
|
```shell
|
||||||
godotenv -f .env.local -- go run ./cmd/bbgo --config config/bbgo.yaml userdatastream --session kucoin
|
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
|
```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
|
```shell
|
||||||
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session kucoin --symbol=BTCUSDT open
|
godotenv -f .env.local -- go run ./cmd/bbgo balances --session $BBGO_SESSION
|
||||||
godotenv -f .env.local -- go run ./cmd/bbgo list-orders --session kucoin --symbol=BTCUSDT closed
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing order cancel
|
### Test order submit
|
||||||
|
|
||||||
```shell
|
```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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
@ -19,41 +17,19 @@ func init() {
|
||||||
|
|
||||||
// go run ./cmd/bbgo balances --session=ftx
|
// go run ./cmd/bbgo balances --session=ftx
|
||||||
var balancesCmd = &cobra.Command{
|
var balancesCmd = &cobra.Command{
|
||||||
Use: "balances",
|
Use: "balances --session SESSION",
|
||||||
Short: "Show user account balances",
|
Short: "Show user account balances",
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
ctx := context.Background()
|
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")
|
sessionName, err := cmd.Flags().GetString("session")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if config file exists, use the config loaded from the config file.
|
if userConfig == nil {
|
||||||
// otherwise, use a empty config object
|
return fmt.Errorf("user config is not loaded")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := bbgo.NewEnvironment()
|
environ := bbgo.NewEnvironment()
|
||||||
|
|
|
@ -19,9 +19,58 @@ import (
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"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
|
// go run ./cmd/bbgo list-orders [open|closed] --session=ftx --symbol=BTCUSDT
|
||||||
var listOrdersCmd = &cobra.Command{
|
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",
|
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.
|
// default is open which means we query open orders if you haven't provided args.
|
||||||
|
@ -39,22 +88,10 @@ var listOrdersCmd = &cobra.Command{
|
||||||
return fmt.Errorf("--config option is required")
|
return fmt.Errorf("--config option is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if config file exists, use the config loaded from the config file.
|
if userConfig == nil {
|
||||||
// otherwise, use a empty config object
|
return errors.New("config file is required")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
environ := bbgo.NewEnvironment()
|
environ := bbgo.NewEnvironment()
|
||||||
|
|
||||||
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
|
if err := environ.ConfigureExchangeSessions(userConfig); err != nil {
|
||||||
|
@ -116,7 +153,7 @@ var listOrdersCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
var executeOrderCmd = &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",
|
Short: "execute buy/sell on the balance/position you have on specific symbol",
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
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
|
// go run ./cmd/bbgo submit-order --session=ftx --symbol=BTCUSDT --side=buy --price=18000 --quantity=0.001
|
||||||
var submitOrderCmd = &cobra.Command{
|
var submitOrderCmd = &cobra.Command{
|
||||||
Use: "submit-order --session SESSION --symbol SYMBOL --side SIDE --quantity QUANTITY [--price PRICE]",
|
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,
|
SilenceUsage: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -345,6 +382,10 @@ func init() {
|
||||||
listOrdersCmd.Flags().String("session", "", "the exchange session name for sync")
|
listOrdersCmd.Flags().String("session", "", "the exchange session name for sync")
|
||||||
listOrdersCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
|
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("session", "", "the exchange session name for sync")
|
||||||
submitOrderCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
|
submitOrderCmd.Flags().String("symbol", "", "the trading pair, like btcusdt")
|
||||||
submitOrderCmd.Flags().String("side", "", "the trading side: buy or sell")
|
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")
|
executeOrderCmd.Flags().Int("price-ticks", 0, "the number of price tick for the jump spread, default to 0")
|
||||||
|
|
||||||
RootCmd.AddCommand(listOrdersCmd)
|
RootCmd.AddCommand(listOrdersCmd)
|
||||||
|
RootCmd.AddCommand(getOrderCmd)
|
||||||
RootCmd.AddCommand(submitOrderCmd)
|
RootCmd.AddCommand(submitOrderCmd)
|
||||||
RootCmd.AddCommand(executeOrderCmd)
|
RootCmd.AddCommand(executeOrderCmd)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,6 +37,67 @@ func TrimLowerString(original string) string {
|
||||||
|
|
||||||
var errUnsupportedOrderStatus = fmt.Errorf("unsupported order status")
|
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) {
|
func toGlobalOrder(r order) (types.Order, error) {
|
||||||
// In exchange/max/convert.go, it only parses these fields.
|
// In exchange/max/convert.go, it only parses these fields.
|
||||||
timeInForce := types.TimeInForceGTC
|
timeInForce := types.TimeInForceGTC
|
||||||
|
@ -125,24 +187,24 @@ func toGlobalDepositStatus(input string) (types.DepositStatus, error) {
|
||||||
return "", fmt.Errorf("unsupported status %s", input)
|
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{
|
return types.Trade{
|
||||||
ID: f.TradeId,
|
ID: f.TradeId,
|
||||||
GID: 0,
|
|
||||||
OrderID: f.OrderId,
|
OrderID: f.OrderId,
|
||||||
Exchange: types.ExchangeFTX,
|
Exchange: types.ExchangeFTX,
|
||||||
Price: f.Price,
|
Price: f.Price,
|
||||||
Quantity: f.Size,
|
Quantity: f.Size,
|
||||||
QuoteQuantity: f.Price.Mul(f.Size),
|
QuoteQuantity: f.Price.Mul(f.Size),
|
||||||
Symbol: toGlobalSymbol(f.Market),
|
Symbol: toGlobalSymbol(f.Market),
|
||||||
Side: f.Side,
|
Side: types.SideType(strings.ToUpper(string(f.Side))),
|
||||||
IsBuyer: f.Side == types.SideTypeBuy,
|
IsBuyer: f.Side == ftxapi.SideBuy,
|
||||||
IsMaker: f.Liquidity == "maker",
|
IsMaker: f.Liquidity == ftxapi.LiquidityMaker,
|
||||||
Time: types.Time(f.Time.Time),
|
Time: types.Time(f.Time),
|
||||||
Fee: f.Fee,
|
Fee: f.Fee,
|
||||||
FeeCurrency: f.FeeCurrency,
|
FeeCurrency: f.FeeCurrency,
|
||||||
IsMargin: false,
|
IsMargin: false,
|
||||||
IsIsolated: false,
|
IsIsolated: false,
|
||||||
|
IsFutures: f.Future != "",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,17 +231,17 @@ const (
|
||||||
OrderTypeMarket OrderType = "market"
|
OrderTypeMarket OrderType = "market"
|
||||||
)
|
)
|
||||||
|
|
||||||
func toLocalOrderType(orderType types.OrderType) (OrderType, error) {
|
func toLocalOrderType(orderType types.OrderType) (ftxapi.OrderType, error) {
|
||||||
switch orderType {
|
switch orderType {
|
||||||
|
|
||||||
case types.OrderTypeLimitMaker:
|
case types.OrderTypeLimitMaker:
|
||||||
return OrderTypeLimit, nil
|
return ftxapi.OrderTypeLimit, nil
|
||||||
|
|
||||||
case types.OrderTypeLimit:
|
case types.OrderTypeLimit:
|
||||||
return OrderTypeLimit, nil
|
return ftxapi.OrderTypeLimit, nil
|
||||||
|
|
||||||
case types.OrderTypeMarket:
|
case types.OrderTypeMarket:
|
||||||
return OrderTypeMarket, nil
|
return ftxapi.OrderTypeMarket, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,18 +105,18 @@ func Test_toGlobalSymbol(t *testing.T) {
|
||||||
func Test_toLocalOrderTypeWithLimitMaker(t *testing.T) {
|
func Test_toLocalOrderTypeWithLimitMaker(t *testing.T) {
|
||||||
orderType, err := toLocalOrderType(types.OrderTypeLimitMaker)
|
orderType, err := toLocalOrderType(types.OrderTypeLimitMaker)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, orderType, OrderTypeLimit)
|
assert.Equal(t, ftxapi.OrderTypeLimit, orderType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_toLocalOrderTypeWithLimit(t *testing.T) {
|
func Test_toLocalOrderTypeWithLimit(t *testing.T) {
|
||||||
orderType, err := toLocalOrderType(types.OrderTypeLimit)
|
orderType, err := toLocalOrderType(types.OrderTypeLimit)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, orderType, OrderTypeLimit)
|
assert.Equal(t, ftxapi.OrderTypeLimit, orderType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_toLocalOrderTypeWithMarket(t *testing.T) {
|
func Test_toLocalOrderTypeWithMarket(t *testing.T) {
|
||||||
orderType, err := toLocalOrderType(types.OrderTypeMarket)
|
orderType, err := toLocalOrderType(types.OrderTypeMarket)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, orderType, OrderTypeMarket)
|
assert.Equal(t, ftxapi.OrderTypeMarket, orderType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"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
|
//go:generate go run generate_symbol_map.go
|
||||||
|
|
||||||
type Exchange struct {
|
type Exchange struct {
|
||||||
|
client *ftxapi.RestClient
|
||||||
|
|
||||||
key, secret string
|
key, secret string
|
||||||
subAccount string
|
subAccount string
|
||||||
restEndpoint *url.URL
|
restEndpoint *url.URL
|
||||||
|
@ -78,7 +82,10 @@ func NewExchange(key, secret string, subAccount string) *Exchange {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := ftxapi.NewClient()
|
||||||
|
client.Auth(key, secret, subAccount)
|
||||||
return &Exchange{
|
return &Exchange{
|
||||||
|
client: client,
|
||||||
restEndpoint: u,
|
restEndpoint: u,
|
||||||
key: key,
|
key: key,
|
||||||
secret: secret,
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !resp.Success {
|
|
||||||
return nil, fmt.Errorf("ftx returns querying markets failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
markets := MarketMap{}
|
markets := MarketMap{}
|
||||||
for _, m := range resp.Result {
|
for _, m := range ftxMarkets {
|
||||||
symbol := toGlobalSymbol(m.Name)
|
symbol := toGlobalSymbol(m.Name)
|
||||||
symbolMap[symbol] = 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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !resp.Success {
|
|
||||||
return nil, fmt.Errorf("ftx returns querying balances failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
a := &types.Account{
|
a := &types.Account{
|
||||||
MakerCommission: resp.Result.MakerFee,
|
MakerCommission: ftxAccount.MakerFee,
|
||||||
TakerCommission: resp.Result.TakerFee,
|
TakerCommission: ftxAccount.TakerFee,
|
||||||
TotalAccountValue: resp.Result.TotalAccountValue,
|
TotalAccountValue: ftxAccount.TotalAccountValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
balances, err := e.QueryAccountBalances(ctx)
|
balances, err := e.QueryAccountBalances(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.UpdateBalances(balances)
|
|
||||||
|
|
||||||
|
a.UpdateBalances(balances)
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !resp.Success {
|
|
||||||
return nil, fmt.Errorf("ftx returns querying balances failure")
|
|
||||||
}
|
|
||||||
var balances = make(types.BalanceMap)
|
var balances = make(types.BalanceMap)
|
||||||
for _, r := range resp.Result {
|
for _, r := range ftxBalances {
|
||||||
balances[toGlobalCurrency(r.Coin)] = types.Balance{
|
currency := toGlobalCurrency(r.Coin)
|
||||||
Currency: toGlobalCurrency(r.Coin),
|
balances[currency] = types.Balance{
|
||||||
|
Currency: currency,
|
||||||
Available: r.Free,
|
Available: r.Free,
|
||||||
Locked: r.Total.Sub(r.Free),
|
Locked: r.Total.Sub(r.Free),
|
||||||
}
|
}
|
||||||
|
@ -340,76 +344,57 @@ func isIntervalSupportedInKLine(interval types.Interval) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) ([]types.Trade, error) {
|
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{})
|
tradeIDs := make(map[uint64]struct{})
|
||||||
|
|
||||||
lastTradeID := options.LastTradeID
|
lastTradeID := options.LastTradeID
|
||||||
var trades []types.Trade
|
|
||||||
symbol = strings.ToUpper(symbol)
|
|
||||||
|
|
||||||
for since.Before(until) {
|
req := e.client.NewGetFillsRequest()
|
||||||
// DO not set limit to `1` since you will always get the same response.
|
req.Market(toLocalSymbol(symbol))
|
||||||
resp, err := e.newRest().Fills(ctx, toLocalSymbol(symbol), since, until, limit, true)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !resp.Success {
|
|
||||||
return nil, fmt.Errorf("ftx returns failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(resp.Result, func(i, j int) bool {
|
sort.Slice(fills, func(i, j int) bool {
|
||||||
return resp.Result[i].TradeId < resp.Result[j].TradeId
|
return fills[i].Time.Before(fills[j].Time)
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, r := range resp.Result {
|
var trades []types.Trade
|
||||||
// always update since to avoid infinite loop
|
symbol = strings.ToUpper(symbol)
|
||||||
since = r.Time.Time
|
for _, fill := range fills {
|
||||||
|
if _, ok := tradeIDs[fill.TradeId]; ok {
|
||||||
if _, ok := tradeIDs[r.TradeId]; ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.TradeId <= lastTradeID || r.Time.Before(since) || r.Time.After(until) || r.Market != toLocalSymbol(symbol) {
|
if options.StartTime != nil && fill.Time.Before(*options.StartTime) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tradeIDs[r.TradeId] = struct{}{}
|
|
||||||
lastTradeID = r.TradeId
|
|
||||||
|
|
||||||
t, err := toGlobalTrade(r)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
trades = append(trades, t)
|
trades = append(trades, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
if int64(len(resp.Result)) < limit {
|
|
||||||
return trades, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return trades, nil
|
return trades, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,27 +443,34 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
|
||||||
logrus.WithError(err).Error("type error")
|
logrus.WithError(err).Error("type error")
|
||||||
}
|
}
|
||||||
|
|
||||||
or, err := e.newRest().PlaceOrder(ctx, PlaceOrderPayload{
|
req := e.client.NewPlaceOrderRequest()
|
||||||
Market: toLocalSymbol(TrimUpperString(so.Symbol)),
|
req.Market(toLocalSymbol(TrimUpperString(so.Symbol)))
|
||||||
Side: TrimLowerString(string(so.Side)),
|
req.OrderType(orderType)
|
||||||
Price: so.Price,
|
req.Side(ftxapi.Side(TrimLowerString(string(so.Side))))
|
||||||
Type: string(orderType),
|
req.Size(so.Quantity)
|
||||||
Size: so.Quantity,
|
|
||||||
ReduceOnly: false,
|
|
||||||
IOC: so.TimeInForce == types.TimeInForceIOC,
|
|
||||||
PostOnly: so.Type == types.OrderTypeLimitMaker,
|
|
||||||
ClientID: newSpotClientOrderID(so.ClientOrderID),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return createdOrders, fmt.Errorf("failed to place order %+v: %w", so, err)
|
return createdOrders, fmt.Errorf("failed to place order %+v: %w", so, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !or.Success {
|
globalOrder, err := toGlobalOrderNew(*or)
|
||||||
return createdOrders, fmt.Errorf("ftx returns placing order failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
globalOrder, err := toGlobalOrder(or.Result)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return createdOrders, fmt.Errorf("failed to convert response to global order")
|
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
|
return createdOrders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
req := e.client.NewGetOrderStatusRequest(uint64(orderID))
|
||||||
|
ftxOrder, err := req.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
order, err := toGlobalOrderNew(*ftxOrder)
|
||||||
|
return &order, err
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders []types.Order, err error) {
|
||||||
// TODO: invoke open trigger orders
|
// TODO: invoke open trigger orders
|
||||||
resp, err := e.newRest().OpenOrders(ctx, toLocalSymbol(symbol))
|
|
||||||
|
req := e.client.NewGetOpenOrdersRequest(toLocalSymbol(symbol))
|
||||||
|
ftxOrders, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !resp.Success {
|
|
||||||
return nil, fmt.Errorf("ftx returns querying open orders failure")
|
for _, ftxOrder := range ftxOrders {
|
||||||
}
|
o, err := toGlobalOrderNew(ftxOrder)
|
||||||
for _, r := range resp.Result {
|
|
||||||
o, err := toGlobalOrder(r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return orders, err
|
||||||
}
|
}
|
||||||
|
|
||||||
orders = append(orders, o)
|
orders = append(orders, o)
|
||||||
}
|
}
|
||||||
return orders, nil
|
return orders, nil
|
||||||
|
@ -510,75 +519,61 @@ 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.
|
// 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.
|
// 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) {
|
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)
|
symbol = TrimUpperString(symbol)
|
||||||
limit := int64(100)
|
|
||||||
hasMoreData := true
|
|
||||||
s := since
|
|
||||||
var lastOrder order
|
|
||||||
for hasMoreData {
|
|
||||||
|
|
||||||
if err := requestLimit.Wait(ctx); err != nil {
|
req := e.client.NewGetOrderHistoryRequest(toLocalSymbol(symbol))
|
||||||
logrus.WithError(err).Error("rate limit error")
|
|
||||||
|
if since != (time.Time{}) {
|
||||||
|
req.StartTime(since)
|
||||||
|
} else if until != (time.Time{}) {
|
||||||
|
req.EndTime(until)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := e.newRest().OrdersHistory(ctx, toLocalSymbol(symbol), s, until, limit)
|
ftxOrders, err := req.Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !resp.Success {
|
|
||||||
return nil, fmt.Errorf("ftx returns querying orders history failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
sortByCreatedASC(resp.Result)
|
sort.Slice(ftxOrders, func(i, j int) bool {
|
||||||
|
return ftxOrders[i].CreatedAt.Before(ftxOrders[j].CreatedAt)
|
||||||
|
})
|
||||||
|
|
||||||
for _, r := range resp.Result {
|
for _, ftxOrder := range ftxOrders {
|
||||||
// There may be more than one orders at the same time, so also have to check the ID
|
switch ftxOrder.Status {
|
||||||
if r.CreatedAt.Before(lastOrder.CreatedAt.Time) || r.ID == lastOrder.ID || r.Status != "closed" || r.ID < int64(lastOrderID) {
|
case ftxapi.OrderStatusOpen, ftxapi.OrderStatusNew:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastOrder = r
|
|
||||||
o, err := toGlobalOrder(r)
|
o, err := toGlobalOrderNew(ftxOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return orders, err
|
||||||
}
|
}
|
||||||
|
|
||||||
orders = append(orders, o)
|
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)
|
|
||||||
}
|
|
||||||
return orders, nil
|
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 {
|
func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) error {
|
||||||
for _, o := range orders {
|
for _, o := range orders {
|
||||||
rest := e.newRest()
|
|
||||||
if err := requestLimit.Wait(ctx); err != nil {
|
if err := requestLimit.Wait(ctx); err != nil {
|
||||||
logrus.WithError(err).Error("rate limit error")
|
logrus.WithError(err).Error("rate limit error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(o.ClientOrderID) > 0 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
continue
|
} else {
|
||||||
}
|
req := e.client.NewCancelOrderRequest(strconv.FormatUint(o.OrderID, 10))
|
||||||
if _, err := rest.CancelOrderByOrderID(ctx, o.OrderID); err != nil {
|
_, err := req.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,10 +86,11 @@ func TestExchange_QueryAccountBalances(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
resp, err := ex.QueryAccountBalances(context.Background())
|
resp, err := ex.QueryAccountBalances(context.Background())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -99,10 +100,6 @@ func TestExchange_QueryAccountBalances(t *testing.T) {
|
||||||
expectedAvailable := fixedpoint.Must(fixedpoint.NewFromString("19.48085209"))
|
expectedAvailable := fixedpoint.Must(fixedpoint.NewFromString("19.48085209"))
|
||||||
assert.Equal(t, expectedAvailable, b.Available)
|
assert.Equal(t, expectedAvailable, b.Available)
|
||||||
assert.Equal(t, fixedpoint.Must(fixedpoint.NewFromString("1094.66405065")).Sub(expectedAvailable), b.Locked)
|
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) {
|
func TestExchange_QueryOpenOrders(t *testing.T) {
|
||||||
|
@ -136,10 +133,12 @@ func TestExchange_QueryOpenOrders(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
|
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
resp, err := ex.QueryOpenOrders(context.Background(), "XRP-PREP")
|
resp, err := ex.QueryOpenOrders(context.Background(), "XRP-PREP")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, resp, 1)
|
assert.Len(t, resp, 1)
|
||||||
|
@ -155,10 +154,11 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -196,10 +196,11 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, resp, 1)
|
assert.Len(t, resp, 1)
|
||||||
|
@ -240,10 +241,11 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, resp, 3)
|
assert.Len(t, resp, 3)
|
||||||
|
@ -253,76 +255,6 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
||||||
assert.Equal(t, expectedOrderID[i], o.OrderID)
|
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) {
|
func TestExchange_QueryAccount(t *testing.T) {
|
||||||
|
@ -391,10 +323,11 @@ func TestExchange_QueryAccount(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
resp, err := ex.QueryAccount(context.Background())
|
resp, err := ex.QueryAccount(context.Background())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -447,10 +380,12 @@ func TestExchange_QueryMarkets(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
ex.client.BaseURL = serverURL
|
||||||
ex.restEndpoint = serverURL
|
ex.restEndpoint = serverURL
|
||||||
|
|
||||||
resp, err := ex.QueryMarkets(context.Background())
|
resp, err := ex.QueryMarkets(context.Background())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -494,9 +429,10 @@ func TestExchange_QueryDepositHistory(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
ex.client.BaseURL = serverURL
|
||||||
ex.restEndpoint = serverURL
|
ex.restEndpoint = serverURL
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -543,10 +479,10 @@ func TestExchange_QueryTrades(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
actualConfirmedTime, err := parseDatetime("2021-02-23T09:29:08.534000+00:00")
|
actualConfirmedTime, err := parseDatetime("2021-02-23T09:29:08.534000+00:00")
|
||||||
|
@ -619,10 +555,10 @@ func TestExchange_QueryTrades(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
ex := NewExchange("", "", "")
|
ex := NewExchange("test-key", "test-secret", "")
|
||||||
serverURL, err := url.Parse(ts.URL)
|
serverURL, err := url.Parse(ts.URL)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
ex.restEndpoint = serverURL
|
ex.client.BaseURL = serverURL
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
actualConfirmedTime, err := parseDatetime("2021-02-23T09:29:08.534000+00:00")
|
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 {
|
type restRequest struct {
|
||||||
*walletRequest
|
*walletRequest
|
||||||
*orderRequest
|
*orderRequest
|
||||||
*accountRequest
|
|
||||||
*marketRequest
|
*marketRequest
|
||||||
*fillsRequest
|
|
||||||
*transferRequest
|
*transferRequest
|
||||||
|
|
||||||
key, secret string
|
key, secret string
|
||||||
|
@ -88,9 +86,7 @@ func newRestRequest(c *http.Client, baseURL *url.URL) *restRequest {
|
||||||
p: make(map[string]interface{}),
|
p: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
r.fillsRequest = &fillsRequest{restRequest: r}
|
|
||||||
r.marketRequest = &marketRequest{restRequest: r}
|
r.marketRequest = &marketRequest{restRequest: r}
|
||||||
r.accountRequest = &accountRequest{restRequest: r}
|
|
||||||
r.walletRequest = &walletRequest{restRequest: r}
|
r.walletRequest = &walletRequest{restRequest: r}
|
||||||
r.orderRequest = &orderRequest{restRequest: r}
|
r.orderRequest = &orderRequest{restRequest: r}
|
||||||
return r
|
return r
|
||||||
|
@ -241,12 +237,12 @@ func (r *restRequest) sendRequest(req *http.Request) (*util.Response, error) {
|
||||||
type ErrorResponse struct {
|
type ErrorResponse struct {
|
||||||
*util.Response
|
*util.Response
|
||||||
|
|
||||||
IsSuccess bool `json:"Success"`
|
IsSuccess bool `json:"success"`
|
||||||
ErrorString string `json:"error,omitempty"`
|
ErrorString string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ErrorResponse) Error() string {
|
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.Method,
|
||||||
r.Response.Request.URL.String(),
|
r.Response.Request.URL.String(),
|
||||||
r.Response.StatusCode,
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
globalOrder, err := toGlobalOrder(r.Data)
|
globalOrder, err := toGlobalOrderNew(r.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Errorf("failed to convert order update to global order")
|
logger.WithError(err).Errorf("failed to convert order update to global order")
|
||||||
return
|
return
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_messageHandler_handleMessage(t *testing.T) {
|
func Test_messageHandler_handleMessage(t *testing.T) {
|
||||||
|
@ -93,9 +93,8 @@ func Test_messageHandler_handleMessage(t *testing.T) {
|
||||||
h.OnTradeUpdate(func(trade types.Trade) {
|
h.OnTradeUpdate(func(trade types.Trade) {
|
||||||
i++
|
i++
|
||||||
assert.Equal(t, types.Trade{
|
assert.Equal(t, types.Trade{
|
||||||
GID: 0,
|
ID: uint64(6276431),
|
||||||
ID: 6276431,
|
OrderID: uint64(323789),
|
||||||
OrderID: 323789,
|
|
||||||
Exchange: types.ExchangeFTX,
|
Exchange: types.ExchangeFTX,
|
||||||
Price: fixedpoint.NewFromFloat(2.723),
|
Price: fixedpoint.NewFromFloat(2.723),
|
||||||
Quantity: fixedpoint.One,
|
Quantity: fixedpoint.One,
|
||||||
|
@ -109,6 +108,7 @@ func Test_messageHandler_handleMessage(t *testing.T) {
|
||||||
FeeCurrency: "USD",
|
FeeCurrency: "USD",
|
||||||
IsMargin: false,
|
IsMargin: false,
|
||||||
IsIsolated: false,
|
IsIsolated: false,
|
||||||
|
IsFutures: true,
|
||||||
StrategyID: sql.NullString{},
|
StrategyID: sql.NullString{},
|
||||||
PnL: sql.NullFloat64{},
|
PnL: sql.NullFloat64{},
|
||||||
}, trade)
|
}, trade)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
@ -115,7 +116,7 @@ type optionalFields struct {
|
||||||
type orderUpdateResponse struct {
|
type orderUpdateResponse struct {
|
||||||
mandatoryFields
|
mandatoryFields
|
||||||
|
|
||||||
Data order `json:"data"`
|
Data ftxapi.Order `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r websocketResponse) toOrderUpdateResponse() (orderUpdateResponse, error) {
|
func (r websocketResponse) toOrderUpdateResponse() (orderUpdateResponse, error) {
|
||||||
|
@ -133,7 +134,7 @@ func (r websocketResponse) toOrderUpdateResponse() (orderUpdateResponse, error)
|
||||||
type tradeUpdateResponse struct {
|
type tradeUpdateResponse struct {
|
||||||
mandatoryFields
|
mandatoryFields
|
||||||
|
|
||||||
Data fill `json:"data"`
|
Data ftxapi.Fill `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r websocketResponse) toTradeUpdateResponse() (tradeUpdateResponse, error) {
|
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) {
|
func (r websocketResponse) toBookTickerResponse() (bookTickerResponse, error) {
|
||||||
if r.Channel != bookTickerChannel {
|
if r.Channel != bookTickerChannel {
|
||||||
return bookTickerResponse{}, fmt.Errorf("type %s, channel %s: %w", r.Type, r.Channel, errUnsupportedConversion)
|
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{
|
return types.BookTicker{
|
||||||
// ex. BTC/USDT
|
// ex. BTC/USDT
|
||||||
Symbol: toGlobalSymbol(strings.ToUpper(r.Market)),
|
Symbol: toGlobalSymbol(strings.ToUpper(r.Market)),
|
||||||
//Time: r.Timestamp,
|
// Time: r.Timestamp,
|
||||||
Buy: r.Bid,
|
Buy: r.Bid,
|
||||||
BuySize: r.BidSize,
|
BuySize: r.BidSize,
|
||||||
Sell: r.Ask,
|
Sell: r.Ask,
|
||||||
SellSize: r.AskSize,
|
SellSize: r.AskSize,
|
||||||
//Last: r.Last,
|
// Last: r.Last,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/exchange/ftx/ftxapi"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
@ -225,8 +226,8 @@ func Test_websocketResponse_toOrderUpdateResponse(t *testing.T) {
|
||||||
Channel: privateOrdersChannel,
|
Channel: privateOrdersChannel,
|
||||||
Type: updateRespType,
|
Type: updateRespType,
|
||||||
},
|
},
|
||||||
Data: order{
|
Data: ftxapi.Order{
|
||||||
ID: 12345,
|
Id: 12345,
|
||||||
ClientId: "test-client-id",
|
ClientId: "test-client-id",
|
||||||
Market: "SOL/USD",
|
Market: "SOL/USD",
|
||||||
Type: "limit",
|
Type: "limit",
|
||||||
|
@ -237,11 +238,10 @@ func Test_websocketResponse_toOrderUpdateResponse(t *testing.T) {
|
||||||
FilledSize: fixedpoint.Zero,
|
FilledSize: fixedpoint.Zero,
|
||||||
RemainingSize: fixedpoint.Zero,
|
RemainingSize: fixedpoint.Zero,
|
||||||
ReduceOnly: false,
|
ReduceOnly: false,
|
||||||
Liquidation: false,
|
|
||||||
AvgFillPrice: fixedpoint.Zero,
|
AvgFillPrice: fixedpoint.Zero,
|
||||||
PostOnly: false,
|
PostOnly: false,
|
||||||
Ioc: 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: "",
|
Future: "",
|
||||||
},
|
},
|
||||||
}, r)
|
}, r)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user