448 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			448 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package bitgetservice
 | ||
| 
 | ||
| import (
 | ||
| 	"errors"
 | ||
| 	"fmt"
 | ||
| 	"go-admin/common/global"
 | ||
| 	"go-admin/common/helper"
 | ||
| 	"go-admin/common/interfaces"
 | ||
| 	"go-admin/models"
 | ||
| 	"go-admin/pkg/utility"
 | ||
| 	"strconv"
 | ||
| 	"strings"
 | ||
| 
 | ||
| 	"github.com/bytedance/sonic"
 | ||
| )
 | ||
| 
 | ||
| // BitgetClient Bitget交易所客户端实现
 | ||
| type BitgetClient struct {
 | ||
| 	httpClient   *helper.BitgetClient
 | ||
| 	marketClient *BitgetMarketClient // 公开行情客户端
 | ||
| }
 | ||
| 
 | ||
| // NewBitgetClient 创建新的Bitget客户端实例
 | ||
| func NewBitgetClient(apiKey, apiSecret, passphrase, proxyUrl string) (*BitgetClient, error) {
 | ||
| 	httpClient, err := helper.NewBitgetClient(apiKey, apiSecret, passphrase, proxyUrl)
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	client := &BitgetClient{
 | ||
| 		httpClient: httpClient,
 | ||
| 	}
 | ||
| 
 | ||
| 	// 初始化公开行情客户端
 | ||
| 	marketConfig := DefaultMarketConfig()
 | ||
| 	client.marketClient = NewBitgetMarketClient(marketConfig)
 | ||
| 
 | ||
| 	return client, nil
 | ||
| }
 | ||
| 
 | ||
| // GetExchangeName 获取交易所名称
 | ||
| func (c *BitgetClient) GetExchangeName() string {
 | ||
| 	return global.EXCHANGE_BITGET
 | ||
| }
 | ||
| 
 | ||
| // GetExchangeInfo 获取交易所信息
 | ||
| func (c *BitgetClient) GetExchangeInfo() (interfaces.ExchangeInfoResponse, error) {
 | ||
| 	restApi := NewBitgetRestApi()
 | ||
| 
 | ||
| 	datas, err := restApi.GetSpotSymbols()
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return interfaces.ExchangeInfoResponse{}, err
 | ||
| 	}
 | ||
| 
 | ||
| 	symbols := make([]interfaces.SymbolInfo, 0, len(datas))
 | ||
| 	for _, item := range datas {
 | ||
| 		symbols = append(symbols, interfaces.SymbolInfo{
 | ||
| 			Symbol:     item.Symbol,
 | ||
| 			BaseAsset:  item.BaseCoin,
 | ||
| 			QuoteAsset: item.QuoteCoin,
 | ||
| 			Status:     item.Status,
 | ||
| 			MinQty:     item.MinQty,
 | ||
| 			MaxQty:     item.MaxQty,
 | ||
| 			StepSize:   strconv.Itoa(item.QuantityDigit),
 | ||
| 			TickSize:   strconv.Itoa(item.PriceDigit),
 | ||
| 		})
 | ||
| 	}
 | ||
| 
 | ||
| 	return interfaces.ExchangeInfoResponse{Symbols: symbols}, nil
 | ||
| }
 | ||
| 
 | ||
| // GetSpotTickers 获取所有现货行情
 | ||
