├── .vscode └── settings.json ├── .gitignore ├── utils ├── error_check.go ├── symbol.go ├── rabbitmq_util.go └── http_util.go ├── config ├── constants_test.go └── constants.go ├── fcoin ├── fcoin.go ├── start.go ├── data_parser.go └── ws_client.go ├── gateio ├── gateio.go ├── start.go ├── data_parser.go └── ws_client.go ├── hadax ├── hadax.go ├── strat.go ├── data_parser.go └── ws_client.go ├── hitbtc ├── hitbtc.go ├── start.go ├── data_parser.go └── ws_client.go ├── zb ├── zb.go ├── start.go ├── data_parser.go └── ws_client.go ├── bitfinex ├── bitfinex.go ├── start.go ├── data_parser.go └── ws_client.go ├── binance ├── binance.go ├── start.go ├── data_parser.go └── ws_client.go ├── lbank ├── lbank.go ├── start.go ├── data_parser.go └── ws_client.go ├── okex ├── okex.go ├── start.go ├── data_parser.go └── ws_client.go ├── huobi ├── huobipro.go ├── start.go ├── data_parser.go └── ws_client.go ├── model └── out_entity.go ├── README.md ├── ws_server.go └── main.go /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea -------------------------------------------------------------------------------- /utils/error_check.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func CheckErr(err error) { 4 | if err != nil { 5 | panic(err) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /config/constants_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "testing" 4 | 5 | func TestGetExchangeId(t *testing.T) { 6 | val := GetExchangeId("lbank") 7 | if val <= 0 { 8 | t.Error("获取失败") 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /fcoin/fcoin.go: -------------------------------------------------------------------------------- 1 | package fcoin 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | Name = "fcoin" 7 | 8 | FCoinErrorLimit = config.DEFAULT 9 | 10 | FCoinBufferSize = config.DEFAULT_BUFFER_SIZE 11 | 12 | FCoinWsUrl = "wss://api.fcoin.com/v2/ws" 13 | 14 | FCoinSymbol = "https://api.fcoin.com/v2/public/symbols" 15 | ) 16 | -------------------------------------------------------------------------------- /gateio/gateio.go: -------------------------------------------------------------------------------- 1 | package gateio 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | Name = "gateio" 7 | 8 | GeteioErrorLimit = config.DEFAULT 9 | 10 | GateioBufferSize = config.DEFAULT_BUFFER_SIZE 11 | 12 | GateioSymbole = "https://data.gateio.io/api2/1/pairs" 13 | 14 | GateioWsUrl = "wss://ws.gateio.io/v3/" 15 | ) 16 | -------------------------------------------------------------------------------- /hadax/hadax.go: -------------------------------------------------------------------------------- 1 | package hadax 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | Name = "hadax" 7 | 8 | HadaxErrorLimit = config.DEFAULT 9 | 10 | HadaxBufferSize = config.DEFAULT_BUFFER_SIZE 11 | 12 | HadaxSymbol = "https://api.hadax.com/v1/hadax/common/symbols" 13 | 14 | HadaxWsUrl = "wss://api.hadax.com/ws" 15 | ) 16 | -------------------------------------------------------------------------------- /hitbtc/hitbtc.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | Name = "hitbtc2" 7 | 8 | HitbtcErrorLimt = config.DEFAULT 9 | 10 | HitbtcWsUrl = "wss://api.hitbtc.com/api/2/ws" 11 | 12 | HitbtcBufferSize = config.DEFAULT_BUFFER_SIZE 13 | 14 | HitbtcSymbol = "https://api.hitbtc.com/api/2/public/symbol" 15 | ) 16 | -------------------------------------------------------------------------------- /zb/zb.go: -------------------------------------------------------------------------------- 1 | package zb 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | 7 | Name = "zb" 8 | 9 | ZbErrorLimit = config.DEFAULT 10 | 11 | ZbMsgBufferSize = config.DEFAULT_BUFFER_SIZE * 10 12 | 13 | ZbSymbols = "http://api.zb.cn/data/v1/markets" 14 | 15 | ZbOrigi = "https://api.zb.cn/" 16 | ZbWsUrl = "wss://api.zb.cn:9999/websocket" 17 | ) 18 | -------------------------------------------------------------------------------- /bitfinex/bitfinex.go: -------------------------------------------------------------------------------- 1 | package bitfinex 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | Name = "bitfinex" 7 | 8 | BitfinexErrorLimit = config.DEFAULT 9 | 10 | BitfinexBufferSize = config.DEFAULT_BUFFER_SIZE 11 | 12 | //交易对 13 | BitfinexSymbole = "https://api.bitfinex.com/v1/symbols" 14 | 15 | BitfinexOrigin = "https://api.bitfinex.com" 16 | BitfinexWsUrl = "wss://api.bitfinex.com/ws" 17 | ) 18 | -------------------------------------------------------------------------------- /binance/binance.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | Name = "binance" 7 | 8 | BinanceErrorLimit = config.DEFAULT 9 | 10 | BinanceBufferSize = config.DEFAULT_BUFFER_SIZE 11 | 12 | //交易对 13 | BinanceSymbole = "https://api.binance.com/api/v1/exchangeInfo" 14 | 15 | BinanceOrigin = "https://api.binance.com" 16 | BinanceWsUrl = "wss://stream.binance.com:9443/stream?streams=" 17 | ) 18 | -------------------------------------------------------------------------------- /lbank/lbank.go: -------------------------------------------------------------------------------- 1 | package lbank 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | 7 | Name = "lbank" 8 | 9 | LBankErrorLimit = config.DEFAULT 10 | 11 | LBankBufferSzie = config.DEFAULT_BUFFER_SIZE 12 | 13 | LBankOrigin = "https://api.lbank.info" 14 | 15 | //LBankWsUrl = "wss://api.lbank.info/ws/V2/" 16 | LBankWsUrl = "wss://api.lbkex.com/ws/V2/" 17 | 18 | LBankSymbole = "http://api.lbank.info/v1/currencyPairs.do" 19 | ) 20 | -------------------------------------------------------------------------------- /okex/okex.go: -------------------------------------------------------------------------------- 1 | package okex 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | 7 | Name = "okex" 8 | 9 | OkexBufferSize = config.DEFAULT_BUFFER_SIZE 10 | 11 | OKexErrLimit = config.DEFAULT 12 | 13 | OkexOrigin = "https://real.okex.com" 14 | OkexWsUrl = "wss://real.okex.com:10441/websocket" //线上 15 | //OkexWsUrl = "wss://real.okcoin.com:10440/websocket" //开发本地 16 | 17 | //获取所有交易对 18 | OkexSymbols = "https://www.okex.com/v2/spot/markets/products" 19 | ) 20 | -------------------------------------------------------------------------------- /huobi/huobipro.go: -------------------------------------------------------------------------------- 1 | package huobi 2 | 3 | import "GccxtTrades/config" 4 | 5 | const ( 6 | Name = "huobipro" 7 | //火币WS的最大EOF连接次数 8 | HuoBiErroLimit = config.DEFAULT 9 | 10 | HuoBiOrigin = "https://api.huobi.br.com/" 11 | HuoBiWsUrl = "wss://api.huobi.br.com/ws" 12 | 13 | HuoBiSymbols = "https://api.huobi.pro/v1/common/symbols" 14 | 15 | HuoBiMsgBufferSize = config.DEFAULT_BUFFER_SIZE 16 | 17 | //订阅的Generated ID 18 | HuoBiGId = 1000 19 | 20 | //direction成交方向类型:出售 21 | HuoBiSell = "sell" 22 | //direction成交方向类型:购买 23 | HuobiBuy = "buy" 24 | ) 25 | -------------------------------------------------------------------------------- /gateio/start.go: -------------------------------------------------------------------------------- 1 | package gateio 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func StartWs(proxy string, useProxy bool) { 10 | if useProxy { 11 | utils.UseProxy = useProxy 12 | if proxy != "" { 13 | utils.ProxyUrl = proxy 14 | } 15 | } 16 | 17 | resp, err := utils.HttpGet(GateioSymbole).Array() 18 | if err != nil { 19 | log.Panic(err) 20 | } 21 | var symboleList []string 22 | for _, m := range resp { 23 | symboleList = append(symboleList, m.(string)) 24 | } 25 | GateioWsConnect(symboleList) 26 | } 27 | -------------------------------------------------------------------------------- /zb/start.go: -------------------------------------------------------------------------------- 1 | package zb 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | log "github.com/sirupsen/logrus" 7 | 8 | "strings" 9 | ) 10 | 11 | func StartWs(proxy string, useProxy bool) { 12 | 13 | if useProxy { 14 | utils.UseProxy = useProxy 15 | if proxy != "" { 16 | utils.ProxyUrl = proxy 17 | } 18 | } 19 | var syListZb []string 20 | zbSym, _ := utils.HttpGet(ZbSymbols).Map() 21 | for key, _ := range zbSym { 22 | syListZb = append(syListZb, strings.Replace(key, "_", "", -1)) 23 | } 24 | log.Println("ZB:", syListZb) 25 | go ZbWsConnect(syListZb) 26 | } 27 | -------------------------------------------------------------------------------- /bitfinex/start.go: -------------------------------------------------------------------------------- 1 | package bitfinex 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func StartWs(proxy string, useProxy bool) { 10 | if useProxy { 11 | utils.UseProxy = useProxy 12 | if proxy != "" { 13 | utils.ProxyUrl = proxy 14 | } 15 | } 16 | 17 | resp, err := utils.HttpGet(BitfinexSymbole).Array() 18 | if err != nil { 19 | log.Panic(err) 20 | } 21 | var symboleList []string 22 | for _, m := range resp { 23 | symboleList = append(symboleList, m.(string)) 24 | } 25 | log.Println(symboleList) 26 | 27 | go BitfinexWsConnect(symboleList) 28 | } 29 | -------------------------------------------------------------------------------- /okex/start.go: -------------------------------------------------------------------------------- 1 | package okex 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func StartWs(proxy string, useProxy bool) { 10 | 11 | if useProxy { 12 | utils.UseProxy = useProxy 13 | if proxy != "" { 14 | utils.ProxyUrl = proxy 15 | } 16 | } 17 | 18 | okSymbols := utils.HttpGet(OkexSymbols).Get("data").MustArray() 19 | 20 | var syList []string 21 | 22 | for _, m := range okSymbols { 23 | str := m.(map[string]interface{})["symbol"].(string) 24 | syList = append(syList, str) 25 | } 26 | log.Println("okex:", syList) 27 | 28 | go OkexWsConnect(syList) 29 | } 30 | -------------------------------------------------------------------------------- /fcoin/start.go: -------------------------------------------------------------------------------- 1 | package fcoin 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func StartWs(proxy string, useProxy bool) { 10 | if useProxy { 11 | utils.UseProxy = useProxy 12 | if proxy != "" { 13 | utils.ProxyUrl = proxy 14 | } 15 | } 16 | 17 | resp, err := utils.HttpGet(FCoinSymbol).Get("data").Array() 18 | if err != nil { 19 | logrus.Panic(err) 20 | } 21 | var symboleList []string 22 | for _, m := range resp { 23 | symboleList = append(symboleList, m.(map[string]interface{})["name"].(string)) 24 | } 25 | logrus.Warnln(symboleList) 26 | FcoinWsConnect(symboleList) 27 | } 28 | -------------------------------------------------------------------------------- /hadax/strat.go: -------------------------------------------------------------------------------- 1 | package hadax 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func StartWs(proxy string, useProxy bool) { 10 | if useProxy { 11 | utils.UseProxy = useProxy 12 | if proxy != "" { 13 | utils.ProxyUrl = proxy 14 | } 15 | } 16 | 17 | resp, err := utils.HttpGet(HadaxSymbol).Get("data").Array() 18 | if err != nil { 19 | logrus.Panic(err) 20 | } 21 | var symboleList []string 22 | for _, m := range resp { 23 | symboleList = append(symboleList, m.(map[string]interface{})["symbol"].(string)) 24 | } 25 | logrus.Warnln(symboleList) 26 | HadaxWsConnect(symboleList) 27 | } 28 | -------------------------------------------------------------------------------- /binance/start.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func StartWs(proxy string, useProxy bool) { 10 | if useProxy { 11 | utils.UseProxy = useProxy 12 | if proxy != "" { 13 | utils.ProxyUrl = proxy 14 | } 15 | } 16 | //获取火币的所以交易对 17 | binanceSymbols := utils.HttpGet(BinanceSymbole).Get("symbols").MustArray() 18 | 19 | var syList []string 20 | for _, m := range binanceSymbols { 21 | str := m.(map[string]interface{})["symbol"].(string) 22 | syList = append(syList, str) 23 | } 24 | log.Println(len(syList), syList) 25 | go BinanceWsConnect(syList) 26 | } 27 | -------------------------------------------------------------------------------- /hitbtc/start.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | "log" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | func StartWs(proxy string, useProxy bool) { 11 | if useProxy { 12 | utils.UseProxy = useProxy 13 | if proxy != "" { 14 | utils.ProxyUrl = proxy 15 | } 16 | } 17 | resp, err := utils.HttpGet(HitbtcSymbol).Array() 18 | if err != nil { 19 | log.Panic(err) 20 | } 21 | 22 | var symboleList []string 23 | 24 | for _, m := range resp { 25 | symboleList = append(symboleList, m.(map[string]interface{})["id"].(string)) 26 | } 27 | 28 | logrus.Info(symboleList) 29 | 30 | go HitbtcWsConnect([]string{"ETHBTC"}) 31 | } 32 | -------------------------------------------------------------------------------- /lbank/start.go: -------------------------------------------------------------------------------- 1 | package lbank 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | "encoding/json" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | func StartWs(proxy string, useProxy bool) { 11 | 12 | if useProxy { 13 | utils.UseProxy = useProxy 14 | if proxy != "" { 15 | utils.ProxyUrl = proxy 16 | } 17 | } 18 | 19 | resp := utils.HttpRequest(LBankSymbole, "GET", map[string]string{"contentType": "application/x-www-form-urlencoded"}) 20 | var symboleList []string 21 | err := json.Unmarshal([]byte(resp), &symboleList) 22 | 23 | if err != nil { 24 | log.Panic(err) 25 | } 26 | log.Println(symboleList) 27 | go LBankWsConnect(symboleList) 28 | } 29 | -------------------------------------------------------------------------------- /huobi/start.go: -------------------------------------------------------------------------------- 1 | package huobi 2 | 3 | import ( 4 | "GccxtTrades/utils" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func StartWs(proxy string, useProxy bool) { 10 | 11 | if useProxy { 12 | utils.UseProxy = useProxy 13 | if proxy != "" { 14 | utils.ProxyUrl = proxy 15 | } 16 | } 17 | //获取火币的所以交易对 18 | huobiSymbols := utils.HttpGet(HuoBiSymbols).Get("data").MustArray() 19 | var syList []string 20 | for _, m := range huobiSymbols { 21 | // str := m.(map[string]interface{})["symbol"].(string) 22 | syList = append(syList, m.(map[string]interface{})["symbol"].(string)) 23 | } 24 | log.Println("huobi:", syList) 25 | 26 | go HuobiWsConnect(syList) 27 | } 28 | -------------------------------------------------------------------------------- /model/out_entity.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "GccxtTrades/config" 5 | "encoding/json" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | var DataChannel = make(chan TradeTransData, config.ChannelSize) 11 | 12 | type TradeEntity struct { 13 | Symbol string `json:"symbol"` 14 | // datetime: 2018-09-12T09:47:05.000Z, 15 | Side string `json:"side"` 16 | Amount string `json:"amount"` 17 | Price string `json:"price"` 18 | Timestamp string `json:"timestamp"` 19 | } 20 | 21 | type TradeTransData struct { 22 | ExchangeId int `json:"exchangeId"` 23 | Symbol string `json:"symbol"` 24 | Trades []TradeEntity `json:"trades"` 25 | Exchange string `json:"exchange"` 26 | } 27 | 28 | func (t *TradeTransData) ToBody() []byte { 29 | body, err := json.Marshal(t) 30 | if err != nil { 31 | logrus.Errorln(err) 32 | return nil 33 | } 34 | return body 35 | } 36 | -------------------------------------------------------------------------------- /gateio/data_parser.go: -------------------------------------------------------------------------------- 1 | package gateio 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | //格式化为统一的输出结构 12 | func DataParser(t TradeDetail, id int) { 13 | 14 | var commonData = model.TradeTransData{} 15 | 16 | commonData.Exchange = Name 17 | commonData.ExchangeId = id 18 | a, b := utils.FmtSymbol(t.Symbole) 19 | 20 | if a != "" && b != "" { 21 | commonData.Symbol = a + "/" + b 22 | 23 | for _, tradeTemp := range t.tradeList { 24 | var trade model.TradeEntity 25 | trade.Amount = tradeTemp.Amount 26 | trade.Price = tradeTemp.Price 27 | trade.Symbol = commonData.Symbol 28 | trade.Side = tradeTemp.Type 29 | trade.Timestamp = strconv.Itoa(int(tradeTemp.Time * 1000)) 30 | 31 | commonData.Trades = append(commonData.Trades, trade) 32 | } 33 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 34 | model.DataChannel <- commonData 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /binance/data_parser.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | //格式化为统一的输出结构 12 | func DataParser(t TradeDetail, id int) { 13 | 14 | var commonData = model.TradeTransData{} 15 | 16 | commonData.Exchange = Name 17 | commonData.ExchangeId = id 18 | 19 | var trades []model.TradeEntity 20 | var trade model.TradeEntity 21 | trade.Amount = t.Data.Quantity 22 | trade.Price = t.Data.Price 23 | if t.Data.Buy { 24 | trade.Side = "buy" 25 | } else { 26 | trade.Side = "sell" 27 | } 28 | a, b := utils.FmtSymbol(t.Data.Symbol) 29 | if a != "" && b != "" { 30 | trade.Symbol = a + "/" + b 31 | trade.Timestamp = strconv.Itoa(t.Data.Ts) 32 | trades = append(trades, trade) 33 | 34 | commonData.Symbol = a + "/" + b 35 | commonData.Trades = trades 36 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 37 | model.DataChannel <- commonData 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /zb/data_parser.go: -------------------------------------------------------------------------------- 1 | package zb 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "github.com/sirupsen/logrus" 8 | "strings" 9 | ) 10 | 11 | //格式化为统一的输出结构 12 | func DataParser(t TradeDetail, id int) { 13 | 14 | var commonData = model.TradeTransData{} 15 | 16 | commonData.Exchange = Name 17 | commonData.ExchangeId = id 18 | 19 | t.Channel = strings.Replace(t.Channel , "_trades","" , -1) 20 | 21 | a, b := utils.FmtSymbol(t.Channel) 22 | 23 | if a != "" && b != "" { 24 | commonData.Symbol = a + "/" + b 25 | 26 | for _,td := range t.Data { 27 | var trade model.TradeEntity 28 | 29 | trade.Amount = td.Amount 30 | trade.Price = td.Price 31 | trade.Symbol = commonData.Symbol 32 | trade.Side = td.Type 33 | trade.Timestamp = strconv.Itoa(td.Date*1000 ) 34 | commonData.Trades = append(commonData.Trades, trade) 35 | } 36 | 37 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 38 | model.DataChannel <- commonData 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /hadax/data_parser.go: -------------------------------------------------------------------------------- 1 | package hadax 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | //格式化为统一的输出结构 13 | func DataParser(t TradeDetail, id int) { 14 | 15 | var commonData = model.TradeTransData{} 16 | 17 | commonData.Exchange = Name 18 | commonData.ExchangeId = id 19 | a, b := utils.FmtSymbol(strings.Split(t.Ch, ".")[1]) 20 | 21 | if a != "" && b != "" { 22 | commonData.Symbol = a + "/" + b 23 | 24 | for _, tradeTemp := range t.Tick.Data { 25 | var trade model.TradeEntity 26 | trade.Amount = strconv.FormatFloat(tradeTemp.Amount, 'f', -1, 64) 27 | trade.Price = strconv.FormatFloat(tradeTemp.Price, 'f', -1, 64) 28 | trade.Symbol = commonData.Symbol 29 | trade.Side = tradeTemp.Direction 30 | trade.Timestamp = strconv.Itoa(tradeTemp.Ts) 31 | 32 | commonData.Trades = append(commonData.Trades, trade) 33 | } 34 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 35 | model.DataChannel <- commonData 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /huobi/data_parser.go: -------------------------------------------------------------------------------- 1 | package huobi 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | //格式化为统一的输出结构 13 | func DataParser(t TradeDetail, id int) { 14 | 15 | var commonData = model.TradeTransData{} 16 | 17 | commonData.Exchange = Name 18 | commonData.ExchangeId = id 19 | a, b := utils.FmtSymbol(strings.Split(t.Ch, ".")[1]) 20 | 21 | if a != "" && b != "" { 22 | commonData.Symbol = a + "/" + b 23 | 24 | for _, tradeTemp := range t.Tick.Data { 25 | var trade model.TradeEntity 26 | trade.Amount = strconv.FormatFloat(tradeTemp.Amount, 'f', -1, 64) 27 | trade.Price = strconv.FormatFloat(tradeTemp.Price, 'f', -1, 64) 28 | trade.Symbol = commonData.Symbol 29 | trade.Side = tradeTemp.Direction 30 | trade.Timestamp = strconv.Itoa(tradeTemp.Ts) 31 | 32 | commonData.Trades = append(commonData.Trades, trade) 33 | } 34 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 35 | model.DataChannel <- commonData 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bitfinex/data_parser.go: -------------------------------------------------------------------------------- 1 | package bitfinex 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | //格式化为统一的输出结构 13 | func DataParser(t TradeDetail, id int) { 14 | 15 | var commonData = model.TradeTransData{} 16 | 17 | commonData.Exchange = Name 18 | commonData.ExchangeId = id 19 | 20 | var trades []model.TradeEntity 21 | var trade model.TradeEntity 22 | trade.Amount = strconv.FormatFloat(t.Amount, 'f', -1, 64) 23 | trade.Price = strconv.FormatFloat(t.Price, 'f', -1, 64) 24 | if t.Amount >= 0 { 25 | trade.Side = "buy" 26 | } else { 27 | trade.Side = "sell" 28 | } 29 | a, b := utils.FmtSymbol(strings.Split(t.Symbole, "-")[1]) 30 | if a != "" && b != "" { 31 | trade.Symbol = a + "/" + b 32 | trade.Timestamp = strconv.Itoa(t.Ts * 1000) 33 | trades = append(trades, trade) 34 | 35 | commonData.Symbol = a + "/" + b 36 | commonData.Trades = trades 37 | 38 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 39 | model.DataChannel <- commonData 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /fcoin/data_parser.go: -------------------------------------------------------------------------------- 1 | package fcoin 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | //格式化为统一的输出结构 13 | func DataParser(t TradeDetail, id int) { 14 | 15 | var commonData = model.TradeTransData{} 16 | 17 | commonData.Exchange = Name 18 | commonData.ExchangeId = id 19 | 20 | var trades []model.TradeEntity 21 | var trade model.TradeEntity 22 | trade.Amount = strconv.FormatFloat(t.Amount, 'f', -1, 64) 23 | trade.Price = strconv.FormatFloat(t.Price, 'f', -1, 64) 24 | if t.Amount >= 0 { 25 | trade.Side = "buy" 26 | } else { 27 | trade.Side = "sell" 28 | } 29 | if len(strings.Split(t.Type, ".")) <= 1 { 30 | return 31 | } 32 | a, b := utils.FmtSymbol(strings.Split(t.Type, ".")[1]) 33 | if a != "" && b != "" { 34 | trade.Symbol = a + "/" + b 35 | trade.Timestamp = strconv.Itoa(t.Ts) 36 | trades = append(trades, trade) 37 | 38 | commonData.Symbol = a + "/" + b 39 | commonData.Trades = trades 40 | 41 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 42 | model.DataChannel <- commonData 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /okex/data_parser.go: -------------------------------------------------------------------------------- 1 | package okex 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "github.com/sirupsen/logrus" 8 | "strings" 9 | ) 10 | 11 | //格式化为统一的输出结构 12 | func DataParser(t TradeDetail, id int) { 13 | 14 | var commonData = model.TradeTransData{} 15 | 16 | commonData.Exchange = Name 17 | commonData.ExchangeId = id 18 | 19 | t.Channel = strings.Replace(t.Channel , "ok_sub_spot_","" , -1) 20 | t.Channel = strings.Replace(t.Channel , "_deals","" , -1) 21 | 22 | a, b := utils.FmtSymbol(t.Channel) 23 | 24 | if a != "" && b != "" { 25 | commonData.Symbol = a + "/" + b 26 | 27 | var trade model.TradeEntity 28 | 29 | trade.Amount = t.Amount 30 | trade.Price = t.Price 31 | trade.Symbol = commonData.Symbol 32 | 33 | if "ask" == t.Type { 34 | trade.Side = "sell" 35 | } 36 | if "bid" == t.Type { 37 | trade.Side = "buy" 38 | } 39 | 40 | trade.Timestamp = strconv.FormatInt(t.Ts , 10) 41 | commonData.Trades = append(commonData.Trades, trade) 42 | 43 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 44 | model.DataChannel <- commonData 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /hitbtc/data_parser.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | //格式化为统一的输出结构 14 | func DataParser(t TradeDetail, id int) { 15 | 16 | var commonData = model.TradeTransData{} 17 | 18 | commonData.Exchange = Name 19 | commonData.ExchangeId = id 20 | a, b := utils.FmtSymbol(t.Params.Symbol) 21 | 22 | if a != "" && b != "" { 23 | commonData.Symbol = a + "/" + b 24 | 25 | for _, tradeTemp := range t.Params.Data { 26 | var trade model.TradeEntity 27 | trade.Amount = tradeTemp.Quantity 28 | trade.Price = tradeTemp.Price 29 | trade.Symbol = commonData.Symbol 30 | trade.Side = tradeTemp.Side 31 | 32 | //将英国伦敦时间 33 | toBeCharge := strings.Replace(tradeTemp.Timestamp, "Z", "", -1) 34 | timeLayout := "2006-01-02T15:04:05" 35 | loc, err := time.LoadLocation("GMT") 36 | if err != nil { 37 | logrus.Error(err) 38 | return 39 | } 40 | theTime, err := time.ParseInLocation(timeLayout, toBeCharge, loc) 41 | if err != nil { 42 | logrus.Error(err) 43 | return 44 | } 45 | 46 | trade.Timestamp = strconv.FormatInt(theTime.Unix()*1000, 10) 47 | 48 | commonData.Trades = append(commonData.Trades, trade) 49 | } 50 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 51 | model.DataChannel <- commonData 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gccxt 2 | 数字货币交易所WebSocket对接历史交易记录 Go版本实现,主要对接各大交易所的历史交易单数据,提供到消息队列进行统一数据分析 3 | 4 | 目前已经对接交易所(有时间的话会持续更新。。。): 5 | 6 | 交易所名称 | WebSocket | 官网 7 | --- | ----- | ----- 8 | huobipro | wss://api.huobi.br.com/ws | https://www.hbg.com 9 | lbank | wss://api.lbkex.com/ws/V2/ | https://www.lbank.info/ 10 | zb | wss://api.zb.cn:9999/websocket | https://www.zb.com/ 11 | okex | wss://real.okex.com:10441/websocket | https://www.okex.com/ 12 | bitfinex | wss://api.bitfinex.com/ws | https://www.binance.com/ 13 | binance | wss://stream.binance.com:9443/stream| https://www.bitfinex.com/ 14 | gateio | wss://ws.gateio.io/v3/ | https://www.gate.io/ 15 | fcoin | wss://api.fcoin.com/v2/ws | https://www.fcoin.com/ 16 | hitbtc2 | wss://api.hitbtc.com/api/2/ws | https://www.hitbtc.com/ 17 | hadax | wss://api.hadax.com/ws | https://www.hadax.com 18 | 19 | 每个交易所根据不同包名分割,根目录下启动(部分交易所需要翻墙,建议海外或者香港服务器) 20 | ```bash 21 | go run main.go -name="交易所名字" -mq="amqp://user:pwd@host:port/vhost" 22 | ``` 23 | 24 | # 项目规划 25 | * 对接比较出名的几家交易所历史交易WebSocket接口(其实K线、涨幅等数据都可以根据历史交易计算得出) 26 | * 断线重连 27 | * 统一的数据格式输出到RabbitMQ : 28 | ```json 29 | {"exchangeId":11,"symbol":"NANO/BTC","trades":[{"symbol":"NANO/BTC","side":"buy","amount":"12.84000000","price":"0.00037230","timestamp":"1536910985284"}],"exchange":"binance"} 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /ws_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "runtime" 8 | 9 | "golang.org/x/net/websocket" 10 | "time" 11 | ) 12 | 13 | func echoHandler(ws *websocket.Conn) { 14 | msg := make([]byte, 512) 15 | for { 16 | if ws.IsClientConn() { 17 | log.Print("IsClientConn..........") 18 | } 19 | if ws.IsServerConn() { 20 | log.Print("IsServerConn..........") 21 | } 22 | n, err := ws.Read(msg) 23 | if err != nil { 24 | //log.Fatal(err) 25 | log.Print(err.Error()) 26 | break 27 | } 28 | fmt.Printf("Receive: %s\n", msg[:n]) 29 | 30 | send_msg := "[" + string(msg[:n]) + "]" 31 | _, err = ws.Write([]byte(send_msg)) 32 | if err != nil { 33 | log.Print(err.Error()) 34 | break 35 | //log.Fatal(err) 36 | } 37 | fmt.Printf("Send: %s\n", send_msg) 38 | } 39 | 40 | } 41 | 42 | func mainBak() { 43 | runtime.GOMAXPROCS(4) 44 | http.Handle("/echo", websocket.Handler(echoHandler)) 45 | http.Handle("/", http.FileServer(http.Dir("."))) 46 | 47 | err := http.ListenAndServe(":8080", nil) 48 | 49 | if err != nil { 50 | panic("ListenAndServe: " + err.Error()) 51 | } 52 | } 53 | 54 | func main() { 55 | //timeStr := "13:53:13" 56 | ////转成时间戳 57 | // loc, _ := time.LoadLocation("Asia/Shanghai") 58 | //nowStr := time.Now().In(loc).Format("2006-01-02 ") 59 | //tm, err := time.ParseInLocation("2006-01-02 15:04:05", nowStr+timeStr, loc) 60 | //if err != nil{ 61 | // panic(err) 62 | //} 63 | fmt.Println(time.Now().Unix()) 64 | fmt.Println(time.Now().UnixNano()) 65 | } -------------------------------------------------------------------------------- /lbank/data_parser.go: -------------------------------------------------------------------------------- 1 | package lbank 2 | 3 | import ( 4 | "GccxtTrades/model" 5 | "GccxtTrades/utils" 6 | "strconv" 7 | "github.com/sirupsen/logrus" 8 | "time" 9 | ) 10 | 11 | //格式化为统一的输出结构 12 | func DataParser(t TradeDetail, id int) { 13 | 14 | var commonData = model.TradeTransData{} 15 | 16 | commonData.Exchange = Name 17 | commonData.ExchangeId = id 18 | a, b := utils.FmtSymbol(t.Pair) 19 | 20 | if a != "" && b != "" { 21 | commonData.Symbol = a + "/" + b 22 | 23 | var trade model.TradeEntity 24 | trade.Amount = strconv.FormatFloat(t.Trade.Amount, 'f', -1, 64) 25 | trade.Price = strconv.FormatFloat(t.Trade.Price, 'f', -1, 64) 26 | //trade.Amount = strconv.FormatFloat(t.Trade[2].(float64), 'f', -1, 64) 27 | //trade.Price = strconv.FormatFloat(t.Trade[1].(float64), 'f', -1, 64) 28 | trade.Symbol = commonData.Symbol 29 | //trade.Side = t.Trade[3].(string) 30 | trade.Side = t.Trade.Direction 31 | 32 | timeLayout := "2006-01-02T15:04:05" 33 | loc, err := time.LoadLocation("Asia/Shanghai") 34 | if err != nil { 35 | logrus.Error(err) 36 | return 37 | } 38 | theTime, err := time.ParseInLocation(timeLayout, t.Trade.TS, loc) 39 | if err != nil { 40 | logrus.Error(err) 41 | return 42 | } 43 | //trade.Timestamp = strconv.FormatFloat(t.Trade[0].(float64)*1000, 'f' , -1,64) 44 | trade.Timestamp = strconv.FormatInt(theTime.Unix()*1000,10) 45 | 46 | commonData.Trades = append(commonData.Trades, trade) 47 | logrus.Infof("输出MQ消息:%s", commonData.ToBody()) 48 | model.DataChannel <- commonData 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /config/constants.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | 7 | "github.com/bitly/go-simplejson" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | const ( 12 | //最大读取错误限制次数 13 | DEFAULT = 100 14 | 15 | DEFAULT_BUFFER_SIZE = 1024 16 | 17 | ChannelSize = 1024 18 | 19 | MqExchange = "ex-api-mq" 20 | 21 | QueuePre = "trades_" 22 | 23 | ExchangeMap = "[{\"exchangeName\":\"binance\",\"id\":11},{\"exchangeName\":\"huobipro\",\"id\":12},{\"exchangeName\":\"\",\"id\":13},{\"exchangeName\":\"bitfinex\",\"id\":14},{\"exchangeName\":\"\",\"id\":15},{\"exchangeName\":\"okex\",\"id\":16},{\"exchangeName\":\"\",\"id\":17},{\"exchangeName\":\"\",\"id\":18},{\"exchangeName\":\"hitbtc2\",\"id\":19},{\"exchangeName\":\"bithumb\",\"id\":20},{\"exchangeName\":\"\",\"id\":21},{\"exchangeName\":\"\",\"id\":22},{\"exchangeName\":\"zb\",\"id\":23},{\"exchangeName\":\"\",\"id\":24},{\"exchangeName\":\"\",\"id\":25},{\"exchangeName\":\"gateio\",\"id\":60},{\"exchangeName\":\"kucoin\",\"id\":61},{\"exchangeName\":\"bittrex\",\"id\":62},{\"exchangeName\":\"coinsuper\",\"id\":63},{\"exchangeName\":\"fcoin\",\"id\":64},{\"exchangeName\":\"hadax\",\"id\":65},{\"exchangeName\":\"lbank\",\"id\":66}]" 24 | ) 25 | 26 | func GetExchangeId(exName string) int { 27 | jsonOb, err := simplejson.NewJson([]byte(ExchangeMap)) 28 | if err != nil { 29 | logrus.Errorln(err) 30 | return 0 31 | } 32 | arr, err := jsonOb.Array() 33 | if err != nil { 34 | logrus.Errorln(err) 35 | return 0 36 | } 37 | 38 | for _, m := range arr { 39 | if m.(map[string]interface{})["exchangeName"] == exName { 40 | a, _ := strconv.Atoi(m.(map[string]interface{})["id"].(json.Number).String()) 41 | return a 42 | } 43 | } 44 | return 0 45 | } 46 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/sirupsen/logrus" 7 | "GccxtTrades/binance" 8 | "flag" 9 | "GccxtTrades/bitfinex" 10 | "GccxtTrades/huobi" 11 | "GccxtTrades/lbank" 12 | "GccxtTrades/okex" 13 | "GccxtTrades/zb" 14 | "GccxtTrades/gateio" 15 | "GccxtTrades/hitbtc" 16 | "GccxtTrades/fcoin" 17 | "GccxtTrades/hadax" 18 | "GccxtTrades/utils" 19 | ) 20 | 21 | //初始化日志输出格式 22 | func init() { 23 | customFormatter := new(logrus.TextFormatter) 24 | customFormatter.TimestampFormat = "2006-01-02 15:04:05" 25 | logrus.SetFormatter(customFormatter) 26 | customFormatter.FullTimestamp = true 27 | 28 | // log.SetFormatter(&log.JSONFormatter{}) 29 | } 30 | 31 | func main() { 32 | 33 | defer func() { // 必须要先声明defer,否则不能捕获到panic异常 34 | if err := recover(); err != nil { 35 | logrus.Error(err) // 这里的err其实就是panic传入的内容 36 | } 37 | logrus.Error("程序进程退出") 38 | }() 39 | 40 | name := flag.String("name", "", "交易所名称") 41 | mq := flag.String("mq", "", "amqp://user:pwd@host:port/vhost") 42 | 43 | flag.Parse() 44 | logrus.Info(*name , *mq) 45 | 46 | utils.AmqpUrl = *mq 47 | runtime.GOMAXPROCS(runtime.NumCPU()) 48 | 49 | switch *name { 50 | case binance.Name: 51 | binance.StartWs("", false) 52 | case bitfinex.Name: 53 | bitfinex.StartWs("", false) 54 | case huobi.Name: 55 | huobi.StartWs("", false) 56 | case gateio.Name: 57 | gateio.StartWs("", false) 58 | case hitbtc.Name: 59 | hitbtc.StartWs("", false) 60 | case fcoin.Name: 61 | fcoin.StartWs("", false) 62 | case hadax.Name: 63 | hadax.StartWs("", false) 64 | case lbank.Name: 65 | lbank.StartWs("", false) 66 | case okex.Name: 67 | okex.StartWs("", false) 68 | case zb.Name: 69 | zb.StartWs("", false) 70 | default: 71 | logrus.Panic("name is not set") 72 | } 73 | // go func() { 74 | // for { 75 | // utils.SendMsg("ex-api-mq", "trades_binance_btc", []byte("go-1:"+time.Now().String())) 76 | // // time.Sleep(time.Second * 1) 77 | // } 78 | // }() 79 | 80 | // go utils.ReceiveMsg("goDzq", "trades_binance_btc", func(b []byte) { 81 | // logrus.Errorf("trades_binance_btc : %s", b) 82 | // }) 83 | loop := make(chan bool) 84 | <-loop 85 | } 86 | -------------------------------------------------------------------------------- /utils/symbol.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | const ( 10 | SYMBOL_USDT = "USDT" 11 | SYMBOL_USD = "USD" 12 | SYMBOL_BTC = "BTC" 13 | SYMBOL_ETH = "ETH" 14 | SYMBOL_BNB = "BNB" 15 | SYMBOL_EUR = "EUR" 16 | SYMBOL_HT = "HT" 17 | SYMBOL_OKB = "OKB" 18 | SYMBOL_ZB = "ZB" 19 | SYMBOL_QC = "QC" 20 | SYMBOL_KRW = "KRW" 21 | SYMBOL_QTUM = "QTUM" 22 | SYMBOL_KCS = "KCS" 23 | SYMBOL_NEO = "NEO" 24 | SYMBOL_CNY = "CNY" 25 | SYMBOL_GBP = "GBP" //英镑 26 | SYMBOL_JPY = "JPY" //日元 27 | ) 28 | 29 | func FmtSymbol(s string) (string, string) { 30 | if len(s) <= 0 { 31 | return "", "" 32 | } 33 | s = strings.Replace(s, "_", "", -1) 34 | s = strings.Replace(s, "/", "", -1) 35 | upStr := strings.ToUpper(s) 36 | 37 | var currency, suffix string 38 | if strings.HasSuffix(upStr, SYMBOL_USDT) { 39 | suffix = SYMBOL_USDT 40 | } else if strings.HasSuffix(upStr, SYMBOL_USD) { 41 | suffix = SYMBOL_USD 42 | } else if strings.HasSuffix(upStr, SYMBOL_CNY) { 43 | suffix = SYMBOL_CNY 44 | } else if strings.HasSuffix(upStr, SYMBOL_BTC) { 45 | suffix = SYMBOL_BTC 46 | } else if strings.HasSuffix(upStr, SYMBOL_ETH) { 47 | suffix = SYMBOL_ETH 48 | } else if strings.HasSuffix(upStr, SYMBOL_BNB) { 49 | suffix = SYMBOL_BNB 50 | } else if strings.HasSuffix(upStr, SYMBOL_EUR) { 51 | suffix = SYMBOL_EUR 52 | } else if strings.HasSuffix(upStr, SYMBOL_HT) { 53 | suffix = SYMBOL_HT 54 | } else if strings.HasSuffix(upStr, SYMBOL_OKB) { 55 | suffix = SYMBOL_OKB 56 | } else if strings.HasSuffix(upStr, SYMBOL_ZB) { 57 | suffix = SYMBOL_ZB 58 | } else if strings.HasSuffix(upStr, SYMBOL_QC) { 59 | suffix = SYMBOL_QC 60 | } else if strings.HasSuffix(upStr, SYMBOL_KRW) { 61 | suffix = SYMBOL_KRW 62 | } else if strings.HasSuffix(upStr, SYMBOL_QTUM) { 63 | suffix = SYMBOL_QTUM 64 | } else if strings.HasSuffix(upStr, SYMBOL_KCS) { 65 | suffix = SYMBOL_KCS 66 | } else if strings.HasSuffix(upStr, SYMBOL_NEO) { 67 | suffix = SYMBOL_NEO 68 | } else if strings.HasSuffix(upStr, SYMBOL_JPY) { 69 | suffix = SYMBOL_JPY 70 | } else if strings.HasSuffix(upStr, SYMBOL_GBP) { 71 | suffix = SYMBOL_GBP 72 | } else { 73 | logrus.Errorln("未标记的交易对:" + s) 74 | return "", "" 75 | } 76 | 77 | currency = string([]rune(upStr)[:(len(upStr) - len(suffix))]) 78 | 79 | return currency, suffix 80 | } 81 | -------------------------------------------------------------------------------- /utils/rabbitmq_util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/streadway/amqp" 6 | ) 7 | 8 | var AmqpUrl = "" 9 | 10 | //发送消息到 11 | func SendMsg(exchange, queue string, body []byte) { 12 | defer func() { // 必须要先声明defer,否则不能捕获到panic异常 13 | if err := recover(); err != nil { 14 | logrus.Error(err) // 这里的err其实就是panic传入的内容 15 | } 16 | }() 17 | if AmqpUrl==""{ 18 | return 19 | } 20 | conn, err := amqp.Dial(AmqpUrl) 21 | defer conn.Close() 22 | if err != nil { 23 | logrus.Error(err) 24 | logrus.Errorf(AmqpUrl+"连接失败 %s", body) 25 | return 26 | } 27 | 28 | ch, err := conn.Channel() 29 | if err != nil { 30 | logrus.Error(err) 31 | logrus.Errorf("Channel open 失败 %s", body) 32 | return 33 | } 34 | 35 | defer ch.Close() 36 | q, err := ch.QueueDeclare( 37 | queue, // name 38 | true, // durable 39 | false, // delete when unused 40 | false, // exclusive 41 | false, // no-wait 42 | nil, // arguments 43 | ) 44 | CheckErr(err) 45 | 46 | err = ch.Publish( 47 | exchange, // exchange 48 | q.Name, // routing key 49 | false, // mandatory 50 | false, // immediate 51 | amqp.Publishing{ 52 | ContentType: "application/json", 53 | Body: body, 54 | }) 55 | CheckErr(err) 56 | 57 | logrus.Infof("发送消息:%s", body) 58 | } 59 | 60 | //该方法会造成阻塞,协程调用 61 | func ReceiveMsg(consumer, queue string, f func([]byte)) { 62 | if AmqpUrl==""{ 63 | return 64 | } 65 | conn, err := amqp.Dial(AmqpUrl) 66 | 67 | if err != nil { 68 | logrus.Error(err) 69 | logrus.Errorf(AmqpUrl+"连接失败 %s", queue) 70 | return 71 | } 72 | defer conn.Close() 73 | ch, err := conn.Channel() 74 | if err != nil { 75 | logrus.Error(err) 76 | logrus.Errorf("连接失败 %s", consumer, queue) 77 | return 78 | } 79 | 80 | CheckErr(err) 81 | defer ch.Close() 82 | q, err := ch.QueueDeclare( 83 | queue, // name 84 | true, // durable 85 | false, // delete when unused 86 | false, // exclusive 87 | false, // no-wait 88 | nil, // arguments 89 | ) 90 | CheckErr(err) 91 | msgs, err := ch.Consume( 92 | q.Name, // queue 93 | consumer, // consumer 94 | true, // auto-ack 95 | false, // exclusive 96 | false, // no-local 97 | false, // no-wait 98 | nil, // args 99 | ) 100 | for d := range msgs { 101 | f(d.Body) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /utils/http_util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/tls" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | 10 | log "github.com/sirupsen/logrus" 11 | 12 | "github.com/bitly/go-simplejson" 13 | ) 14 | 15 | //是否使用代理 16 | var UseProxy = false 17 | 18 | //代理服务 19 | var ProxyUrl = "socks5://127.0.0.1:1086" 20 | 21 | //设置代理 22 | func proxyReqClient() *http.Client { 23 | proxy, _ := url.Parse(ProxyUrl) 24 | tr := &http.Transport{ 25 | Proxy: http.ProxyURL(proxy), 26 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 27 | } 28 | client := &http.Client{ 29 | Transport: tr, 30 | Timeout: time.Second * 10, //超时时间 31 | } 32 | return client 33 | } 34 | 35 | //Get 请求工具类 返回JSON对象 36 | func HttpGet(url string) *simplejson.Json { 37 | 38 | defer func() { // 必须要先声明defer,否则不能捕获到panic异常 39 | if err := recover(); err != nil { 40 | log.Println(err) 41 | } 42 | }() 43 | 44 | if url == "" { 45 | panic("GET请求url不合法") 46 | } 47 | var resp *http.Response 48 | var err error 49 | if UseProxy { 50 | resp, err = proxyReqClient().Get(url) 51 | } else { 52 | resp, err = http.Get(url) 53 | } 54 | CheckErr(err) 55 | 56 | defer resp.Body.Close() 57 | body, _ := ioutil.ReadAll(resp.Body) 58 | log.Println("请求返回:", string(body)) 59 | json, err := simplejson.NewJson(body) 60 | return json 61 | } 62 | 63 | // method: GET || POST headers:自定义的头部 64 | func HttpRequest(webUrl string, method string, headers map[string]string) string { 65 | /* 66 | 1. 代理请求 67 | 2. 跳过https不安全验证 68 | 3. 自定义请求头 User-Agent 69 | */ 70 | request, _ := http.NewRequest(method, webUrl, nil) 71 | 72 | for key, value := range headers { 73 | request.Header.Set(key, value) 74 | } 75 | //request.Header.Set("Connection", "keep-alive") 76 | //request.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") 77 | // proxy, _ := url.Parse(ProxyUrl) 78 | tr := &http.Transport{ 79 | // Proxy: http.ProxyURL(proxy), 80 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 81 | } 82 | client := &http.Client{ 83 | Transport: tr, 84 | Timeout: time.Second * 10, //超时时间 85 | } 86 | resp, err := client.Do(request) 87 | if err != nil { 88 | log.Println("请求出错了", err) 89 | return "" 90 | } 91 | defer resp.Body.Close() 92 | body, _ := ioutil.ReadAll(resp.Body) 93 | return (string(body)) 94 | } 95 | -------------------------------------------------------------------------------- /binance/ws_client.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | 7 | "github.com/sirupsen/logrus" 8 | 9 | log "github.com/sirupsen/logrus" 10 | 11 | "GccxtTrades/config" 12 | "GccxtTrades/model" 13 | "GccxtTrades/utils" 14 | "strconv" 15 | "strings" 16 | 17 | "golang.org/x/net/websocket" 18 | ) 19 | 20 | type data struct { 21 | EventType string `json:"e"` 22 | EventTime int `json:"E"` 23 | Symbol string `json:"s"` 24 | TradeId int `json:"t"` 25 | Price string `json:"p"` 26 | Quantity string `json:"q"` 27 | BuyerId int `json:"b"` 28 | SellerId int `json:"a"` 29 | Ts int `json:"T"` 30 | Buy bool `json:"m"` 31 | M bool `json:"M"` 32 | } 33 | 34 | type TradeDetail struct { 35 | Stream string `json:"stream"` 36 | Data data `json:"data"` 37 | } 38 | 39 | func BinanceWsConnect(symbolList []string) { 40 | if len(symbolList) <= 0 { 41 | log.Println(errors.New("Binance订阅的交易对数量为空")) 42 | return 43 | } 44 | id := config.GetExchangeId(Name) 45 | 46 | if id <= 0 { 47 | log.Println(errors.New(Name + "未找到交易ID")) 48 | return 49 | } 50 | 51 | ws := subWs(symbolList) 52 | if ws == nil { 53 | logrus.Panic("WS连接失败") 54 | } 55 | //统计连续错误次数 56 | var readErrCount = 0 57 | var msg = make([]byte, BinanceBufferSize) 58 | for { 59 | if readErrCount > BinanceErrorLimit { 60 | ws.Close() 61 | log.Error(errors.New("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 62 | ws = subWs(symbolList) 63 | if ws == nil{ 64 | continue 65 | } 66 | } 67 | m, err := ws.Read(msg) 68 | if err != nil { 69 | log.Println(err.Error()) 70 | readErrCount++ 71 | continue 72 | } 73 | //连接正常重置 74 | readErrCount = 0 75 | 76 | // log.Printf("Binance接收:%s \n", msg[:m]) 77 | 78 | var t TradeDetail 79 | err = json.Unmarshal(msg[:m], &t) 80 | if err != nil { 81 | log.Println(err) 82 | continue 83 | } 84 | 85 | // log.Println("Binance输出对象:", t.Data.Buy, t.Data.M, t) 86 | 87 | go DataParser(t, id) 88 | go func() { 89 | select { 90 | case data := <-model.DataChannel: 91 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 92 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 93 | default: 94 | logrus.Warn("Binance无消息发送") 95 | } 96 | }() 97 | } 98 | 99 | } 100 | 101 | func subWs(symbolList []string) *websocket.Conn { 102 | var subUrl string 103 | for _, s := range symbolList { 104 | subUrl += strings.ToLower(s) + "@aggTrade/" 105 | } 106 | ws, err := websocket.Dial(BinanceWsUrl+subUrl, "", BinanceOrigin) 107 | log.Printf("订阅: %s \n", subUrl) 108 | if err != nil { 109 | log.Println(err.Error()) 110 | return nil 111 | } 112 | return ws 113 | } 114 | -------------------------------------------------------------------------------- /fcoin/ws_client.go: -------------------------------------------------------------------------------- 1 | package fcoin 2 | 3 | import ( 4 | "GccxtTrades/config" 5 | "GccxtTrades/model" 6 | "GccxtTrades/utils" 7 | "encoding/json" 8 | "errors" 9 | "log" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/sirupsen/logrus" 14 | "golang.org/x/net/websocket" 15 | ) 16 | 17 | type subModel struct { 18 | Cmd string `json:"cmd"` 19 | Args []string `json:"args"` 20 | } 21 | 22 | //{"amount":890.000000000, 23 | // "type":"trade.mitxeth", 24 | // "ts":1536647971928, 25 | // "id":310930000, 26 | // "side":"buy", 27 | // "price":0.000026940} 28 | type TradeDetail struct { 29 | //amount 30 | Amount float64 `json:"amount"` 31 | Type string `json:"type"` 32 | Ts int `json:"ts"` 33 | Id int64 `json:"id"` 34 | Side string `json:"side"` 35 | Price float64 `json:"price"` 36 | } 37 | 38 | func FcoinWsConnect(symbolList []string) { 39 | if len(symbolList) <= 0 { 40 | logrus.Error("Fcoin订阅的交易对数量为空") 41 | return 42 | } 43 | id := config.GetExchangeId(Name) 44 | if id <= 0 { 45 | log.Println(errors.New(Name + "未找到交易所ID")) 46 | return 47 | } 48 | ws := subWs(symbolList) 49 | if ws == nil { 50 | logrus.Panic("WS连接失败") 51 | } 52 | //统计连续错误次数 53 | var readErrCount = 0 54 | 55 | var msg = make([]byte, FCoinBufferSize) 56 | 57 | for { 58 | if readErrCount > FCoinErrorLimit { 59 | //异常退出 60 | ws.Close() 61 | logrus.Error(("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 62 | ws = subWs(symbolList) 63 | if ws == nil{ 64 | continue 65 | } 66 | } 67 | m, err := ws.Read(msg) 68 | if err != nil { 69 | logrus.Info(err.Error()) 70 | readErrCount++ 71 | continue 72 | } 73 | //连接正常重置 74 | readErrCount = 0 75 | 76 | logrus.Infof("FCoin接收:%s \n", msg[:m]) 77 | var t TradeDetail 78 | err = json.Unmarshal(msg[:m], &t) 79 | if err != nil { 80 | logrus.Error(err) 81 | continue 82 | } 83 | b, _ := json.Marshal(t) 84 | logrus.Infoln("FCoin对象输出:", t, string(b)) 85 | 86 | go DataParser(t, id) 87 | go func() { 88 | select { 89 | case data := <-model.DataChannel: 90 | log.Println("获取消息:", data.Symbol, data) 91 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 92 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 93 | default: 94 | logrus.Warn(Name + "无消息发送") 95 | } 96 | }() 97 | } 98 | 99 | } 100 | 101 | func subWs(symbolList []string) *websocket.Conn { 102 | ws, err := websocket.Dial(FCoinWsUrl, "", FCoinWsUrl) 103 | if err != nil { 104 | logrus.Error(err.Error()) 105 | return nil 106 | } 107 | 108 | for index, s := range symbolList { 109 | symbolList[index] = "trade." + s 110 | } 111 | subModel := subModel{"sub", symbolList} 112 | subData, err := json.Marshal(subModel) 113 | if err != nil { 114 | logrus.Panic("Fcoin订阅JSON转换失败") 115 | return nil 116 | } 117 | logrus.Infof("订阅 %s", subData) 118 | ws.Write(subData) 119 | return ws 120 | 121 | } 122 | -------------------------------------------------------------------------------- /zb/ws_client.go: -------------------------------------------------------------------------------- 1 | package zb 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | 7 | log "github.com/sirupsen/logrus" 8 | 9 | "strconv" 10 | 11 | "golang.org/x/net/websocket" 12 | "GccxtTrades/config" 13 | "GccxtTrades/model" 14 | "strings" 15 | "GccxtTrades/utils" 16 | ) 17 | 18 | type detail struct { 19 | Amount string `json:"amount"` 20 | Price string `json:"price"` 21 | Tid int `json:"tid"` 22 | Date int `json:"date"` 23 | Type string `json:"type"` // sell || buy 24 | Trade_type string `json:"trade_type"` 25 | } 26 | 27 | type TradeDetail struct { 28 | DataType string `json:"dataType"` 29 | Data []detail `json:"data"` 30 | Channel string `json:"channel"` 31 | } 32 | 33 | func ZbWsConnect(symbolList []string) { 34 | 35 | if len(symbolList) <= 0 { 36 | log.Println(errors.New("ZB订阅的交易对数量为空")) 37 | return 38 | } 39 | id := config.GetExchangeId(Name) 40 | if id <= 0 { 41 | log.Println(errors.New(Name + "未找到交易所ID")) 42 | return 43 | } 44 | ws := subWs(symbolList) 45 | 46 | //统计连续错误次数 47 | var readErrCount = 0 48 | 49 | var msg = make([]byte, ZbMsgBufferSize) 50 | 51 | for { 52 | var data string 53 | for { 54 | if readErrCount > ZbErrorLimit { 55 | //异常退出 56 | ws.Close() 57 | log.Error(errors.New("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 58 | ws = subWs(symbolList) 59 | if ws == nil{ 60 | continue 61 | } 62 | } 63 | m, err := ws.Read(msg) 64 | if err != nil { 65 | log.Error(err.Error()) 66 | readErrCount++ 67 | continue 68 | } 69 | data += string(msg[:m]) 70 | if m <= (ZbMsgBufferSize - 1) { 71 | break 72 | } 73 | } 74 | //连接正常重置 75 | readErrCount = 0 76 | log.Printf("Zb接收:%s \n", data) 77 | var tradeDetail TradeDetail 78 | err := json.Unmarshal([]byte(data), &tradeDetail) 79 | if err != nil { 80 | log.Println(err) 81 | continue 82 | } 83 | if tradeDetail.Channel != "" { 84 | log.Println("Zb输出对象:", tradeDetail) 85 | 86 | go DataParser(tradeDetail, id) 87 | go func() { 88 | select { 89 | case data := <-model.DataChannel: 90 | log.Println("获取消息:", data.Symbol, data) 91 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 92 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 93 | default: 94 | log.Warn(Name + "无消息发送") 95 | } 96 | }() 97 | } 98 | } 99 | } 100 | 101 | func subWs(symbolList []string) *websocket.Conn { 102 | ws, err := websocket.Dial(ZbWsUrl, "", ZbOrigi) 103 | 104 | if err != nil { 105 | log.Println(err.Error()) 106 | return nil 107 | } 108 | //循环订阅交易对 109 | for _, symbol := range symbolList { 110 | 111 | subStr := "{\"event\":\"addChannel\",\"channel\":\"" + symbol + "_trades\"}" 112 | //subStr := "{\"event\":\"addChannel\",\"channel\":\"btcusdt_trades\"}" 113 | _, err = ws.Write([]byte(subStr)) 114 | if err != nil { 115 | log.Println(err.Error()) 116 | return nil 117 | } 118 | log.Printf("订阅: %s \n", subStr) 119 | } 120 | 121 | return ws 122 | } -------------------------------------------------------------------------------- /bitfinex/ws_client.go: -------------------------------------------------------------------------------- 1 | package bitfinex 2 | 3 | import ( 4 | "GccxtTrades/config" 5 | "GccxtTrades/model" 6 | "GccxtTrades/utils" 7 | "encoding/json" 8 | "errors" 9 | "strings" 10 | 11 | log "github.com/sirupsen/logrus" 12 | "golang.org/x/net/websocket" 13 | 14 | "strconv" 15 | ) 16 | 17 | type TradeDetail struct { 18 | Symbole string 19 | Ts int 20 | Price float64 21 | Amount float64 22 | } 23 | 24 | func BitfinexWsConnect(symbolList []string) { 25 | if len(symbolList) <= 0 { 26 | log.Println(errors.New("Binance订阅的交易对数量为空")) 27 | return 28 | } 29 | id := config.GetExchangeId(Name) 30 | 31 | if id <= 0 { 32 | log.Println(errors.New(Name + "未找到交易所ID")) 33 | return 34 | } 35 | ws := subWs(symbolList) 36 | if ws == nil { 37 | log.Panic("WS连接失败") 38 | } 39 | //统计连续错误次数 40 | var readErrCount = 0 41 | 42 | var msg = make([]byte, BitfinexBufferSize) 43 | 44 | for { 45 | if readErrCount > BitfinexErrorLimit { 46 | //异常退出 47 | ws.Close() 48 | log.Error(errors.New("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 49 | ws = subWs(symbolList) 50 | if ws == nil{ 51 | continue 52 | } 53 | } 54 | m, err := ws.Read(msg) 55 | if err != nil { 56 | log.Println(err.Error()) 57 | readErrCount++ 58 | continue 59 | } 60 | //连接正常重置 61 | readErrCount = 0 62 | 63 | // log.Printf("Bitfinex接收:%s \n", msg[:m]) 64 | 65 | var revData = make([]interface{}, 7) 66 | err = json.Unmarshal(msg[:m], &revData) 67 | if err != nil { 68 | log.Println(err) 69 | continue 70 | } 71 | // revDataStr := string(msg[:m]) 72 | // revDataStr = strings.Replace(revDataStr , "[" ,-1) 73 | // revDataStr = strings.Replace(revDataStr , "]" ,-1) 74 | // revDataStr = strings.Replace(revDataStr , "]" ,-1) 75 | // revDataStr = strings.Replace(revDataStr , "\"" ,-1) 76 | 77 | // revData := strings.Split(revDataStr , ",") 78 | var t = TradeDetail{} 79 | if revData[1] == "tu" { 80 | t = TradeDetail{ 81 | revData[2].(string), 82 | int(revData[4].(float64)), 83 | revData[5].(float64), 84 | revData[6].(float64)} 85 | // log.Println("Bitfinex输出对象:", t) 86 | 87 | go DataParser(t, id) 88 | go func() { 89 | select { 90 | case data := <-model.DataChannel: 91 | log.Println("获取消息:", data.Symbol, data) 92 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 93 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 94 | default: 95 | log.Warn(Name + "无消息发送") 96 | } 97 | }() 98 | } 99 | 100 | } 101 | } 102 | 103 | func subWs(symbolList []string) *websocket.Conn { 104 | ws, err := websocket.Dial(BitfinexWsUrl, "", BitfinexOrigin) 105 | if err != nil { 106 | log.Println(err.Error()) 107 | return nil 108 | } 109 | for _, s := range symbolList { 110 | subStr := "{\"event\": \"subscribe\", \"channel\": \"trades\", \"pair\":\"" + s + "\" }" 111 | 112 | _, err = ws.Write([]byte(subStr)) 113 | if err != nil { 114 | log.Println(err.Error()) 115 | return nil 116 | } 117 | log.Printf("订阅: %s \n", subStr) 118 | } 119 | return ws 120 | } 121 | -------------------------------------------------------------------------------- /lbank/ws_client.go: -------------------------------------------------------------------------------- 1 | package lbank 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | 7 | log "github.com/sirupsen/logrus" 8 | "golang.org/x/net/websocket" 9 | 10 | "strconv" 11 | "strings" 12 | "GccxtTrades/model" 13 | "GccxtTrades/config" 14 | "GccxtTrades/utils" 15 | ) 16 | 17 | type trade struct { 18 | Volume float64 `json:"volume"` 19 | Price float64 `json:"price"` 20 | Amount float64 `json:"amount"` 21 | Direction string `json:"direction"` 22 | TS string `json:"TS"` 23 | } 24 | 25 | type TradeDetail struct { 26 | Pair string `json:"pair"` 27 | Trade trade `json:"trade"` 28 | Type string `json:"type"` 29 | SERVER string `json:"SERVER"` 30 | TS string `json:"TS"` 31 | } 32 | 33 | func LBankWsConnect(symbolList []string) { 34 | 35 | if len(symbolList) <= 0 { 36 | log.Panic(errors.New("Okex订阅的交易对数量为空")) 37 | } 38 | id := config.GetExchangeId(Name) 39 | if id <= 0 { 40 | log.Println(errors.New(Name + "未找到交易所ID")) 41 | return 42 | } 43 | ws := subWs(symbolList) 44 | if ws == nil { 45 | log.Panic("WS连接失败") 46 | } 47 | //统计连续错误次数 48 | var readErrCount = 0 49 | var msg = make([]byte, LBankBufferSzie) 50 | for { 51 | if readErrCount > LBankErrorLimit { 52 | ws.Close() 53 | //异常退出 54 | log.Error(errors.New("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 55 | ws = subWs(symbolList) 56 | if ws == nil{ 57 | continue 58 | } 59 | } 60 | m, err := ws.Read(msg) 61 | if err != nil { 62 | log.Println(err.Error()) 63 | readErrCount++ 64 | continue 65 | } 66 | //连接正常重置 67 | readErrCount = 0 68 | revMsg := string(msg[:m]) 69 | log.Printf("LBank接收:%s \n", revMsg) 70 | if strings.Contains(revMsg, "ping") { 71 | var ping map[string]string 72 | json.Unmarshal(msg[:m], &ping) 73 | pongStr := "{\"action\": \"pong\", \"pong\": \"" + ping["ping"] + "\"}" 74 | log.Println("ping消息回应", pongStr) 75 | ws.Write([]byte(pongStr)) 76 | continue 77 | } 78 | var t TradeDetail 79 | err = json.Unmarshal(msg[:m], &t) 80 | if err != nil { 81 | log.Println(err) 82 | continue 83 | } 84 | log.Println("Lbank输出对象", t) 85 | 86 | go DataParser(t, id) 87 | go func() { 88 | select { 89 | case data := <-model.DataChannel: 90 | log.Println("获取消息:", data.Symbol, data) 91 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 92 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 93 | default: 94 | log.Warn(Name + "无消息发送") 95 | } 96 | }() 97 | } 98 | } 99 | 100 | func subWs(symbolList []string) *websocket.Conn { 101 | ws, err := websocket.Dial(LBankWsUrl, "", LBankOrigin) 102 | 103 | if err != nil { 104 | log.Println(err.Error()) 105 | return nil 106 | } 107 | 108 | //循环订阅交易对 109 | for _, symbol := range symbolList { 110 | message := "{\"action\": \"subscribe\", \"subscribe\": \"trade\", \"pair\": \"" + symbol + "\"}" 111 | 112 | _, err = ws.Write([]byte(message)) 113 | if err != nil { 114 | log.Println(err.Error()) 115 | return nil 116 | } 117 | log.Printf("订阅: %s \n", message) 118 | } 119 | return ws 120 | } 121 | -------------------------------------------------------------------------------- /hitbtc/ws_client.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "GccxtTrades/config" 5 | "GccxtTrades/model" 6 | "GccxtTrades/utils" 7 | "errors" 8 | "log" 9 | "strings" 10 | 11 | "encoding/json" 12 | "strconv" 13 | 14 | "github.com/sirupsen/logrus" 15 | "golang.org/x/net/websocket" 16 | ) 17 | 18 | type tradeInfo struct { 19 | //{"id":360841011,"price":"0.031089","quantity":"0.977","side":"sell","timestamp":"2018-09-10T12:42:12.905Z"} 20 | Price string `json:"price"` 21 | Quantity string `json:"quantity"` 22 | Side string `json:"side"` //buy sell 23 | Timestamp string `json:"timestamp"` 24 | } 25 | 26 | type paramsEntry struct { 27 | Data []tradeInfo `json:"data"` 28 | Symbol string `json:"symbol"` 29 | } 30 | 31 | type TradeDetail struct { 32 | Jsonrpc string `json:"jsonrpc"` 33 | Method string `json:"method"` 34 | Params paramsEntry `json:"params"` 35 | } 36 | 37 | func HitbtcWsConnect(symbolList []string) { 38 | 39 | if len(symbolList) <= 0 { 40 | logrus.Error("订阅交易对数量为空") 41 | return 42 | } 43 | id := config.GetExchangeId(Name) 44 | if id <= 0 { 45 | log.Println(errors.New(Name + "未找到交易所ID")) 46 | return 47 | } 48 | ws := subWs(symbolList) 49 | if ws == nil { 50 | return 51 | } 52 | //统计连续错误次数 53 | var readErrCount = 0 54 | 55 | var msg = make([]byte, HitbtcBufferSize) 56 | 57 | for { 58 | var data string 59 | for { 60 | if readErrCount > HitbtcErrorLimt { 61 | //异常退出 62 | ws.Close() 63 | logrus.Error(errors.New("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 64 | ws = subWs(symbolList) 65 | if ws == nil{ 66 | continue 67 | } 68 | } 69 | m, err := ws.Read(msg) 70 | if err != nil { 71 | logrus.Error(err.Error()) 72 | readErrCount++ 73 | continue 74 | } 75 | data += string(msg[:m]) 76 | if m <= (HitbtcBufferSize - 1) { 77 | break 78 | } 79 | } 80 | //连接正常重置 81 | readErrCount = 0 82 | 83 | logrus.Infof("Hitbtc接收:%s \n", data) 84 | var t TradeDetail 85 | err := json.Unmarshal([]byte(data), &t) 86 | if err != nil { 87 | logrus.Errorln(err) 88 | continue 89 | } 90 | // logrus.Info("Hitbtc对象输出", t) 91 | 92 | go DataParser(t, id) 93 | go func() { 94 | select { 95 | case data := <-model.DataChannel: 96 | log.Println("获取消息:", data.Symbol, data) 97 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 98 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 99 | default: 100 | logrus.Warn(Name + "无消息发送") 101 | } 102 | }() 103 | } 104 | } 105 | 106 | func subWs(symbolList []string) *websocket.Conn { 107 | //重新订阅 108 | ws, err := websocket.Dial(HitbtcWsUrl, "", HitbtcWsUrl) 109 | if err != nil { 110 | logrus.Error(err.Error()) 111 | return nil 112 | } 113 | 114 | for _, s := range symbolList { 115 | 116 | subStr := "{\"method\": \"subscribeTrades\", \"params\":{\"symbol\": \"" + s + "\"} ,\"id\": 123}" 117 | 118 | _, err = ws.Write([]byte(subStr)) 119 | if err != nil { 120 | logrus.Error(err.Error()) 121 | return nil 122 | } 123 | logrus.Infof("订阅: %s \n", subStr) 124 | } 125 | return ws 126 | } 127 | -------------------------------------------------------------------------------- /okex/ws_client.go: -------------------------------------------------------------------------------- 1 | package okex 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | 7 | log "github.com/sirupsen/logrus" 8 | 9 | "strconv" 10 | "time" 11 | 12 | "golang.org/x/net/websocket" 13 | "GccxtTrades/config" 14 | "GccxtTrades/model" 15 | "strings" 16 | "GccxtTrades/utils" 17 | ) 18 | 19 | type tradeDetail struct { 20 | Channel string `json:"channel"` 21 | Data [][]string `json:"data"` 22 | } 23 | 24 | type TradeDetail struct { 25 | Channel string 26 | SerNo string 27 | Price string 28 | Amount string 29 | Ts int64 30 | Type string 31 | } 32 | 33 | func OkexWsConnect(symbolList []string) { 34 | 35 | if len(symbolList) <= 0 { 36 | log.Panic(errors.New("Okex订阅的交易对数量为空")) 37 | } 38 | 39 | id := config.GetExchangeId(Name) 40 | if id <= 0 { 41 | log.Println(errors.New(Name + "未找到交易所ID")) 42 | return 43 | } 44 | ws := subWs(symbolList) 45 | 46 | if ws == nil{ 47 | log.Panic("WS连接失败") 48 | } 49 | //统计连续错误次数 50 | var readErrCount = 0 51 | var msg = make([]byte, OkexBufferSize) 52 | 53 | for { 54 | if readErrCount > OKexErrLimit { 55 | //异常退出 56 | log.Error(errors.New("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 57 | ws = subWs(symbolList) 58 | if ws == nil{ 59 | continue 60 | } 61 | } 62 | m, err := ws.Read(msg) 63 | if err != nil { 64 | log.Println(err.Error()) 65 | readErrCount++ 66 | continue 67 | } 68 | //连接正常重置 69 | readErrCount = 0 70 | log.Printf("Okex接收:%s \n", msg[:m]) 71 | var tradeDetail []tradeDetail 72 | err = json.Unmarshal(msg[:m], &tradeDetail) 73 | if err != nil { 74 | log.Println(err) 75 | continue 76 | } 77 | timeStr := tradeDetail[0].Data[0][3] 78 | //转成时间戳 79 | loc, _ := time.LoadLocation("Asia/Shanghai") 80 | nowStr := time.Now().In(loc).Format("2006-01-02 ") 81 | tm, err := time.ParseInLocation("2006-01-02 15:04:05", nowStr+timeStr, loc) 82 | 83 | if err != nil { 84 | log.Println(err) 85 | continue 86 | } 87 | 88 | var transData = TradeDetail{ 89 | tradeDetail[0].Channel, 90 | tradeDetail[0].Data[0][0], 91 | tradeDetail[0].Data[0][1], 92 | tradeDetail[0].Data[0][2], 93 | tm.Unix()*1000, 94 | tradeDetail[0].Data[0][4]} 95 | log.Println("Okex输出对象:", transData) 96 | 97 | go DataParser(transData, id) 98 | go func() { 99 | select { 100 | case data := <-model.DataChannel: 101 | log.Println("获取消息:", data.Symbol, data) 102 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 103 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 104 | default: 105 | log.Warn(Name + "无消息发送") 106 | } 107 | }() 108 | } 109 | 110 | } 111 | 112 | 113 | func subWs(symbolList []string) *websocket.Conn{ 114 | ws, err := websocket.Dial(OkexWsUrl, "", OkexOrigin) 115 | 116 | if err != nil { 117 | log.Println(err.Error()) 118 | return nil 119 | } 120 | var subList []map[string]string 121 | //循环订阅交易对 122 | for _, symbol := range symbolList { 123 | 124 | subStr := map[string]string{"event": "addChannel", "channel": "ok_sub_spot_" + symbol + "_deals"} 125 | subList = append(subList, subStr) 126 | 127 | } 128 | sub, _ := json.Marshal(subList) 129 | _, err = ws.Write(sub) 130 | if err != nil { 131 | log.Println(err.Error()) 132 | return nil 133 | } 134 | log.Printf("订阅: %s \n", sub) 135 | return ws 136 | } -------------------------------------------------------------------------------- /gateio/ws_client.go: -------------------------------------------------------------------------------- 1 | package gateio 2 | 3 | import ( 4 | "GccxtTrades/config" 5 | "GccxtTrades/model" 6 | "GccxtTrades/utils" 7 | "encoding/json" 8 | "errors" 9 | "log" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/sirupsen/logrus" 14 | "golang.org/x/net/websocket" 15 | ) 16 | 17 | type subModel struct { 18 | Id int `json:"id"` 19 | Method string `json:"method"` 20 | Params []string `json:"params"` 21 | } 22 | 23 | type TradeDetail struct { 24 | tradeList []tradeEntry 25 | Symbole string 26 | } 27 | 28 | type tradeEntry struct { 29 | Time int `json:"time"` 30 | Price string `json:"price"` 31 | Amount string `json:"amount"` 32 | Type string `json:"type"` 33 | Symbole string `json:"symbole"` 34 | } 35 | 36 | type tradeData struct { 37 | Method string 38 | Params []interface{} 39 | } 40 | 41 | func GateioWsConnect(sysList []string) { 42 | 43 | if len(sysList) <= 0 { 44 | logrus.Error("Gateio订阅的交易对数量为空") 45 | return 46 | } 47 | id := config.GetExchangeId(Name) 48 | if id <= 0 { 49 | log.Println(errors.New(Name + "未找到交易所ID")) 50 | return 51 | } 52 | ws := subWs(sysList) 53 | if ws == nil { 54 | logrus.Panic("WS连接失败") 55 | } 56 | //统计连续错误次数 57 | var readErrCount = 0 58 | 59 | var msg = make([]byte, GateioBufferSize) 60 | 61 | for { 62 | var data string 63 | for { 64 | if readErrCount > GeteioErrorLimit { 65 | //异常退出 66 | ws.Close() 67 | logrus.Error(("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 68 | ws = subWs(sysList) 69 | if ws == nil{ 70 | continue 71 | } 72 | } 73 | m, err := ws.Read(msg) 74 | if err != nil { 75 | logrus.Info(err.Error()) 76 | readErrCount++ 77 | continue 78 | } 79 | data += string(msg[:m]) 80 | if m <= (GateioBufferSize-1) && strings.HasSuffix(data, "}") { 81 | break 82 | } 83 | } 84 | 85 | //连接正常重置 86 | readErrCount = 0 87 | 88 | logrus.Infof("Gateio接收:%s \n", data) 89 | var t tradeData 90 | err := json.Unmarshal([]byte(data), &t) 91 | if err != nil { 92 | logrus.Error(err) 93 | continue 94 | } 95 | // logrus.Info(t) 96 | if len(t.Params) > 0 { 97 | sym := t.Params[0].(string) 98 | 99 | var tradeDetial = TradeDetail{} 100 | tradeDetial.Symbole = sym 101 | params := t.Params[1].([]interface{}) 102 | for _, p := range params { 103 | mapData := p.(map[string]interface{}) 104 | tradeEntry := tradeEntry{ 105 | Time: int(mapData["time"].(float64)), 106 | Price: mapData["price"].(string), 107 | Amount: mapData["amount"].(string), 108 | Type: mapData["type"].(string), 109 | Symbole: sym, 110 | } 111 | tradeDetial.tradeList = append(tradeDetial.tradeList, tradeEntry) 112 | } 113 | 114 | // logrus.Info("Gateio对象输出", tradeDetial) 115 | 116 | go DataParser(tradeDetial, id) 117 | go func() { 118 | select { 119 | case data := <-model.DataChannel: 120 | log.Println("获取消息:", data.Symbol, data) 121 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 122 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 123 | default: 124 | logrus.Warn(Name + "无消息发送") 125 | } 126 | }() 127 | } 128 | } 129 | } 130 | 131 | func subWs(sysList []string) *websocket.Conn { 132 | ws, err := websocket.Dial(GateioWsUrl, "", GateioWsUrl) 133 | if err != nil { 134 | logrus.Error(err.Error()) 135 | return nil 136 | } 137 | subModel := subModel{12312, "trades.subscribe", sysList} 138 | subData, err := json.Marshal(subModel) 139 | if err != nil { 140 | logrus.Panic("Gateio订阅JSON转换失败") 141 | return nil 142 | } 143 | logrus.Infof("订阅 %s", subData) 144 | ws.Write(subData) 145 | return ws 146 | } 147 | -------------------------------------------------------------------------------- /huobi/ws_client.go: -------------------------------------------------------------------------------- 1 | package huobi 2 | 3 | import ( 4 | "bytes" 5 | "GccxtTrades/config" 6 | "GccxtTrades/model" 7 | "GccxtTrades/utils" 8 | "compress/gzip" 9 | "encoding/json" 10 | "errors" 11 | "io/ioutil" 12 | 13 | log "github.com/sirupsen/logrus" 14 | "golang.org/x/net/websocket" 15 | 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | type subModel struct { 21 | Sub string `json:"sub"` 22 | Id int `json:"id"` 23 | } 24 | 25 | type trade struct { 26 | // Id big.Int `json:"id"` 27 | Price float64 `json:"price"` 28 | Direction string `json:"direction"` 29 | Amount float64 `json:"amount"` 30 | Ts int `json:"ts"` 31 | } 32 | 33 | type tick struct { 34 | Id int `json:"id"` 35 | Ts int `json:"ts"` 36 | Data []trade `json:"data"` 37 | } 38 | 39 | type TradeDetail struct { 40 | Ch string `json:"ch"` 41 | Ts int `json:"ts"` 42 | Tick tick `json:"tick"` 43 | } 44 | 45 | func HuobiWsConnect(symbolList []string) { 46 | 47 | if len(symbolList) <= 0 { 48 | log.Println(errors.New("火币订阅的交易对数量为空")) 49 | return 50 | } 51 | id := config.GetExchangeId(Name) 52 | if id <= 0 { 53 | log.Println(errors.New(Name + "未找到交易所ID")) 54 | return 55 | } 56 | ws := subWs(symbolList) 57 | if ws == nil { 58 | log.Panic("WS连接失败") 59 | } 60 | //统计连续错误次数 61 | var readErrCount = 0 62 | var msg = make([]byte, HuoBiMsgBufferSize) 63 | for { 64 | if readErrCount > HuoBiErroLimit { 65 | //异常退出 66 | ws.Close() 67 | log.Error(errors.New("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount))) 68 | ws = subWs(symbolList) 69 | if ws == nil{ 70 | continue 71 | } 72 | } 73 | m, err := ws.Read(msg) 74 | if err != nil { 75 | log.Println(err.Error()) 76 | readErrCount++ 77 | continue 78 | } 79 | //连接正常重置 80 | readErrCount = 0 81 | reader, err := gzip.NewReader(bytes.NewReader(msg[:m])) 82 | if err != nil { 83 | log.Println(err) 84 | continue 85 | } 86 | b, err := ioutil.ReadAll(reader) 87 | if err != nil { 88 | log.Println(err) 89 | continue 90 | } 91 | revMsg := string(b) 92 | //ping pong 心跳防止断开 93 | if strings.Contains(revMsg, "ping") { 94 | ws.Write([]byte(strings.Replace(revMsg, "ping", "pong", 1))) 95 | } 96 | log.Println("Huobi接收:", revMsg) 97 | var tradeDetail TradeDetail 98 | err = json.Unmarshal(b, &tradeDetail) 99 | if err != nil { 100 | log.Println(err) 101 | continue 102 | } 103 | //json , _ :=simplejson.NewJson(b) 104 | //temp ,_ :=json.Marshal(tradeDetail) 105 | if tradeDetail.Ch != "" { 106 | //log.Println("转化:", string(temp)) 107 | log.Println("Huobi输出对象:", tradeDetail) 108 | go DataParser(tradeDetail, id) 109 | go func() { 110 | select { 111 | case data := <-model.DataChannel: 112 | log.Println("获取消息:", data.Symbol, data) 113 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 114 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 115 | default: 116 | log.Warn(Name + "无消息发送") 117 | } 118 | }() 119 | } 120 | } 121 | 122 | } 123 | 124 | func subWs(symbolList []string) *websocket.Conn { 125 | ws, err := websocket.Dial(HuoBiWsUrl, "", HuoBiOrigin) 126 | 127 | if err != nil { 128 | log.Println(err.Error()) 129 | return nil 130 | } 131 | //循环订阅交易对 132 | for _, symbol := range symbolList { 133 | sub := subModel{"market." + symbol + ".trade.detail", HuoBiGId} 134 | message, err := json.Marshal(sub) 135 | if err != nil { 136 | log.Println(err.Error()) 137 | return nil 138 | } 139 | _, err = ws.Write(message) 140 | if err != nil { 141 | log.Println(err.Error()) 142 | return nil 143 | } 144 | log.Printf("订阅: %s \n", message) 145 | } 146 | return ws 147 | } 148 | -------------------------------------------------------------------------------- /hadax/ws_client.go: -------------------------------------------------------------------------------- 1 | package hadax 2 | 3 | import ( 4 | "bytes" 5 | "GccxtTrades/config" 6 | "GccxtTrades/model" 7 | "GccxtTrades/utils" 8 | "compress/gzip" 9 | "encoding/json" 10 | "errors" 11 | "io/ioutil" 12 | "log" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/sirupsen/logrus" 17 | "golang.org/x/net/websocket" 18 | ) 19 | 20 | type subModel struct { 21 | Sub string `json:"sub"` 22 | Id int `json:"id"` 23 | } 24 | 25 | type trade struct { 26 | // Id int64 `json:"id"` 27 | Price float64 `json:"price"` 28 | Direction string `json:"direction"` 29 | Amount float64 `json:"amount"` 30 | Ts int `json:"ts"` 31 | } 32 | 33 | type tick struct { 34 | Id int `json:"id"` 35 | Ts int `json:"ts"` 36 | Data []trade `json:"data"` 37 | } 38 | 39 | type TradeDetail struct { 40 | Ch string `json:"ch"` 41 | Ts int `json:"ts"` 42 | Tick tick `json:"tick"` 43 | } 44 | 45 | func HadaxWsConnect(symbolList []string) { 46 | 47 | if len(symbolList) <= 0 { 48 | log.Println(errors.New("Hadax订阅的交易对数量为空")) 49 | return 50 | } 51 | id := config.GetExchangeId(Name) 52 | if id <= 0 { 53 | log.Println(errors.New(Name + "未找到交易所ID")) 54 | return 55 | } 56 | ws := subWs(symbolList) 57 | if ws == nil { 58 | logrus.Panic("WS连接失败") 59 | } 60 | //统计连续错误次数 61 | var readErrCount = 0 62 | var msg = make([]byte, HadaxBufferSize) 63 | for { 64 | if readErrCount > HadaxErrorLimit { 65 | //异常退出 66 | ws.Close() 67 | logrus.Error("WebSocket异常连接数连续大于" + strconv.Itoa(readErrCount)) 68 | ws = subWs(symbolList) 69 | if ws == nil{ 70 | continue 71 | } 72 | } 73 | m, err := ws.Read(msg) 74 | if err != nil { 75 | log.Println(err.Error()) 76 | readErrCount++ 77 | continue 78 | } 79 | //连接正常重置 80 | readErrCount = 0 81 | reader, err := gzip.NewReader(bytes.NewReader(msg[:m])) 82 | if err != nil { 83 | log.Println(err) 84 | continue 85 | } 86 | b, err := ioutil.ReadAll(reader) 87 | if err != nil { 88 | log.Println(err) 89 | continue 90 | } 91 | revMsg := string(b) 92 | //ping pong 心跳防止断开 93 | if strings.Contains(revMsg, "ping") { 94 | ws.Write([]byte(strings.Replace(revMsg, "ping", "pong", 1))) 95 | } 96 | log.Println("Hadax接收:", revMsg) 97 | var tradeDetail TradeDetail 98 | err = json.Unmarshal(b, &tradeDetail) 99 | if err != nil { 100 | log.Println(err) 101 | continue 102 | } 103 | //json , _ :=simplejson.NewJson(b) 104 | //temp ,_ :=json.Marshal(tradeDetail) 105 | if tradeDetail.Ch != "" { 106 | //log.Println("转化:", string(temp)) 107 | // log.Println("Hadax输出对象:", tradeDetail) 108 | 109 | go DataParser(tradeDetail, id) 110 | go func() { 111 | select { 112 | case data := <-model.DataChannel: 113 | log.Println("获取消息:", data.Symbol, data) 114 | queueName := config.QueuePre + data.Exchange + "_" + strings.ToLower(strings.Split(data.Symbol, "/")[1]) 115 | utils.SendMsg(config.MqExchange, queueName, data.ToBody()) 116 | default: 117 | logrus.Warn(Name + "无消息发送") 118 | } 119 | }() 120 | } 121 | } 122 | 123 | } 124 | 125 | func subWs(symbolList []string) *websocket.Conn { 126 | ws, err := websocket.Dial(HadaxWsUrl, "", HadaxWsUrl) 127 | 128 | if err != nil { 129 | log.Println(err.Error()) 130 | return nil 131 | } 132 | //循环订阅交易对 133 | for _, symbol := range symbolList { 134 | sub := subModel{"market." + symbol + ".trade.detail", 1001} 135 | message, err := json.Marshal(sub) 136 | if err != nil { 137 | log.Println(err.Error()) 138 | return nil 139 | } 140 | _, err = ws.Write(message) 141 | if err != nil { 142 | log.Println(err.Error()) 143 | return nil 144 | } 145 | log.Printf("订阅: %s \n", message) 146 | } 147 | return ws 148 | } 149 | --------------------------------------------------------------------------------