Skip to content

Commit 13f482f

Browse files
committed
add spot ticker 24 hr
1 parent 501c837 commit 13f482f

File tree

4 files changed

+259
-14
lines changed

4 files changed

+259
-14
lines changed

src/binance-proxy/handler/handler.go

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ func (s *Handler) Router(w http.ResponseWriter, r *http.Request) {
3434
case "/api/v3/depth", "/fapi/v1/depth":
3535
s.depth(w, r)
3636

37+
case "/api/v3/ticker/24hr":
38+
s.ticker(w, r)
39+
3740
case "/api/v3/exchangeInfo", "/fapi/v1/exchangeInfo":
3841
s.exchangeInfo(w, r)
3942

src/binance-proxy/handler/ticker.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package handler
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
)
7+
8+
func (s *Handler) ticker(w http.ResponseWriter, r *http.Request) {
9+
symbol := r.URL.Query().Get("symbol")
10+
11+
ticker := s.srv.Ticker(symbol)
12+
if ticker == nil {
13+
s.reverseProxy(w, r)
14+
return
15+
}
16+
17+
w.Header().Set("Content-Type", "application/json")
18+
w.Header().Set("Data-Source", "websocket")
19+
20+
encoder := json.NewEncoder(w)
21+
encoder.SetEscapeHTML(false)
22+
encoder.Encode(ticker)
23+
}

src/binance-proxy/service/service.go

+37-14
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ type Service struct {
1616
exchangeInfoSrv *ExchangeInfoSrv
1717
klinesSrv sync.Map // map[symbolInterval]*Klines
1818
depthSrv sync.Map // map[symbolInterval]*Depth
19+
tickerSrv sync.Map // map[symbolInterval]*Ticker
1920

2021
lastGetKlines sync.Map // map[symbolInterval]time.Time
2122
lastGetDepth sync.Map // map[symbolInterval]time.Time
23+
lastGetTicker sync.Map // map[symbolInterval]time.Time
2224
}
2325

