mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 17:13:51 +00:00
136 lines
3.1 KiB
Go
136 lines
3.1 KiB
Go
package ftx
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
|
"github.com/c9s/bbgo/pkg/types"
|
|
)
|
|
|
|
type operation string
|
|
|
|
const subscribe operation = "subscribe"
|
|
const unsubscribe operation = "unsubscribe"
|
|
|
|
type channel string
|
|
|
|
const orderbook channel = "orderbook"
|
|
const trades channel = "trades"
|
|
const ticker channel = "ticker"
|
|
|
|
// {'op': 'subscribe', 'channel': 'trades', 'market': 'BTC-PERP'}
|
|
type SubscribeRequest struct {
|
|
Operation operation `json:"op"`
|
|
Channel channel `json:"channel"`
|
|
Market string `json:"market"`
|
|
}
|
|
|
|
type respType string
|
|
|
|
const errRespType respType = "error"
|
|
const subscribedRespType respType = "subscribed"
|
|
const unsubscribedRespType respType = "unsubscribed"
|
|
const infoRespType respType = "info"
|
|
const partialRespType respType = "partial"
|
|
const updateRespType respType = "update"
|
|
|
|
type mandatoryFields struct {
|
|
Type respType `json:"type"`
|
|
|
|
// Channel is mandatory
|
|
Channel channel `json:"channel"`
|
|
|
|
// Market is mandatory
|
|
Market string `json:"market"`
|
|
}
|
|
|
|
// doc: https://docs.ftx.com/#response-format
|
|
type rawResponse struct {
|
|
mandatoryFields
|
|
|
|
// The following fields are optional.
|
|
// Example 1: {"type": "error", "code": 404, "msg": "No such market: BTCUSDT"}
|
|
Code int64 `json:"code"`
|
|
Message string `json:"msg"`
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
|
|
func (r rawResponse) toSubscribedResp() subscribedResponse {
|
|
return subscribedResponse{
|
|
mandatoryFields: r.mandatoryFields,
|
|
}
|
|
}
|
|
|
|
func (r rawResponse) toDataResponse() (dataResponse, error) {
|
|
o := dataResponse{
|
|
mandatoryFields: r.mandatoryFields,
|
|
}
|
|
|
|
if err := json.Unmarshal(r.Data, &o); err != nil {
|
|
return dataResponse{}, err
|
|
}
|
|
|
|
sec, dec := math.Modf(o.Time)
|
|
o.Timestamp = time.Unix(int64(sec), int64(dec*1e9))
|
|
|
|
return o, nil
|
|
}
|
|
|
|
// {"type": "subscribed", "channel": "orderbook", "market": "BTC/USDT"}
|
|
type subscribedResponse struct {
|
|
mandatoryFields
|
|
}
|
|
|
|
type dataResponse struct {
|
|
mandatoryFields
|
|
|
|
Action string `json:"action"`
|
|
|
|
Time float64 `json:"time"`
|
|
|
|
Timestamp time.Time
|
|
|
|
Checksum int64 `json:"checksum"`
|
|
|
|
Bids [][]json.Number `json:"bids"`
|
|
|
|
Asks [][]json.Number `json:"asks"`
|
|
}
|
|
|
|
func (r dataResponse) toGlobalOrderBook() (types.OrderBook, error) {
|
|
bids, err := toPriceVolumeSlice(r.Bids)
|
|
if err != nil {
|
|
return types.OrderBook{}, fmt.Errorf("can't convert bids to priceVolumeSlice: %w", err)
|
|
}
|
|
asks, err := toPriceVolumeSlice(r.Asks)
|
|
if err != nil {
|
|
return types.OrderBook{}, fmt.Errorf("can't convert asks to priceVolumeSlice: %w", err)
|
|
}
|
|
return types.OrderBook{
|
|
// ex. BTC/USDT
|
|
Symbol: strings.ToUpper(r.Market),
|
|
Bids: bids,
|
|
Asks: asks,
|
|
}, nil
|
|
}
|
|
|
|
func toPriceVolumeSlice(orders [][]json.Number) (types.PriceVolumeSlice, error) {
|
|
var pv types.PriceVolumeSlice
|
|
for _, o := range orders {
|
|
p, err := fixedpoint.NewFromString(string(o[0]))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't convert price %+v to fixedpoint: %w", o[0], err)
|
|
}
|
|
v, err := fixedpoint.NewFromString(string(o[1]))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can't convert volume %+v to fixedpoint: %w", o[0], err)
|
|
}
|
|
pv = append(pv, types.PriceVolume{Price: p, Volume: v})
|
|
}
|
|
return pv, nil
|
|
}
|