|
@@ -0,0 +1,227 @@
|
|
|
|
|
+package binance
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "encoding/json"
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "io/ioutil"
|
|
|
|
|
+ "net/http"
|
|
|
|
|
+
|
|
|
|
|
+ "golang.org/x/net/websocket"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const (
|
|
|
|
|
+ XRPBTC = "xrpbtc"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+type Options struct {
|
|
|
|
|
+ Symbol string
|
|
|
|
|
+ LookbackLimit int
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func NewBinanceStream(opts Options) BinanceStream {
|
|
|
|
|
+ return &binanceStream{
|
|
|
|
|
+ Options: opts,
|
|
|
|
|
+ LastUpdateID: -1,
|
|
|
|
|
+ DepthEventChannel: make(chan DepthEvent),
|
|
|
|
|
+ ErrorChannel: make(chan error),
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type binanceStream struct {
|
|
|
|
|
+ Options Options
|
|
|
|
|
+ LastUpdateID int64
|
|
|
|
|
+ DepthEventChannel chan DepthEvent
|
|
|
|
|
+ ErrorChannel chan error
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type BinanceStream interface {
|
|
|
|
|
+ Start()
|
|
|
|
|
+ GetDepthEventChannel() chan DepthEvent
|
|
|
|
|
+ GetErrorChannel() chan error
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type BidCount struct {
|
|
|
|
|
+ Price string
|
|
|
|
|
+ Volume string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type Bids []*BidCount
|
|
|
|
|
+
|
|
|
|
|
+type DepthEvent struct {
|
|
|
|
|
+ EventType string
|
|
|
|
|
+ EventTime int64
|
|
|
|
|
+ Symbol string
|
|
|
|
|
+ FirstUpdateID int64
|
|
|
|
|
+ FinalUpdateID int64
|
|
|
|
|
+ BidsToUpdate Bids
|
|
|
|
|
+ AsksToUpdate Bids
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type rawBids [][]interface{}
|
|
|
|
|
+type depth struct {
|
|
|
|
|
+ LastUpdateID int64 `json:"lastUpdateId"`
|
|
|
|
|
+ Bids rawBids `json:"bids"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type depthEvent struct {
|
|
|
|
|
+ EventType string `json:"e"`
|
|
|
|
|
+ EventTime int64 `json:"E"`
|
|
|
|
|
+ Symbol string `json:"s"`
|
|
|
|
|
+ FirstUpdateID int64 `json:"U"`
|
|
|
|
|
+ FinalUpdateID int64 `json:"u"`
|
|
|
|
|
+ BidsToUpdate rawBids `json:"b"`
|
|
|
|
|
+ AsksToUpdate rawBids `json:"a"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (b *binanceStream) GetDepthEventChannel() chan DepthEvent {
|
|
|
|
|
+ return b.DepthEventChannel
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (b *binanceStream) GetErrorChannel() chan error {
|
|
|
|
|
+ return b.ErrorChannel
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (b *binanceStream) Start() {
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ e := b.getDepth(b.Options.Symbol, 1000)
|
|
|
|
|
+ if e != nil {
|
|
|
|
|
+ b.ErrorChannel <- e
|
|
|
|
|
+ }
|
|
|
|
|
+ b.processDepthEvents()
|
|
|
|
|
+ }()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func transformRawBids(rawBids rawBids) Bids {
|
|
|
|
|
+ bids := Bids{}
|
|
|
|
|
+ for _, bSet := range rawBids {
|
|
|
|
|
+ price := ""
|
|
|
|
|
+ vol := ""
|
|
|
|
|
+ for idx, rBid := range bSet {
|
|
|
|
|
+ val, ok := rBid.(string)
|
|
|
|
|
+ if ok {
|
|
|
|
|
+ if idx == 0 {
|
|
|
|
|
+ price = val
|
|
|
|
|
+ }
|
|
|
|
|
+ if idx == 1 {
|
|
|
|
|
+ vol = val
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ bids = append(bids, &BidCount{
|
|
|
|
|
+ Price: price,
|
|
|
|
|
+ Volume: vol,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ return bids
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (b *binanceStream) processTradeEvents() {
|
|
|
|
|
+ origin := "http://localhost/"
|
|
|
|
|
+ url := b.getWebsocketURL("trade")
|
|
|
|
|
+ ws, err := websocket.Dial(url, "", origin)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ b.ErrorChannel <- err
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if _, err := ws.Write(nil); err != nil {
|
|
|
|
|
+ b.ErrorChannel <- err
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ var msg = make([]byte, 4096)
|
|
|
|
|
+ var n int
|
|
|
|
|
+ n, err = ws.Read(msg)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ b.ErrorChannel <- err
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ for err == nil {
|
|
|
|
|
+ evt := depthEvent{}
|
|
|
|
|
+ e := json.Unmarshal(msg[:n], &evt)
|
|
|
|
|
+ if e != nil {
|
|
|
|
|
+ b.ErrorChannel <- e
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if evt.FinalUpdateID > b.LastUpdateID {
|
|
|
|
|
+ b.DepthEventChannel <- DepthEvent{
|
|
|
|
|
+ EventType: evt.EventType,
|
|
|
|
|
+ EventTime: evt.EventTime,
|
|
|
|
|
+ Symbol: evt.Symbol,
|
|
|
|
|
+ FirstUpdateID: evt.FirstUpdateID,
|
|
|
|
|
+ FinalUpdateID: evt.FinalUpdateID,
|
|
|
|
|
+ BidsToUpdate: transformRawBids(evt.BidsToUpdate),
|
|
|
|
|
+ AsksToUpdate: transformRawBids(evt.AsksToUpdate),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ n, err = ws.Read(msg)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (b *binanceStream) getWebsocketURL(view string) string {
|
|
|
|
|
+ return fmt.Sprintf("wss://stream.binance.com:9443/ws/%s@%s", b.Options.Symbol, view)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (b *binanceStream) processDepthEvents() {
|
|
|
|
|
+ origin := "http://localhost/"
|
|
|
|
|
+ url := b.getWebsocketURL("depth")
|
|
|
|
|
+ ws, err := websocket.Dial(url, "", origin)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ fmt.Println(err)
|
|
|
|
|
+ b.ErrorChannel <- err
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if _, err := ws.Write(nil); err != nil {
|
|
|
|
|
+ b.ErrorChannel <- err
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ var msg = make([]byte, 4096)
|
|
|
|
|
+ var n int
|
|
|
|
|
+ n, err = ws.Read(msg)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ b.ErrorChannel <- err
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ for err == nil {
|
|
|
|
|
+ evt := depthEvent{}
|
|
|
|
|
+ e := json.Unmarshal(msg[:n], &evt)
|
|
|
|
|
+ if e != nil {
|
|
|
|
|
+ fmt.Println(msg)
|
|
|
|
|
+ b.ErrorChannel <- e
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if evt.FinalUpdateID > b.LastUpdateID {
|
|
|
|
|
+ b.DepthEventChannel <- DepthEvent{
|
|
|
|
|
+ EventType: evt.EventType,
|
|
|
|
|
+ EventTime: evt.EventTime,
|
|
|
|
|
+ Symbol: evt.Symbol,
|
|
|
|
|
+ FirstUpdateID: evt.FirstUpdateID,
|
|
|
|
|
+ FinalUpdateID: evt.FinalUpdateID,
|
|
|
|
|
+ BidsToUpdate: transformRawBids(evt.BidsToUpdate),
|
|
|
|
|
+ AsksToUpdate: transformRawBids(evt.AsksToUpdate),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ n, err = ws.Read(msg)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (b *binanceStream) getDepth(symbol string, limit int) error {
|
|
|
|
|
+ req, err := http.NewRequest("GET", fmt.Sprintf("https://www.binance.com/api/v1/depth?symbol=%s&limit=%d", symbol, limit), nil)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ resp, err := http.DefaultClient.Do(req)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ defer resp.Body.Close()
|
|
|
|
|
+ rawData, err := ioutil.ReadAll(resp.Body)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ depth := &depth{}
|
|
|
|
|
+ err = json.Unmarshal(rawData, depth)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ b.LastUpdateID = depth.LastUpdateID
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|