mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-22 06:53:52 +00:00
Merge pull request #145 from c9s/ftx/order-update
feature: emit orderbook update
This commit is contained in:
commit
bd4168d3fc
|
@ -37,7 +37,10 @@ var orderbookCmd = &cobra.Command{
|
|||
s := ex.NewStream()
|
||||
s.Subscribe(types.BookChannel, symbol, types.SubscribeOptions{})
|
||||
s.OnBookSnapshot(func(book types.OrderBook) {
|
||||
log.Infof("orderbook snapshot: %+v", book)
|
||||
log.Infof("orderbook snapshot: %s", book.String())
|
||||
})
|
||||
s.OnBookUpdate(func(book types.OrderBook) {
|
||||
log.Infof("orderbook update: %s", book.String())
|
||||
})
|
||||
|
||||
if err := s.Connect(ctx); err != nil {
|
||||
|
|
26
pkg/exchange/ftx/orderbook_update.json
Normal file
26
pkg/exchange/ftx/orderbook_update.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"channel": "orderbook",
|
||||
"market": "BTC/USDT",
|
||||
"type": "update",
|
||||
"data": {
|
||||
"time": 1614737706.650016,
|
||||
"checksum": 3976343467,
|
||||
"bids": [
|
||||
[
|
||||
48763.0,
|
||||
0.5001
|
||||
]
|
||||
],
|
||||
"asks": [
|
||||
[
|
||||
48826.0,
|
||||
0.3385
|
||||
],
|
||||
[
|
||||
48929.0,
|
||||
26.8713
|
||||
]
|
||||
],
|
||||
"action": "update"
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ func NewStream(key, secret string) *Stream {
|
|||
wsService: wss,
|
||||
}
|
||||
|
||||
wss.OnMessage(messageHandler{StandardStream: s.StandardStream}.handleMessage)
|
||||
wss.OnMessage((&messageHandler{StandardStream: s.StandardStream}).handleMessage)
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
|
@ -22,11 +22,8 @@ func (h messageHandler) handleMessage(message []byte) {
|
|||
switch r.Type {
|
||||
case subscribedRespType:
|
||||
h.handleSubscribedMessage(r)
|
||||
case partialRespType:
|
||||
// snapshot of current market data
|
||||
h.handleSnapshot(r)
|
||||
case updateRespType:
|
||||
//log.Infof("update=> %s", string(message))
|
||||
case partialRespType, updateRespType:
|
||||
h.handleMarketData(r)
|
||||
default:
|
||||
logger.Errorf("unsupported message type: %+v", r.Type)
|
||||
}
|
||||
|
@ -38,11 +35,36 @@ func (h messageHandler) handleSubscribedMessage(response rawResponse) {
|
|||
logger.Infof("%s %s is subscribed", r.Market, r.Channel)
|
||||
}
|
||||
|
||||
func (h messageHandler) handleSnapshot(response rawResponse) {
|
||||
r, err := response.toSnapshotResp()
|
||||
func (h *messageHandler) handleMarketData(response rawResponse) {
|
||||
r, err := response.toDataResponse()
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("failed to convert the partial response to snapshot")
|
||||
log.WithError(err).Errorf("failed to convert the partial response to data response")
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Channel {
|
||||
case orderbook:
|
||||
h.handleOrderBook(r)
|
||||
default:
|
||||
log.Errorf("unsupported market data channel %s", r.Channel)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h messageHandler) handleOrderBook(r dataResponse) {
|
||||
ob, err := toGlobalOrderBook(r)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("failed to generate orderbook snapshot")
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Type {
|
||||
case partialRespType:
|
||||
h.EmitBookSnapshot(ob)
|
||||
case updateRespType:
|
||||
h.EmitBookUpdate(ob)
|
||||
default:
|
||||
log.Errorf("unsupported order book data type %s", r.Type)
|
||||
return
|
||||
}
|
||||
h.EmitBookSnapshot(r.toGlobalOrderBook())
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package ftx
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -64,13 +65,13 @@ func (r rawResponse) toSubscribedResp() subscribedResponse {
|
|||
}
|
||||
}
|
||||
|
||||
func (r rawResponse) toSnapshotResp() (snapshotResponse, error) {
|
||||
o := snapshotResponse{
|
||||
func (r rawResponse) toDataResponse() (dataResponse, error) {
|
||||
o := dataResponse{
|
||||
mandatoryFields: r.mandatoryFields,
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(r.Data, &o); err != nil {
|
||||
return snapshotResponse{}, err
|
||||
return dataResponse{}, err
|
||||
}
|
||||
|
||||
sec, dec := math.Modf(o.Time)
|
||||
|
@ -84,7 +85,7 @@ type subscribedResponse struct {
|
|||
mandatoryFields
|
||||
}
|
||||
|
||||
type snapshotResponse struct {
|
||||
type dataResponse struct {
|
||||
mandatoryFields
|
||||
|
||||
Action string `json:"action"`
|
||||
|
@ -93,31 +94,42 @@ type snapshotResponse struct {
|
|||
|
||||
Timestamp time.Time
|
||||
|
||||
Checksum int64 `json:"checksum"`
|
||||
Checksum uint32 `json:"checksum"`
|
||||
|
||||
// Best 100 orders
|
||||
Bids [][]float64 `json:"bids"`
|
||||
Bids [][]json.Number `json:"bids"`
|
||||
|
||||
// Best 100 orders
|
||||
Asks [][]float64 `json:"asks"`
|
||||
Asks [][]json.Number `json:"asks"`
|
||||
}
|
||||
|
||||
func (r snapshotResponse) toGlobalOrderBook() types.OrderBook {
|
||||
func toGlobalOrderBook(r dataResponse) (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: toPriceVolumeSlice(r.Bids),
|
||||
Asks: toPriceVolumeSlice(r.Asks),
|
||||
}
|
||||
Bids: bids,
|
||||
Asks: asks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toPriceVolumeSlice(orders [][]float64) types.PriceVolumeSlice {
|
||||
func toPriceVolumeSlice(orders [][]json.Number) (types.PriceVolumeSlice, error) {
|
||||
var pv types.PriceVolumeSlice
|
||||
for _, o := range orders {
|
||||
pv = append(pv, types.PriceVolume{
|
||||
Price: fixedpoint.NewFromFloat(o[0]),
|
||||
Volume: fixedpoint.NewFromFloat(o[1]),
|
||||
})
|
||||
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
|
||||
return pv, nil
|
||||
}
|
||||
|
|
|
@ -21,35 +21,35 @@ func Test_rawResponse_toSubscribedResp(t *testing.T) {
|
|||
assert.Equal(t, "BTC/USDT", r.Market)
|
||||
}
|
||||
|
||||
func Test_rawResponse_toSnapshotResp(t *testing.T) {
|
||||
func Test_rawResponse_toDataResponse(t *testing.T) {
|
||||
f, err := ioutil.ReadFile("./orderbook_snapshot.json")
|
||||
assert.NoError(t, err)
|
||||
var m rawResponse
|
||||
assert.NoError(t, json.Unmarshal(f, &m))
|
||||
r, err := m.toSnapshotResp()
|
||||
r, err := m.toDataResponse()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, partialRespType, r.Type)
|
||||
assert.Equal(t, orderbook, r.Channel)
|
||||
assert.Equal(t, "BTC/USDT", r.Market)
|
||||
assert.Equal(t, int64(1614520368), r.Timestamp.Unix())
|
||||
assert.Equal(t, int64(2150525410), r.Checksum)
|
||||
assert.Equal(t, uint32(2150525410), r.Checksum)
|
||||
assert.Len(t, r.Bids, 100)
|
||||
assert.Equal(t, []float64{44555.0, 3.3968}, r.Bids[0])
|
||||
assert.Equal(t, []float64{44554.0, 0.0561}, r.Bids[1])
|
||||
assert.Equal(t, []json.Number{"44555.0", "3.3968"}, r.Bids[0])
|
||||
assert.Equal(t, []json.Number{"44554.0", "0.0561"}, r.Bids[1])
|
||||
assert.Len(t, r.Asks, 100)
|
||||
assert.Equal(t, []float64{44574.0, 0.4591}, r.Asks[0])
|
||||
assert.Equal(t, []float64{44579.0, 0.15}, r.Asks[1])
|
||||
assert.Equal(t, []json.Number{"44574.0", "0.4591"}, r.Asks[0])
|
||||
assert.Equal(t, []json.Number{"44579.0", "0.15"}, r.Asks[1])
|
||||
}
|
||||
|
||||
func Test_snapshotResponse_toGlobalOrderBook(t *testing.T) {
|
||||
func Test_DataResponse_toGlobalOrderBook(t *testing.T) {
|
||||
f, err := ioutil.ReadFile("./orderbook_snapshot.json")
|
||||
assert.NoError(t, err)
|
||||
var m rawResponse
|
||||
assert.NoError(t, json.Unmarshal(f, &m))
|
||||
r, err := m.toSnapshotResp()
|
||||
r, err := m.toDataResponse()
|
||||
assert.NoError(t, err)
|
||||
|
||||
b := r.toGlobalOrderBook()
|
||||
b, err := toGlobalOrderBook(r)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "BTC/USDT", b.Symbol)
|
||||
isValid, err := b.IsValid()
|
||||
|
@ -75,4 +75,6 @@ func Test_snapshotResponse_toGlobalOrderBook(t *testing.T) {
|
|||
Price: fixedpoint.MustNewFromString("45010.0"),
|
||||
Volume: fixedpoint.MustNewFromString("0.0003"),
|
||||
}, b.Asks[99])
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package types
|
|||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -227,16 +228,30 @@ func (b *OrderBook) Update(book OrderBook) {
|
|||
}
|
||||
|
||||
func (b *OrderBook) Print() {
|
||||
fmt.Printf("BOOK %s\n", b.Symbol)
|
||||
fmt.Printf("ASKS:\n")
|
||||
fmt.Printf(b.String())
|
||||
}
|
||||
|
||||
func (b *OrderBook) String() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
sb.WriteString("BOOK ")
|
||||
sb.WriteString(b.Symbol)
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString("ASKS:\n")
|
||||
for i := len(b.Asks) - 1; i >= 0; i-- {
|
||||
fmt.Printf("- ASK: %s\n", b.Asks[i].String())
|
||||
sb.WriteString("- ASK: ")
|
||||
sb.WriteString(b.Asks[i].String())
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
fmt.Printf("BIDS:\n")
|
||||
sb.WriteString("BIDS:\n")
|
||||
for _, bid := range b.Bids {
|
||||
fmt.Printf("- BID: %s\n", bid.String())
|
||||
sb.WriteString("- BID: ")
|
||||
sb.WriteString(bid.String())
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type MutexOrderBook struct {
|
||||
|
|
Loading…
Reference in New Issue
Block a user