bbgo_origin/exchange/max/maxapi/public_parser.go

253 lines
5.9 KiB
Go
Raw Normal View History

2020-10-01 08:07:18 +00:00
package max
import (
"strings"
"time"
"github.com/pkg/errors"
"github.com/valyala/fastjson"
2020-10-02 13:15:00 +00:00
2020-10-05 06:25:58 +00:00
"github.com/c9s/bbgo/fixedpoint"
"github.com/c9s/bbgo/types"
2020-10-01 08:07:18 +00:00
)
var ErrIncorrectBookEntryElementLength = errors.New("incorrect book entry element length")
const Buy = 1
const Sell = -1
2020-10-02 04:43:14 +00:00
// ParseMessage accepts the raw messages from max public websocket channels and parses them into market data
2020-10-02 13:29:56 +00:00
// Return types: *BookEvent, *PublicTradeEvent, *SubscriptionEvent, *ErrorEvent
2020-10-02 04:43:14 +00:00
func ParseMessage(payload []byte) (interface{}, error) {
2020-10-01 08:07:18 +00:00
parser := fastjson.Parser{}
val, err := parser.ParseBytes(payload)
if err != nil {
return nil, errors.Wrap(err, "failed to parse payload: "+string(payload))
}
if channel := string(val.GetStringBytes("c")); len(channel) > 0 {
switch channel {
case "book":
return parseBookEvent(val)
case "trade":
2020-10-02 13:29:56 +00:00
return parsePublicTradeEvent(val)
2020-10-02 04:43:14 +00:00
case "user":
return ParseUserEvent(val)
2020-10-01 08:07:18 +00:00
}
}
eventType := string(val.GetStringBytes("e"))
switch eventType {
case "error":
return parseErrorEvent(val)
case "subscribed", "unsubscribed":
return parseSubscriptionEvent(val)
}
return nil, errors.Wrapf(ErrMessageTypeNotSupported, "payload %s", payload)
}
type TradeEntry struct {
Trend string `json:"tr"`
Price string `json:"p"`
Volume string `json:"v"`
Timestamp int64 `json:"T"`
}
func (e TradeEntry) Time() time.Time {
return time.Unix(0, e.Timestamp*int64(time.Millisecond))
}
// parseTradeEntry parse the trade content payload
func parseTradeEntry(val *fastjson.Value) TradeEntry {
return TradeEntry{
Trend: strings.ToLower(string(val.GetStringBytes("tr"))),
Timestamp: val.GetInt64("T"),
Price: string(val.GetStringBytes("p")),
Volume: string(val.GetStringBytes("v")),
}
}
2020-10-02 13:29:56 +00:00
type PublicTradeEvent struct {
2020-10-01 08:07:18 +00:00
Event string `json:"e"`
Market string `json:"M"`
Channel string `json:"c"`
Trades []TradeEntry `json:"t"`
Timestamp int64 `json:"T"`
}
2020-10-02 13:29:56 +00:00
func (e *PublicTradeEvent) Time() time.Time {
2020-10-01 08:07:18 +00:00
return time.Unix(0, e.Timestamp*int64(time.Millisecond))
}
2020-10-02 13:29:56 +00:00
func parsePublicTradeEvent(val *fastjson.Value) (*PublicTradeEvent, error) {
event := PublicTradeEvent{
2020-10-01 08:07:18 +00:00
Event: string(val.GetStringBytes("e")),
Market: string(val.GetStringBytes("M")),
Channel: string(val.GetStringBytes("c")),
Timestamp: val.GetInt64("T"),
}
for _, tradeValue := range val.GetArray("t") {
event.Trades = append(event.Trades, parseTradeEntry(tradeValue))
}
return &event, nil
}
type BookEvent struct {
Event string `json:"e"`
Market string `json:"M"`
Channel string `json:"c"`
Timestamp int64 `json:"t"` // Millisecond timestamp
Bids []BookEntry
Asks []BookEntry
}
func (e *BookEvent) Time() time.Time {
return time.Unix(0, e.Timestamp*int64(time.Millisecond))
}
2020-10-02 13:15:00 +00:00
func (e *BookEvent) OrderBook() (snapshot types.OrderBook, err error) {
for _, bid := range e.Bids {
pv, err := bid.PriceVolumePair()
if err != nil {
return snapshot, err
}
snapshot.Bids = append(snapshot.Bids, pv)
}
for _, ask := range e.Asks {
pv, err := ask.PriceVolumePair()
if err != nil {
return snapshot, err
}
snapshot.Asks = append(snapshot.Asks, pv)
}
return snapshot, nil
}
2020-10-01 08:07:18 +00:00
func parseBookEvent(val *fastjson.Value) (*BookEvent, error) {
event := BookEvent{
Event: string(val.GetStringBytes("e")),
Market: string(val.GetStringBytes("M")),
Channel: string(val.GetStringBytes("c")),
Timestamp: val.GetInt64("T"),
}
t := time.Unix(0, event.Timestamp*int64(time.Millisecond))
var err error
event.Asks, err = parseBookEntries(val.GetArray("a"), Sell, t)
if err != nil {
return nil, err
}
event.Bids, err = parseBookEntries(val.GetArray("b"), Buy, t)
if err != nil {
return nil, err
}
return &event, nil
}
type BookEntry struct {
Side int
Time time.Time
Price string
Volume string
}
2020-10-03 01:19:38 +00:00
func (e *BookEntry) PriceVolumePair() (pv types.PriceVolume, err error) {
2020-10-02 13:15:00 +00:00
pv.Price, err = fixedpoint.NewFromString(e.Price)
if err != nil {
return pv, err
}
pv.Volume, err = fixedpoint.NewFromString(e.Volume)
if err != nil {
return pv, err
}
return pv, err
}
2020-10-01 08:07:18 +00:00
// parseBookEntries parses JSON struct like `[["233330", "0.33"], ....]`
func parseBookEntries(vals []*fastjson.Value, side int, t time.Time) (entries []BookEntry, err error) {
for _, entry := range vals {
pv, err := entry.Array()
if err != nil {
return nil, err
}
if len(pv) < 2 {
return nil, ErrIncorrectBookEntryElementLength
}
entries = append(entries, BookEntry{
Side: side,
Time: t,
2020-10-02 13:15:00 +00:00
Price: string(pv[0].GetStringBytes()),
Volume: string(pv[1].GetStringBytes()),
2020-10-01 08:07:18 +00:00
})
}
return entries, nil
}
type ErrorEvent struct {
Timestamp int64
Errors []string
CommandID string
}
func (e ErrorEvent) Time() time.Time {
return time.Unix(0, e.Timestamp*int64(time.Millisecond))
}
func parseErrorEvent(val *fastjson.Value) (*ErrorEvent, error) {
event := ErrorEvent{
Timestamp: val.GetInt64("T"),
CommandID: string(val.GetStringBytes("i")),
}
for _, entry := range val.GetArray("E") {
event.Errors = append(event.Errors, string(entry.GetStringBytes()))
}
return &event, nil
}
type SubscriptionEvent struct {
Event string `json:"e"`
Timestamp int64 `json:"T"`
CommandID string `json:"i"`
Subscriptions []Subscription `json:"s"`
}
func (e SubscriptionEvent) Time() time.Time {
return time.Unix(0, e.Timestamp*int64(time.Millisecond))
}
func parseSubscriptionEvent(val *fastjson.Value) (*SubscriptionEvent, error) {
event := SubscriptionEvent{
Event: string(val.GetStringBytes("e")),
Timestamp: val.GetInt64("T"),
CommandID: string(val.GetStringBytes("i")),
}
for _, entry := range val.GetArray("s") {
market := string(entry.GetStringBytes("market"))
channel := string(entry.GetStringBytes("channel"))
event.Subscriptions = append(event.Subscriptions, Subscription{
Market: market,
Channel: channel,
})
}
return &event, nil
}