Go语言对接股票、黄金、外汇API实时数据教程

一、对接前准备

1.1 行情服务地址

1.2 技术选型

  • WebSocket客户端:gorilla/websocket

  • HTTP请求:net/http

  • JSON解析:encoding/json

  • 并发控制:goroutine + channel

二、WebSocket实时行情推送

2.1 核心代码实现

2.1.1 客户端连接管理
Go 复制代码
package marketdata

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"

    "github.com/gorilla/websocket"
)

type Client struct {
    conn        *websocket.Conn
    serverURL   string
    mu          sync.RWMutex
    isConnected bool
    handler     MessageHandler
    cancel      context.CancelFunc
}

type MessageHandler interface {
    HandleMarketData(data *MarketData)
    HandleHeartbeat(timestamp int64)
}

func NewClient(serverURL string, handler MessageHandler) *Client {
    return &Client{
        serverURL: serverURL,
        handler:   handler,
    }
}

func (c *Client) Connect() error {
    c.mu.Lock()
    defer c.mu.Unlock()

    if c.isConnected {
        return nil
    }

    dialer := websocket.Dialer{
        HandshakeTimeout: 10 * time.Second,
    }

    conn, _, err := dialer.Dial(c.serverURL, nil)
    if err != nil {
        return fmt.Errorf("连接失败: %v", err)
    }

    c.conn = conn
    c.isConnected = true

    ctx, cancel := context.WithCancel(context.Background())
    c.cancel = cancel

    go c.readPump(ctx)
    go c.heartbeat(ctx)

    log.Println("行情服务连接成功")
    return nil
}
2.1.2 消息处理器
Go 复制代码
type MarketData struct {
    StockCode  string  `json:"StockCode"`
    Price      float64 `json:"Price"`
    Open       float64 `json:"Open"`
    LastClose  float64 `json:"LastClose"`
    High       float64 `json:"High"`
    Low        float64 `json:"Low"`
    Time       string  `json:"Time"`
    LastTime   int64   `json:"LastTime"`
    BP1        float64 `json:"BP1"`
    BV1        float64 `json:"BV1"`
    SP1        float64 `json:"SP1"`
    SV1        float64 `json:"SV1"`
    TotalVol   int64   `json:"TotalVol"`
    DiffRate   float64 `json:"DiffRate"`
    Diff       float64 `json:"Diff"`
}

type DefaultHandler struct{}

func (h *DefaultHandler) HandleMarketData(data *MarketData) {
    log.Printf("行情数据: %s - 最新价: %.4f, 涨跌幅: %.2f%%", 
        data.StockCode, data.Price, data.DiffRate)
}

func (h *DefaultHandler) HandleHeartbeat(timestamp int64) {
    log.Printf("心跳响应: %d", timestamp)
}

func (c *Client) readPump(ctx context.Context) {
    defer func() {
        c.mu.Lock()
        c.isConnected = false
        c.mu.Unlock()
    }()

    for {
        select {
        case <-ctx.Done():
            return
        default:
            _, message, err := c.conn.ReadMessage()
            if err != nil {
                log.Printf("读取消息错误: %v", err)
                c.reconnect()
                return
            }

            var msg map[string]interface{}
            if err := json.Unmarshal(message, &msg); err != nil {
                log.Printf("JSON解析错误: %v", err)
                continue
            }

            c.handleMessage(msg)
        }
    }
}

func (c *Client) handleMessage(msg map[string]interface{}) {
    if ping, exists := msg["ping"]; exists {
        // 处理心跳
        if timestamp, ok := ping.(float64); ok {
            c.handler.HandleHeartbeat(int64(timestamp))
            c.sendPong(int64(timestamp))
        }
        return
    }

    if body, exists := msg["body"]; exists {
        // 处理行情数据
        if bodyMap, ok := body.(map[string]interface{}); ok {
            c.processMarketData(bodyMap)
        }
    }
}

func (c *Client) processMarketData(body map[string]interface{}) {
    jsonData, err := json.Marshal(body)
    if err != nil {
        log.Printf("序列化行情数据错误: %v", err)
        return
    }

    var marketData MarketData
    if err := json.Unmarshal(jsonData, &marketData); err != nil {
        log.Printf("解析行情数据错误: %v", err)
        return
    }

    c.handler.HandleMarketData(&marketData)
}

2.2 心跳机制