| func (c *BitgetClient) GetSpotTickers() ([]models.TradeSet, error) {
 | ||
| 	params := map[string]interface{}{}
 | ||
| 	resp, _, err := c.httpClient.SendSpotRequestAuth("/api/v2/spot/market/tickers", "GET", params)
 | ||
| 	if err != nil {
 | ||
| 		return nil, fmt.Errorf("获取Bitget现货行情列表失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	var response struct {
 | ||
| 		Code string `json:"code"`
 | ||
| 		Msg  string `json:"msg"`
 | ||
| 		Data []struct {
 | ||
| 			Symbol    string `json:"symbol"`
 | ||
| 			High24h   string `json:"high24h"`
 | ||
| 			Low24h    string `json:"low24h"`
 | ||
| 			Close     string `json:"close"`
 | ||
| 			Open      string `json:"open"`
 | ||
| 			Volume24h string `json:"volume24h"`
 | ||
| 			QuoteVol  string `json:"quoteVol"`
 | ||
| 			Change24h string `json:"change24h"`
 | ||
| 			Change    string `json:"change"`
 | ||
| 		} `json:"data"`
 | ||
| 	}
 | ||
| 
 | ||
| 	if err := sonic.Unmarshal(resp, &response); err != nil {
 | ||
| 		return nil, fmt.Errorf("解析Bitget行情列表失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if response.Code != "00000" {
 | ||
| 		return nil, fmt.Errorf("bitget API错误: %s", response.Msg)
 | ||
| 	}
 | ||
| 
 | ||
| 	tradeSets := make([]models.TradeSet, 0, len(response.Data))
 | ||
| 	for _, item := range response.Data {
 | ||
| 		// 解析交易对,获取基础币种和计价币种
 | ||
| 		var coin, currency string
 | ||
| 		if strings.HasSuffix(item.Symbol, "USDT") {
 | ||
| 			coin = strings.TrimSuffix(item.Symbol, "USDT")
 | ||
| 			currency = "USDT"
 | ||
| 		} else if strings.HasSuffix(item.Symbol, "BTC") {
 | ||
| 			coin = strings.TrimSuffix(item.Symbol, "BTC")
 | ||
| 			currency = "BTC"
 | ||
| 		} else if strings.HasSuffix(item.Symbol, "ETH") {
 | ||
| 			coin = strings.TrimSuffix(item.Symbol, "ETH")
 | ||
| 			currency = "ETH"
 | ||
| 		} else {
 | ||
| 			continue // 跳过不支持的交易对
 | ||
| 		}
 | ||
| 
 | ||
| 		tradeSet := models.TradeSet{
 | ||
| 			Coin:        coin,
 | ||
| 			Currency:    currency,
 | ||
| 			LastPrice:   item.Close,
 | ||
| 			HighPrice:   item.High24h,
 | ||
| 			LowPrice:    item.Low24h,
 | ||
| 			Volume:      item.Volume24h,
 | ||
| 			QuoteVolume: item.QuoteVol,
 | ||
| 			OpenPrice:   utility.StringAsFloat(item.Open),
 | ||
| 			PriceChange: utility.StringAsFloat(item.Change),
 | ||
| 		}
 | ||
| 
 | ||
| 		tradeSets = append(tradeSets, tradeSet)
 | ||
| 	}
 | ||
| 
 | ||
| 	return tradeSets, nil
 | ||
| }
 | ||
| 
 | ||
| // GetSpotTicker24h 获取现货24小时行情
 | ||
| func (c *BitgetClient) GetSpotTicker24h(symbol string) (models.Ticker24, error) {
 | ||
| 	params := map[string]interface{}{
 | ||
| 		"symbol": symbol,
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, _, err := c.httpClient.SendSpotRequestAuth("/api/v2/spot/market/ticker", "GET", params)
 | ||
| 	if err != nil {
 | ||
| 		return models.Ticker24{}, fmt.Errorf("获取Bitget现货24h行情失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	var response struct {
 | ||
| 		Code string `json:"code"`
 | ||
| 		Msg  string `json:"msg"`
 | ||
| 		Data struct {
 | ||
| 			Symbol    string `json:"symbol"`
 | ||
| 			High24h   string `json:"high24h"`
 | ||
| 			Low24h    string `json:"low24h"`
 | ||
| 			Close     string `json:"close"`
 | ||
| 			Open      string `json:"open"`
 | ||
| 			Volume24h string `json:"volume24h"`
 | ||
| 			QuoteVol  string `json:"quoteVol"`
 | ||
| 			Change24h string `json:"change24h"`
 | ||
| 			Change    string `json:"change"`
 | ||
| 		} `json:"data"`
 | ||
| 	}
 | ||
| 
 | ||
| 	if err := sonic.Unmarshal(resp, &response); err != nil {
 | ||
| 		return models.Ticker24{}, fmt.Errorf("解析Bitget 24h行情失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if response.Code != "00000" {
 | ||
| 		return models.Ticker24{}, fmt.Errorf("Bitget API错误: %s", response.Msg)
 | ||
| 	}
 | ||
| 
 | ||
| 	ticker := models.Ticker24{
 | ||
| 		HighPrice:     response.Data.High24h,
 | ||
| 		LowPrice:      response.Data.Low24h,
 | ||
| 		LastPrice:     response.Data.Close,
 | ||
| 		OpenPrice:     response.Data.Open,
 | ||
| 		Volume:        response.Data.Volume24h,
 | ||
| 		QuoteVolume:   response.Data.QuoteVol,
 | ||
| 		ChangePercent: response.Data.Change,
 | ||
| 	}
 | ||
| 
 | ||
| 	return ticker, nil
 | ||
| }
 | ||
| 
 | ||
| // PlaceSpotOrder 下现货订单
 | ||
| func (c *BitgetClient) PlaceSpotOrder(params interfaces.SpotOrderParams) (interfaces.SpotOrderResponse, error) {
 | ||
| 	orderParams := map[string]interface{}{
 | ||
| 		"symbol":    params.Symbol,
 | ||
| 		"side":      strings.ToLower(params.Side),
 | ||
| 		"orderType": strings.ToLower(params.Type),
 | ||
| 		"quantity":  params.Quantity.String(),
 | ||
| 	}
 | ||
| 
 | ||
| 	if params.Type == "LIMIT" {
 | ||
| 		orderParams["price"] = params.Price.String()
 | ||
| 	}
 | ||
| 
 | ||
| 	if params.TimeInForce != "" {
 | ||
| 		orderParams["timeInForce"] = params.TimeInForce
 | ||
| 	}
 | ||
| 
 | ||
| 	if params.NewClientOrderId != "" {
 | ||
| 		orderParams["clientOrderId"] = params.NewClientOrderId
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, _, err := c.httpClient.SendSpotRequestAuth("/api/v2/spot/trade/orders", "POST", orderParams)
 | ||
| 	if err != nil {
 | ||
| 		return interfaces.SpotOrderResponse{}, fmt.Errorf("Bitget现货下单失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	var response struct {
 | ||
| 		Code string `json:"code"`
 | ||
| 		Msg  string `json:"msg"`
 | ||
| 		Data struct {
 | ||
| 			OrderId       string `json:"orderId"`
 | ||
| 			ClientOrderId string `json:"clientOrderId"`
 | ||
| 		} `json:"data"`
 | ||
| 	}
 | ||
| 
 | ||
| 	if err := sonic.Unmarshal(resp, &response); err != nil {
 | ||
| 		return interfaces.SpotOrderResponse{}, fmt.Errorf("解析Bitget下单响应失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if response.Code != "00000" {
 | ||
| 		return interfaces.SpotOrderResponse{}, fmt.Errorf("Bitget下单API错误: %s", response.Msg)
 | ||
| 	}
 | ||
| 
 | ||
| 	orderId, _ := strconv.ParseInt(response.Data.OrderId, 10, 64)
 | ||
| 
 | ||
| 	return interfaces.SpotOrderResponse{
 | ||
| 		Symbol:        params.Symbol,
 | ||
| 		OrderId:       orderId,
 | ||
| 		ClientOrderId: response.Data.ClientOrderId,
 | ||
| 		Price:         params.Price.String(),
 | ||
| 		OrigQty:       params.Quantity.String(),
 | ||
| 		Side:          params.Side,
 | ||
| 		Type:          params.Type,
 | ||
| 		Status:        "NEW",
 | ||
| 	}, nil
 | ||
| }
 | ||
| 
 | ||
| // CancelSpotOrder 取消现货订单
 | ||
| func (c *BitgetClient) CancelSpotOrder(symbol, orderID string) error {
 | ||
| 	params := map[string]interface{}{
 | ||
| 		"symbol":  symbol,
 | ||
| 		"orderId": orderID,
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, _, err := c.httpClient.SendSpotRequestAuth("/api/v2/spot/trade/cancel-order", "POST", params)
 | ||
| 	if err != nil {
 | ||
| 		return fmt.Errorf("Bitget取消现货订单失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	var response struct {
 | ||
| 		Code string `json:"code"`
 | ||
| 		Msg  string `json:"msg"`
 | ||
| 	}
 | ||
| 
 | ||
| 	if err := sonic.Unmarshal(resp, &response); err != nil {
 | ||
| 		return fmt.Errorf("解析Bitget取消订单响应失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if response.Code != "00000" {
 | ||
| 		return fmt.Errorf("Bitget取消订单API错误: %s", response.Msg)
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // GetSpotOrder 获取现货订单详情
 | ||
| func (c *BitgetClient) GetSpotOrder(symbol, orderID string) (interfaces.SpotOrderResponse, error) {
 | ||
| 	// 实现订单查询逻辑
 | ||
| 	return interfaces.SpotOrderResponse{}, fmt.Errorf("GetSpotOrder not implemented yet")
 | ||
| }
 | ||
| 
 | ||
| // GetSpotOrders 获取现货订单列表
 | ||
| func (c *BitgetClient) GetSpotOrders(symbol string) ([]interfaces.SpotOrderResponse, error) {
 | ||
| 	params := map[string]interface{}{
 | ||
| 		"symbol": symbol,
 | ||
| 	}
 | ||
| 
 | ||
| 	_, _, err := c.httpClient.SendSpotRequestAuth("/api/v2/spot/trade/open-orders", "GET", params)
 | ||
| 	if err != nil {
 | ||
| 		return nil, fmt.Errorf("获取Bitget现货订单列表失败: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 这里需要根据实际的Bitget API响应格式来解析
 | ||
| 	return []interfaces.SpotOrderResponse{}, nil
 | ||
| }
 | ||
| 
 | ||
| // 合约相关接口实现(暂时返回错误)
 | ||
| func (c *BitgetClient) GetFuturesTicker24h(symbol string) (models.Ticker24, error) {
 | ||
| 	return models.Ticker24{}, fmt.Errorf("futures not implemented yet")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) GetFuturesTickers() ([]models.TradeSet, error) {
 | ||
| 	return nil, fmt.Errorf("futures not implemented yet")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) PlaceFuturesOrder(params interfaces.FuturesOrderParams) (interfaces.FuturesOrderResponse, error) {
 | ||
| 	return interfaces.FuturesOrderResponse{}, fmt.Errorf("futures not implemented yet")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) CancelFuturesOrder(symbol, orderID string) error {
 | ||
| 	return fmt.Errorf("futures not implemented yet")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) GetFuturesOrder(symbol, orderID string) (interfaces.FuturesOrderResponse, error) {
 | ||
| 	return interfaces.FuturesOrderResponse{}, fmt.Errorf("futures not implemented yet")
 | ||
| }
 | ||
| 
 | ||
| // WebSocket相关接口实现(已重构为使用公开行情客户端)
 | ||
| // 注意:这些方法已被重构,现在使用新的公开行情客户端
 | ||
| // 如需私有数据订阅(如订单、持仓),请使用 BitgetWebSocketManager
 | ||
| 
 | ||
| // 旧的订阅方法(保留兼容性,但建议使用新方法)
 | ||
| func (c *BitgetClient) SubscribeSpotTickerLegacy(symbols []string, callback func(models.Ticker24, string, string)) error {
 | ||
| 	return fmt.Errorf("已废弃,请使用 SubscribeSpotTicker(symbol, listener) 方法")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) SubscribeFuturesTickerLegacy(symbols []string, callback func(models.Ticker24, string, string)) error {
 | ||
| 	return fmt.Errorf("已废弃,请使用 SubscribeFuturesTicker(symbol, listener) 方法")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) SubscribeSpotDepthLegacy(symbols []string, callback func(models.DepthBin, string, string)) error {
 | ||
| 	return fmt.Errorf("已废弃,请使用 SubscribeSpotDepth(symbol, listener) 方法")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) SubscribeSpotTradesLegacy(symbols []string, callback func(models.NewDealPush, string, string)) error {
 | ||
| 	return fmt.Errorf("已废弃,请使用 SubscribeSpotTrades(symbol, listener) 方法")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) SubscribeKlineLegacy(symbols []string, interval string, callback func(models.Kline, int, string, string)) error {
 | ||
| 	return fmt.Errorf("已废弃,请使用 SubscribeKline(symbol, interval, listener) 方法")
 | ||
| }
 | ||
| 
 | ||
| func (c *BitgetClient) Close() error {
 | ||
| 	// 关闭HTTP客户端等清理工作
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // SetMarketProxy 设置市场数据代理
 | ||
| func (c *BitgetClient) SetMarketProxy(proxyType, proxyAddress string) {
 | ||
| 	if c.marketClient != nil {
 | ||
| 		c.marketClient.SetProxy(proxyType, proxyAddress)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // SetMarketListeners 设置市场数据监听器
 | ||
| func (c *BitgetClient) SetMarketListeners(msgListener, errorListener OnReceive) {
 | ||
| 	if c.marketClient != nil {
 | ||
| 		c.marketClient.SetListeners(msgListener, errorListener)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // ConnectMarketData 连接市场数据WebSocket
 | ||
| func (c *BitgetClient) ConnectMarketData() error {
 | ||
| 	if c.marketClient == nil {
 | ||
| 		return errors.New("市场数据客户端未初始化")
 | ||
| 	}
 | ||
| 	return c.marketClient.Connect()
 | ||
| }
 | ||
| 
 | ||
| // SubscribeMarketData 订阅市场数据(公开行情)
 | ||
| func (c *BitgetClient) SubscribeMarketData(subscribeReqs []SubscribeReq, listener OnReceive) error {
 | ||
| 	if c.marketClient == nil {
 | ||
| 		return errors.New("市场数据客户端未初始化")
 | ||
| 	}
 | ||
| 	return c.marketClient.Subscribe(subscribeReqs, listener)
 | ||
| }
 | ||
| 
 | ||
| // UnsubscribeMarketData 取消订阅市场数据
 | ||
| func (c *BitgetClient) UnsubscribeMarketData(subscribeReqs []SubscribeReq) error {
 | ||
| 	if c.marketClient == nil {
 | ||
| 		return errors.New("市场数据客户端未初始化")
 | ||
| 	}
 | ||
| 	return c.marketClient.Unsubscribe(subscribeReqs)
 | ||
| }
 | ||
| 
 | ||
| // IsMarketDataConnected 检查市场数据连接状态
 | ||
| func (c *BitgetClient) IsMarketDataConnected() bool {
 | ||
| 	if c.marketClient == nil {
 | ||
| 		return false
 | ||
| 	}
 | ||
| 	return c.marketClient.IsConnected()
 | ||
| }
 | ||
| 
 | ||
| // CloseMarketData 关闭市场数据连接
 | ||
| func (c *BitgetClient) CloseMarketData() error {
 | ||
| 	if c.marketClient == nil {
 | ||
| 		return nil
 | ||
| 	}
 | ||
| 	return c.marketClient.Close()
 | ||
| }
 | ||
| 
 | ||
| // SubscribeSpotTicker 订阅现货行情(使用公开行情客户端)
 | ||
| func (c *BitgetClient) SubscribeSpotTicker(symbol string, listener OnReceive) error {
 | ||
| 	subscribeReq := SubscribeReq{
 | ||
| 		InstType: "SPOT",
 | ||
| 		Channel:  "ticker",
 | ||
| 		InstId:   symbol,
 | ||
| 	}
 | ||
| 	return c.SubscribeMarketData([]SubscribeReq{subscribeReq}, listener)
 | ||
| }
 | ||
| 
 | ||
| // SubscribeFuturesTicker 订阅合约行情(使用公开行情客户端)
 | ||
| func (c *BitgetClient) SubscribeFuturesTicker(symbol string, listener OnReceive) error {
 | ||
| 	subscribeReq := SubscribeReq{
 | ||
| 		InstType: "UMCBL",
 | ||
| 		Channel:  "ticker",
 | ||
| 		InstId:   symbol,
 | ||
| 	}
 | ||
| 	return c.SubscribeMarketData([]SubscribeReq{subscribeReq}, listener)
 | ||
| }
 | ||
| 
 | ||
| // SubscribeSpotDepth 订阅现货深度(使用公开行情客户端)
 | ||
| func (c *BitgetClient) SubscribeSpotDepth(symbol string, listener OnReceive) error {
 | ||
| 	subscribeReq := SubscribeReq{
 | ||
| 		InstType: "SPOT",
 | ||
| 		Channel:  "books",
 | ||
| 		InstId:   symbol,
 | ||
| 	}
 | ||
| 	return c.SubscribeMarketData([]SubscribeReq{subscribeReq}, listener)
 | ||
| }
 | ||
| 
 | ||
| // SubscribeSpotTrades 订阅现货成交(使用公开行情客户端)
 | ||
| func (c *BitgetClient) SubscribeSpotTrades(symbol string, listener OnReceive) error {
 | ||
| 	subscribeReq := SubscribeReq{
 | ||
| 		InstType: "SPOT",
 | ||
| 		Channel:  "trade",
 | ||
| 		InstId:   symbol,
 | ||
| 	}
 | ||
| 	return c.SubscribeMarketData([]SubscribeReq{subscribeReq}, listener)
 | ||
| }
 | ||
| 
 | ||
| // SubscribeKline 订阅K线数据(使用公开行情客户端)
 | ||
| func (c *BitgetClient) SubscribeKline(symbol, interval string, listener OnReceive) error {
 | ||
| 	subscribeReq := SubscribeReq{
 | ||
| 		InstType: "SPOT",
 | ||
| 		Channel:  "candle" + interval,
 | ||
| 		InstId:   symbol,
 | ||
| 	}
 | ||
| 	return c.SubscribeMarketData([]SubscribeReq{subscribeReq}, listener)
 | ||
| }
 |