You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
725 lines
21 KiB
725 lines
21 KiB
package forex
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/shopspring/decimal"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"wss-pool/config"
|
|
"wss-pool/internal"
|
|
"wss-pool/internal/data"
|
|
"wss-pool/logging/applogger"
|
|
"wss-pool/pkg/model"
|
|
)
|
|
|
|
var topic = "forex" // 实时报价
|
|
var topic_d = "forexDay" // 实时天报价
|
|
var topic_t = "trade" // 成交报价
|
|
var topic_s = "tradeStorage" // 成交报价存储
|
|
var topic_c = "quotes" // 盘口(买一卖一)报价
|
|
var upGrader = websocket.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
// Verification request`
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
// If it is not a get request, return an error
|
|
if r.Method != "GET" {
|
|
fmt.Println("Request method error")
|
|
return false
|
|
}
|
|
// If chat is not included in the path, an error is returned
|
|
if r.URL.Path != "/forexWs" {
|
|
fmt.Println("Request path error")
|
|
return false
|
|
}
|
|
// Verification rules can also be customized according to other needs
|
|
return true
|
|
},
|
|
}
|
|
var forexLastKline = make(chan []model.ForexJsonData)
|
|
var forexDayLastKline = make(chan []model.ForexJsonData)
|
|
|
|
// 订阅数据类型
|
|
type Symbol struct {
|
|
Code string `json:"code"`
|
|
DepthLevel int `json:"depth_level"`
|
|
}
|
|
type Request struct {
|
|
CmdID int `json:"cmd_id"`
|
|
SeqID int `json:"seq_id"`
|
|
Trace string `json:"trace"`
|
|
Data DataList `json:"data"`
|
|
}
|
|
type DataList struct {
|
|
SymbolList []Symbol `json:"symbol_list"`
|
|
}
|
|
type ResultBbo struct {
|
|
CmdID int `json:"cmd_id"`
|
|
Data struct {
|
|
Code string `json:"code"`
|
|
Seq string `json:"seq"`
|
|
TickTime string `json:"tick_time"`
|
|
Bids []struct {
|
|
Price string `json:"price"`
|
|
Volume string `json:"volume"`
|
|
} `json:"bids"`
|
|
Asks []struct {
|
|
Price string `json:"price"`
|
|
Volume string `json:"volume"`
|
|
} `json:"asks"`
|
|
} `json:"data"`
|
|
}
|
|
type ResultsTrade struct {
|
|
CmdID int `json:"cmd_id"`
|
|
Data struct {
|
|
Code string `json:"code"`
|
|
Seq string `json:"seq"`
|
|
TickTime string `json:"tick_time"`
|
|
Price string `json:"price"`
|
|
Volume string `json:"volume"`
|
|
Turnover string `json:"turnover"`
|
|
TradeDirection int `json:"trade_direction"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
// Client represents a WebSocket client.
|
|
type Client struct {
|
|
conn *websocket.Conn
|
|
subscriptions map[string]bool // Tracks which topics the client is subscribed to
|
|
}
|
|
|
|
// Hub maintains the set of active clients and broadcasts messages to the clients.
|
|
type Hub struct {
|
|
clients map[*Client]bool // All connected clients
|
|
broadcast chan Message // Broadcast channel for messages
|
|
topics map[string][]*Client // Topic to clients mapping for subscriptions
|
|
mu sync.Mutex // Mutex for safe concurrent access
|
|
}
|
|
|
|
// Message structure to hold the message content and the topic
|
|
type Message struct {
|
|
Topic string `json:"topic"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
// newHub Initialize a new Hub
|
|
func newHub() *Hub {
|
|
return &Hub{
|
|
clients: make(map[*Client]bool),
|
|
broadcast: make(chan Message),
|
|
topics: make(map[string][]*Client),
|
|
}
|
|
}
|
|
|
|
// run Start the Hub to listen for messages
|
|
func (h *Hub) run() {
|
|
for {
|
|
msg := <-h.broadcast
|
|
// Lock the hub to safely access the topics map
|
|
h.mu.Lock()
|
|
if clients, ok := h.topics[msg.Topic]; ok {
|
|
for _, client := range clients {
|
|
err := client.conn.WriteJSON(msg)
|
|
if err != nil {
|
|
log.Printf("Error writing message to client: %v", err)
|
|
client.conn.Close()
|
|
delete(h.clients, client)
|
|
}
|
|
}
|
|
}
|
|
h.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// Handle WebSocket connections
|
|
func handleConnection(hub *Hub, w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upGrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Println("Error during connection upgrade:", err)
|
|
return
|
|
}
|
|
client := &Client{conn: conn, subscriptions: make(map[string]bool)}
|
|
hub.clients[client] = true
|
|
|
|
go func() {
|
|
defer func() {
|
|
delete(hub.clients, client)
|
|
for topic := range client.subscriptions {
|
|
hub.mu.Lock()
|
|
hub.topics[topic] = removeClientFromTopic(hub.topics[topic], client)
|
|
hub.mu.Unlock()
|
|
}
|
|
}()
|
|
for {
|
|
var msg Message
|
|
if err = conn.ReadJSON(&msg); err != nil {
|
|
log.Println("Error reading message:", err)
|
|
break
|
|
}
|
|
applogger.Info("message info:%v---%v", msg.Topic, msg.Content)
|
|
|
|
if msg.Content == "ping" || msg.Content == "subscribe" {
|
|
client.subscriptions[msg.Topic] = true
|
|
hub.mu.Lock()
|
|
hub.topics[msg.Topic] = append(hub.topics[msg.Topic], client)
|
|
hub.mu.Unlock()
|
|
}
|
|
switch msg.Content {
|
|
case "subscribe":
|
|
hub.broadcast <- Message{Topic: msg.Topic, Content: "subscribe success"}
|
|
case "ping":
|
|
hub.broadcast <- Message{Topic: msg.Topic, Content: "pong"}
|
|
default:
|
|
// TODO: 广播客户端订阅消息
|
|
//hub.broadcast <- msg
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// subscribeTradeSwitcher content new
|
|
func subscribeTradeSwitcher() *websocket.Conn {
|
|
url := "wss://quote.tradeswitcher.com/quote-b-ws-api?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
|
|
applogger.Info("subscribePolygon info Url:%v", url)
|
|
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
|
|
if err != nil {
|
|
applogger.Error("Failed to link to wss server:%v", err)
|
|
return nil
|
|
}
|
|
|
|
return conn
|
|
}
|
|
func (h *Hub) ForexMarketBBOSwitcher(codeList []string) {
|
|
var dataList DataList // 构造订阅参数
|
|
for _, value := range codeList {
|
|
dataList.SymbolList = append(dataList.SymbolList, Symbol{
|
|
Code: value,
|
|
DepthLevel: 1,
|
|
})
|
|
}
|
|
ticker := time.NewTicker(time.Second * 1)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
applogger.Info("Execute automatic subscription........")
|
|
if conn := subscribeTradeSwitcher(); conn != nil {
|
|
// Send heartbeat every 10 seconds
|
|
go func() {
|
|
for range time.NewTicker(10 * time.Second).C {
|
|
req := Request{
|
|
CmdID: 22000,
|
|
SeqID: 1447560092,
|
|
Trace: "b9e14618-b43f-4fb3-bf08-0bd1b60f285d",
|
|
Data: DataList{},
|
|
}
|
|
messageBytes, err := json.Marshal(req)
|
|
if err != nil {
|
|
applogger.Error("json.Marshal err:%v", err)
|
|
return
|
|
}
|
|
applogger.Debug("Message send info:%v", string(messageBytes))
|
|
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
|
|
if err != nil {
|
|
applogger.Error("write heartbeat err:%v", err)
|
|
}
|
|
}
|
|
}()
|
|
// Construct subscription parameters
|
|
req := Request{
|
|
CmdID: 22002,
|
|
SeqID: 1447560092,
|
|
Trace: uuid.New().String(),
|
|
Data: dataList,
|
|
}
|
|
messageBytes, err := json.Marshal(req)
|
|
if err != nil {
|
|
applogger.Error("json.Marshal err:%v", err)
|
|
return
|
|
}
|
|
applogger.Debug("Message send info:%v", string(messageBytes))
|
|
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
|
|
if err != nil {
|
|
applogger.Debug("write err:%v", err)
|
|
return
|
|
}
|
|
// 客户端broadcast,消息推送
|
|
h.SendAllClientBBOSwitcher(conn)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
func (h *Hub) ForexMarketTradeSwitcher(codeList []string) {
|
|
var dataList DataList // 构造订阅参数
|
|
for _, value := range codeList {
|
|
dataList.SymbolList = append(dataList.SymbolList, Symbol{
|
|
Code: value,
|
|
DepthLevel: 1,
|
|
})
|
|
}
|
|
ticker := time.NewTicker(time.Second * 1)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
applogger.Info("Execute automatic subscription........")
|
|
if conn := subscribeTradeSwitcher(); conn != nil {
|
|
// Send heartbeat every 10 seconds
|
|
go func() {
|
|
for range time.NewTicker(10 * time.Second).C {
|
|
req := Request{
|
|
CmdID: 22000,
|
|
SeqID: 1148509458,
|
|
Trace: "a5e37d07-168f-403a-b4cb-08be813e0b91",
|
|
Data: DataList{},
|
|
}
|
|
messageBytes, err := json.Marshal(req)
|
|
if err != nil {
|
|
applogger.Error("json.Marshal err:%v", err)
|
|
return
|
|
}
|
|
applogger.Debug("Message send info:%v", string(messageBytes))
|
|
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
|
|
if err != nil {
|
|
applogger.Error("heartbeat write err:%v", err)
|
|
}
|
|
}
|
|
}()
|
|
// Construct subscription parameters
|
|
req := Request{
|
|
CmdID: 22004,
|
|
SeqID: 1148509458,
|
|
Trace: uuid.New().String(),
|
|
Data: dataList,
|
|
}
|
|
messageBytes, err := json.Marshal(req)
|
|
if err != nil {
|
|
applogger.Error("json.Marshal err:%v", err)
|
|
return
|
|
}
|
|
applogger.Debug("Message send info:%v", string(messageBytes))
|
|
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
|
|
if err != nil {
|
|
applogger.Debug("write err:%v", err)
|
|
return
|
|
}
|
|
// 客户端broadcast,消息推送
|
|
h.SendAllClientTradeSwitcher(conn)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
func (h *Hub) ForexMarketBatchPrice(codeList []string) {
|
|
var dataList model.ConstructParametersPost
|
|
for _, value := range codeList {
|
|
dataList.Trace = uuid.New().String()
|
|
dataList.Data.DataList = append(dataList.Data.DataList, model.DataParameters{
|
|
Code: value,
|
|
KlineType: 1,
|
|
KlineTimestampEnd: 0,
|
|
QueryKlineNum: 1,
|
|
AdjustType: 0,
|
|
})
|
|
}
|
|
go func() {
|
|
for {
|
|
UrlBatchKline := "https://quote.tradeswitcher.com/quote-b-api/batch-kline?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
|
|
queryStr, err := json.Marshal(&dataList)
|
|
if err != nil {
|
|
return
|
|
}
|
|
bodyStr, err := internal.HttpPost(UrlBatchKline, string(queryStr))
|
|
if err != nil {
|
|
return
|
|
}
|
|
//applogger.Debug("klineBatchPrice data:%v", bodyStr)
|
|
var klineNew model.KlinePostReturnStruct
|
|
if err = json.Unmarshal([]byte(bodyStr), &klineNew); err != nil {
|
|
time.Sleep(3 * time.Second)
|
|
continue
|
|
}
|
|
//applogger.Debug("klineNew count:%v", len(klineNew.Data.KlineList))
|
|
// 将数据写入缓存用来推送K线
|
|
for _, value := range klineNew.Data.KlineList {
|
|
if len(value.KlineData) == 0 {
|
|
continue
|
|
}
|
|
code := model.Check_Code[value.Code]
|
|
if len(code) == 0 {
|
|
code = value.Code
|
|
}
|
|
openPrice := decimal.RequireFromString(value.KlineData[0].OpenPrice).InexactFloat64()
|
|
closePrice := decimal.RequireFromString(value.KlineData[0].ClosePrice).InexactFloat64()
|
|
highPrice := decimal.RequireFromString(value.KlineData[0].HighPrice).InexactFloat64()
|
|
lowPrice := decimal.RequireFromString(value.KlineData[0].LowPrice).InexactFloat64()
|
|
volume := decimal.RequireFromString(value.KlineData[0].Volume).InexactFloat64()
|
|
timestamp := decimal.RequireFromString(value.KlineData[0].Timestamp).IntPart()
|
|
forexJson := []model.ForexJsonData{
|
|
{
|
|
Event: "CAS",
|
|
Pair: code,
|
|
Open: openPrice,
|
|
Close: closePrice,
|
|
High: highPrice,
|
|
Low: lowPrice,
|
|
Volume: int(volume),
|
|
Timestamp: timestamp,
|
|
},
|
|
}
|
|
forexLastKline <- forexJson
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
}()
|
|
h.ForexMarketBatchPriceBatch()
|
|
}
|
|
func (h *Hub) ForexMarketBatchDayPrice(codeList []string) {
|
|
var dataList model.ConstructParametersPost
|
|
for _, value := range codeList {
|
|
dataList.Trace = uuid.New().String()
|
|
dataList.Data.DataList = append(dataList.Data.DataList, model.DataParameters{
|
|
Code: value,
|
|
KlineType: 8,
|
|
KlineTimestampEnd: 0,
|
|
QueryKlineNum: 1,
|
|
AdjustType: 0,
|
|
})
|
|
}
|
|
go func() {
|
|
for {
|
|
UrlBatchKline := "https://quote.tradeswitcher.com/quote-b-api/batch-kline?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
|
|
queryStr, err := json.Marshal(&dataList)
|
|
if err != nil {
|
|
return
|
|
}
|
|
bodyStr, err := internal.HttpPost(UrlBatchKline, string(queryStr))
|
|
if err != nil {
|
|
return
|
|
}
|
|
//applogger.Debug("klineBatchPrice data:%v", bodyStr)
|
|
var klineNew model.KlinePostReturnStruct
|
|
if err = json.Unmarshal([]byte(bodyStr), &klineNew); err != nil {
|
|
time.Sleep(3 * time.Second)
|
|
continue
|
|
}
|
|
//applogger.Debug("klineNew count:%v", len(klineNew.Data.KlineList))
|
|
// 将数据写入缓存用来推送K线
|
|
for _, value := range klineNew.Data.KlineList {
|
|
if len(value.KlineData) == 0 {
|
|
continue
|
|
}
|
|
code := model.Check_Code[value.Code]
|
|
if len(code) == 0 {
|
|
code = value.Code
|
|
}
|
|
openPrice := decimal.RequireFromString(value.KlineData[0].OpenPrice).InexactFloat64()
|
|
closePrice := decimal.RequireFromString(value.KlineData[0].ClosePrice).InexactFloat64()
|
|
highPrice := decimal.RequireFromString(value.KlineData[0].HighPrice).InexactFloat64()
|
|
lowPrice := decimal.RequireFromString(value.KlineData[0].LowPrice).InexactFloat64()
|
|
volume := decimal.RequireFromString(value.KlineData[0].Volume).InexactFloat64()
|
|
timestamp := decimal.RequireFromString(value.KlineData[0].Timestamp).IntPart()
|
|
forexJson := []model.ForexJsonData{
|
|
{
|
|
Event: "CAS-D",
|
|
Pair: code,
|
|
Open: openPrice,
|
|
Close: closePrice,
|
|
High: highPrice,
|
|
Low: lowPrice,
|
|
Volume: int(volume),
|
|
Timestamp: timestamp,
|
|
},
|
|
}
|
|
forexDayLastKline <- forexJson
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
}()
|
|
h.ForexMarketBatchDayPriceBatch()
|
|
}
|
|
|
|
// SendAllClientTradeSwitcher send message to all clients
|
|
func (h *Hub) SendAllClientBBOSwitcher(conn *websocket.Conn) {
|
|
for {
|
|
_, msg, err := conn.ReadMessage()
|
|
if err != nil {
|
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
applogger.Error("IsUnexpectedCloseError err:%v", err)
|
|
}
|
|
break
|
|
}
|
|
var messageToJson ResultBbo
|
|
if err = json.Unmarshal([]byte(string(msg)), &messageToJson); err != nil {
|
|
applogger.Error("SendAllClientTradeSwitcher err:%v", err)
|
|
time.Sleep(5 * time.Second)
|
|
continue
|
|
}
|
|
switch messageToJson.CmdID {
|
|
case 22001: // 心跳
|
|
// {"ret":200,"msg":"ok","cmd_id":22001,"seq_id":123,"trace":"3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462"}
|
|
applogger.Debug("Heartbeat results:%v", string(msg))
|
|
case 22999: // 处理订阅数据
|
|
// {"cmd_id":22999,"data":{"code":"EURUSD","seq":"114101723","tick_time":"1732168153591","bids":[{"price":"1.05478","volume":"100000.00"}],"asks":[{"price":"1.05479","volume":"100000.00"}]}}
|
|
code := model.Check_Code[messageToJson.Data.Code]
|
|
if len(code) == 0 {
|
|
code = messageToJson.Data.Code
|
|
}
|
|
tickTime := decimal.RequireFromString(messageToJson.Data.TickTime).IntPart()
|
|
bids := decimal.RequireFromString(messageToJson.Data.Bids[0].Price).InexactFloat64()
|
|
asks := decimal.RequireFromString(messageToJson.Data.Asks[0].Price).InexactFloat64()
|
|
volume := decimal.RequireFromString(messageToJson.Data.Asks[0].Volume).IntPart()
|
|
quotesMsg := &[]model.ForexLastQuote{
|
|
{
|
|
Ev: "C",
|
|
P: code,
|
|
T: tickTime,
|
|
B: bids,
|
|
A: asks,
|
|
X: int(volume),
|
|
},
|
|
}
|
|
msgStr, err := json.Marshal(quotesMsg)
|
|
if err != nil {
|
|
applogger.Error("json.Marshal err:%v", err)
|
|
time.Sleep(5 * time.Second)
|
|
continue
|
|
}
|
|
applogger.Info("Message result:%v", string(msgStr))
|
|
// 广播数据
|
|
h.broadcast <- Message{Topic: topic_c, Content: string(msgStr)}
|
|
default:
|
|
applogger.Debug("Message result:%v", string(msg))
|
|
}
|
|
}
|
|
}
|
|
func (h *Hub) SendAllClientTradeSwitcher(conn *websocket.Conn) {
|
|
for {
|
|
_, msg, err := conn.ReadMessage()
|
|
if err != nil {
|
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
applogger.Error("IsUnexpectedCloseError err:%v", err)
|
|
}
|
|
break
|
|
}
|
|
var messageToJson ResultsTrade
|
|
if err = json.Unmarshal(msg, &messageToJson); err != nil {
|
|
applogger.Error("json.Unmarshal err:%v", err)
|
|
continue
|
|
}
|
|
switch messageToJson.CmdID {
|
|
case 22001: // 处理心跳
|
|
// {"ret":200,"msg":"ok","cmd_id":22001,"seq_id":123456,"trace":"3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462787878"}
|
|
applogger.Debug("Heartbeat results:%v", string(msg))
|
|
case 22998: // 处理订阅数据
|
|
// {"cmd_id":22998,"data":{"code":"XAUUSD","seq":"65087341","tick_time":"1732267727182","price":"2694.84","volume":"95.00","turnover":"0.00","trade_direction":0}}
|
|
code := model.Check_Code[messageToJson.Data.Code]
|
|
if len(code) == 0 {
|
|
code = messageToJson.Data.Code
|
|
}
|
|
tradeMsg := &[]model.ForexTrade{
|
|
{
|
|
Ev: "T",
|
|
Code: code,
|
|
Seq: messageToJson.Data.Seq,
|
|
TickTime: messageToJson.Data.TickTime,
|
|
Price: messageToJson.Data.Price,
|
|
Volume: messageToJson.Data.Volume,
|
|
Turnover: messageToJson.Data.Turnover,
|
|
TradeDirection: messageToJson.Data.TradeDirection,
|
|
},
|
|
}
|
|
msgStr, err := json.Marshal(tradeMsg)
|
|
if err != nil {
|
|
applogger.Error("json.Marshal err:%v", err)
|
|
time.Sleep(5 * time.Second)
|
|
continue
|
|
}
|
|
applogger.Info("Message result:%v", string(msgStr))
|
|
// 广播数据
|
|
h.broadcast <- Message{
|
|
Topic: topic_t,
|
|
Content: string(msgStr),
|
|
}
|
|
h.broadcast <- Message{
|
|
Topic: topic_s,
|
|
Content: string(msgStr),
|
|
}
|
|
default:
|
|
applogger.Debug("Message result:%v", string(msg))
|
|
}
|
|
}
|
|
}
|
|
func (h *Hub) ForexMarketBatchPriceBatch() {
|
|
for dataValue := range forexLastKline {
|
|
applogger.Info("Message result:%v", dataValue)
|
|
message, err := json.Marshal(&dataValue)
|
|
if err != nil {
|
|
time.Sleep(5 * time.Second)
|
|
continue
|
|
}
|
|
// 广播数据
|
|
h.broadcast <- Message{Topic: topic, Content: string(message)}
|
|
}
|
|
}
|
|
func (h *Hub) ForexMarketBatchDayPriceBatch() {
|
|
for dataValue := range forexDayLastKline {
|
|
applogger.Info("Message result:%v", dataValue)
|
|
message, err := json.Marshal(&dataValue)
|
|
if err != nil {
|
|
time.Sleep(5 * time.Second)
|
|
continue
|
|
}
|
|
// 广播数据
|
|
h.broadcast <- Message{Topic: topic_d, Content: string(message)}
|
|
}
|
|
}
|
|
|
|
// ForexMarket content
|
|
func (h *Hub) ForexMarket() {
|
|
ticker := time.NewTicker(time.Second * 10)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
applogger.Info("Execute automatic subscription........")
|
|
if conn := subscribePolygon(); conn != nil {
|
|
h.SendAllClientNew(conn) // 客户端broadcast,消息推送
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// subscribePolygon Link to third-party services
|
|
func subscribePolygon() *websocket.Conn {
|
|
url := fmt.Sprintf("wss://%v/forex", config.Config.ShareGather.PolygonWss)
|
|
applogger.Info("subscribePolygon info Url:%v", url)
|
|
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
|
|
if err != nil {
|
|
applogger.Error("Failed to link to wss server:%v", err)
|
|
return nil
|
|
}
|
|
|
|
return conn
|
|
}
|
|
|
|
// SendAllClientNew send message to all clients
|
|
func (h *Hub) SendAllClientNew(conn *websocket.Conn) {
|
|
for {
|
|
_, msg, err := conn.ReadMessage()
|
|
if err != nil {
|
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
applogger.Error("IsUnexpectedCloseError error: %v", err)
|
|
}
|
|
break
|
|
}
|
|
|
|
applogger.Debug("ReadMessage data info:%v", string(msg))
|
|
|
|
message := string(msg)
|
|
if strings.Contains(message, "Connected Successfully") {
|
|
// 鉴权
|
|
// {"action":"auth","params":"vG4tCD5emAFPkS4kWtXxJntMASyN4dnv"}
|
|
keyStr := model.SendAuthority{
|
|
Action: "auth",
|
|
Params: config.Config.ShareGather.PolygonKey,
|
|
}
|
|
authStr, err := JsonMarshal(keyStr)
|
|
if err != nil {
|
|
break
|
|
}
|
|
if err = Send(conn, string(authStr)); err != nil {
|
|
break
|
|
}
|
|
} else if strings.Contains(message, "authenticated") {
|
|
// 发起实时价格订阅
|
|
// {"action":"subscribe", "params":"CAS.*"}
|
|
subStr := model.SendAuthority{Action: "subscribe", Params: "CAS.*"}
|
|
resultStr, err := JsonMarshal(subStr)
|
|
if err != nil {
|
|
break
|
|
}
|
|
if err = Send(conn, string(resultStr)); err != nil {
|
|
break
|
|
}
|
|
// 发起报价(卖一|买一)订阅
|
|
// {"action":"subscribe", "params":"C.*"}
|
|
subStrC := model.SendAuthority{Action: "subscribe", Params: "C.*"}
|
|
resultStrC, err := JsonMarshal(subStrC)
|
|
if err != nil {
|
|
break
|
|
}
|
|
if err = Send(conn, string(resultStrC)); err != nil {
|
|
break
|
|
}
|
|
} else {
|
|
// 广播数据
|
|
//[{"ev":"CAS","sym":"C.C:EUR-USD","i":"50578","x":4,"p":215.9721,"s":100,"t":1611082428813,"z":3}]
|
|
//[{"ev":"C","p":"PLN/NOK","i":0,"a":2.7225,"b":2.7214,"x":48,"t":1730194133000}]
|
|
var topicTop string
|
|
if strings.Contains(message, "\"ev\":\"CAS\"") {
|
|
topicTop = topic
|
|
} else if strings.Contains(message, "\"ev\":\"C\"") {
|
|
topicTop = topic_c
|
|
}
|
|
h.broadcast <- Message{Topic: topicTop, Content: message}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send Subscribe send message
|
|
func Send(conn *websocket.Conn, data string) error {
|
|
if conn == nil {
|
|
applogger.Error("WebSocket sent error: no connection available")
|
|
return errors.New("WebSocket sent error: no connection available")
|
|
}
|
|
if err := conn.WriteMessage(websocket.TextMessage, []byte(data)); err != nil {
|
|
applogger.Error("WebSocket sent error: data=%s, error=%s", data, err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// JsonMarshal marshal json
|
|
func JsonMarshal(v interface{}) ([]byte, error) {
|
|
str, err := json.Marshal(v)
|
|
if err != nil {
|
|
applogger.Error("json.Marshal error: %v", err)
|
|
return []byte{}, err
|
|
}
|
|
return str, nil
|
|
}
|
|
|
|
// removeClientFromTopic Remove a client from a topic
|
|
func removeClientFromTopic(clients []*Client, c *Client) []*Client {
|
|
for i, client := range clients {
|
|
if client == c {
|
|
return append(clients[:i], clients[i+1:]...)
|
|
}
|
|
}
|
|
return clients
|
|
}
|
|
|
|
// GetMongodbForexCode select forex code
|
|
func GetMongodbForexCode() []string {
|
|
dateList, err := data.MgoFind(data.ForexListBak, bson.M{})
|
|
if err != nil {
|
|
applogger.Error("MgoFind info err: %v", err)
|
|
return []string{}
|
|
}
|
|
//applogger.Info("MgoFind info len: %v", dateList)
|
|
var dateListStr []string
|
|
for _, value := range dateList.([]primitive.M) {
|
|
code := value["code"].(string)
|
|
dateListStr = append(dateListStr, code)
|
|
}
|
|
return dateListStr
|
|
}
|
|
|