Go 复制代码
func (c *Client) heartbeat(ctx context.Context) {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            timestamp := time.Now().Unix()
            heartbeat := map[string]interface{}{
                "ping": timestamp,
            }
            if err := c.sendJSON(heartbeat); err != nil {
                log.Printf("发送心跳失败: %v", err)
                c.reconnect()
                return
            }
        }
    }
}

func (c *Client) sendPong(timestamp int64) error {
    pong := map[string]interface{}{
        "pong": timestamp,
    }
    return c.sendJSON(pong)
}

func (c *Client) sendJSON(data interface{}) error {
    c.mu.RLock()
    defer c.mu.RUnlock()

    if !c.isConnected {
        return fmt.Errorf("连接未就绪")
    }

    return c.conn.WriteJSON(data)
}

2.3 数据订阅

Go 复制代码
func (c *Client) Subscribe(symbols []string) error {
    subscription := map[string]interface{}{
        "Key": symbols,
    }
    return c.sendJSON(subscription)
}

2.4 断线重连策略

Go 复制代码
func (c *Client) reconnect() {
    c.mu.Lock()
    if c.conn != nil {
        c.conn.Close()
        c.conn = nil
    }
    c.isConnected = false
    c.mu.Unlock()

    retryCount := 0
    maxRetries := 5

    for retryCount < maxRetries {
        select {
        case <-time.After(time.Duration(retryCount) * time.Second):
            if err := c.Connect(); err == nil {
                log.Println("重连成功")
                return
            }
            retryCount++
            log.Printf("重连尝试 %d/%d 失败", retryCount, maxRetries)
        }
    }

    log.Println("达到最大重连次数,连接终止")
}

func (c *Client) Disconnect() {
    if c.cancel != nil {
        c.cancel()
    }

    c.mu.Lock()
    defer c.mu.Unlock()

    if c.conn != nil {
        c.conn.Close()
        c.conn = nil
    }
    c.isConnected = false
}

三、K线数据接口

3.1 接口调用封装

Go 复制代码
type KLineData struct {
    Timestamp   int64   `json:"timestamp"`
    Open        float64 `json:"open"`
    High        float64 `json:"high"`
    Low         float64 `json:"low"`
    Close       float64 `json:"close"`
    FormattedTime string `json:"formatted_time"`
    Volume      int64   `json:"volume"`
}

type KLineClient struct {
    baseURL string
    client  *http.Client
}

func NewKLineClient(baseURL string) *KLineClient {
    return &KLineClient{
        baseURL: baseURL,
        client: &http.Client{
            Timeout: 30 * time.Second,
        },
    }
}

func (k *KLineClient) GetKLineData(code, period string, rows int) ([]KLineData, error) {
    url := fmt.Sprintf("%s/redis.php?code=%s&time=%s&rows=%d", 
        k.baseURL, code, period, rows)

    resp, err := k.client.Get(url)
    if err != nil {
        return nil, fmt.Errorf("请求K线数据失败: %v", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode)
    }

    var rawData [][]interface{}
    decoder := json.NewDecoder(resp.Body)
    if err := decoder.Decode(&rawData); err != nil {
        return nil, fmt.Errorf("解析K线数据失败: %v", err)
    }

    return k.parseKLineData(rawData)
}

func (k *KLineClient) parseKLineData(rawData [][]interface{}) ([]KLineData, error) {
    var klineData []KLineData

    for _, item := range rawData {
        if len(item) < 7 {
            continue
        }

        data := KLineData{}

        // 解析时间戳
        if timestamp, ok := item[0].(float64); ok {
            data.Timestamp = int64(timestamp)
        }

        // 解析价格数据
        if open, ok := item[1].(float64); ok {
            data.Open = open
        }
        if high, ok := item[2].(float64); ok {
            data.High = high
        }
        if low, ok := item[3].(float64); ok {
            data.Low = low
        }
        if close, ok := item[4].(float64); ok {
            data.Close = close
        }

        // 解析格式化时间
        if formattedTime, ok := item[5].(string); ok {
            data.FormattedTime = formattedTime
        }

        // 解析成交量
        if volume, ok := item[6].(float64); ok {
            data.Volume = int64(volume)
        }

        klineData = append(klineData, data)
    }

    return klineData, nil
}

3.2 使用示例

