mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 23:05:15 +00:00
ftx:support deposit histories
This commit is contained in:
parent
f84b3a5177
commit
ab743f85c2
97
pkg/cmd/deposit.go
Normal file
97
pkg/cmd/deposit.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
depositsCmd.Flags().String("session", "", "the exchange session name for querying balances")
|
||||
depositsCmd.Flags().String("asset", "", "the trading pair, like btcusdt")
|
||||
RootCmd.AddCommand(depositsCmd)
|
||||
}
|
||||
|
||||
// go run ./cmd/bbgo deposits --session=ftx --asset="BTC"
|
||||
// This is a testing util and will query deposits in last 7 days.
|
||||
var depositsCmd = &cobra.Command{
|
||||
Use: "deposits",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(configFile) == 0 {
|
||||
return errors.New("--config option is required")
|
||||
}
|
||||
|
||||
// if config file exists, use the config loaded from the config file.
|
||||
// otherwise, use a empty config object
|
||||
var userConfig *bbgo.Config
|
||||
if _, err := os.Stat(configFile); err == nil {
|
||||
// load successfully
|
||||
userConfig, err = bbgo.Load(configFile, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// config file doesn't exist
|
||||
userConfig = &bbgo.Config{}
|
||||
} else {
|
||||
// other error
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
asset, err := cmd.Flags().GetString("asset")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get the asset from flags: %w", err)
|
||||
}
|
||||
if asset == "" {
|
||||
return fmt.Errorf("asset is not found")
|
||||
}
|
||||
|
||||
until := time.Now()
|
||||
since := until.Add(-7 * 24 * time.Hour)
|
||||
exchange, ok := session.Exchange.(types.ExchangeTransferService)
|
||||
if !ok {
|
||||
return fmt.Errorf("exchange session %s does not implement transfer service", sessionName)
|
||||
}
|
||||
histories, err := exchange.QueryDepositHistory(ctx, asset, since, until)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, h := range histories {
|
||||
log.Infof("deposit history: %+v", h)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
|
@ -3,6 +3,9 @@ package ftx
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
@ -73,3 +76,35 @@ func toGlobalOrder(r order) (types.Order, error) {
|
|||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func toGlobalDeposit(input depositHistory) (types.Deposit, error) {
|
||||
s, err := toGlobalDepositStatus(input.Status)
|
||||
if err != nil {
|
||||
log.WithError(err).Warnf("assign empty string to the deposit status")
|
||||
}
|
||||
t := input.Time
|
||||
if input.ConfirmedTime != (time.Time{}) {
|
||||
t = input.ConfirmedTime
|
||||
}
|
||||
d := types.Deposit{
|
||||
GID: 0,
|
||||
Exchange: types.ExchangeFTX,
|
||||
Time: datatype.Time(t),
|
||||
Amount: input.Size,
|
||||
Asset: toGlobalCurrency(input.Coin),
|
||||
TransactionID: input.TxID,
|
||||
Status: s,
|
||||
Address: input.Address.Address,
|
||||
AddressTag: input.Address.Tag,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func toGlobalDepositStatus(input string) (types.DepositStatus, error) {
|
||||
// The document only list `confirmed` status
|
||||
switch input {
|
||||
case "confirmed", "complete":
|
||||
return types.DepositSuccess, nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported status %s", input)
|
||||
}
|
||||
|
|
|
@ -149,7 +149,27 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
|||
}
|
||||
|
||||
func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since, until time.Time) (allDeposits []types.Deposit, err error) {
|
||||
panic("implement me")
|
||||
if err = verifySinceUntil(since, until); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := e.newRest().DepositHistory(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(resp.Result, func(i, j int) bool {
|
||||
return resp.Result[i].Time.Before(resp.Result[j].Time)
|
||||
})
|
||||
for _, r := range resp.Result {
|
||||
d, err := toGlobalDeposit(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Asset == asset && !since.After(d.Time.Time()) && !until.Before(d.Time.Time()) {
|
||||
allDeposits = append(allDeposits, d)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []types.Withdraw, err error) {
|
||||
|
@ -212,8 +232,8 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [
|
|||
// symbol, since and until are all optional. FTX can only query by order created time, not updated time.
|
||||
// FTX doesn't support lastOrderID, so we will query by the time range first, and filter by the lastOrderID.
|
||||
func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, until time.Time, lastOrderID uint64) (orders []types.Order, err error) {
|
||||
if since.After(until) {
|
||||
return nil, fmt.Errorf("since can't be after until")
|
||||
if err := verifySinceUntil(since, until); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lastOrderID > 0 {
|
||||
logger.Warn("FTX doesn't support lastOrderID")
|
||||
|
@ -281,3 +301,16 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke
|
|||
func (e *Exchange) QueryTickers(ctx context.Context, symbol ...string) (map[string]types.Ticker, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func verifySinceUntil(since, until time.Time) error {
|
||||
if since.After(until) {
|
||||
return fmt.Errorf("since can't be greater than until")
|
||||
}
|
||||
if since == (time.Time{}) {
|
||||
return fmt.Errorf("since not found")
|
||||
}
|
||||
if until == (time.Time{}) {
|
||||
return fmt.Errorf("until not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
@ -116,7 +117,7 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp, 0)
|
||||
|
@ -157,7 +158,7 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, resp, 1)
|
||||
assert.Equal(t, "BTC-PERP", resp[0].Symbol)
|
||||
|
@ -201,7 +202,7 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Now(), time.Now(), 100)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, resp, 3)
|
||||
|
||||
|
@ -272,7 +273,7 @@ func TestExchange_QueryClosedOrders(t *testing.T) {
|
|||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
resp, err := ex.QueryClosedOrders(context.Background(), "BTC-PERP", time.Time{}, time.Time{}, 100)
|
||||
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}
|
||||
|
@ -423,3 +424,63 @@ func TestExchange_QueryMarkets(t *testing.T) {
|
|||
TickSize: 1,
|
||||
}, resp["BTC/USD"])
|
||||
}
|
||||
|
||||
func TestExchange_QueryDepositHistory(t *testing.T) {
|
||||
respJSON := `
|
||||
{
|
||||
"success": true,
|
||||
"result": [
|
||||
{
|
||||
"coin": "TUSD",
|
||||
"confirmations": 64,
|
||||
"confirmedTime": "2019-03-05T09:56:55.728933+00:00",
|
||||
"fee": 0,
|
||||
"id": 1,
|
||||
"sentTime": "2019-03-05T09:56:55.735929+00:00",
|
||||
"size": 99.0,
|
||||
"status": "confirmed",
|
||||
"time": "2019-03-05T09:56:55.728933+00:00",
|
||||
"txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1",
|
||||
"address": {"address": "test-addr", "tag": "test-tag"}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, respJSON)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
ex := NewExchange("", "", "")
|
||||
serverURL, err := url.Parse(ts.URL)
|
||||
assert.NoError(t, err)
|
||||
ex.restEndpoint = serverURL
|
||||
|
||||
ctx := context.Background()
|
||||
layout := "2006-01-02T15:04:05.999999Z07:00"
|
||||
actualConfirmedTime, err := time.Parse(layout, "2019-03-05T09:56:55.728933+00:00")
|
||||
assert.NoError(t, err)
|
||||
dh, err := ex.QueryDepositHistory(ctx, "TUSD", actualConfirmedTime.Add(-1*time.Hour), actualConfirmedTime.Add(1*time.Hour))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, dh, 1)
|
||||
assert.Equal(t, types.Deposit{
|
||||
Exchange: types.ExchangeFTX,
|
||||
Time: datatype.Time(actualConfirmedTime),
|
||||
Amount: 99.0,
|
||||
Asset: "TUSD",
|
||||
TransactionID: "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1",
|
||||
Status: types.DepositSuccess,
|
||||
Address: "test-addr",
|
||||
AddressTag: "test-tag",
|
||||
}, dh[0])
|
||||
|
||||
// not in the time range
|
||||
dh, err = ex.QueryDepositHistory(ctx, "TUSD", actualConfirmedTime.Add(1*time.Hour), actualConfirmedTime.Add(2*time.Hour))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, dh, 0)
|
||||
|
||||
// exclude by asset
|
||||
dh, err = ex.QueryDepositHistory(ctx, "BTC", actualConfirmedTime.Add(-1*time.Hour), actualConfirmedTime.Add(1*time.Hour))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, dh, 0)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
type restRequest struct {
|
||||
*balanceRequest
|
||||
*walletRequest
|
||||
*orderRequest
|
||||
*accountRequest
|
||||
*marketRequest
|
||||
|
@ -46,7 +46,7 @@ func newRestRequest(c *http.Client, baseURL *url.URL) *restRequest {
|
|||
|
||||
r.marketRequest = &marketRequest{restRequest: r}
|
||||
r.accountRequest = &accountRequest{restRequest: r}
|
||||
r.balanceRequest = &balanceRequest{restRequest: r}
|
||||
r.walletRequest = &walletRequest{restRequest: r}
|
||||
r.orderRequest = &orderRequest{restRequest: r}
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package ftx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type balanceRequest struct {
|
||||
*restRequest
|
||||
}
|
||||
|
||||
func (r *balanceRequest) Balances(ctx context.Context) (balances, error) {
|
||||
resp, err := r.
|
||||
Method("GET").
|
||||
ReferenceURL("api/wallet/balances").
|
||||
DoAuthenticatedRequest(ctx)
|
||||
|
||||
if err != nil {
|
||||
return balances{}, err
|
||||
}
|
||||
|
||||
var b balances
|
||||
if err := json.Unmarshal(resp.Body, &b); err != nil {
|
||||
return balances{}, fmt.Errorf("failed to unmarshal balance response body to json: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
|
@ -200,3 +200,57 @@ type orderResponse struct {
|
|||
|
||||
Result order `json:"result"`
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"success": true,
|
||||
"result": [
|
||||
{
|
||||
"coin": "TUSD",
|
||||
"confirmations": 64,
|
||||
"confirmedTime": "2019-03-05T09:56:55.728933+00:00",
|
||||
"fee": 0,
|
||||
"id": 1,
|
||||
"sentTime": "2019-03-05T09:56:55.735929+00:00",
|
||||
"size": 99.0,
|
||||
"status": "confirmed",
|
||||
"time": "2019-03-05T09:56:55.728933+00:00",
|
||||
"txid": "0x8078356ae4b06a036d64747546c274af19581f1c78c510b60505798a7ffcaf1"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
type depositHistoryResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Result []depositHistory `json:"result"`
|
||||
}
|
||||
|
||||
type depositHistory struct {
|
||||
ID int64 `json:"id"`
|
||||
Coin string `json:"coin"`
|
||||
TxID string `json:"txid"`
|
||||
Address address `json:"address"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
ConfirmedTime time.Time `json:"confirmedTime"`
|
||||
Fee float64 `json:"fee"`
|
||||
SentTime time.Time `json:"sentTime"`
|
||||
Size float64 `json:"size"`
|
||||
Status string `json:"status"`
|
||||
Time time.Time `json:"time"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
/**
|
||||
{
|
||||
"address": "test123",
|
||||
"tag": null,
|
||||
"method": "ltc",
|
||||
"coin": null
|
||||
}
|
||||
*/
|
||||
type address struct {
|
||||
Address string `json:"address"`
|
||||
Tag string `json:"tag"`
|
||||
Method string `json:"method"`
|
||||
Coin string `json:"coin"`
|
||||
}
|
||||
|
|
47
pkg/exchange/ftx/rest_wallet_request.go
Normal file
47
pkg/exchange/ftx/rest_wallet_request.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package ftx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type walletRequest struct {
|
||||
*restRequest
|
||||
}
|
||||
|
||||
func (r *walletRequest) DepositHistory(ctx context.Context) (depositHistoryResponse, error) {
|
||||
resp, err := r.
|
||||
Method("GET").
|
||||
ReferenceURL("api/wallet/deposits").
|
||||
DoAuthenticatedRequest(ctx)
|
||||
|
||||
if err != nil {
|
||||
return depositHistoryResponse{}, err
|
||||
}
|
||||
|
||||
var d depositHistoryResponse
|
||||
if err := json.Unmarshal(resp.Body, &d); err != nil {
|
||||
return depositHistoryResponse{}, fmt.Errorf("failed to unmarshal deposit history response body to json: %w", err)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (r *walletRequest) Balances(ctx context.Context) (balances, error) {
|
||||
resp, err := r.
|
||||
Method("GET").
|
||||
ReferenceURL("api/wallet/balances").
|
||||
DoAuthenticatedRequest(ctx)
|
||||
|
||||
if err != nil {
|
||||
return balances{}, err
|
||||
}
|
||||
|
||||
var b balances
|
||||
if err := json.Unmarshal(resp.Body, &b); err != nil {
|
||||
return balances{}, fmt.Errorf("failed to unmarshal balance response body to json: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
type DepositStatus string
|
||||
|
||||
const (
|
||||
DepositOther = DepositStatus("")
|
||||
// EMPTY string means not supported
|
||||
|
||||
DepositPending = DepositStatus("pending")
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user