2426
func NewService(ctx context.Context, class Class) *Service {
@@ -47,10 +49,6 @@ func NewService(ctx context.Context, class Class) *Service {
4749
return s
4850
}
4951

50-
func (s *Service) ExchangeInfo() []byte {
51-
return s.exchangeInfoSrv.GetExchangeInfo()
52-
}
53-
5452
func (s *Service) autoRemoveExpired() {
5553
s.klinesSrv.Range(func(k, v interface{}) bool {
5654
si := k.(symbolInterval)
@@ -88,6 +86,41 @@ func (s *Service) autoRemoveExpired() {
8886

8987
return true
9088
})
89+
s.tickerSrv.Range(func(k, v interface{}) bool {
90+
si := k.(symbolInterval)
91+
srv := v.(*TickerSrv)
92+
93+
if t, ok := s.lastGetTicker.Load(si); ok {
94+
if time.Now().Sub(t.(time.Time)) > 2*time.Minute {
95+
log.Debugf("%s.Ticker srv expired!Removed", si)
96+
s.lastGetTicker.Delete(si)
97+
98+
s.tickerSrv.Delete(si)
99+
srv.Stop()
100+
}
101+
} else {
102+
s.lastGetTicker.Store(si, time.Now())
103+
}
104+
105+
return true
106+
})
107+
}
108+
109+
func (s *Service) Ticker(symbol string) *Ticker24hr {
110+
si := NewSymbolInterval(s.class, symbol, "")
111+
srv, loaded := s.tickerSrv.Load(*si)
112+
if !loaded {
113+
if srv, loaded = s.tickerSrv.LoadOrStore(*si, NewTickerSrv(s.ctx, si)); loaded == false {
114+
srv.(*TickerSrv).Start()
115+
}
116+
}
117+
s.lastGetTicker.Store(*si, time.Now())
118+
119+
return srv.(*TickerSrv).GetTicker()
120+
}
121+
122+
func (s *Service) ExchangeInfo() []byte {
123+
return s.exchangeInfoSrv.GetExchangeInfo()
91124
}
92125

93126
func (s *Service) Klines(symbol, interval string) []*Kline {
@@ -115,13 +148,3 @@ func (s *Service) Depth(symbol string) *Depth {
115148

116149
return srv.(*DepthSrv).GetDepth()
117150
}
118-
119-
// func (s *Service) autoRemoveDepthSrv(symbol string) *Depth {
120-
// si := NewSymbolInterval(s.class, symbol, "")
121-
// srv, loaded := s.klinesSrv.LoadOrStore(*si, NewDepthSrv(s.ctx, si))
122-
// if loaded == false {
123-
// srv.(*DepthSrv).Start()
124-
// }
125-
126-
// return srv.(*DepthSrv).GetDepth()
127-
// }

src/binance-proxy/service/ticker.go

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package service
2+
3+
import (
4+
"binance-proxy/tool"
5+
"context"
6+
"sync"
7+
8+
log "github.com/sirupsen/logrus"
9+
10+
spot "github.com/adshao/go-binance/v2"
11+
)
12+
13+
type TickerSrv struct {
14+
rw sync.RWMutex
15+
16+
ctx context.Context
17+
cancel context.CancelFunc
18+
19+
initCtx context.Context
20+
initDone context.CancelFunc
21+
22+
si *symbolInterval
23+
ticker24hr *Ticker24hr
24+
bookTicker *BookTicker
25+
}
26+
27+
type BookTicker struct {
28+
Symbol string `json:"symbol"`
29+
BidPrice string `json:"bidPrice"`
30+
BidQuantity string `json:"bidQty"`
31+
AskPrice string `json:"askPrice"`
32+
AskQuantity string `json:"askQty"`
33+
}
34+
35+
type Ticker24hr struct {
36+
Symbol string `json:"symbol"`
37+
PriceChange string `json:"priceChange"`
38+
PriceChangePercent string `json:"priceChangePercent"`
39+
WeightedAvgPrice string `json:"weightedAvgPrice"`
40+
PrevClosePrice string `json:"prevClosePrice"`
41+
LastPrice string `json:"lastPrice"`
42+
LastQty string `json:"lastQty"`
43+
BidPrice string `json:"bidPrice"`
44+
AskPrice string `json:"askPrice"`
45+
OpenPrice string `json:"openPrice"`
46+
HighPrice string `json:"highPrice"`
47+
LowPrice string `json:"lowPrice"`
48+
Volume string `json:"volume"`
49+
QuoteVolume string `json:"quoteVolume"`
50+
OpenTime int64 `json:"openTime"`
51+
CloseTime int64 `json:"closeTime"`
52+
FirstID int64 `json:"firstId"`
53+
LastID int64 `json:"lastId"`
54+
Count int64 `json:"count"`
55+
}
56+
57+
func NewTickerSrv(ctx context.Context, si *symbolInterval) *TickerSrv {
58+
s := &TickerSrv{si: si}
59+
s.ctx, s.cancel = context.WithCancel(ctx)
60+
s.initCtx, s.initDone = context.WithCancel(context.Background())
61+
62+
return s
63+
}
64+
65+
func (s *TickerSrv) Start() {
66+
go func() {
67+
for d := tool.NewDelayIterator(); ; d.Delay() {
68+
s.rw.Lock()
69+
s.ticker24hr = nil
70+
s.bookTicker = nil
71+
s.rw.Unlock()
72+
73+
ticker24hrDoneC, ticker24hrstopC, err := s.connectTicker24hr()
74+
if err != nil {
75+
log.Errorf("%s.Websocket 24hr ticker connect error!Error:%s", s.si, err)
76+
continue
77+
}
78+
79+
bookDoneC, bookStopC, err := s.connectTickerBook()
80+
if err != nil {
81+
bookStopC <- struct{}{}
82+
log.Errorf("%s.Websocket book ticker connect error!Error:%s", s.si, err)
83+
continue
84+
}
85+
86+
log.Debugf("%s.Websocket ticker connect success!", s.si)
87+
select {
88+
case <-s.ctx.Done():
89+
bookStopC <- struct{}{}
90+
ticker24hrstopC <- struct{}{}
91+
return
92+
case <-bookDoneC:
93+
ticker24hrstopC <- struct{}{}
94+
case <-ticker24hrDoneC:
95+
bookStopC <- struct{}{}
96+
}
97+
98+
log.Debugf("%s.Websocket book ticker or ticker 24hr disconnected!Reconnecting", s.si)
99+
}
100+
}()
101+
}
102+
103+
func (s *TickerSrv) Stop() {
104+
s.cancel()
105+
}
106+
107+
func (s *TickerSrv) connectTickerBook() (doneC, stopC chan struct{}, err error) {
108+
return spot.WsBookTickerServe(s.si.Symbol, s.wsHandlerBookTicker, s.errHandler)
109+
}
110+
111+
func (s *TickerSrv) connectTicker24hr() (doneC, stopC chan struct{}, err error) {
112+
return spot.WsMarketStatServe(s.si.Symbol, s.wsHandlerTicker24hr, s.errHandler)
113+
}
114+
115+
func (s *TickerSrv) GetTicker() *Ticker24hr {
116+
<-s.initCtx.Done()
117+
s.rw.RLock()
118+
defer s.rw.RUnlock()
119+
120+
bidPrice := s.ticker24hr.BidPrice
121+
askPrice := s.ticker24hr.AskPrice
122+
if s.bookTicker != nil {
123+
bidPrice = s.bookTicker.BidPrice
124+
askPrice = s.bookTicker.AskPrice
125+
}
126+
127+
return &Ticker24hr{
128+
Symbol: s.ticker24hr.Symbol,
129+
PriceChange: s.ticker24hr.PriceChange,
130+
PriceChangePercent: s.ticker24hr.PriceChangePercent,
131+
WeightedAvgPrice: s.ticker24hr.WeightedAvgPrice,
132+
PrevClosePrice: s.ticker24hr.PrevClosePrice,
133+
LastPrice: s.ticker24hr.LastPrice,
134+
LastQty: s.ticker24hr.LastQty,
135+
BidPrice: bidPrice,
136+
AskPrice: askPrice,
137+
OpenPrice: s.ticker24hr.OpenPrice,
138+
HighPrice: s.ticker24hr.HighPrice,
139+
LowPrice: s.ticker24hr.LowPrice,
140+
Volume: s.ticker24hr.Volume,
141+
QuoteVolume: s.ticker24hr.QuoteVolume,
142+
OpenTime: s.ticker24hr.OpenTime,
143+
CloseTime: s.ticker24hr.CloseTime,
144+
FirstID: s.ticker24hr.FirstID,
145+
LastID: s.ticker24hr.LastID,
146+
Count: s.ticker24hr.Count,
147+
}
148+
}
149+
150+
func (s *TickerSrv) wsHandlerBookTicker(event *spot.WsBookTickerEvent) {
151+
s.rw.Lock()
152+
defer s.rw.Unlock()
153+
154+
s.bookTicker = &BookTicker{
155+
Symbol: event.Symbol,
156+
BidPrice: event.BestBidPrice,
157+
BidQuantity: event.BestBidQty,
158+
AskPrice: event.BestAskPrice,
159+
AskQuantity: event.BestAskQty,
160+
}
161+
}
162+
163+
func (s *TickerSrv) wsHandlerTicker24hr(event *spot.WsMarketStatEvent) {
164+
s.rw.Lock()
165+
defer s.rw.Unlock()
166+
167+
if s.ticker24hr == nil {
168+
defer s.initDone()
169+
}
170+
171+
s.ticker24hr = &Ticker24hr{
172+
Symbol: event.Symbol,
173+
PriceChange: event.PriceChange,
174+
PriceChangePercent: event.PriceChangePercent,
175+
WeightedAvgPrice: event.WeightedAvgPrice,
176+
PrevClosePrice: event.PrevClosePrice,
177+
LastPrice: event.LastPrice,
178+
LastQty: event.CloseQty,
179+
BidPrice: event.BidPrice,
180+
AskPrice: event.AskPrice,
181+
OpenPrice: event.OpenPrice,
182+
HighPrice: event.HighPrice,
183+
LowPrice: event.LowPrice,
184+
Volume: event.BaseVolume,
185+
QuoteVolume: event.QuoteVolume,
186+
OpenTime: event.OpenTime,
187+
CloseTime: event.CloseTime,
188+
FirstID: event.FirstID,
189+
LastID: event.LastID,
190+
Count: event.Count,
191+
}
192+
}
193+
194+
func (s *TickerSrv) errHandler(err error) {
195+
log.Errorf("%s.Ticker websocket throw error!Error:%s", s.si, err)
196+
}

0 commit comments

Comments
 (0)