Go 复制代码
func main() {
    // 实时行情示例
    handler := &DefaultHandler{}
    client := NewClient("ws://39.107.99.235:1008", handler)
    
    if err := client.Connect(); err != nil {
        log.Fatal(err)
    }
    defer client.Disconnect()

    // 订阅品种
    symbols := []string{"btcusdt", "ethusdt", "xrpusdt"}
    if err := client.Subscribe(symbols); err != nil {
        log.Printf("订阅失败: %v", err)
    }

    // K线数据示例
    klineClient := NewKLineClient("http://39.107.99.235:1008")
    klineData, err := klineClient.GetKLineData("fx_sgbpusd", "1m", 40)
    if err != nil {
        log.Printf("获取K线数据失败: %v", err)
    } else {
        for _, data := range klineData {
            fmt.Printf("时间: %s, 开: %.4f, 高: %.4f, 低: %.4f, 收: %.4f\n",
                data.FormattedTime, data.Open, data.High, data.Low, data.Close)
        }
    }

    // 保持程序运行
    select {}
}

四、数据管理最佳实践

4.1 连接状态监控

Go 复制代码
type ConnectionMonitor struct {
    clients    []*Client
    statusChan chan string
}

func (m *ConnectionMonitor) Start() {
    go m.monitorConnections()
}

func (m *ConnectionMonitor) monitorConnections() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()

    for range ticker.C {
        for i, client := range m.clients {
            if !client.IsConnected() {
                log.Printf("客户端 %d 连接断开,尝试重连", i)
                go client.Connect()
            }
        }
    }
}

4.2 数据缓存处理

Go 复制代码
type DataCache struct {
    cache sync.Map
    ttl   time.Duration
}

func NewDataCache(ttl time.Duration) *DataCache {
    return &DataCache{ttl: ttl}
}

func (d *DataCache) Store(symbol string, data *MarketData) {
    d.cache.Store(symbol, &CacheItem{
        Data:      data,
        Timestamp: time.Now(),
    })
}

func (d *DataCache) Load(symbol string) (*MarketData, bool) {
    if item, ok := d.cache.Load(symbol); ok {
        cacheItem := item.(*CacheItem)
        if time.Since(cacheItem.Timestamp) < d.ttl {
            return cacheItem.Data, true
        }
        d.cache.Delete(symbol)
    }
    return nil, false
}

五、总结

本文详细介绍了使用Go语言对接金融实时行情数据的完整方案,重点突出了以下几个核心要点:

  1. 稳定可靠的连接管理:通过完善的连接状态监控和自动重连机制,确保行情服务的持续稳定。

  2. 高效的数据处理:利用Go语言的并发特性,实现多品种数据的并行处理,提升系统吞吐量。

  3. 灵活的可扩展架构:采用接口设计,便于后续扩展新的数据源和处理逻辑。

  4. 生产环境就绪:包含完整的错误处理、日志记录和性能监控,满足企业级应用需求。

该方案已在多个量化交易系统中得到实际验证,能够稳定支撑高并发、低延迟的行情数据需求。开发者可以根据具体业务场景,在此基础框架上进行定制化开发。

相关推荐
kfyty7251 小时前
loveqq 作为网关框架时如何修改请求体 / 响应体,和 spring 又有什么区别?
后端·架构
aiopencode2 小时前
Swift 加密工具推荐,构建可落地的多层安全体系(源码混淆+IPA 加固+动态对抗+映射治理)
后端
橘子真甜~2 小时前
C/C++ Linux网络编程5 - 网络IO模型与select解决客户端并发连接问题
linux·运维·服务器·c语言·开发语言·网络·c++
Moe4882 小时前
合并Pdf、excel、图片、word为单个Pdf文件的工具类(技术点的选择与深度解析)
java·后端
又过一个秋2 小时前
CyberRT Transport传输层设计
后端
霖002 小时前
ZYNQ——ultra scale+ IP 核详解与配置
服务器·开发语言·网络·笔记·网络协议·tcp/ip
Java水解2 小时前
20个高级Java开发面试题及答案!
spring boot·后端·面试
Moe4882 小时前
合并Pdf、excel、图片、word为单个Pdf文件的工具类(拿来即用版)
java·后端
bcbnb2 小时前
手机崩溃日志导出的工程化方法,构建多工具协同的跨平台日志获取与分析体系(iOS/Android 全场景 2025 进阶版)
后端