├── .dockerignore ├── .env.local.env ├── .gitignore ├── README.md ├── binance_websocket ├── binance_cycle.go ├── binance_spot_websocket.go └── binance_symbol.go ├── bitfinex_websocket ├── bitfinex_spot_websocket.go ├── bitfinex_symbol.go └── bitmex_cycle.go ├── bitmex_websocket ├── bitmex_cycle.go ├── bitmex_sustainable_websocket.go └── bitmex_symbol.go ├── common ├── constant.go └── utils.go ├── go.mod ├── go.sum ├── huobi_websocket ├── huobi_cycle.go ├── huobi_spot_websocket.go ├── huobi_symbol.go └── huobi_util.go ├── main ├── binance_btc_main.go ├── binance_depth_main.go ├── binance_eth_main.go ├── binance_tick_main.go ├── binance_trade_main.go ├── binance_usdt_kline.go ├── bitfinex_depth_main.go ├── bitfinex_kline_main.go ├── bitfinex_tick_main.go ├── bitfinex_trade_main.go ├── bitmex_depth_funding_instrument_main.go ├── bitmex_future_kline_main.go ├── bitmex_futures_instrument_queto_main.go ├── bitmex_futures_trade_depth_main.go ├── bitmex_trade_queto_kline_main.go ├── huobi_depth_main.go ├── huobi_kline_main.go ├── huobi_tick_main.go ├── huobi_trade_main.go ├── okex_depth_main.go ├── okex_kline_main.go ├── okex_tick_main.go └── okex_trade_main.go ├── okex_websocket ├── okex_cycle.go ├── okex_spot_websocket.go ├── okex_symbol.go └── okex_util.go └── test └── symbol_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .git -------------------------------------------------------------------------------- /.env.local.env: -------------------------------------------------------------------------------- 1 | ################# env config #################### 2 | 3 | 4 | 5 | # okex websocket url 6 | OKEX_URL=wss://real.okex.com:10441/websocket?compress=true 7 | 8 | 9 | # huobi websocket url 10 | HUOBI_URL=wss://api.huobipro.com/ws 11 | 12 | # huobi depth step 13 | HUOBI_STEP=step0 14 | 15 | 16 | # bitmex websocket url 17 | BITMEX_URL=wss://testnet.bitmex.com/realtime 18 | # BITMEX_URL=wss://www.bitmex.com/realtime 19 | 20 | # bitmex futures symbols 21 | BITMEX_DQ=ADAZ18,BCHZ18,EOSZ18,ETHZ18,LTCZ18,TRXZ18,XRPZ18,XBTZ18,XBTH19 22 | 23 | # bitfinex websocket url 24 | BITFINEX_URL=wss://api.bitfinex.com/ws/2 25 | 26 | # binance websocket url 27 | BINANCE_URL=wss://stream.binance.com:9443/stream?streams= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.db 3 | *.log 4 | log/ 5 | custom/ 6 | data/ 7 | .vendor/ 8 | .idea/ 9 | *.iml 10 | 11 | 12 | # Binaries for programs and plugins 13 | *.exe 14 | *.exe~ 15 | *.dll 16 | *.so 17 | *.dylib 18 | 19 | # Test binary, build with `go test -c` 20 | *.test 21 | 22 | # Output of the go coverage tool, specifically when used with LiteIDE 23 | *.out 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moore-Liu/exchange_websocket/a484a8e5c3f9774744ba1eb48ba073d38c9a9cc6/README.md -------------------------------------------------------------------------------- /binance_websocket/binance_cycle.go: -------------------------------------------------------------------------------- 1 | package binance_websocket 2 | 3 | import "exchange_websocket/common" 4 | 5 | type BinanceCycle struct { 6 | BinanceCycles []string 7 | BinanceCycleMap map[string]int 8 | } 9 | 10 | func NewBinanceCycle() *BinanceCycle { 11 | bfCycle := new(BinanceCycle) 12 | return bfCycle.BinanceCycleInit() 13 | } 14 | 15 | func (o *BinanceCycle) BinanceCycleInit() *BinanceCycle { 16 | o.BinanceCycles = []string{"1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "1M"} 17 | o.BinanceCycleMap = map[string]int{ 18 | "1m": int(common.KLine1Min), 19 | "5m": int(common.KLine5Min), 20 | "15m": int(common.KLine15Min), 21 | "30m": int(common.KLine30Min), 22 | "1h": int(common.KLine1hour), 23 | "1D": int(common.KLineDay), 24 | "7D": int(common.KLineWeek), 25 | "1M": int(common.KLineMonth), 26 | } 27 | return o 28 | } 29 | 30 | func (o *BinanceCycle) BinanceCycleTransfer(cycle string) int { 31 | isExist, _ := common.Contain(cycle, o.BinanceCycles) 32 | if isExist { 33 | return o.BinanceCycleMap[cycle] 34 | } 35 | return 0 36 | } 37 | -------------------------------------------------------------------------------- /binance_websocket/binance_spot_websocket.go: -------------------------------------------------------------------------------- 1 | package binance_websocket 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gorilla/websocket" 6 | "os" 7 | ) 8 | 9 | type Binance interface { 10 | // connect 11 | WsConnect() 12 | // read message 13 | ReadMessage() 14 | // tick 15 | BATickWebsocket() 16 | // depth 17 | BADepthWebsocket() 18 | // trade 19 | BATradeWebsocket() 20 | // kline 21 | BAKlineUsdtWebsocket() 22 | BAKlineBtcWebsocket() 23 | BAKlineEthWebsocket() 24 | } 25 | 26 | type binance struct { 27 | Url string 28 | Ws *websocket.Conn 29 | } 30 | 31 | // 初始化 32 | func BinanceWebsocketInit() *binance { 33 | ba := new(binance) 34 | ba.Url = os.Getenv("BINANCE_URL") 35 | return ba 36 | } 37 | 38 | // websocket connect 39 | func (o *binance) WsConnect() { 40 | dialer := new(websocket.Dialer) 41 | ws, _, err := dialer.Dial(o.Url, nil) 42 | if err != nil { 43 | fmt.Println("websocket connect error:", err) 44 | return 45 | } 46 | o.Ws = ws 47 | } 48 | 49 | // read message 50 | func (o *binance) ReadMessage() { 51 | for true { 52 | msgType, msg, err := o.Ws.ReadMessage() 53 | if err != nil { 54 | fmt.Println("read message error: ", err) 55 | break 56 | } 57 | if msgType == websocket.TextMessage { 58 | data := string(msg) 59 | fmt.Println("message is:", msgType, data) 60 | } else if msgType == websocket.CloseMessage { 61 | // 重新连接 62 | err := o.Ws.Close() 63 | if err != nil { 64 | fmt.Println("error:", err) 65 | } 66 | break 67 | } 68 | } 69 | } 70 | 71 | // tick 72 | func (o *binance) BATickWebsocket() { 73 | baSymbols := NewBinanceSymbol() 74 | for _, symbol := range baSymbols.BinanceSymbols { 75 | o.Url += symbol + "@miniTicker/" 76 | } 77 | } 78 | 79 | // trade 80 | func (o *binance) BATradeWebsocket() { 81 | baSymbols := NewBinanceSymbol() 82 | for _, symbol := range baSymbols.BinanceSymbols { 83 | o.Url += symbol + "@trade/" 84 | } 85 | } 86 | 87 | // depth 88 | func (o *binance) BADepthWebsocket() { 89 | baSymbols := NewBinanceSymbol() 90 | for _, symbol := range baSymbols.BinanceSymbols { 91 | o.Url += symbol + "@depth10/" 92 | } 93 | } 94 | 95 | /*****************************************/ 96 | /**** 参数长度限制,需将三个币种分开 ****/ 97 | /*****************************************/ 98 | // usdt kline 99 | func (o *binance) BAUsdtKlineWebsocket() { 100 | baSymbols := NewBinanceSymbol() 101 | baCycles := NewBinanceCycle() 102 | for _, symbol := range baSymbols.BinanceUsdtSymbol { 103 | for _, cycle := range baCycles.BinanceCycles { 104 | o.Url += symbol + "@kline_" + cycle + "/" 105 | } 106 | } 107 | } 108 | 109 | // btc kline 110 | func (o *binance) BABtcKlineWebsocket() { 111 | baSymbols := NewBinanceSymbol() 112 | baCycles := NewBinanceCycle() 113 | for _, symbol := range baSymbols.BinanceBtcSymbol { 114 | for _, cycle := range baCycles.BinanceCycles { 115 | o.Url += symbol + "@kline_" + cycle + "/" 116 | } 117 | } 118 | } 119 | 120 | // eth kline 121 | func (o *binance) BAEthKlineWebsocket() { 122 | baSymbols := NewBinanceSymbol() 123 | baCycles := NewBinanceCycle() 124 | for _, symbol := range baSymbols.BinanceEthSymbol { 125 | for _, cycle := range baCycles.BinanceCycles { 126 | o.Url += symbol + "@kline_" + cycle + "/" 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /binance_websocket/binance_symbol.go: -------------------------------------------------------------------------------- 1 | package binance_websocket 2 | 3 | import ( 4 | . "exchange_websocket/common" 5 | "strings" 6 | ) 7 | 8 | // Binance symbols 9 | type BinanceSymbol struct { 10 | BinanceUsdtSymbol []string 11 | BinanceBtcSymbol []string 12 | BinanceEthSymbol []string 13 | BinanceSymbols []string 14 | } 15 | 16 | func NewBinanceSymbol() *BinanceSymbol { 17 | ba := new(BinanceSymbol) 18 | return ba.binanceSymbolInit() 19 | } 20 | 21 | func (o *BinanceSymbol) binanceSymbolInit() *BinanceSymbol { 22 | for _, symbol := range append(CommonUsdt, BinanceUsdt...) { 23 | //[symbol.replace('_', '').lower() for symbol in self.USDT] 24 | o.BinanceUsdtSymbol = append(o.BinanceUsdtSymbol, strings.ToLower(strings.Replace(symbol, "_", "", -1))) 25 | } 26 | 27 | for _, symbol := range append(CommonBtc, BinanceBtc...) { 28 | o.BinanceBtcSymbol = append(o.BinanceBtcSymbol, strings.ToLower(strings.Replace(symbol, "_", "", -1))) 29 | } 30 | 31 | for _, symbol := range append(CommonEth, BinanceEth...) { 32 | o.BinanceEthSymbol = append(o.BinanceEthSymbol, strings.ToLower(strings.Replace(symbol, "_", "", -1))) 33 | } 34 | 35 | o.BinanceSymbols = append(o.BinanceUsdtSymbol, append(o.BinanceBtcSymbol, o.BinanceEthSymbol...)...) 36 | return o 37 | } 38 | 39 | func (o *BinanceSymbol) BinanceSymbolTransfer(symbol string) string { 40 | isExist1, _ := Contain(symbol, o.BinanceUsdtSymbol) 41 | if isExist1 { 42 | return strings.ToUpper(strings.Replace(symbol, "usdt", "_usdt", -1)) 43 | } 44 | 45 | isExist2, _ := Contain(symbol, o.BinanceBtcSymbol) 46 | if isExist2 { 47 | return strings.ToUpper(strings.Replace(symbol, "btc", "_btc", -1)) 48 | } 49 | 50 | isExist3, _ := Contain(symbol, o.BinanceEthSymbol) 51 | if isExist3 { 52 | return strings.ToUpper(strings.Replace(symbol, "eth", "_eth", -1)) 53 | } 54 | 55 | return "" 56 | } 57 | -------------------------------------------------------------------------------- /bitfinex_websocket/bitfinex_spot_websocket.go: -------------------------------------------------------------------------------- 1 | package bitfinex_websocket 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gorilla/websocket" 6 | "os" 7 | "time" 8 | ) 9 | 10 | type Bitfinex interface { 11 | //ping 12 | Ping() 13 | // connect 14 | WsConnect() 15 | // subscribe 16 | Subscribe() 17 | // read message 18 | ReadMessage() 19 | // tick 20 | BFTickWebsocket() 21 | // depth 22 | BFDepthWebsocket() 23 | // trade 24 | BFTradeWebsocket() 25 | // kline 26 | BFKlineWebsocket() 27 | } 28 | 29 | type bitfinex struct { 30 | Url string 31 | Ws *websocket.Conn 32 | Channels []*BitfinexWebsocketRequest 33 | DepthChannels []*BitfinexDepthWebsocketRequest 34 | KlineChannels []*BitfinexKlineWebsocketRequest 35 | } 36 | 37 | // 初始化 38 | func BitfinexWebsocketInit() *bitfinex { 39 | bf := new(bitfinex) 40 | bf.Url = os.Getenv("BITFINEX_URL") 41 | return bf 42 | } 43 | 44 | // websocket connect 45 | func (o *bitfinex) WsConnect() { 46 | dialer := new(websocket.Dialer) 47 | ws, _, err := dialer.Dial(o.Url, nil) 48 | if err != nil { 49 | fmt.Println("websocket connect error:", err) 50 | return 51 | } 52 | o.Ws = ws 53 | } 54 | 55 | // ping 保持连接 56 | func (o *bitfinex) Ping() { 57 | pingMsg := ping{Event: "ping"} 58 | done := make(chan struct{}) 59 | // 5s定时 60 | ticker := time.NewTicker(5 * time.Second) 61 | defer ticker.Stop() 62 | 63 | for { 64 | select { 65 | case <-done: 66 | return 67 | case <-ticker.C: // ping消息 68 | err := o.Ws.WriteJSON(pingMsg) 69 | if err != nil { 70 | fmt.Println("ping error: ", err) 71 | return 72 | } 73 | 74 | } 75 | } 76 | } 77 | 78 | // subscribe 79 | func (o *bitfinex) Subscribe(channel string) { 80 | switch channel { 81 | case "depth": 82 | if o.DepthChannels == nil { 83 | fmt.Println("no channels") 84 | return 85 | } 86 | for _, channel := range o.DepthChannels { 87 | err := o.Ws.WriteJSON(*channel) 88 | if err != nil { 89 | fmt.Println("subscribe error: ", err) 90 | return 91 | } 92 | } 93 | case "kline": 94 | if o.KlineChannels == nil { 95 | fmt.Println("no channels") 96 | return 97 | } 98 | for _, channel := range o.KlineChannels { 99 | err := o.Ws.WriteJSON(*channel) 100 | if err != nil { 101 | fmt.Println("subscribe error: ", err) 102 | return 103 | } 104 | } 105 | default: 106 | if o.Channels == nil { 107 | fmt.Println("no channels") 108 | return 109 | } 110 | for _, channel := range o.Channels { 111 | err := o.Ws.WriteJSON(*channel) 112 | if err != nil { 113 | fmt.Println("subscribe error: ", err) 114 | return 115 | } 116 | } 117 | } 118 | 119 | } 120 | 121 | // read message 122 | func (o *bitfinex) ReadMessage() { 123 | for true { 124 | msgType, msg, err := o.Ws.ReadMessage() 125 | if err != nil { 126 | fmt.Println("read message error: ", err) 127 | break 128 | } 129 | if msgType == websocket.TextMessage { 130 | data := string(msg) 131 | fmt.Println("message is:", msgType, data) 132 | } else if msgType == websocket.CloseMessage { 133 | // 重新连接 134 | err := o.Ws.Close() 135 | if err != nil { 136 | fmt.Println("error:", err) 137 | } 138 | break 139 | } 140 | } 141 | } 142 | 143 | // tick 144 | func (o *bitfinex) BFTickWebsocket() { 145 | bfSymbols := NewBitfinexSymbol() 146 | for _, symbol := range bfSymbols.BitfinexSymbols { 147 | channel := BitfinexWebsocketRequest{ 148 | "subscribe", 149 | "ticker", 150 | symbol, 151 | } 152 | o.Channels = append(o.Channels, &channel) 153 | } 154 | } 155 | 156 | // trade 157 | func (o *bitfinex) BFTradeWebsocket() { 158 | bfSymbols := NewBitfinexSymbol() 159 | for _, symbol := range bfSymbols.BitfinexSymbols { 160 | channel := BitfinexWebsocketRequest{ 161 | "subscribe", 162 | "trades", 163 | symbol, 164 | } 165 | o.Channels = append(o.Channels, &channel) 166 | } 167 | } 168 | 169 | // depth 170 | func (o *bitfinex) BFDepthWebsocket() { 171 | bfSymbols := NewBitfinexSymbol() 172 | for _, symbol := range bfSymbols.BitfinexSymbols { 173 | channel := BitfinexDepthWebsocketRequest{ 174 | "subscribe", 175 | "book", 176 | "P0", 177 | symbol, 178 | "25", 179 | } 180 | o.DepthChannels = append(o.DepthChannels, &channel) 181 | } 182 | } 183 | 184 | // kline 185 | func (o *bitfinex) BFKlineWebsocket() { 186 | bfSymbols := NewBitfinexSymbol() 187 | bfCylces := NewBitfinexCycle() 188 | for _, cycle := range bfCylces.BitfinexCycles { 189 | for _, symbol := range bfSymbols.BitfinexSymbols { 190 | channel := BitfinexKlineWebsocketRequest{ 191 | "subscribe", 192 | "candles", 193 | "trade:" + cycle + ":t" + symbol, 194 | } 195 | o.KlineChannels = append(o.KlineChannels, &channel) 196 | } 197 | } 198 | 199 | } 200 | 201 | type ping struct { 202 | Event string `json:"event"` 203 | } 204 | 205 | type BitfinexWebsocketRequest struct { 206 | Event string `json:"event"` 207 | Channel string `json:"channel"` 208 | Pair string `json:"pair"` 209 | } 210 | 211 | type BitfinexDepthWebsocketRequest struct { 212 | Event string `json:"event"` 213 | Channel string `json:"channel"` 214 | Prec string `json:"prec"` 215 | Symbol string `json:"symbol"` 216 | Len string `json:"len"` 217 | } 218 | 219 | type BitfinexKlineWebsocketRequest struct { 220 | Event string `json:"event"` 221 | Channel string `json:"channel"` 222 | Key string `json:"key"` 223 | } 224 | -------------------------------------------------------------------------------- /bitfinex_websocket/bitfinex_symbol.go: -------------------------------------------------------------------------------- 1 | package bitfinex_websocket 2 | 3 | import ( 4 | . "exchange_websocket/common" 5 | "strings" 6 | ) 7 | 8 | // bitfinex symbol 9 | type BitfinexSymbol struct { 10 | BitfinexUsdtSymbol []string 11 | BitfinexBtcSymbol []string 12 | BitfinexEthSymbol []string 13 | BitfinexSymbols []string 14 | } 15 | 16 | func NewBitfinexSymbol() *BitfinexSymbol { 17 | bf := new(BitfinexSymbol) 18 | return bf.bitfinexSymbolInit() 19 | } 20 | 21 | func (o *BitfinexSymbol) bitfinexSymbolInit() *BitfinexSymbol { 22 | for _, symbol := range append(CommonUsdt, BitfinexUsdt...) { 23 | // return [symbol.replace('_', '').rsplit('T', 1)[0] for symbol in self.USDT] 24 | o.BitfinexUsdtSymbol = append(o.BitfinexUsdtSymbol, strings.Replace(symbol[0:len(symbol)-1], "_", "", -1)) 25 | } 26 | 27 | for _, symbol := range append(CommonBtc, BitfinexBtc...) { 28 | o.BitfinexBtcSymbol = append(o.BitfinexBtcSymbol, strings.Replace(symbol, "_", "", -1)) 29 | } 30 | 31 | for _, symbol := range append(CommonEth, BitfinexEth...) { 32 | o.BitfinexEthSymbol = append(o.BitfinexEthSymbol, strings.Replace(symbol, "_", "", -1)) 33 | } 34 | 35 | o.BitfinexSymbols = append(o.BitfinexUsdtSymbol, append(o.BitfinexBtcSymbol, o.BitfinexEthSymbol...)...) 36 | return o 37 | } 38 | 39 | func (o *BitfinexSymbol) BitfinexSymbolTransfer(symbol string) string { 40 | 41 | isExist1, _ := Contain(symbol, o.BitfinexUsdtSymbol) 42 | isExist2, _ := Contain(symbol, o.BitfinexBtcSymbol) 43 | isExist3, _ := Contain(symbol, o.BitfinexEthSymbol) 44 | if isExist1 { 45 | symbol = strings.Replace(symbol, "USD", "_USD", -1) 46 | } else if isExist2 { 47 | symbol = strings.Replace(symbol, "BTC", "_BTC", -1) 48 | } else if isExist3 { 49 | symbol = strings.Replace(symbol, "ETH", "_ETH", -1) 50 | } else { 51 | return "" 52 | } 53 | 54 | if strings.HasPrefix(symbol, "IOS") { 55 | return strings.Replace(symbol, "IOS", "IOST", -1) 56 | } 57 | if strings.HasPrefix(symbol, "DSH") { 58 | return strings.Replace(symbol, "DSH", "DASH", -1) 59 | } 60 | if strings.HasPrefix(symbol, "IOT") { 61 | return strings.Replace(symbol, "IOT", "IOTA", -1) 62 | } 63 | 64 | return symbol 65 | 66 | } 67 | -------------------------------------------------------------------------------- /bitfinex_websocket/bitmex_cycle.go: -------------------------------------------------------------------------------- 1 | package bitfinex_websocket 2 | 3 | import "exchange_websocket/common" 4 | 5 | type BitfinexCycle struct { 6 | BitfinexCycles []string 7 | BitfinexCycleMap map[string]int 8 | } 9 | 10 | func NewBitfinexCycle() *BitfinexCycle { 11 | bfCycle := new(BitfinexCycle) 12 | return bfCycle.BitfinexCycleInit() 13 | } 14 | 15 | func (o *BitfinexCycle) BitfinexCycleInit() *BitfinexCycle { 16 | o.BitfinexCycles = []string{"1m", "5m", "15m", "30m", "1h", "1D", "7D", "1M"} 17 | o.BitfinexCycleMap = map[string]int{ 18 | "1m": int(common.KLine1Min), 19 | "5m": int(common.KLine5Min), 20 | "15m": int(common.KLine15Min), 21 | "30m": int(common.KLine30Min), 22 | "1h": int(common.KLine1hour), 23 | "1D": int(common.KLineDay), 24 | "7D": int(common.KLineWeek), 25 | "1M": int(common.KLineMonth), 26 | } 27 | return o 28 | } 29 | 30 | func (o *BitfinexCycle) BitfinexCycleTransfer(cycle string) int { 31 | isExist, _ := common.Contain(cycle, o.BitfinexCycles) 32 | if isExist { 33 | return o.BitfinexCycleMap[cycle] 34 | } 35 | return 0 36 | } 37 | -------------------------------------------------------------------------------- /bitmex_websocket/bitmex_cycle.go: -------------------------------------------------------------------------------- 1 | package bitmex_websocket 2 | 3 | import "exchange_websocket/common" 4 | 5 | type BitmexCycle struct { 6 | BitmexCycles []string 7 | BitmexCycleMap map[string]int 8 | } 9 | 10 | func NewBitmexCycle() *BitmexCycle { 11 | bmCycle := new(BitmexCycle) 12 | return bmCycle.BitmexCycleInit() 13 | } 14 | 15 | func (o *BitmexCycle) BitmexCycleInit() *BitmexCycle { 16 | o.BitmexCycles = []string{"1m", "5m", "1h", "1d"} 17 | o.BitmexCycleMap = map[string]int{ 18 | "1m": int(common.KLine1Min), 19 | "5m": int(common.KLine5Min), 20 | "1h": int(common.KLine1hour), 21 | "1d": int(common.KLineDay), 22 | } 23 | return o 24 | } 25 | 26 | func (o *BitmexCycle) BitmexCycleTransfer(cycle string) int { 27 | isExist, _ := common.Contain(cycle, o.BitmexCycles) 28 | if isExist { 29 | return o.BitmexCycleMap[cycle] 30 | } 31 | return 0 32 | } 33 | -------------------------------------------------------------------------------- /bitmex_websocket/bitmex_sustainable_websocket.go: -------------------------------------------------------------------------------- 1 | package bitmex_websocket 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gorilla/websocket" 6 | "os" 7 | "time" 8 | ) 9 | 10 | type Bitmex interface { 11 | // ping 12 | Ping() 13 | // ws connect 14 | WsConnect() 15 | // subscribe 16 | Subscribe() 17 | // reade message 18 | ReadMessage() 19 | // trade 20 | BmTradeWebsocket() 21 | // depth 22 | BmDepthWebsocket() 23 | // kline 24 | BmKlineWebsocket() 25 | } 26 | 27 | type bitmex struct { 28 | Url string 29 | Ws *websocket.Conn 30 | Channels []*BmWebsocketRequest 31 | } 32 | 33 | // 初始化 34 | func BmWebsocketInit() *bitmex { 35 | bm := new(bitmex) 36 | bm.Url = os.Getenv("BITMEX_URL") 37 | bm.WsConnect() 38 | return bm 39 | } 40 | 41 | // 连接websocket 42 | func (o *bitmex) WsConnect() { 43 | dailer := new(websocket.Dialer) 44 | ws, _, err := dailer.Dial(o.Url, nil) 45 | if err != nil { 46 | fmt.Println("websocket connect error:", err) 47 | } 48 | o.Ws = ws 49 | } 50 | 51 | // ping 52 | func (o *bitmex) Ping() { 53 | done := make(chan struct{}) 54 | // 5s定时 55 | ticker := time.NewTicker(5 * time.Second) 56 | defer ticker.Stop() 57 | 58 | for { 59 | select { 60 | case <-done: 61 | return 62 | case <-ticker.C: // ping消息 63 | err := o.Ws.WriteMessage(websocket.TextMessage, []byte("ping")) 64 | if err != nil { 65 | fmt.Println("ping error: ", err) 66 | return 67 | } 68 | 69 | } 70 | } 71 | 72 | } 73 | 74 | // 订阅subscribe 75 | func (o *bitmex) Subscribe() { 76 | if o.Channels == nil { 77 | fmt.Println("no channels") 78 | return 79 | } 80 | for _, channel := range o.Channels { 81 | err := o.Ws.WriteJSON(*channel) 82 | if err != nil { 83 | fmt.Println("subscribe error: ", err) 84 | } 85 | } 86 | } 87 | 88 | // 读取消息 89 | func (o *bitmex) ReadMessage() { 90 | for true { 91 | msgType, msg, err := o.Ws.ReadMessage() 92 | if err != nil { 93 | fmt.Println("read message error: ", err) 94 | break 95 | } 96 | if msgType == websocket.TextMessage { 97 | data := string(msg) 98 | fmt.Println("message: ", msgType, data) 99 | } else if msgType == websocket.CloseMessage { 100 | // 关闭当前websocket 101 | err := o.Ws.Close() 102 | if err != nil { 103 | fmt.Println("close websocket error: ", err) 104 | } 105 | break 106 | } 107 | 108 | } 109 | 110 | } 111 | 112 | // depth funding Instrument消息 113 | func (o *bitmex) BmDepthFundingInstrumentWebsocket() { 114 | bmSymbols := NewBitmexSymbol() 115 | var depthArgs []string 116 | var fundingArgs []string 117 | var instrumentArgs []string 118 | for _, symbol := range bmSymbols.BitmexSymbols { 119 | depthCh := "orderBook10:" + symbol 120 | fundingCh := "funding:" + symbol 121 | instrumentCh := "instrument:" + symbol 122 | depthArgs = append(depthArgs, depthCh) 123 | fundingArgs = append(fundingArgs, fundingCh) 124 | instrumentArgs = append(instrumentArgs, instrumentCh) 125 | } 126 | args := append(append(depthArgs, fundingArgs...), instrumentArgs...) 127 | channel := BmWebsocketRequest{"subscribe", args} 128 | o.Channels = append(o.Channels, &channel) 129 | } 130 | 131 | // trade quote kline消息 132 | func (o *bitmex) BmTradeQuoteKlineWebsocket() { 133 | bmSymbols := NewBitmexSymbol() 134 | bmCycles := NewBitmexCycle() 135 | var tradeArgs []string 136 | var quoteArgs []string 137 | var klineArgs []string 138 | for _, symbol := range bmSymbols.BitmexSymbols { 139 | tradeCh := "trade:" + symbol 140 | quoteCh := "quote:" + symbol 141 | tradeArgs = append(tradeArgs, tradeCh) 142 | quoteArgs = append(quoteArgs, quoteCh) 143 | for _, cycle := range bmCycles.BitmexCycles { 144 | klineCh := "tradeBin" + cycle + ":" + symbol 145 | klineArgs = append(klineArgs, klineCh) 146 | } 147 | } 148 | args := append(append(tradeArgs, quoteArgs...), klineArgs...) 149 | channel := BmWebsocketRequest{"subscribe", args} 150 | o.Channels = append(o.Channels, &channel) 151 | } 152 | 153 | func (o *bitmex) BmFuturesTradeDepthWebsocket() { 154 | bmFuturesSymbols := NewBitmexFuturesSymbol() 155 | var tradeArgs []string 156 | var depthArgs []string 157 | for _, symbol := range bmFuturesSymbols.BitmexSymbols { 158 | tradeCh := "trade:" + symbol 159 | depthCh := "orderBook10:" + symbol 160 | tradeArgs = append(tradeArgs, tradeCh) 161 | depthArgs = append(depthArgs, depthCh) 162 | } 163 | args := append(tradeArgs, depthArgs...) 164 | channel := BmWebsocketRequest{"subscribe", args} 165 | o.Channels = append(o.Channels, &channel) 166 | } 167 | 168 | func (o *bitmex) BmFuturesInstrumentQuetoWebsocket() { 169 | bmFuturesSymbols := NewBitmexFuturesSymbol() 170 | var instrumentArgs []string 171 | var quetoArgs []string 172 | for _, symbol := range bmFuturesSymbols.BitmexSymbols { 173 | instrumentCh := "instrument:" + symbol 174 | quetoCh := "queto:" + symbol 175 | instrumentArgs = append(instrumentArgs, instrumentCh) 176 | quetoArgs = append(quetoArgs, quetoCh) 177 | } 178 | args := append(instrumentArgs, quetoArgs...) 179 | channel := BmWebsocketRequest{"subscribe", args} 180 | o.Channels = append(o.Channels, &channel) 181 | } 182 | 183 | func (o *bitmex) BmFuturesKlineWebsocket() { 184 | bmFuturesSymbols := NewBitmexFuturesSymbol() 185 | bmCycles := NewBitmexCycle() 186 | var klineArgs []string 187 | for _, symbol := range bmFuturesSymbols.BitmexSymbols { 188 | for _, cycle := range bmCycles.BitmexCycles[0:2] { 189 | klineCh := "tradeBin" + cycle + ":" + symbol 190 | klineArgs = append(klineArgs, klineCh) 191 | } 192 | } 193 | channel := BmWebsocketRequest{"subscribe", klineArgs} 194 | o.Channels = append(o.Channels, &channel) 195 | } 196 | 197 | type BmWebsocketRequest struct { 198 | Op string `json:"op"` 199 | Args []string `json:"args"` 200 | } 201 | -------------------------------------------------------------------------------- /bitmex_websocket/bitmex_symbol.go: -------------------------------------------------------------------------------- 1 | package bitmex_websocket 2 | 3 | import . "exchange_websocket/common" 4 | 5 | // bitmex symbols 6 | type BitmexSymbol struct { 7 | BitmexSymbols []string 8 | } 9 | 10 | func NewBitmexSymbol() *BitmexSymbol { 11 | bm := new(BitmexSymbol) 12 | return bm.bitmexSymbolInit() 13 | } 14 | 15 | func (o *BitmexSymbol) bitmexSymbolInit() *BitmexSymbol { 16 | 17 | o.BitmexSymbols = BitmexYx 18 | return o 19 | } 20 | 21 | func NewBitmexFuturesSymbol() *BitmexSymbol { 22 | bm := new(BitmexSymbol) 23 | return bm.bitmexFuturesSymbolInit() 24 | } 25 | 26 | func (o *BitmexSymbol) bitmexFuturesSymbolInit() *BitmexSymbol { 27 | o.BitmexSymbols = BitmexDq 28 | return o 29 | } 30 | -------------------------------------------------------------------------------- /common/constant.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | /************************* BTC交易对 **************************/ 10 | CommonUsdt = []string{"BTC_USDT", "ETH_USDT", "EOS_USDT", "XRP_USDT", "ETC_USDT", "LTC_USDT", "NEO_USDT", 11 | "TRX_USDT"} 12 | 13 | OkexUsdt = []string{"XLM_USDT", "XMR_USDT", "OKB_USDT", "DASH_USDT", "ZEC_USDT", "IOTA_USDT", "IOST_USDT", 14 | "OMG_USDT", "ONT_USDT", "ADA_USDT", "BCHABC_USDT", "BCHSV_USDT"} 15 | 16 | HuobiUsdt = []string{"HT_USDT", "DASH_USDT", "ZEC_USDT", "IOTA_USDT", "IOST_USDT", "OMG_USDT", "ONT_USDT", 17 | "ADA_USDT", "BCH_USDT"} 18 | 19 | BinanceUsdt = []string{"XLM_USDT", "BNB_USDT", "ONT_USDT", "ADA_USDT", "IOTA_USDT", "BCHABC_USDT", "BCHSV_USDT"} 20 | 21 | BitfinexUsdt = []string{"DSH_USDT", "XLM_USDT", "ZEC_USDT", "XMR_USDT", "IOS_USDT", "OMG_USDT", "IOT_USDT"} 22 | 23 | /************************* BTC交易对 **************************/ 24 | CommonBtc = []string{"ETH_BTC", "EOS_BTC", "XRP_BTC", "ETC_BTC", "LTC_BTC", "NEO_BTC", "TRX_BTC", 25 | "XLM_BTC", "ZEC_BTC", "XMR_BTC", "OMG_BTC"} 26 | 27 | OkexBtc = []string{"OKB_BTC", "ONT_BTC", "ADA_BTC", "IOTA_BTC", "IOST_BTC", "DASH_BTC", "BCHABC_BTC", "BCHSV_BTC"} 28 | 29 | HuobiBtc = []string{"HT_BTC", "ONT_BTC", "ADA_BTC", "IOTA_BTC", "IOST_BTC", "DASH_BTC", "BCH_BTC"} 30 | 31 | BinanceBtc = []string{"BNB_BTC", "ONT_BTC", "ADA_BTC", "IOTA_BTC", "IOST_BTC", "DASH_BTC", "BCHABC_BTC", 32 | "BCHSV_BTC"} 33 | 34 | BitfinexBtc = []string{"IOT_BTC", "IOS_BTC", "DSH_BTC"} 35 | 36 | /************************* ETH交易对 **************************/ 37 | CommonEth = []string{"EOS_ETH", "TRX_ETH", "XLM_ETH", "OMG_ETH"} 38 | 39 | OkexEth = []string{"ETC_ETH", "LTC_ETH", "NEO_ETH", "XRP_ETH", "DASH_ETH", "ZEC_ETH", "OKB_ETH", "XMR_ETH", 40 | "ONT_ETH", "ADA_ETH", "IOTA_ETH", "IOST_ETH"} 41 | 42 | HuobiEth = []string{"HT_ETH", "XMR_ETH", "ONT_ETH", "ADA_ETH", "IOTA_ETH", "IOST_ETH"} 43 | 44 | BinanceEth = []string{"XRP_ETH", "ETC_ETH", "LTC_ETH", "NEO_ETH", "DASH_ETH", "ZEC_ETH", "BNB_ETH", 45 | "XMR_ETH", "ONT_ETH", "ADA_ETH", "IOTA_ETH", "IOST_ETH"} 46 | 47 | BitfinexEth = []string{"NEO_ETH", "IOT_ETH", "IOS_ETH"} 48 | 49 | /************************* bitmex 永续合约 *********************/ 50 | BitmexYx = []string{"XBTUSD"} 51 | 52 | BitmexDq = strings.Split(os.Getenv("BITMEX_DQ"), ",") 53 | ) 54 | 55 | /************ kline cycle *****************/ 56 | type Cycle int 57 | 58 | var ( 59 | KLine1Min = Cycle(1) 60 | KLine3Min = Cycle(2) 61 | KLine5Min = Cycle(3) 62 | KLine15Min = Cycle(4) 63 | KLine30Min = Cycle(5) 64 | KLine1hour = Cycle(6) 65 | KLine4hour = Cycle(7) 66 | KLineDay = Cycle(8) 67 | KLineWeek = Cycle(10) 68 | KLineMonth = Cycle(11) 69 | ) 70 | -------------------------------------------------------------------------------- /common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | ) 7 | 8 | // 判断obj是否在target中,target支持的类型arrary,slice,map 9 | func Contain(obj interface{}, target interface{}) (bool, error) { 10 | targetValue := reflect.ValueOf(target) 11 | switch reflect.TypeOf(target).Kind() { 12 | case reflect.Slice, reflect.Array: 13 | for i := 0; i < targetValue.Len(); i++ { 14 | if targetValue.Index(i).Interface() == obj { 15 | return true, nil 16 | } 17 | } 18 | case reflect.Map: 19 | if targetValue.MapIndex(reflect.ValueOf(obj)).IsValid() { 20 | return true, nil 21 | } 22 | } 23 | 24 | return false, errors.New("not in array") 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module exchange_websocket 2 | 3 | require ( 4 | github.com/go-log/log v0.1.0 5 | github.com/gorilla/websocket v1.4.0 6 | ) 7 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U= 2 | github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= 3 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 4 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 5 | -------------------------------------------------------------------------------- /huobi_websocket/huobi_cycle.go: -------------------------------------------------------------------------------- 1 | package huobi_websocket 2 | 3 | import . "exchange_websocket/common" 4 | 5 | type HuobiCylce struct { 6 | HuobiCycles []string 7 | HuobiCycleMap map[string]int 8 | } 9 | 10 | func NewHuobiCylce() *HuobiCylce { 11 | hbCycle := new(HuobiCylce) 12 | return hbCycle.HuobiCylceInit() 13 | } 14 | 15 | func (o *HuobiCylce) HuobiCylceInit() *HuobiCylce { 16 | o.HuobiCycles = []string{"1min", "5min", "15min", "30min", "60min", "1day", "1week", "1mon"} 17 | o.HuobiCycleMap = map[string]int{ 18 | "1min": int(KLine1Min), 19 | "5min": int(KLine5Min), 20 | "15min": int(KLine15Min), 21 | "30min": int(KLine30Min), 22 | "60min": int(KLine1hour), 23 | "1day": int(KLineDay), 24 | "1week": int(KLineWeek), 25 | "1mon": int(KLineMonth), 26 | } 27 | return o 28 | } 29 | 30 | func (o *HuobiCylce) HuobiCylceTransfer(cycle string) int { 31 | isExist, _ := Contain(cycle, o.HuobiCycles) 32 | if isExist { 33 | return o.HuobiCycleMap[cycle] 34 | } 35 | return 0 36 | } 37 | -------------------------------------------------------------------------------- /huobi_websocket/huobi_spot_websocket.go: -------------------------------------------------------------------------------- 1 | package huobi_websocket 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-log/log" 6 | "github.com/gorilla/websocket" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | type Huobi interface { 12 | // ws ping pong 13 | Pong() 14 | // connect 15 | WsConnect() 16 | // Subscribe 17 | Subscribe() 18 | // ReadMessage 19 | ReadMessage() 20 | // tick 21 | HbTickWebsocket() 22 | // depth 23 | HbDepthWebsocket() 24 | // trade 25 | HbTradeWebsocket() 26 | // Kline 27 | HbKlineWebsocket() 28 | } 29 | 30 | type huobi struct { 31 | Url string 32 | Ws *websocket.Conn 33 | Channels []*HbWebsocketRequest 34 | } 35 | 36 | // 初始化 37 | func HbWebsocketInit() *huobi { 38 | huobi := new(huobi) 39 | huobi.Url = os.Getenv("HUOBI_URL") 40 | return huobi 41 | } 42 | 43 | // 连接websocket 44 | func (o *huobi) WsConnect() { 45 | dailer := new(websocket.Dialer) 46 | ws, _, err := dailer.Dial(o.Url, nil) 47 | if err != nil { 48 | log.Log("websocket connect error:", err) 49 | } 50 | o.Ws = ws 51 | 52 | } 53 | 54 | // pong 55 | func (o *huobi) Pong(pingData string) { 56 | ts := pingData[8:21] 57 | pong := pong{Pong: ts} 58 | err := o.Ws.WriteJSON(pong) 59 | if err != nil { 60 | log.Log("pong error: ", err) 61 | } 62 | } 63 | 64 | type pong struct { 65 | Pong string `json:"pong"` 66 | } 67 | 68 | func (o huobi) Subscribe() { 69 | if o.Channels == nil { 70 | log.Log("no channels") 71 | return 72 | } 73 | fmt.Println(o.Channels) 74 | 75 | for _, channel := range o.Channels { 76 | fmt.Println(*channel) 77 | err := o.Ws.WriteJSON(*channel) 78 | if err != nil { 79 | log.Log("channel subscribe error!") 80 | } 81 | } 82 | } 83 | 84 | func (o huobi) ReadMessage() { 85 | for true { 86 | msgType, msg, err := o.Ws.ReadMessage() 87 | if err != nil { 88 | log.Log("read message error:", err) 89 | break 90 | } 91 | if msgType == websocket.TextMessage { 92 | data := string(msg) 93 | fmt.Println("message is:", msgType, data) 94 | } else if msgType == websocket.BinaryMessage { 95 | message, err := GzipDecompress(msg) 96 | if err != nil { 97 | log.Log("message error:", err) 98 | } 99 | data := string(message) 100 | if strings.Contains(data, "ping") { 101 | o.Pong(data) 102 | } 103 | fmt.Println("message is:", msgType, data) 104 | } else if msgType == websocket.CloseMessage { 105 | //重连 106 | err := o.Ws.Close() 107 | if err != nil { 108 | log.Log("websocket close error:", err) 109 | } else { 110 | log.Log("websocket close") 111 | } 112 | break 113 | } 114 | 115 | } 116 | } 117 | 118 | // tick 119 | func (o *huobi) HbTickWebsocket() { 120 | hbSymbols := NewHuobiSymbol() 121 | for _, symbol := range hbSymbols.HuobiSymbols { 122 | channel := HbWebsocketRequest{ 123 | "market." + symbol + ".detail", 124 | "id12", 125 | } 126 | o.Channels = append(o.Channels, &channel) 127 | } 128 | } 129 | 130 | //depth 131 | func (o *huobi) HbDepthWebsocket() { 132 | hbSymbols := NewHuobiSymbol() 133 | var step string 134 | if os.Getenv("HUOBI_STEP") != "" { 135 | step = os.Getenv("HUOBI_STEP") 136 | } else { 137 | step = "step0" 138 | } 139 | for _, symbol := range hbSymbols.HuobiSymbols { 140 | channel := HbWebsocketRequest{ 141 | "market." + symbol + ".depth." + step, 142 | "id1", 143 | } 144 | o.Channels = append(o.Channels, &channel) 145 | } 146 | } 147 | 148 | //trade 149 | func (o *huobi) HbTradeWebsocket() { 150 | hbSymbols := NewHuobiSymbol() 151 | for _, symbol := range hbSymbols.HuobiSymbols { 152 | channel := HbWebsocketRequest{ 153 | "market." + symbol + ".trade.detail", 154 | "id1", 155 | } 156 | o.Channels = append(o.Channels, &channel) 157 | } 158 | } 159 | 160 | //kline 161 | func (o *huobi) HbKlineWebsocket() { 162 | hbSymbols := NewHuobiSymbol() 163 | hbCycles := NewHuobiCylce() 164 | for _, symbol := range hbSymbols.HuobiSymbols { 165 | for _, cycle := range hbCycles.HuobiCycles { 166 | channel := HbWebsocketRequest{ 167 | "market." + symbol + ".kline." + cycle, 168 | "id10", 169 | } 170 | o.Channels = append(o.Channels, &channel) 171 | } 172 | } 173 | } 174 | 175 | /********** huobi websocket request *************/ 176 | type HbWebsocketRequest struct { 177 | Sub string `json:"sub"` 178 | Id string `json:"id"` 179 | } 180 | 181 | /*************** huobi tick *********************/ 182 | type tick struct { 183 | Amount float32 `json:"amount"` 184 | Open float32 `json:"open"` 185 | Close float32 `json:"close"` 186 | High float32 `json:"high"` 187 | Ts int64 `json:"ts"` 188 | Id int64 `json:"id"` 189 | Count int32 `json:"count"` 190 | Low float32 `json:"low"` 191 | Vol float32 `json:"volume"` 192 | } 193 | 194 | type HbTickData struct { 195 | Ch string `json:"ch"` 196 | Ts int64 `json:"ts"` 197 | Data tick `json:"data"` 198 | } 199 | 200 | /**************** huobi depth ********************/ 201 | type depthTick struct { 202 | Bids [][2]float32 `json:"bids"` 203 | Asks [][2]float32 `json:"asks"` 204 | } 205 | 206 | type HbDepthData struct { 207 | Ch string `json:"ch"` 208 | Ts int64 `json:"ts"` 209 | Tick depthTick `json:"tick"` 210 | } 211 | 212 | /**************** huobi trade ********************/ 213 | type tradeData struct { 214 | Amount float32 `json:"amount"` 215 | Ts int64 `json:"ts"` 216 | Id int64 `json:"id"` 217 | Price float32 `json:"price"` 218 | Direction string `json:"direction"` 219 | } 220 | 221 | type tradeTick struct { 222 | Id int64 `json:"id"` 223 | Ts int64 `json:"ts"` 224 | Data []tradeData `json:"data"` 225 | } 226 | 227 | type HbTradeTick struct { 228 | Ch string `json:"ch"` 229 | Ts int64 `json:"ts"` 230 | Tick tradeTick `json:"tick"` 231 | } 232 | 233 | /*************** huobi kline ******************/ 234 | type klineTick struct { 235 | Amount float32 `json:"amount"` 236 | Open float32 `json:"open"` 237 | Close float32 `json:"close"` 238 | High float32 `json:"high"` 239 | Id int64 `json:"id"` 240 | Count int32 `json:"count"` 241 | Low float32 `json:"low"` 242 | Vol float32 `json:"volume"` 243 | } 244 | 245 | type HbKlineData struct { 246 | Ch string `json:"ch"` 247 | Ts int64 `json:"ts"` 248 | Tick klineTick `json:"tick"` 249 | } 250 | -------------------------------------------------------------------------------- /huobi_websocket/huobi_symbol.go: -------------------------------------------------------------------------------- 1 | package huobi_websocket 2 | 3 | import ( 4 | . "exchange_websocket/common" 5 | "strings" 6 | ) 7 | 8 | // huobi symbols 9 | type HuobiSymbol struct { 10 | HuobiUsdtSymbol []string 11 | HuobiBtcSymbol []string 12 | HuobiEthSymbol []string 13 | HuobiSymbols []string 14 | } 15 | 16 | func NewHuobiSymbol() *HuobiSymbol { 17 | hb := new(HuobiSymbol) 18 | return hb.huobiSymbolInit() 19 | } 20 | 21 | func (o *HuobiSymbol) huobiSymbolInit() *HuobiSymbol { 22 | for _, symbol := range append(CommonUsdt, HuobiUsdt...) { 23 | o.HuobiUsdtSymbol = append(o.HuobiUsdtSymbol, strings.ToLower(strings.Replace(symbol, "_", "", -1))) 24 | } 25 | 26 | for _, symbol := range append(CommonBtc, HuobiBtc...) { 27 | o.HuobiBtcSymbol = append(o.HuobiBtcSymbol, strings.ToLower(strings.Replace(symbol, "_", "", -1))) 28 | } 29 | 30 | for _, symbol := range append(CommonEth, HuobiEth...) { 31 | o.HuobiEthSymbol = append(o.HuobiEthSymbol, strings.ToLower(strings.Replace(symbol, "_", "", -1))) 32 | } 33 | 34 | o.HuobiSymbols = append(o.HuobiUsdtSymbol, append(o.HuobiBtcSymbol, o.HuobiBtcSymbol...)...) 35 | return o 36 | } 37 | 38 | func (o *HuobiSymbol) huobiSymbolTransfer(symbol string) string { 39 | isExist1, _ := Contain(symbol, o.HuobiUsdtSymbol) 40 | if isExist1 { 41 | return strings.ToUpper(strings.Replace(symbol, "usdt", "_usdt", -1)) 42 | } 43 | 44 | isExist2, _ := Contain(symbol, o.HuobiBtcSymbol) 45 | if isExist2 { 46 | return strings.ToUpper(strings.Replace(symbol, "btc", "_btc", -1)) 47 | } 48 | 49 | isExist3, _ := Contain(symbol, o.HuobiEthSymbol) 50 | if isExist3 { 51 | return strings.ToUpper(strings.Replace(symbol, "eth", "_eth", -1)) 52 | } 53 | 54 | return "" 55 | } 56 | -------------------------------------------------------------------------------- /huobi_websocket/huobi_util.go: -------------------------------------------------------------------------------- 1 | package huobi_websocket 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/binary" 7 | "fmt" 8 | "io/ioutil" 9 | ) 10 | 11 | func GzipDecompress(in []byte) ([]byte, error) { 12 | b := new(bytes.Buffer) 13 | binary.Write(b, binary.LittleEndian, in) 14 | r, err := gzip.NewReader(b) 15 | if err != nil { 16 | fmt.Println("[ParseGzip] NewReader error: ", err) 17 | return nil, err 18 | } else { 19 | defer r.Close() 20 | undatas, err := ioutil.ReadAll(r) 21 | if err != nil { 22 | fmt.Println("[ParseGzip] ioutil.ReadAll error: ", err) 23 | return nil, err 24 | } 25 | return undatas, nil 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /main/binance_btc_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/binance_websocket" 4 | 5 | func main() { 6 | ba := binance_websocket.BinanceWebsocketInit() 7 | ba.BABtcKlineWebsocket() 8 | for true { 9 | ba.WsConnect() 10 | ba.ReadMessage() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /main/binance_depth_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/binance_websocket" 4 | 5 | func main() { 6 | ba := binance_websocket.BinanceWebsocketInit() 7 | ba.BADepthWebsocket() 8 | for true { 9 | ba.WsConnect() 10 | ba.ReadMessage() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /main/binance_eth_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/binance_websocket" 4 | 5 | func main() { 6 | ba := binance_websocket.BinanceWebsocketInit() 7 | ba.BAEthKlineWebsocket() 8 | for true { 9 | ba.WsConnect() 10 | ba.ReadMessage() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /main/binance_tick_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "exchange_websocket/binance_websocket" 5 | ) 6 | 7 | func main() { 8 | ba := binance_websocket.BinanceWebsocketInit() 9 | ba.BATickWebsocket() 10 | for true { 11 | ba.WsConnect() 12 | ba.ReadMessage() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /main/binance_trade_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/binance_websocket" 4 | 5 | func main() { 6 | ba := binance_websocket.BinanceWebsocketInit() 7 | ba.BATradeWebsocket() 8 | for true { 9 | ba.WsConnect() 10 | ba.ReadMessage() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /main/binance_usdt_kline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/binance_websocket" 4 | 5 | func main() { 6 | ba := binance_websocket.BinanceWebsocketInit() 7 | ba.BAUsdtKlineWebsocket() 8 | for true { 9 | ba.WsConnect() 10 | ba.ReadMessage() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /main/bitfinex_depth_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitfinex_websocket" 4 | 5 | func main() { 6 | bf := bitfinex_websocket.BitfinexWebsocketInit() 7 | bf.BFDepthWebsocket() 8 | for true { 9 | bf.WsConnect() 10 | go func() { 11 | bf.Ping() 12 | }() 13 | bf.Subscribe("depth") 14 | bf.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitfinex_kline_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitfinex_websocket" 4 | 5 | func main() { 6 | bf := bitfinex_websocket.BitfinexWebsocketInit() 7 | bf.BFKlineWebsocket() 8 | for true { 9 | bf.WsConnect() 10 | go func() { 11 | bf.Ping() 12 | }() 13 | bf.Subscribe("kline") 14 | bf.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitfinex_tick_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitfinex_websocket" 4 | 5 | func main() { 6 | bf := bitfinex_websocket.BitfinexWebsocketInit() 7 | bf.BFTickWebsocket() 8 | for true { 9 | bf.WsConnect() 10 | go func() { 11 | bf.Ping() 12 | }() 13 | bf.Subscribe("tick") 14 | bf.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitfinex_trade_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitfinex_websocket" 4 | 5 | func main() { 6 | bf := bitfinex_websocket.BitfinexWebsocketInit() 7 | bf.BFTradeWebsocket() 8 | for true { 9 | bf.WsConnect() 10 | go func() { 11 | bf.Ping() 12 | }() 13 | bf.Subscribe("trade") 14 | bf.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitmex_depth_funding_instrument_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitmex_websocket" 4 | 5 | func main() { 6 | bm := bitmex_websocket.BmWebsocketInit() 7 | bm.BmDepthFundingInstrumentWebsocket() 8 | for true { 9 | bm.WsConnect() 10 | go func() { 11 | bm.Ping() 12 | }() 13 | bm.Subscribe() 14 | bm.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitmex_future_kline_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitmex_websocket" 4 | 5 | func main() { 6 | bm := bitmex_websocket.BmWebsocketInit() 7 | bm.BmFuturesKlineWebsocket() 8 | for true { 9 | bm.WsConnect() 10 | go func() { 11 | bm.Ping() 12 | }() 13 | bm.Subscribe() 14 | bm.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitmex_futures_instrument_queto_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitmex_websocket" 4 | 5 | func main() { 6 | bm := bitmex_websocket.BmWebsocketInit() 7 | bm.BmFuturesInstrumentQuetoWebsocket() 8 | for true { 9 | bm.WsConnect() 10 | go func() { 11 | bm.Ping() 12 | }() 13 | bm.Subscribe() 14 | bm.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitmex_futures_trade_depth_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitmex_websocket" 4 | 5 | func main() { 6 | bm := bitmex_websocket.BmWebsocketInit() 7 | bm.BmFuturesTradeDepthWebsocket() 8 | for true { 9 | bm.WsConnect() 10 | go func() { 11 | bm.Ping() 12 | }() 13 | bm.Subscribe() 14 | bm.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/bitmex_trade_queto_kline_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/bitmex_websocket" 4 | 5 | func main() { 6 | bm := bitmex_websocket.BmWebsocketInit() 7 | bm.BmTradeQuoteKlineWebsocket() 8 | for true { 9 | bm.WsConnect() 10 | go func() { 11 | bm.Ping() 12 | }() 13 | bm.Subscribe() 14 | bm.ReadMessage() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /main/huobi_depth_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/huobi_websocket" 4 | 5 | func main() { 6 | hb := huobi_websocket.HbWebsocketInit() 7 | hb.HbDepthWebsocket() 8 | for { 9 | hb.WsConnect() 10 | hb.Subscribe() 11 | hb.ReadMessage() 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /main/huobi_kline_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/huobi_websocket" 4 | 5 | func main() { 6 | hb := huobi_websocket.HbWebsocketInit() 7 | hb.HbKlineWebsocket() 8 | for { 9 | hb.WsConnect() 10 | hb.Subscribe() 11 | hb.ReadMessage() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /main/huobi_tick_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/huobi_websocket" 4 | 5 | func main() { 6 | hb := huobi_websocket.HbWebsocketInit() 7 | hb.HbTickWebsocket() 8 | for { 9 | hb.WsConnect() 10 | hb.Subscribe() 11 | hb.ReadMessage() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /main/huobi_trade_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "exchange_websocket/huobi_websocket" 4 | 5 | func main() { 6 | hb := huobi_websocket.HbWebsocketInit() 7 | hb.HbTradeWebsocket() 8 | for { 9 | hb.WsConnect() 10 | hb.Subscribe() 11 | hb.ReadMessage() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /main/okex_depth_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "exchange_websocket/okex_websocket" 4 | 5 | func main() { 6 | okex := OkexWebsocketInit() 7 | okex.OkexDepthWebsocket() 8 | for { 9 | okex.WsConnect() 10 | go func() { 11 | okex.Ping() 12 | }() 13 | okex.Subscribe() 14 | okex.ReadMessage() 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /main/okex_kline_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "exchange_websocket/okex_websocket" 4 | 5 | func main() { 6 | okex := OkexWebsocketInit() 7 | okex.OkexKlineWebsocket() 8 | for { 9 | okex.WsConnect() 10 | go func() { 11 | okex.Ping() 12 | }() 13 | okex.Subscribe() 14 | okex.ReadMessage() 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /main/okex_tick_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "exchange_websocket/okex_websocket" 5 | ) 6 | 7 | func main() { 8 | okex := OkexWebsocketInit() 9 | okex.OkexTickWebsocket() 10 | for { 11 | okex.WsConnect() 12 | go func() { 13 | okex.Ping() 14 | }() 15 | okex.Subscribe() 16 | okex.ReadMessage() 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /main/okex_trade_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import . "exchange_websocket/okex_websocket" 4 | 5 | func main() { 6 | okex := OkexWebsocketInit() 7 | okex.OkexTradeWebsocket() 8 | for { 9 | okex.WsConnect() 10 | go func() { 11 | okex.Ping() 12 | }() 13 | okex.Subscribe() 14 | okex.ReadMessage() 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /okex_websocket/okex_cycle.go: -------------------------------------------------------------------------------- 1 | package okex_websocket 2 | 3 | import . "exchange_websocket/common" 4 | 5 | type OkexCylce struct { 6 | OkexCycles []string 7 | OkexCycleMap map[string]int 8 | } 9 | 10 | func NewOkexCycle() *OkexCylce { 11 | okCycle := new(OkexCylce) 12 | return okCycle.OkexCycleInit() 13 | } 14 | 15 | func (o *OkexCylce) OkexCycleInit() *OkexCylce { 16 | o.OkexCycles = []string{"1min", "3min", "5min", "15min", "30min", "1hour", "4hour", "day", "week"} 17 | o.OkexCycleMap = map[string]int{ 18 | "1min": int(KLine1Min), 19 | "3min": int(KLine3Min), 20 | "5min": int(KLine5Min), 21 | "15min": int(KLine15Min), 22 | "30min": int(KLine30Min), 23 | "1hour": int(KLine1hour), 24 | "4hour": int(KLine4hour), 25 | "day": int(KLineDay), 26 | "week": int(KLineWeek), 27 | } 28 | return o 29 | } 30 | 31 | func (o *OkexCylce) OkexCycleTransfer(cycle string) int { 32 | isExist, _ := Contain(cycle, o.OkexCycles) 33 | if isExist { 34 | return o.OkexCycleMap[cycle] 35 | } 36 | return 0 37 | } 38 | -------------------------------------------------------------------------------- /okex_websocket/okex_spot_websocket.go: -------------------------------------------------------------------------------- 1 | package okex_websocket 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-log/log" 6 | "github.com/gorilla/websocket" 7 | "os" 8 | "time" 9 | ) 10 | 11 | type Okex interface { 12 | //ping 13 | Ping() 14 | // connect 15 | WsConnect() 16 | // subscribe 17 | Subscribe() 18 | // read message 19 | ReadMessage() 20 | // tick 21 | OkexTickWebsocket() 22 | // depth 23 | OkexDepthWebsocket() 24 | // trade 25 | OkexTradeWebsocket() 26 | // kline 27 | OkexKlineWebsocket() 28 | } 29 | 30 | type okex struct { 31 | Url string 32 | Ws *websocket.Conn 33 | Channels []*OkexWebsocketRequest 34 | } 35 | 36 | // 初始化 37 | func OkexWebsocketInit() *okex { 38 | okex := new(okex) 39 | okex.Url = os.Getenv("OKEX_URL") 40 | return okex 41 | } 42 | 43 | // 连接websocket 44 | func (o *okex) WsConnect() { 45 | 46 | dialer := new(websocket.Dialer) 47 | 48 | ws, _, err := dialer.Dial(o.Url, nil) 49 | if err != nil { 50 | fmt.Println("websocket connect error:", err) 51 | return 52 | } 53 | o.Ws = ws 54 | 55 | } 56 | 57 | //ping 保持连接 58 | func (o *okex) Ping() { 59 | pingMsg := ping{Event: "ping"} 60 | for true { 61 | err := o.Ws.WriteJSON(pingMsg) 62 | if err != nil { 63 | fmt.Println("ping error:", err) 64 | } 65 | time.Sleep(25 * time.Second) 66 | } 67 | 68 | } 69 | 70 | type ping struct { 71 | Event string `json:"event"` 72 | } 73 | 74 | // 订阅channel 75 | func (o *okex) Subscribe() { 76 | if o.Channels == nil { 77 | log.Log("no channels") 78 | return 79 | } 80 | fmt.Println(o.Channels) 81 | for _, channel := range o.Channels { 82 | fmt.Println(*channel) 83 | err := o.Ws.WriteJSON(*channel) 84 | if err != nil { 85 | fmt.Println("subscribe error: ", err) 86 | return 87 | } 88 | } 89 | 90 | } 91 | 92 | //读取消息 93 | func (o *okex) ReadMessage() { 94 | for true { 95 | msgType, msg, err := o.Ws.ReadMessage() 96 | if err != nil { 97 | fmt.Println("read message error: ", err) 98 | break 99 | } 100 | if msgType == websocket.TextMessage { 101 | data := string(msg) 102 | fmt.Println("message is:", msgType, data) 103 | } else if msgType == websocket.BinaryMessage { 104 | message, err := GzipDecode(msg) 105 | if err != nil { 106 | fmt.Println("error:", err) 107 | continue 108 | } 109 | data := string(message) 110 | fmt.Println("message is:", msgType, data) 111 | } else if msgType == websocket.CloseMessage { 112 | // 重新连接 113 | err := o.Ws.Close() 114 | if err != nil { 115 | fmt.Println("error:", err) 116 | } 117 | break 118 | } 119 | } 120 | } 121 | 122 | // tick 123 | func (o *okex) OkexTickWebsocket() { 124 | okSymbols := NewOkexSymbol() 125 | for _, symbol := range okSymbols.OkexSymbols { 126 | channel := OkexWebsocketRequest{"addChannel", "ok_sub_spot_" + symbol + "_ticker"} 127 | o.Channels = append(o.Channels, &channel) 128 | } 129 | } 130 | 131 | // depth 132 | func (o *okex) OkexDepthWebsocket() { 133 | okSymbols := NewOkexSymbol() 134 | for _, symbol := range okSymbols.OkexSymbols { 135 | channel := OkexWebsocketRequest{"addChannel", "ok_sub_spot_" + symbol + "_depth_10"} 136 | o.Channels = append(o.Channels, &channel) 137 | } 138 | } 139 | 140 | // trade 141 | func (o *okex) OkexTradeWebsocket() { 142 | okSymbols := NewOkexSymbol() 143 | for _, symbol := range okSymbols.OkexSymbols { 144 | channel := OkexWebsocketRequest{"addChannel", "ok_sub_spot_" + symbol + "_deals"} 145 | o.Channels = append(o.Channels, &channel) 146 | } 147 | } 148 | 149 | // kline 150 | func (o *okex) OkexKlineWebsocket() { 151 | okSymbols := NewOkexSymbol() 152 | okCycles := NewOkexCycle() 153 | for _, symbol := range okSymbols.OkexSymbols { 154 | for _, cycle := range okCycles.OkexCycles { 155 | channel := OkexWebsocketRequest{"addChannel", "ok_sub_spot_" + symbol + "_kline_" + cycle} 156 | o.Channels = append(o.Channels, &channel) 157 | } 158 | } 159 | } 160 | 161 | /************* okex websocket request **********/ 162 | type OkexWebsocketRequest struct { 163 | Event string `json:"event"` 164 | Channel string `json:"channel"` 165 | } 166 | 167 | /************ okex tick *****************/ 168 | type OkexTickWebsocketResponse struct { 169 | Channel string `json:"channel"` 170 | Data OkexTickData 171 | } 172 | 173 | type OkexTickData struct { 174 | High string `json:"high"` 175 | Vol string `json:"vol"` 176 | Last string `json:"last"` 177 | Low string `json:"low"` 178 | Buy string `json:"buy"` 179 | Change string `json:"change"` 180 | Sell string `json:"sell"` 181 | DayLow string `json:"dayLow"` 182 | DayHigh string `json:"dayHigh"` 183 | Timestamp int64 `json:"timestamp"` 184 | } 185 | 186 | /************** okex depth **************/ 187 | type OkexDepthWebsocketResponse struct { 188 | Channel string `json:"channel"` 189 | Data OkexDepthData 190 | } 191 | 192 | type BidsAsksData struct { 193 | Price string 194 | Volume string 195 | } 196 | 197 | type OkexDepthData struct { 198 | Asks []*BidsAsksData `json:"asks"` 199 | Bids []*BidsAsksData `json:"bids"` 200 | Timestamp int64 `json:"timestamp"` 201 | } 202 | 203 | /************* okex trade **************/ 204 | type OkexTradeWebsocketResponse struct { 205 | Channel string `json:"channel"` 206 | Data []*OkexTradeData 207 | } 208 | 209 | type OkexTradeData struct { 210 | Id string `json:"id"` 211 | Price string `json:"price"` 212 | Volume string `json:"volume"` 213 | Time string `json:"time"` 214 | Type string `json:"type"` 215 | } 216 | 217 | /*************** okex kline ******************/ 218 | type OkexKlineWebsocketResponse struct { 219 | Channel string `json:"channel"` 220 | Data [][]*OkexKlinehData 221 | } 222 | 223 | type OkexKlinehData struct { 224 | Time string `json:"time"` 225 | Open string `json:"open"` 226 | High string `json:"high"` 227 | Low string `json:"low"` 228 | Close string `json:"close"` 229 | Volume string `json:"volume"` 230 | } 231 | -------------------------------------------------------------------------------- /okex_websocket/okex_symbol.go: -------------------------------------------------------------------------------- 1 | package okex_websocket 2 | 3 | import ( 4 | . "exchange_websocket/common" 5 | "strings" 6 | ) 7 | 8 | // okex symbols 9 | type OkexSymbol struct { 10 | OkexUsdtSymbol []string 11 | OkexBtcSymbol []string 12 | OkexEthSymbol []string 13 | OkexSymbols []string 14 | } 15 | 16 | func NewOkexSymbol() *OkexSymbol { 17 | okex := new(OkexSymbol) 18 | return okex.okexSymbolInit() 19 | } 20 | 21 | func (o *OkexSymbol) okexSymbolInit() *OkexSymbol { 22 | for _, symbol := range append(CommonUsdt, OkexUsdt...) { 23 | o.OkexUsdtSymbol = append(o.OkexUsdtSymbol, strings.ToLower(symbol)) 24 | } 25 | 26 | for _, symbol := range append(CommonBtc, OkexBtc...) { 27 | o.OkexBtcSymbol = append(o.OkexBtcSymbol, strings.ToLower(symbol)) 28 | } 29 | 30 | for _, symbol := range append(CommonEth, OkexEth...) { 31 | o.OkexEthSymbol = append(o.OkexEthSymbol, strings.ToLower(symbol)) 32 | } 33 | 34 | o.OkexSymbols = append(o.OkexUsdtSymbol, append(o.OkexBtcSymbol, o.OkexEthSymbol...)...) 35 | return o 36 | } 37 | 38 | func (o *OkexSymbol) OkexSymbolTransfer(symbol string) string { 39 | isExist, _ := Contain(symbol, o.OkexSymbols) 40 | if isExist { 41 | return strings.ToUpper(symbol) 42 | } 43 | return "" 44 | } 45 | -------------------------------------------------------------------------------- /okex_websocket/okex_util.go: -------------------------------------------------------------------------------- 1 | package okex_websocket 2 | 3 | import ( 4 | "bytes" 5 | "compress/flate" 6 | "io/ioutil" 7 | ) 8 | 9 | func GzipDecode(in []byte) ([]byte, error) { 10 | reader := flate.NewReader(bytes.NewReader(in)) 11 | defer reader.Close() 12 | 13 | return ioutil.ReadAll(reader) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /test/symbol_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | . "exchange_websocket/okex_websocket" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestSymbol(t *testing.T) { 10 | okex := NewOkexSymbol() 11 | fmt.Println(okex) 12 | } 13 | --------------------------------------------------------------------------------