ftx: query kline using rest api

This commit is contained in:
ycdesu 2021-03-31 18:09:13 +08:00
parent 82abd5489b
commit 28c9ac95ac
5 changed files with 148 additions and 5 deletions

View File

@ -129,3 +129,19 @@ func toGlobalTrade(f fill) (types.Trade, error) {
IsIsolated: false, IsIsolated: false,
}, nil }, nil
} }
func toGlobalKLine(symbol string, interval types.Interval, h Candle) (types.KLine, error) {
return types.KLine{
Exchange: types.ExchangeFTX.String(),
Symbol: symbol,
StartTime: h.StartTime.Time,
EndTime: h.StartTime.Add(interval.Duration()),
Interval: interval,
Open: h.Open,
Close: h.Close,
High: h.High,
Low: h.Low,
Volume: h.Volume,
Closed: true,
}, nil
}

View File

@ -142,7 +142,51 @@ func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap,
} }
func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) { func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) {
panic("implement me") 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 klines time range, since: %+v, until: %+v", since, until)
}
if !isIntervalSupportedInKLine(interval) {
return nil, fmt.Errorf("interval %s is not supported", interval.String())
}
resp, err := e.newRest().HistoricalPrices(ctx, symbol, interval, int64(options.Limit), since, until)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("ftx returns failure")
}
var kline []types.KLine
for _, r := range resp.Result {
globalKline, err := toGlobalKLine(symbol, interval, r)
if err != nil {
return nil, err
}
kline = append(kline, globalKline)
}
return kline, nil
}
func isIntervalSupportedInKLine(interval types.Interval) bool {
_, ok := map[int]struct{}{
15: {},
60: {},
300: {},
900: {},
3600: {},
14400: {},
86400: {},
}[interval.Minutes()*60]
return ok
} }
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) {
@ -247,10 +291,6 @@ func (e *Exchange) QueryDepositHistory(ctx context.Context, asset string, since,
return return
} }
func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []types.Withdraw, err error) {
panic("implement me")
}
func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) { func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder) (types.OrderSlice, error) {
var createdOrders types.OrderSlice var createdOrders types.OrderSlice
// TODO: currently only support limit and market order // TODO: currently only support limit and market order

View File

@ -620,3 +620,19 @@ func TestExchange_QueryTrades(t *testing.T) {
}, trades[0]) }, trades[0])
}) })
} }
func Test_isIntervalSupportedInKLine(t *testing.T) {
supportedIntervals := []types.Interval{
types.Interval1m,
types.Interval5m,
types.Interval15m,
types.Interval1h,
types.Interval4h,
types.Interval1d,
}
for _, i := range supportedIntervals {
assert.True(t, isIntervalSupportedInKLine(i))
}
assert.False(t, isIntervalSupportedInKLine(types.Interval30m))
assert.False(t, isIntervalSupportedInKLine(types.Interval3d))
}

View File

@ -4,6 +4,10 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"time"
"github.com/c9s/bbgo/pkg/types"
) )
type marketRequest struct { type marketRequest struct {
@ -27,3 +31,41 @@ func (r *marketRequest) Markets(ctx context.Context) (marketsResponse, error) {
return m, nil return m, nil
} }
/*
supported resolutions: window length in seconds. options: 15, 60, 300, 900, 3600, 14400, 86400
doc: https://docs.ftx.com/?javascript#get-historical-prices
*/
func (r *marketRequest) HistoricalPrices(ctx context.Context, market string, interval types.Interval, limit int64, start, end time.Time) (HistoricalPricesResponse, error) {
q := map[string]string{
"resolution": strconv.FormatInt(int64(interval.Minutes())*60, 10),
}
if limit > 0 {
q["limit"] = strconv.FormatInt(limit, 10)
}
if start != (time.Time{}) {
q["start_time"] = strconv.FormatInt(start.Unix(), 10)
}
if end != (time.Time{}) {
q["end_time"] = strconv.FormatInt(end.Unix(), 10)
}
resp, err := r.
Method("GET").
Query(q).
ReferenceURL(fmt.Sprintf("api/markets/%s/candles", market)).
DoAuthenticatedRequest(ctx)
if err != nil {
return HistoricalPricesResponse{}, err
}
var h HistoricalPricesResponse
if err := json.Unmarshal(resp.Body, &h); err != nil {
return HistoricalPricesResponse{}, fmt.Errorf("failed to unmarshal historical prices response body to json: %w", err)
}
return h, nil
}

View File

@ -198,6 +198,35 @@ type market struct {
VolumeUsd24h float64 `json:"volumeUsd24h"` VolumeUsd24h float64 `json:"volumeUsd24h"`
} }
/*
{
"success": true,
"result": [
{
"close": 11055.25,
"high": 11089.0,
"low": 11043.5,
"open": 11059.25,
"startTime": "2019-06-24T17:15:00+00:00",
"volume": 464193.95725
}
]
}
*/
type HistoricalPricesResponse struct {
Success bool `json:"success"`
Result []Candle `json:"result"`
}
type Candle struct {
Close float64 `json:"close"`
High float64 `json:"high"`
Low float64 `json:"low"`
Open float64 `json:"open"`
StartTime datetime `json:"startTime"`
Volume float64 `json:"volume"`
}
type ordersHistoryResponse struct { type ordersHistoryResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Result []order `json:"result"` Result []order `json:"result"`