From 8a75b21a38eaf4cfec51cae7f4e6b1b73bd58256 Mon Sep 17 00:00:00 2001 From: ycdesu Date: Thu, 18 Mar 2021 08:33:14 +0800 Subject: [PATCH 1/3] ftx: support account info --- pkg/cmd/account.go | 78 +++++++++++++++++++++ pkg/exchange/ftx/exchange.go | 22 +++++- pkg/exchange/ftx/rest.go | 2 + pkg/exchange/ftx/rest_account_request.go | 47 +++++++++++++ pkg/exchange/ftx/rest_responses.go | 88 ++++++++++++++++++++++++ pkg/types/account.go | 1 + 6 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 pkg/cmd/account.go create mode 100644 pkg/exchange/ftx/rest_account_request.go diff --git a/pkg/cmd/account.go b/pkg/cmd/account.go new file mode 100644 index 000000000..31d39fdea --- /dev/null +++ b/pkg/cmd/account.go @@ -0,0 +1,78 @@ +package cmd + +import ( + "context" + "fmt" + "os" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/c9s/bbgo/pkg/bbgo" +) + +func init() { + accountCmd.Flags().String("session", "", "the exchange session name for querying information") + RootCmd.AddCommand(accountCmd) +} + +// go run ./cmd/bbgo account --session=ftx --config=config/bbgo.yaml +var accountCmd = &cobra.Command{ + Use: "account", + 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") + } + + 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.ConfigureDatabase(ctx); err != nil { + return err + } + + 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) + } + + a, err := session.Exchange.QueryAccount(ctx) + if err != nil { + return err + } + + log.Infof("account info: %+v", a) + return nil + }, +} diff --git a/pkg/exchange/ftx/exchange.go b/pkg/exchange/ftx/exchange.go index 0706dfc41..02c84b941 100644 --- a/pkg/exchange/ftx/exchange.go +++ b/pkg/exchange/ftx/exchange.go @@ -65,7 +65,27 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) { } func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) { - panic("implement me") + resp, err := e.newRest().Account(ctx) + if err != nil { + return nil, err + } + if !resp.Success { + return nil, fmt.Errorf("ftx returns querying balances failure") + } + + // TODO + a := &types.Account{ + MakerCommission: 0, + TakerCommission: 0, + } + + balances, err := e.QueryAccountBalances(ctx) + if err != nil { + return nil, err + } + a.UpdateBalances(balances) + + return a, nil } func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap, error) { diff --git a/pkg/exchange/ftx/rest.go b/pkg/exchange/ftx/rest.go index 563d6e415..5bf11209c 100644 --- a/pkg/exchange/ftx/rest.go +++ b/pkg/exchange/ftx/rest.go @@ -21,6 +21,7 @@ import ( type restRequest struct { *balanceRequest *orderRequest + *accountRequest key, secret string // Optional sub-account name @@ -42,6 +43,7 @@ func newRestRequest(c *http.Client, baseURL *url.URL) *restRequest { baseURL: baseURL, } + r.accountRequest = &accountRequest{restRequest: r} r.balanceRequest = &balanceRequest{restRequest: r} r.orderRequest = &orderRequest{restRequest: r} return r diff --git a/pkg/exchange/ftx/rest_account_request.go b/pkg/exchange/ftx/rest_account_request.go new file mode 100644 index 000000000..36c075ec0 --- /dev/null +++ b/pkg/exchange/ftx/rest_account_request.go @@ -0,0 +1,47 @@ +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 +} diff --git a/pkg/exchange/ftx/rest_responses.go b/pkg/exchange/ftx/rest_responses.go index 68100f2c1..e486926a2 100644 --- a/pkg/exchange/ftx/rest_responses.go +++ b/pkg/exchange/ftx/rest_responses.go @@ -2,6 +2,94 @@ package ftx import "time" +/* +{ + "success": true, + "result": { + "backstopProvider": true, + "collateral": 3568181.02691129, + "freeCollateral": 1786071.456884368, + "initialMarginRequirement": 0.12222384240257728, + "leverage": 10, + "liquidating": false, + "maintenanceMarginRequirement": 0.07177992558058484, + "makerFee": 0.0002, + "marginFraction": 0.5588433331419503, + "openMarginFraction": 0.2447194090423075, + "takerFee": 0.0005, + "totalAccountValue": 3568180.98341129, + "totalPositionSize": 6384939.6992, + "username": "user@domain.com", + "positions": [ + { + "cost": -31.7906, + "entryPrice": 138.22, + "future": "ETH-PERP", + "initialMarginRequirement": 0.1, + "longOrderSize": 1744.55, + "maintenanceMarginRequirement": 0.04, + "netSize": -0.23, + "openSize": 1744.32, + "realizedPnl": 3.39441714, + "shortOrderSize": 1732.09, + "side": "sell", + "size": 0.23, + "unrealizedPnl": 0 + } + ] + } +} +*/ +type accountResponse struct { + Success bool `json:"success"` + Result account `json:"result"` +} + +type account struct { +} + +type positionsResponse struct { + Success bool `json:"success"` + Result []position `json:"result"` +} + +/* +{ + "cost": -31.7906, + "entryPrice": 138.22, + "estimatedLiquidationPrice": 152.1, + "future": "ETH-PERP", + "initialMarginRequirement": 0.1, + "longOrderSize": 1744.55, + "maintenanceMarginRequirement": 0.04, + "netSize": -0.23, + "openSize": 1744.32, + "realizedPnl": 3.39441714, + "shortOrderSize": 1732.09, + "side": "sell", + "size": 0.23, + "unrealizedPnl": 0, + "collateralUsed": 3.17906 +} +*/ +type position struct { + Cost float64 `json:"cost"` + EntryPrice float64 `json:"entryPrice"` + EstimatedLiquidationPrice float64 `json:"estimatedLiquidationPrice"` + Future string `json:"future"` + InitialMarginRequirement float64 `json:"initialMarginRequirement"` + LongOrderSize float64 `json:"longOrderSize"` + MaintenanceMarginRequirement float64 `json:"maintenanceMarginRequirement"` + NetSize float64 `json:"netSize"` + OpenSize float64 `json:"openSize"` + RealizedPnl float64 `json:"realizedPnl"` + ShortOrderSize float64 `json:"shortOrderSize"` + Side string `json:"Side"` + Size float64 `json:"size"` + UnrealizedPnl float64 `json:"unrealizedPnl"` + CollateralUsed float64 `json:"collateralUsed"` +} + type balances struct { Success bool `json:"success"` diff --git a/pkg/types/account.go b/pkg/types/account.go index 922fabeae..7ee68e38d 100644 --- a/pkg/types/account.go +++ b/pkg/types/account.go @@ -102,6 +102,7 @@ func (m BalanceMap) Print() { type Account struct { sync.Mutex `json:"-"` + // bps. 0.15% fee will be 15. MakerCommission int `json:"makerCommission,omitempty"` TakerCommission int `json:"takerCommission,omitempty"` AccountType string `json:"accountType,omitempty"` From a62481590e0ba045012e3fbec9ecb25f26c18766 Mon Sep 17 00:00:00 2001 From: ycdesu Date: Thu, 18 Mar 2021 08:49:33 +0800 Subject: [PATCH 2/3] ftx: support PlatformCurrency --- pkg/exchange/ftx/exchange.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/exchange/ftx/exchange.go b/pkg/exchange/ftx/exchange.go index 02c84b941..c529558e9 100644 --- a/pkg/exchange/ftx/exchange.go +++ b/pkg/exchange/ftx/exchange.go @@ -53,7 +53,7 @@ func (e *Exchange) Name() types.ExchangeName { } func (e *Exchange) PlatformFeeCurrency() string { - panic("implement me") + return toGlobalCurrency("FTT") } func (e *Exchange) NewStream() types.Stream { From 83ae943a4f044bc706bafaf6bb28809bd18e8b65 Mon Sep 17 00:00:00 2001 From: ycdesu Date: Thu, 18 Mar 2021 10:04:48 +0800 Subject: [PATCH 3/3] ftx: calculate commission --- pkg/backtest/matching.go | 8 +-- pkg/bbgo/config.go | 4 +- pkg/exchange/binance/exchange.go | 4 +- pkg/exchange/ftx/exchange.go | 6 +- pkg/exchange/ftx/exchange_test.go | 88 ++++++++++++++++++++++++++++++ pkg/exchange/ftx/rest_responses.go | 2 + pkg/types/account.go | 6 +- 7 files changed, 104 insertions(+), 14 deletions(-) diff --git a/pkg/backtest/matching.go b/pkg/backtest/matching.go index 0ad6adb83..c6e1b4686 100644 --- a/pkg/backtest/matching.go +++ b/pkg/backtest/matching.go @@ -47,8 +47,8 @@ type SimplePriceMatching struct { Account *types.Account - MakerCommission int `json:"makerCommission"` - TakerCommission int `json:"takerCommission"` + MakerCommission float64 `json:"makerCommission"` + TakerCommission float64 `json:"takerCommission"` tradeUpdateCallbacks []func(trade types.Trade) orderUpdateCallbacks []func(order types.Order) @@ -205,9 +205,9 @@ func (m *SimplePriceMatching) newTradeFromOrder(order types.Order, isMaker bool) // MAX uses 0.050% for maker and 0.15% for taker var commission = DefaultFeeRate if isMaker && m.Account.MakerCommission > 0 { - commission = 0.0001 * float64(m.Account.MakerCommission) // binance uses 10~15 + commission = 0.0001 * m.Account.MakerCommission // binance uses 10~15 } else if m.Account.TakerCommission > 0 { - commission = 0.0001 * float64(m.Account.TakerCommission) // binance uses 10~15 + commission = 0.0001 * m.Account.TakerCommission // binance uses 10~15 } var fee float64 diff --git a/pkg/bbgo/config.go b/pkg/bbgo/config.go index 918901fe2..610c68109 100644 --- a/pkg/bbgo/config.go +++ b/pkg/bbgo/config.go @@ -113,8 +113,8 @@ func (t Backtest) ParseStartTime() (time.Time, error) { } type BacktestAccount struct { - MakerCommission int `json:"makerCommission"` - TakerCommission int `json:"takerCommission"` + MakerCommission float64 `json:"makerCommission"` + TakerCommission float64 `json:"takerCommission"` BuyerCommission int `json:"buyerCommission"` SellerCommission int `json:"sellerCommission"` Balances BacktestAccountBalanceMap `json:"balances" yaml:"balances"` diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 43f16f44f..2a40e5565 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -378,8 +378,8 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) { } a := &types.Account{ - MakerCommission: int(account.MakerCommission), - TakerCommission: int(account.TakerCommission), + MakerCommission: float64(account.MakerCommission), + TakerCommission: float64(account.TakerCommission), } a.UpdateBalances(balances) return a, nil diff --git a/pkg/exchange/ftx/exchange.go b/pkg/exchange/ftx/exchange.go index c529558e9..69e4f7f8c 100644 --- a/pkg/exchange/ftx/exchange.go +++ b/pkg/exchange/ftx/exchange.go @@ -73,10 +73,10 @@ func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) { return nil, fmt.Errorf("ftx returns querying balances failure") } - // TODO + bps := fixedpoint.NewFromFloat(10000) a := &types.Account{ - MakerCommission: 0, - TakerCommission: 0, + MakerCommission: fixedpoint.NewFromFloat(resp.Result.MakerFee).Mul(bps).Float64(), + TakerCommission: fixedpoint.NewFromFloat(resp.Result.TakerFee).Mul(bps).Float64(), } balances, err := e.QueryAccountBalances(ctx) diff --git a/pkg/exchange/ftx/exchange_test.go b/pkg/exchange/ftx/exchange_test.go index e568c6cc2..d60ddab43 100644 --- a/pkg/exchange/ftx/exchange_test.go +++ b/pkg/exchange/ftx/exchange_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" ) func TestExchange_QueryAccountBalances(t *testing.T) { @@ -280,3 +281,90 @@ func TestExchange_QueryClosedOrders(t *testing.T) { } }) } + +func TestExchange_QueryAccount(t *testing.T) { + balanceResp := ` +{ + "result": [ + { + "availableWithoutBorrow": 19.47458865, + "coin": "USD", + "free": 19.48085209, + "spotBorrow": 0.0, + "total": 1094.66405065, + "usdValue": 1094.664050651561 + } + ], + "success": true +} +` + + accountInfoResp := ` +{ + "success": true, + "result": { + "backstopProvider": true, + "collateral": 3568181.02691129, + "freeCollateral": 1786071.456884368, + "initialMarginRequirement": 0.12222384240257728, + "leverage": 10, + "liquidating": false, + "maintenanceMarginRequirement": 0.07177992558058484, + "makerFee": 0.0002, + "marginFraction": 0.5588433331419503, + "openMarginFraction": 0.2447194090423075, + "takerFee": 0.0005, + "totalAccountValue": 3568180.98341129, + "totalPositionSize": 6384939.6992, + "username": "user@domain.com", + "positions": [ + { + "cost": -31.7906, + "entryPrice": 138.22, + "future": "ETH-PERP", + "initialMarginRequirement": 0.1, + "longOrderSize": 1744.55, + "maintenanceMarginRequirement": 0.04, + "netSize": -0.23, + "openSize": 1744.32, + "realizedPnl": 3.39441714, + "shortOrderSize": 1732.09, + "side": "sell", + "size": 0.23, + "unrealizedPnl": 0 + } + ] + } +} +` + returnBalance := false + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if returnBalance { + fmt.Fprintln(w, balanceResp) + return + } + returnBalance = true + fmt.Fprintln(w, accountInfoResp) + })) + defer ts.Close() + + ex := NewExchange("", "", "") + serverURL, err := url.Parse(ts.URL) + assert.NoError(t, err) + ex.restEndpoint = serverURL + resp, err := ex.QueryAccount(context.Background()) + assert.NoError(t, err) + + b, ok := resp.Balance("USD") + assert.True(t, ok) + expected := types.Balance{ + Currency: "USD", + Available: fixedpoint.MustNewFromString("19.48085209"), + Locked: fixedpoint.MustNewFromString("1094.66405065"), + } + expected.Locked = expected.Locked.Sub(expected.Available) + assert.Equal(t, expected, b) + + assert.Equal(t, 0.0002*10000, resp.MakerCommission) + assert.Equal(t, 0.0005*10000, resp.TakerCommission) +} diff --git a/pkg/exchange/ftx/rest_responses.go b/pkg/exchange/ftx/rest_responses.go index e486926a2..4ae026b3a 100644 --- a/pkg/exchange/ftx/rest_responses.go +++ b/pkg/exchange/ftx/rest_responses.go @@ -46,6 +46,8 @@ type accountResponse struct { } type account struct { + MakerFee float64 `json:"makerFee"` + TakerFee float64 `json:"takerFee"` } type positionsResponse struct { diff --git a/pkg/types/account.go b/pkg/types/account.go index 7ee68e38d..1fce0d39f 100644 --- a/pkg/types/account.go +++ b/pkg/types/account.go @@ -103,9 +103,9 @@ type Account struct { sync.Mutex `json:"-"` // bps. 0.15% fee will be 15. - MakerCommission int `json:"makerCommission,omitempty"` - TakerCommission int `json:"takerCommission,omitempty"` - AccountType string `json:"accountType,omitempty"` + MakerCommission float64 `json:"makerCommission,omitempty"` + TakerCommission float64 `json:"takerCommission,omitempty"` + AccountType string `json:"accountType,omitempty"` balances BalanceMap }