bbgo_origin/pkg/exchange/ftx/websocket_messages.go

139 lines
3.3 KiB
Go
Raw Normal View History

2021-02-27 10:41:46 +00:00
package ftx
2021-03-01 00:13:21 +00:00
import (
"encoding/json"
"fmt"
"math"
2021-03-02 14:18:41 +00:00
"strings"
2021-03-01 00:13:21 +00:00
"time"
2021-03-02 14:18:41 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
2021-03-01 00:13:21 +00:00
)
2021-02-27 11:27:26 +00:00
2021-02-27 10:41:46 +00:00
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"`
}
2021-02-27 11:27:26 +00:00
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 map[string]json.RawMessage `json:"data"`
}
func (r rawResponse) toSubscribedResp() subscribedResponse {
return subscribedResponse{
mandatoryFields: r.mandatoryFields,
}
}
2021-03-01 00:13:21 +00:00
func (r rawResponse) toSnapshotResp() (snapshotResponse, error) {
o := snapshotResponse{
mandatoryFields: r.mandatoryFields,
}
if err := json.Unmarshal(r.Data["action"], &o.Action); err != nil {
return snapshotResponse{}, fmt.Errorf("failed to unmarshal data.action field: %w", err)
}
var t float64
if err := json.Unmarshal(r.Data["time"], &t); err != nil {
return snapshotResponse{}, fmt.Errorf("failed to unmarshal data.time field: %w", err)
}
sec, dec := math.Modf(t)
o.Time = time.Unix(int64(sec), int64(dec*1e9))
if err := json.Unmarshal(r.Data["checksum"], &o.Checksum); err != nil {
return snapshotResponse{}, fmt.Errorf("failed to unmarshal data.checksum field: %w", err)
}
if err := json.Unmarshal(r.Data["bids"], &o.Bids); err != nil {
return snapshotResponse{}, fmt.Errorf("failed to unmarshal data.bids field: %w", err)
}
if err := json.Unmarshal(r.Data["asks"], &o.Asks); err != nil {
return snapshotResponse{}, fmt.Errorf("failed to unmarshal data.asks field: %w", err)
}
return o, nil
}
2021-02-27 11:27:26 +00:00
// {"type": "subscribed", "channel": "orderbook", "market": "BTC/USDT"}
type subscribedResponse struct {
mandatoryFields
}
2021-03-01 00:13:21 +00:00
type snapshotResponse struct {
mandatoryFields
Action string
Time time.Time
Checksum int64
// Best 100 orders
Bids [][]float64
// Best 100 orders
Asks [][]float64
}
2021-03-02 14:18:41 +00:00
func (r snapshotResponse) toGlobalOrderBook() types.OrderBook {
return types.OrderBook{
// ex. BTC/USDT
Symbol: strings.ToUpper(r.Market),
Bids: toPriceVolumeSlice(r.Bids),
Asks: toPriceVolumeSlice(r.Asks),
}
}
func toPriceVolumeSlice(orders [][]float64) types.PriceVolumeSlice {
var pv types.PriceVolumeSlice
for _, o := range orders {
pv = append(pv, types.PriceVolume{
Price: fixedpoint.NewFromFloat(o[0]),
Volume: fixedpoint.NewFromFloat(o[1]),
})
}
return pv
}