mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-11 01:23:51 +00:00
Merge pull request #169 from c9s/ftx/account
feature: support FTX account information api
This commit is contained in:
commit
25dff00410
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
78
pkg/cmd/account.go
Normal file
78
pkg/cmd/account.go
Normal file
|
@ -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
|
||||
},
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
bps := fixedpoint.NewFromFloat(10000)
|
||||
a := &types.Account{
|
||||
MakerCommission: fixedpoint.NewFromFloat(resp.Result.MakerFee).Mul(bps).Float64(),
|
||||
TakerCommission: fixedpoint.NewFromFloat(resp.Result.TakerFee).Mul(bps).Float64(),
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
47
pkg/exchange/ftx/rest_account_request.go
Normal file
47
pkg/exchange/ftx/rest_account_request.go
Normal file
|
@ -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
|
||||
}
|
|
@ -2,6 +2,96 @@ 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 {
|
||||
MakerFee float64 `json:"makerFee"`
|
||||
TakerFee float64 `json:"takerFee"`
|
||||
}
|
||||
|
||||
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"`
|
||||
|
||||
|
|
|
@ -102,9 +102,10 @@ func (m BalanceMap) Print() {
|
|||
type Account struct {
|
||||
sync.Mutex `json:"-"`
|
||||
|
||||
MakerCommission int `json:"makerCommission,omitempty"`
|
||||
TakerCommission int `json:"takerCommission,omitempty"`
|
||||
AccountType string `json:"accountType,omitempty"`
|
||||
// bps. 0.15% fee will be 15.
|
||||
MakerCommission float64 `json:"makerCommission,omitempty"`
|
||||
TakerCommission float64 `json:"takerCommission,omitempty"`
|
||||
AccountType string `json:"accountType,omitempty"`
|
||||
|
||||
balances BalanceMap
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user