mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
max: clean up and refactor max stream
This commit is contained in:
parent
cc0e5f71b0
commit
e04139a330
|
@ -104,7 +104,7 @@ func toGlobalOrderStatus(orderState max.OrderState, executedVolume, remainingVol
|
|||
|
||||
}
|
||||
|
||||
logger.Errorf("unknown order status: %v", orderState)
|
||||
log.Errorf("unknown order status: %v", orderState)
|
||||
return types.OrderStatus(orderState)
|
||||
}
|
||||
|
||||
|
@ -130,7 +130,7 @@ func toGlobalOrderType(orderType max.OrderType) types.OrderType {
|
|||
|
||||
}
|
||||
|
||||
logger.Errorf("order convert error, unknown order type: %v", orderType)
|
||||
log.Errorf("order convert error, unknown order type: %v", orderType)
|
||||
return types.OrderType(orderType)
|
||||
}
|
||||
|
||||
|
|
|
@ -758,7 +758,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
|
|||
for _, t := range remoteTrades {
|
||||
localTrade, err := toGlobalTrade(t)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("can not convert trade: %+v", t)
|
||||
log.WithError(err).Errorf("can not convert trade: %+v", t)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
package max
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var WebSocketURL = "wss://max-stream.maicoin.com/ws"
|
||||
|
@ -40,307 +31,3 @@ type WebsocketCommand struct {
|
|||
|
||||
var SubscribeAction = "subscribe"
|
||||
var UnsubscribeAction = "unsubscribe"
|
||||
|
||||
//go:generate callbackgen -type WebSocketService
|
||||
type WebSocketService struct {
|
||||
baseURL, key, secret string
|
||||
|
||||
mu sync.Mutex
|
||||
conn *websocket.Conn
|
||||
|
||||
reconnectC chan struct{}
|
||||
|
||||
// Subscriptions is the subscription request payloads that will be used for sending subscription request
|
||||
Subscriptions []Subscription
|
||||
|
||||
connectCallbacks []func(conn *websocket.Conn)
|
||||
disconnectCallbacks []func()
|
||||
|
||||
errorCallbacks []func(err error)
|
||||
messageCallbacks []func(message []byte)
|
||||
bookEventCallbacks []func(e BookEvent)
|
||||
tradeEventCallbacks []func(e PublicTradeEvent)
|
||||
kLineEventCallbacks []func(e KLineEvent)
|
||||
errorEventCallbacks []func(e ErrorEvent)
|
||||
subscriptionEventCallbacks []func(e SubscriptionEvent)
|
||||
|
||||
tradeUpdateEventCallbacks []func(e TradeUpdateEvent)
|
||||
tradeSnapshotEventCallbacks []func(e TradeSnapshotEvent)
|
||||
orderUpdateEventCallbacks []func(e OrderUpdateEvent)
|
||||
orderSnapshotEventCallbacks []func(e OrderSnapshotEvent)
|
||||
|
||||
accountSnapshotEventCallbacks []func(e AccountSnapshotEvent)
|
||||
accountUpdateEventCallbacks []func(e AccountUpdateEvent)
|
||||
}
|
||||
|
||||
func NewWebSocketService(wsURL string, key, secret string) *WebSocketService {
|
||||
return &WebSocketService{
|
||||
key: key,
|
||||
secret: secret,
|
||||
reconnectC: make(chan struct{}, 1),
|
||||
baseURL: wsURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) Connect(ctx context.Context) error {
|
||||
s.OnConnect(func(c *websocket.Conn) {
|
||||
if err := s.SendSubscriptionRequest(SubscribeAction); err != nil {
|
||||
s.EmitError(err)
|
||||
logger.WithError(err).Error("failed to subscribe")
|
||||
}
|
||||
})
|
||||
|
||||
// pre-allocate the websocket client, the websocket client can be used for reconnecting.
|
||||
if err := s.connect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go s.reconnector(ctx)
|
||||
go s.ping(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WebSocketService) ping(ctx context.Context) {
|
||||
pingTicker := time.NewTicker(5 * time.Second)
|
||||
defer pingTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case <-ctx.Done():
|
||||
log.Debug("ping worker stopped")
|
||||
return
|
||||
|
||||
case <-pingTicker.C:
|
||||
s.mu.Lock()
|
||||
conn := s.conn
|
||||
s.mu.Unlock()
|
||||
|
||||
if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(3*time.Second)); err != nil {
|
||||
log.WithError(err).Error("ping error", err)
|
||||
s.Reconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) Auth() error {
|
||||
nonce := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
auth := &AuthMessage{
|
||||
Action: "auth",
|
||||
APIKey: s.key,
|
||||
Nonce: nonce,
|
||||
Signature: signPayload(fmt.Sprintf("%d", nonce), s.secret),
|
||||
ID: uuid.New().String(),
|
||||
}
|
||||
return s.conn.WriteJSON(auth)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) connect(ctx context.Context) error {
|
||||
dialer := websocket.DefaultDialer
|
||||
conn, _, err := dialer.DialContext(ctx, s.baseURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(15 * time.Second))
|
||||
conn.SetPongHandler(func(string) error {
|
||||
conn.SetReadDeadline(time.Now().Add(15 * time.Second))
|
||||
return nil
|
||||
})
|
||||
|
||||
s.mu.Lock()
|
||||
s.conn = conn
|
||||
s.mu.Unlock()
|
||||
|
||||
s.EmitConnect(conn)
|
||||
|
||||
go s.read(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WebSocketService) emitReconnect() {
|
||||
select {
|
||||
case s.reconnectC <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) reconnector(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case <-s.reconnectC:
|
||||
log.Warnf("received reconnect signal, reconnecting...")
|
||||
time.Sleep(3 * time.Second)
|
||||
if err := s.connect(ctx); err != nil {
|
||||
s.emitReconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) read(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
default:
|
||||
s.mu.Lock()
|
||||
conn := s.conn
|
||||
s.mu.Unlock()
|
||||
|
||||
if err := conn.SetReadDeadline(time.Now().Add(time.Second * 5)); err != nil {
|
||||
log.WithError(err).Error("can not set read deadline")
|
||||
}
|
||||
|
||||
mt, msg, err := conn.ReadMessage()
|
||||
|
||||
if err != nil {
|
||||
// if it's a network timeout error, we should re-connect
|
||||
switch err := err.(type) {
|
||||
|
||||
// if it's a websocket related error
|
||||
case *websocket.CloseError:
|
||||
if err.Code == websocket.CloseNormalClosure {
|
||||
return
|
||||
}
|
||||
// for unexpected close error, we should re-connect
|
||||
// emit reconnect to start a new connection
|
||||
s.EmitDisconnect()
|
||||
s.emitReconnect()
|
||||
return
|
||||
|
||||
case net.Error:
|
||||
s.EmitDisconnect()
|
||||
s.emitReconnect()
|
||||
return
|
||||
|
||||
default:
|
||||
log.WithError(err).Error("unexpected websocket connection error")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if mt != websocket.TextMessage {
|
||||
continue
|
||||
}
|
||||
|
||||
s.EmitMessage(msg)
|
||||
|
||||
m, err := ParseMessage(msg)
|
||||
if err != nil {
|
||||
s.EmitError(errors.Wrapf(err, "failed to parse message: %s", msg))
|
||||
continue
|
||||
}
|
||||
|
||||
if m != nil {
|
||||
s.dispatch(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) dispatch(msg interface{}) {
|
||||
switch e := msg.(type) {
|
||||
|
||||
case *BookEvent:
|
||||
s.EmitBookEvent(*e)
|
||||
|
||||
case *PublicTradeEvent:
|
||||
s.EmitTradeEvent(*e)
|
||||
|
||||
case *KLineEvent:
|
||||
s.EmitKLineEvent(*e)
|
||||
|
||||
case *ErrorEvent:
|
||||
s.EmitErrorEvent(*e)
|
||||
|
||||
case *SubscriptionEvent:
|
||||
s.EmitSubscriptionEvent(*e)
|
||||
|
||||
case *TradeSnapshotEvent:
|
||||
s.EmitTradeSnapshotEvent(*e)
|
||||
|
||||
case *TradeUpdateEvent:
|
||||
s.EmitTradeUpdateEvent(*e)
|
||||
|
||||
case *AccountSnapshotEvent:
|
||||
s.EmitAccountSnapshotEvent(*e)
|
||||
|
||||
case *AccountUpdateEvent:
|
||||
s.EmitAccountUpdateEvent(*e)
|
||||
|
||||
case *OrderSnapshotEvent:
|
||||
s.EmitOrderSnapshotEvent(*e)
|
||||
|
||||
case *OrderUpdateEvent:
|
||||
s.EmitOrderUpdateEvent(*e)
|
||||
|
||||
default:
|
||||
s.EmitError(fmt.Errorf("unsupported %T event: %+v", e, e))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) ClearSubscriptions() {
|
||||
s.Subscriptions = nil
|
||||
}
|
||||
|
||||
func (s *WebSocketService) Reconnect() {
|
||||
logger.Info("reconnecting...")
|
||||
s.emitReconnect()
|
||||
}
|
||||
|
||||
// Subscribe is a helper method for building subscription request from the internal mapping types.
|
||||
// (Internal public method)
|
||||
func (s *WebSocketService) Subscribe(channel, market string, options SubscribeOptions) {
|
||||
s.AddSubscription(Subscription{
|
||||
Channel: channel,
|
||||
Market: market,
|
||||
Depth: options.Depth,
|
||||
Resolution: options.Resolution,
|
||||
})
|
||||
}
|
||||
|
||||
// AddSubscription adds the subscription request to the buffer, these requests will be sent to the server right after connecting to the endpoint.
|
||||
func (s *WebSocketService) AddSubscription(subscription Subscription) {
|
||||
s.Subscriptions = append(s.Subscriptions, subscription)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) Resubscribe() {
|
||||
// Calling Resubscribe() by websocket is not enough to refresh orderbook.
|
||||
// We still need to get orderbook snapshot by rest client.
|
||||
// Therefore Reconnect() is used to simplify implementation.
|
||||
logger.Info("resubscribing all subscription...")
|
||||
if err := s.SendSubscriptionRequest(UnsubscribeAction); err != nil {
|
||||
logger.WithError(err).Error("failed to unsubscribe")
|
||||
}
|
||||
|
||||
if err := s.SendSubscriptionRequest(SubscribeAction); err != nil {
|
||||
logger.WithError(err).Error("failed to unsubscribe")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) SendSubscriptionRequest(action string) error {
|
||||
request := WebsocketCommand{
|
||||
Action: action,
|
||||
Subscriptions: s.Subscriptions,
|
||||
}
|
||||
|
||||
logger.Debugf("sending websocket subscription: %+v", request)
|
||||
|
||||
if err := s.conn.WriteJSON(request); err != nil {
|
||||
return errors.Wrap(err, "Failed to send subscribe event")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close web socket connection
|
||||
func (s *WebSocketService) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
// Code generated by "callbackgen -type WebSocketService"; DO NOT EDIT.
|
||||
|
||||
package max
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func (s *WebSocketService) OnConnect(cb func(conn *websocket.Conn)) {
|
||||
s.connectCallbacks = append(s.connectCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitConnect(conn *websocket.Conn) {
|
||||
for _, cb := range s.connectCallbacks {
|
||||
cb(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnDisconnect(cb func()) {
|
||||
s.disconnectCallbacks = append(s.disconnectCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitDisconnect() {
|
||||
for _, cb := range s.disconnectCallbacks {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnError(cb func(err error)) {
|
||||
s.errorCallbacks = append(s.errorCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitError(err error) {
|
||||
for _, cb := range s.errorCallbacks {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnMessage(cb func(message []byte)) {
|
||||
s.messageCallbacks = append(s.messageCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitMessage(message []byte) {
|
||||
for _, cb := range s.messageCallbacks {
|
||||
cb(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnBookEvent(cb func(e BookEvent)) {
|
||||
s.bookEventCallbacks = append(s.bookEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitBookEvent(e BookEvent) {
|
||||
for _, cb := range s.bookEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnTradeEvent(cb func(e PublicTradeEvent)) {
|
||||
s.tradeEventCallbacks = append(s.tradeEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitTradeEvent(e PublicTradeEvent) {
|
||||
for _, cb := range s.tradeEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnKLineEvent(cb func(e KLineEvent)) {
|
||||
s.kLineEventCallbacks = append(s.kLineEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitKLineEvent(e KLineEvent) {
|
||||
for _, cb := range s.kLineEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnErrorEvent(cb func(e ErrorEvent)) {
|
||||
s.errorEventCallbacks = append(s.errorEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitErrorEvent(e ErrorEvent) {
|
||||
for _, cb := range s.errorEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnSubscriptionEvent(cb func(e SubscriptionEvent)) {
|
||||
s.subscriptionEventCallbacks = append(s.subscriptionEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitSubscriptionEvent(e SubscriptionEvent) {
|
||||
for _, cb := range s.subscriptionEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnTradeUpdateEvent(cb func(e TradeUpdateEvent)) {
|
||||
s.tradeUpdateEventCallbacks = append(s.tradeUpdateEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitTradeUpdateEvent(e TradeUpdateEvent) {
|
||||
for _, cb := range s.tradeUpdateEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnTradeSnapshotEvent(cb func(e TradeSnapshotEvent)) {
|
||||
s.tradeSnapshotEventCallbacks = append(s.tradeSnapshotEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitTradeSnapshotEvent(e TradeSnapshotEvent) {
|
||||
for _, cb := range s.tradeSnapshotEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnOrderUpdateEvent(cb func(e OrderUpdateEvent)) {
|
||||
s.orderUpdateEventCallbacks = append(s.orderUpdateEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitOrderUpdateEvent(e OrderUpdateEvent) {
|
||||
for _, cb := range s.orderUpdateEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnOrderSnapshotEvent(cb func(e OrderSnapshotEvent)) {
|
||||
s.orderSnapshotEventCallbacks = append(s.orderSnapshotEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitOrderSnapshotEvent(e OrderSnapshotEvent) {
|
||||
for _, cb := range s.orderSnapshotEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnAccountSnapshotEvent(cb func(e AccountSnapshotEvent)) {
|
||||
s.accountSnapshotEventCallbacks = append(s.accountSnapshotEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitAccountSnapshotEvent(e AccountSnapshotEvent) {
|
||||
for _, cb := range s.accountSnapshotEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebSocketService) OnAccountUpdateEvent(cb func(e AccountUpdateEvent)) {
|
||||
s.accountUpdateEventCallbacks = append(s.accountUpdateEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *WebSocketService) EmitAccountUpdateEvent(e AccountUpdateEvent) {
|
||||
for _, cb := range s.accountUpdateEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
|
@ -2,11 +2,15 @@ package max
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/google/uuid"
|
||||
|
||||
max "github.com/c9s/bbgo/pkg/exchange/max/maxapi"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
@ -14,172 +18,184 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/util"
|
||||
)
|
||||
|
||||
var logger = log.WithField("exchange", "max")
|
||||
|
||||
//go:generate callbackgen -type Stream
|
||||
type Stream struct {
|
||||
types.StandardStream
|
||||
|
||||
websocketService *max.WebSocketService
|
||||
key, secret string
|
||||
|
||||
bookEventCallbacks []func(e max.BookEvent)
|
||||
tradeEventCallbacks []func(e max.PublicTradeEvent)
|
||||
kLineEventCallbacks []func(e max.KLineEvent)
|
||||
errorEventCallbacks []func(e max.ErrorEvent)
|
||||
subscriptionEventCallbacks []func(e max.SubscriptionEvent)
|
||||
|
||||
tradeUpdateEventCallbacks []func(e max.TradeUpdateEvent)
|
||||
tradeSnapshotEventCallbacks []func(e max.TradeSnapshotEvent)
|
||||
orderUpdateEventCallbacks []func(e max.OrderUpdateEvent)
|
||||
orderSnapshotEventCallbacks []func(e max.OrderSnapshotEvent)
|
||||
|
||||
accountSnapshotEventCallbacks []func(e max.AccountSnapshotEvent)
|
||||
accountUpdateEventCallbacks []func(e max.AccountUpdateEvent)
|
||||
}
|
||||
|
||||
func NewStream(key, secret string) *Stream {
|
||||
stream := &Stream{
|
||||
StandardStream: types.NewStandardStream(),
|
||||
key: key,
|
||||
secret: secret,
|
||||
}
|
||||
stream.SetEndpointCreator(stream.getEndpoint)
|
||||
stream.SetParser(max.ParseMessage)
|
||||
stream.SetDispatcher(stream.dispatchEvent)
|
||||
|
||||
stream.OnConnect(stream.handleConnect)
|
||||
stream.OnKLineEvent(stream.handleKLineEvent)
|
||||
stream.OnOrderSnapshotEvent(stream.handleOrderSnapshotEvent)
|
||||
stream.OnOrderUpdateEvent(stream.handleOrderUpdateEvent)
|
||||
stream.OnTradeUpdateEvent(stream.handleTradeEvent)
|
||||
stream.OnBookEvent(stream.handleBookEvent)
|
||||
stream.OnAccountSnapshotEvent(stream.handleAccountSnapshotEvent)
|
||||
stream.OnAccountUpdateEvent(stream.handleAccountUpdateEvent)
|
||||
return stream
|
||||
}
|
||||
|
||||
func (s *Stream) getEndpoint(ctx context.Context) (string, error) {
|
||||
url := os.Getenv("MAX_API_WS_URL")
|
||||
if url == "" {
|
||||
url = max.WebSocketURL
|
||||
}
|
||||
return url, nil
|
||||
}
|
||||
|
||||
wss := max.NewWebSocketService(url, key, secret)
|
||||
stream := &Stream{
|
||||
websocketService: wss,
|
||||
func (s *Stream) handleConnect() {
|
||||
if s.PublicOnly {
|
||||
cmd := &max.WebsocketCommand{
|
||||
Action: "subscribe",
|
||||
}
|
||||
for _, sub := range s.Subscriptions {
|
||||
var err error
|
||||
var depth int
|
||||
|
||||
if len(sub.Options.Depth) > 0 {
|
||||
depth, err = strconv.Atoi(sub.Options.Depth)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("depth parse error, given %v", sub.Options.Depth)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Subscriptions = append(cmd.Subscriptions, max.Subscription{
|
||||
Channel: string(sub.Channel),
|
||||
Market: toLocalSymbol(sub.Symbol),
|
||||
Depth: depth,
|
||||
Resolution: sub.Options.Interval,
|
||||
})
|
||||
}
|
||||
|
||||
s.Conn.WriteJSON(cmd)
|
||||
} else {
|
||||
nonce := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
auth := &max.AuthMessage{
|
||||
Action: "auth",
|
||||
APIKey: s.key,
|
||||
Nonce: nonce,
|
||||
Signature: signPayload(fmt.Sprintf("%d", nonce), s.secret),
|
||||
ID: uuid.New().String(),
|
||||
}
|
||||
if err := s.Conn.WriteJSON(auth); err != nil {
|
||||
log.WithError(err).Error("failed to send auth request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wss.OnConnect(func(conn *websocket.Conn) {
|
||||
if key == "" || secret == "" {
|
||||
log.Warn("MAX API key or secret is empty, will not send authentication command")
|
||||
} else {
|
||||
if err := wss.Auth(); err != nil {
|
||||
wss.EmitError(err)
|
||||
logger.WithError(err).Error("failed to send auth request")
|
||||
}
|
||||
}
|
||||
})
|
||||
func (s *Stream) handleKLineEvent(e max.KLineEvent) {
|
||||
kline := e.KLine.KLine()
|
||||
s.EmitKLine(kline)
|
||||
if kline.Closed {
|
||||
s.EmitKLineClosed(kline)
|
||||
}
|
||||
}
|
||||
|
||||
wss.OnDisconnect(stream.EmitDisconnect)
|
||||
|
||||
wss.OnMessage(func(message []byte) {
|
||||
logger.Debugf("M: %s", message)
|
||||
})
|
||||
|
||||
wss.OnKLineEvent(func(e max.KLineEvent) {
|
||||
kline := e.KLine.KLine()
|
||||
stream.EmitKLine(kline)
|
||||
if kline.Closed {
|
||||
stream.EmitKLineClosed(kline)
|
||||
}
|
||||
})
|
||||
|
||||
wss.OnOrderSnapshotEvent(func(e max.OrderSnapshotEvent) {
|
||||
for _, o := range e.Orders {
|
||||
globalOrder, err := toGlobalOrderUpdate(o)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("websocket order snapshot convert error")
|
||||
continue
|
||||
}
|
||||
|
||||
stream.EmitOrderUpdate(*globalOrder)
|
||||
}
|
||||
})
|
||||
|
||||
wss.OnOrderUpdateEvent(func(e max.OrderUpdateEvent) {
|
||||
for _, o := range e.Orders {
|
||||
globalOrder, err := toGlobalOrderUpdate(o)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("websocket order update convert error")
|
||||
continue
|
||||
}
|
||||
|
||||
stream.EmitOrderUpdate(*globalOrder)
|
||||
}
|
||||
})
|
||||
|
||||
wss.OnTradeUpdateEvent(func(e max.TradeUpdateEvent) {
|
||||
for _, tradeUpdate := range e.Trades {
|
||||
trade, err := convertWebSocketTrade(tradeUpdate)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("websocket trade update convert error")
|
||||
return
|
||||
}
|
||||
|
||||
stream.EmitTradeUpdate(*trade)
|
||||
}
|
||||
})
|
||||
|
||||
wss.OnBookEvent(func(e max.BookEvent) {
|
||||
newBook, err := e.OrderBook()
|
||||
func (s *Stream) handleOrderSnapshotEvent(e max.OrderSnapshotEvent) {
|
||||
for _, o := range e.Orders {
|
||||
globalOrder, err := toGlobalOrderUpdate(o)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("book convert error")
|
||||
log.WithError(err).Error("websocket order snapshot convert error")
|
||||
continue
|
||||
}
|
||||
|
||||
s.EmitOrderUpdate(*globalOrder)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) handleOrderUpdateEvent(e max.OrderUpdateEvent) {
|
||||
for _, o := range e.Orders {
|
||||
globalOrder, err := toGlobalOrderUpdate(o)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("websocket order update convert error")
|
||||
continue
|
||||
}
|
||||
|
||||
s.EmitOrderUpdate(*globalOrder)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) handleTradeEvent(e max.TradeUpdateEvent) {
|
||||
for _, tradeUpdate := range e.Trades {
|
||||
trade, err := convertWebSocketTrade(tradeUpdate)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("websocket trade update convert error")
|
||||
return
|
||||
}
|
||||
|
||||
newBook.Symbol = toGlobalSymbol(e.Market)
|
||||
|
||||
switch e.Event {
|
||||
case "snapshot":
|
||||
stream.EmitBookSnapshot(newBook)
|
||||
case "update":
|
||||
stream.EmitBookUpdate(newBook)
|
||||
}
|
||||
})
|
||||
|
||||
wss.OnConnect(func(conn *websocket.Conn) {
|
||||
stream.EmitConnect()
|
||||
})
|
||||
|
||||
wss.OnAccountSnapshotEvent(func(e max.AccountSnapshotEvent) {
|
||||
snapshot := map[string]types.Balance{}
|
||||
for _, bm := range e.Balances {
|
||||
balance, err := bm.Balance()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshot[toGlobalCurrency(balance.Currency)] = *balance
|
||||
}
|
||||
|
||||
stream.EmitBalanceSnapshot(snapshot)
|
||||
})
|
||||
|
||||
wss.OnAccountUpdateEvent(func(e max.AccountUpdateEvent) {
|
||||
snapshot := map[string]types.Balance{}
|
||||
for _, bm := range e.Balances {
|
||||
balance, err := bm.Balance()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshot[toGlobalCurrency(balance.Currency)] = *balance
|
||||
}
|
||||
|
||||
stream.EmitBalanceUpdate(snapshot)
|
||||
})
|
||||
|
||||
wss.OnError(func(err error) {
|
||||
log.WithError(err).Error("websocket error")
|
||||
})
|
||||
|
||||
return stream
|
||||
s.EmitTradeUpdate(*trade)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) Subscribe(channel types.Channel, symbol string, options types.SubscribeOptions) {
|
||||
opt := max.SubscribeOptions{}
|
||||
|
||||
if len(options.Depth) > 0 {
|
||||
depth, err := strconv.Atoi(options.Depth)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
opt.Depth = depth
|
||||
}
|
||||
|
||||
if len(options.Interval) > 0 {
|
||||
opt.Resolution = options.Interval
|
||||
}
|
||||
|
||||
s.websocketService.Subscribe(string(channel), toLocalSymbol(symbol), opt)
|
||||
}
|
||||
|
||||
func (s *Stream) Connect(ctx context.Context) error {
|
||||
err := s.websocketService.Connect(ctx)
|
||||
func (s *Stream) handleBookEvent(e max.BookEvent) {
|
||||
newBook, err := e.OrderBook()
|
||||
if err != nil {
|
||||
return err
|
||||
log.WithError(err).Error("book convert error")
|
||||
return
|
||||
}
|
||||
|
||||
s.EmitStart()
|
||||
return nil
|
||||
newBook.Symbol = toGlobalSymbol(e.Market)
|
||||
|
||||
switch e.Event {
|
||||
case "snapshot":
|
||||
s.EmitBookSnapshot(newBook)
|
||||
case "update":
|
||||
s.EmitBookUpdate(newBook)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) Close() error {
|
||||
return s.websocketService.Close()
|
||||
func (s *Stream) handleAccountSnapshotEvent(e max.AccountSnapshotEvent) {
|
||||
snapshot := map[string]types.Balance{}
|
||||
for _, bm := range e.Balances {
|
||||
balance, err := bm.Balance()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshot[balance.Currency] = *balance
|
||||
}
|
||||
|
||||
s.EmitBalanceSnapshot(snapshot)
|
||||
}
|
||||
|
||||
func (s *Stream) handleAccountUpdateEvent(e max.AccountUpdateEvent) {
|
||||
snapshot := map[string]types.Balance{}
|
||||
for _, bm := range e.Balances {
|
||||
balance, err := bm.Balance()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshot[toGlobalCurrency(balance.Currency)] = *balance
|
||||
}
|
||||
|
||||
s.EmitBalanceUpdate(snapshot)
|
||||
}
|
||||
|
||||
func convertWebSocketTrade(t max.TradeUpdate) (*types.Trade, error) {
|
||||
|
@ -253,3 +269,53 @@ func toGlobalOrderUpdate(u max.OrderUpdate) (*types.Order, error) {
|
|||
CreationTime: types.Time(time.Unix(0, u.CreatedAtMs*int64(time.Millisecond))),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Stream) dispatchEvent(e interface{}) {
|
||||
switch e := e.(type) {
|
||||
|
||||
case *max.BookEvent:
|
||||
s.EmitBookEvent(*e)
|
||||
|
||||
case *max.PublicTradeEvent:
|
||||
s.EmitTradeEvent(*e)
|
||||
|
||||
case *max.KLineEvent:
|
||||
s.EmitKLineEvent(*e)
|
||||
|
||||
case *max.ErrorEvent:
|
||||
s.EmitErrorEvent(*e)
|
||||
|
||||
case *max.SubscriptionEvent:
|
||||
s.EmitSubscriptionEvent(*e)
|
||||
|
||||
case *max.TradeSnapshotEvent:
|
||||
s.EmitTradeSnapshotEvent(*e)
|
||||
|
||||
case *max.TradeUpdateEvent:
|
||||
s.EmitTradeUpdateEvent(*e)
|
||||
|
||||
case *max.AccountSnapshotEvent:
|
||||
s.EmitAccountSnapshotEvent(*e)
|
||||
|
||||
case *max.AccountUpdateEvent:
|
||||
s.EmitAccountUpdateEvent(*e)
|
||||
|
||||
case *max.OrderSnapshotEvent:
|
||||
s.EmitOrderSnapshotEvent(*e)
|
||||
|
||||
case *max.OrderUpdateEvent:
|
||||
s.EmitOrderUpdateEvent(*e)
|
||||
|
||||
default:
|
||||
log.Errorf("unsupported %T event: %+v", e, e)
|
||||
}
|
||||
}
|
||||
|
||||
func signPayload(payload string, secret string) string {
|
||||
var sig = hmac.New(sha256.New, []byte(secret))
|
||||
_, err := sig.Write([]byte(payload))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(sig.Sum(nil))
|
||||
}
|
||||
|
|
117
pkg/exchange/max/stream_callbacks.go
Normal file
117
pkg/exchange/max/stream_callbacks.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
// Code generated by "callbackgen -type Stream"; DO NOT EDIT.
|
||||
|
||||
package max
|
||||
|
||||
import (
|
||||
"github.com/c9s/bbgo/pkg/exchange/max/maxapi"
|
||||
)
|
||||
|
||||
func (s *Stream) OnBookEvent(cb func(e max.BookEvent)) {
|
||||
s.bookEventCallbacks = append(s.bookEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitBookEvent(e max.BookEvent) {
|
||||
for _, cb := range s.bookEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnTradeEvent(cb func(e max.PublicTradeEvent)) {
|
||||
s.tradeEventCallbacks = append(s.tradeEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitTradeEvent(e max.PublicTradeEvent) {
|
||||
for _, cb := range s.tradeEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnKLineEvent(cb func(e max.KLineEvent)) {
|
||||
s.kLineEventCallbacks = append(s.kLineEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitKLineEvent(e max.KLineEvent) {
|
||||
for _, cb := range s.kLineEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnErrorEvent(cb func(e max.ErrorEvent)) {
|
||||
s.errorEventCallbacks = append(s.errorEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitErrorEvent(e max.ErrorEvent) {
|
||||
for _, cb := range s.errorEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnSubscriptionEvent(cb func(e max.SubscriptionEvent)) {
|
||||
s.subscriptionEventCallbacks = append(s.subscriptionEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitSubscriptionEvent(e max.SubscriptionEvent) {
|
||||
for _, cb := range s.subscriptionEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnTradeUpdateEvent(cb func(e max.TradeUpdateEvent)) {
|
||||
s.tradeUpdateEventCallbacks = append(s.tradeUpdateEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitTradeUpdateEvent(e max.TradeUpdateEvent) {
|
||||
for _, cb := range s.tradeUpdateEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnTradeSnapshotEvent(cb func(e max.TradeSnapshotEvent)) {
|
||||
s.tradeSnapshotEventCallbacks = append(s.tradeSnapshotEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitTradeSnapshotEvent(e max.TradeSnapshotEvent) {
|
||||
for _, cb := range s.tradeSnapshotEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnOrderUpdateEvent(cb func(e max.OrderUpdateEvent)) {
|
||||
s.orderUpdateEventCallbacks = append(s.orderUpdateEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitOrderUpdateEvent(e max.OrderUpdateEvent) {
|
||||
for _, cb := range s.orderUpdateEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnOrderSnapshotEvent(cb func(e max.OrderSnapshotEvent)) {
|
||||
s.orderSnapshotEventCallbacks = append(s.orderSnapshotEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitOrderSnapshotEvent(e max.OrderSnapshotEvent) {
|
||||
for _, cb := range s.orderSnapshotEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnAccountSnapshotEvent(cb func(e max.AccountSnapshotEvent)) {
|
||||
s.accountSnapshotEventCallbacks = append(s.accountSnapshotEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitAccountSnapshotEvent(e max.AccountSnapshotEvent) {
|
||||
for _, cb := range s.accountSnapshotEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) OnAccountUpdateEvent(cb func(e max.AccountUpdateEvent)) {
|
||||
s.accountUpdateEventCallbacks = append(s.accountUpdateEventCallbacks, cb)
|
||||
}
|
||||
|
||||
func (s *Stream) EmitAccountUpdateEvent(e max.AccountUpdateEvent) {
|
||||
for _, cb := range s.accountUpdateEventCallbacks {
|
||||
cb(e)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user