Merge pull request #181 from c9s/ftx/market

This commit is contained in:
YC 2021-03-21 14:17:20 +08:00 committed by GitHub
commit f84b3a5177
6 changed files with 250 additions and 1 deletions

74
pkg/cmd/market.go Normal file
View File

@ -0,0 +1,74 @@
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() {
marketCmd.Flags().String("session", "", "the exchange session name for querying information")
RootCmd.AddCommand(marketCmd)
}
// go run ./cmd/bbgo market --session=ftx --config=config/bbgo.yaml
var marketCmd = &cobra.Command{
Use: "market",
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 _, err := os.Stat(configFile); os.IsNotExist(err) {
return err
}
userConfig, err := bbgo.Load(configFile, false)
if err != nil {
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)
}
markets, err := session.Exchange.QueryMarkets(ctx)
if err != nil {
return err
}
for _, m := range markets {
log.Infof("market: %+v", m)
}
return nil
},
}

View File

@ -61,7 +61,40 @@ func (e *Exchange) NewStream() types.Stream {
}
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
panic("implement me")
resp, err := e.newRest().Markets(ctx)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("ftx returns querying markets failure")
}
markets := types.MarketMap{}
for _, m := range resp.Result {
symbol := toGlobalSymbol(m.Name)
market := types.Market{
Symbol: symbol,
// The max precision is length(DefaultPow). For example, currently fixedpoint.DefaultPow
// is 1e8, so the max precision will be 8.
PricePrecision: fixedpoint.NumFractionalDigits(fixedpoint.NewFromFloat(m.PriceIncrement)),
VolumePrecision: fixedpoint.NumFractionalDigits(fixedpoint.NewFromFloat(m.SizeIncrement)),
QuoteCurrency: toGlobalCurrency(m.QuoteCurrency),
BaseCurrency: toGlobalCurrency(m.BaseCurrency),
// FTX only limit your order by `MinProvideSize`, so I assign zero value to unsupported fields:
// MinNotional, MinAmount, MaxQuantity, MinPrice and MaxPrice.
MinNotional: 0,
MinAmount: 0,
MinQuantity: m.MinProvideSize,
MaxQuantity: 0,
StepSize: m.SizeIncrement,
MinPrice: 0,
MaxPrice: 0,
TickSize: m.PriceIncrement,
}
markets[symbol] = market
}
return markets, nil
}
func (e *Exchange) QueryAccount(ctx context.Context) (*types.Account, error) {

View File

@ -368,3 +368,58 @@ func TestExchange_QueryAccount(t *testing.T) {
assert.Equal(t, fixedpoint.NewFromFloat(0.0002), resp.MakerCommission)
assert.Equal(t, fixedpoint.NewFromFloat(0.0005), resp.TakerCommission)
}
func TestExchange_QueryMarkets(t *testing.T) {
respJSON := `{
"success": true,
"result": [
{
"name": "BTC/USD",
"enabled": true,
"postOnly": false,
"priceIncrement": 1.0,
"sizeIncrement": 0.0001,
"minProvideSize": 0.001,
"last": 59039.0,
"bid": 59038.0,
"ask": 59040.0,
"price": 59039.0,
"type": "spot",
"baseCurrency": "BTC",
"quoteCurrency": "USD",
"underlying": null,
"restricted": false,
"highLeverageFeeExempt": true,
"change1h": 0.0015777151969599294,
"change24h": 0.05475756601279165,
"changeBod": -0.0035107262814994852,
"quoteVolume24h": 316493675.5463,
"volumeUsd24h": 316493675.5463
}
]
}`
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
resp, err := ex.QueryMarkets(context.Background())
assert.NoError(t, err)
assert.Len(t, resp, 1)
assert.Equal(t, types.Market{
Symbol: "BTC/USD",
PricePrecision: 0,
VolumePrecision: 4,
QuoteCurrency: "USD",
BaseCurrency: "BTC",
MinQuantity: 0.001,
StepSize: 0.0001,
TickSize: 1,
}, resp["BTC/USD"])
}

View File

@ -22,6 +22,7 @@ type restRequest struct {
*balanceRequest
*orderRequest
*accountRequest
*marketRequest
key, secret string
// Optional sub-account name
@ -43,6 +44,7 @@ func newRestRequest(c *http.Client, baseURL *url.URL) *restRequest {
baseURL: baseURL,
}
r.marketRequest = &marketRequest{restRequest: r}
r.accountRequest = &accountRequest{restRequest: r}
r.balanceRequest = &balanceRequest{restRequest: r}
r.orderRequest = &orderRequest{restRequest: r}

View File

@ -0,0 +1,29 @@
package ftx
import (
"context"
"encoding/json"
"fmt"
)
type marketRequest struct {
*restRequest
}
func (r *marketRequest) Markets(ctx context.Context) (marketsResponse, error) {
resp, err := r.
Method("GET").
ReferenceURL("api/markets").
DoAuthenticatedRequest(ctx)
if err != nil {
return marketsResponse{}, err
}
var m marketsResponse
if err := json.Unmarshal(resp.Body, &m); err != nil {
return marketsResponse{}, fmt.Errorf("failed to unmarshal market response body to json: %w", err)
}
return m, nil
}

View File

@ -102,6 +102,62 @@ type balances struct {
} `json:"result"`
}
/*
[
{
"name": "BTC/USD",
"enabled": true,
"postOnly": false,
"priceIncrement": 1.0,
"sizeIncrement": 0.0001,
"minProvideSize": 0.0001,
"last": 59039.0,
"bid": 59038.0,
"ask": 59040.0,
"price": 59039.0,
"type": "spot",
"baseCurrency": "BTC",
"quoteCurrency": "USD",
"underlying": null,
"restricted": false,
"highLeverageFeeExempt": true,
"change1h": 0.0015777151969599294,
"change24h": 0.05475756601279165,
"changeBod": -0.0035107262814994852,
"quoteVolume24h": 316493675.5463,
"volumeUsd24h": 316493675.5463
}
]
*/
type marketsResponse struct {
Success bool `json:"success"`
Result []market `json:"result"`
}
type market struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
PostOnly bool `json:"postOnly"`
PriceIncrement float64 `json:"priceIncrement"`
SizeIncrement float64 `json:"sizeIncrement"`
MinProvideSize float64 `json:"minProvideSize"`
Last float64 `json:"last"`
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
Price float64 `json:"price"`
Type string `json:"type"`
BaseCurrency string `json:"baseCurrency"`
QuoteCurrency string `json:"quoteCurrency"`
Underlying string `json:"underlying"`
Restricted bool `json:"restricted"`
HighLeverageFeeExempt bool `json:"highLeverageFeeExempt"`
Change1h float64 `json:"change1h"`
Change24h float64 `json:"change24h"`
ChangeBod float64 `json:"changeBod"`
QuoteVolume24h float64 `json:"quoteVolume24h"`
VolumeUsd24h float64 `json:"volumeUsd24h"`
}
type ordersHistoryResponse struct {
Success bool `json:"success"`
Result []order `json:"result"`