blockchain/internal/repository/binance_adapter.go

150 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository
import (
"blockchain/internal/domain/blockchain"
"blockchain/internal/domain/repository"
"fmt"
"github.com/goccy/go-json"
"strings"
"time"
)
type BinanceAdapterParam struct {
Name string
WsURL string
ClientPingInterval time.Duration
ReadDeadline time.Duration
}
type BinanceAdapter struct {
name string
wsUrl string
clientPingInterval time.Duration
readDeadline time.Duration
}
func NewBinanceAdapter(param BinanceAdapterParam) repository.ExchangeAdapter {
return &BinanceAdapter{
name: param.Name,
wsUrl: param.WsURL,
clientPingInterval: param.ClientPingInterval,
readDeadline: param.ReadDeadline,
}
}
func (repo *BinanceAdapter) Name() string {
return repo.name
}
func (repo *BinanceAdapter) URL() string {
return repo.wsUrl
}
// NormalizeSymbol 我系統內部也是統一用 大寫 BTCUSDT
func (repo *BinanceAdapter) NormalizeSymbol(internal string) string {
// 驗證:必須全部是大寫英文,且長度至少 6如 BTCUSDT
if !isAllUpperAlpha(internal) {
panic(fmt.Sprintf("invalid symbol format: %s (must be all uppercase letters, e.g., BTCUSDT)", internal))
}
// Binance 訂閱格式是小寫
return strings.ToLower(internal)
}
func (repo *BinanceAdapter) DenormalizeSymbol(external string) string {
// Binance 回傳小寫,轉回內部格式(大寫)
return strings.ToUpper(external)
}
func (repo *BinanceAdapter) BuildSubscribe(symbols []string, interval blockchain.Interval) ([][]byte, error) {
params := make([]string, 0, len(symbols))
for _, s := range symbols {
params = append(params, fmt.Sprintf("%s@kline_%s", repo.NormalizeSymbol(s), interval))
}
req := map[string]any{"method": "SUBSCRIBE", "params": params, "id": time.Now().UnixNano()}
b, _ := json.Marshal(req)
return [][]byte{b}, nil
}
func (repo *BinanceAdapter) BuildUnsubscribe(symbols []string, interval blockchain.Interval) ([][]byte, error) {
params := make([]string, 0, len(symbols))
for _, s := range symbols {
params = append(params, fmt.Sprintf("%s@kline_%s", repo.NormalizeSymbol(s), interval))
}
req := map[string]any{"method": "UNSUBSCRIBE", "params": params, "id": time.Now().UnixNano()}
b, _ := json.Marshal(req)
return [][]byte{b}, nil
}
func (repo *BinanceAdapter) ParseKLines(msg []byte) ([]blockchain.Kline, error) {
res := BinanceKlineEvent{}
if err := json.Unmarshal(msg, &res); err != nil {
return nil, nil
} // 不是 kline 就略過
if res.EventType != "kline" {
return nil, nil
}
return []blockchain.Kline{{
Exchange: repo.Name(),
Symbol: repo.DenormalizeSymbol(res.Symbol),
Interval: blockchain.Interval(res.K.Interval),
OpenTime: res.K.StartTime,
CloseTime: res.K.CloseTime,
Open: res.K.Open, High: res.K.High, Low: res.K.Low, Close: res.K.Close, Volume: res.K.Volume,
Final: res.K.Final,
Raw: msg,
}}, nil
}
func (repo *BinanceAdapter) ClientPingInterval() time.Duration {
return repo.clientPingInterval
}
func (repo *BinanceAdapter) ReadDeadline() time.Duration {
return repo.readDeadline
}
// 工具函式:檢查是否全為大寫英文字母
func isAllUpperAlpha(s string) bool {
if len(s) == 0 {
return false
}
for _, r := range s {
if r < 'A' || r > 'Z' {
return false
}
}
return true
}
// BinanceKlineEvent 代表幣安 WebSocket 推送的 kline 事件
// 範例:{"e":"kline","E":..., "s":"BTCUSDT", "k":{...}}
type BinanceKlineEvent struct {
EventType string `json:"e"` // 事件類型,固定為 "kline"
EventTime int64 `json:"E"` // 事件時間 (毫秒 UNIX 時戳)
Symbol string `json:"s"` // 交易對,例如 "BTCUSDT"
K BinanceKlineBody `json:"k"` // K 線細節
}
// BinanceKlineBody 對應 "k" 物件(單一根 K 線的詳細資訊)
type BinanceKlineBody struct {
StartTime int64 `json:"t"` // 本根 K 線開盤時間 (ms)
CloseTime int64 `json:"T"` // 本根 K 線關閉時間/結束時間 (ms)
Symbol string `json:"s"` // 交易對(與外層 s 相同)
Interval string `json:"i"` // 週期,例如 "1m","5m","1h","1d","1M"(月線)
FirstTradeID int64 `json:"f"` // 本根K線包含的第一筆成交ID
LastTradeID int64 `json:"L"` // 本根K線包含的最後一筆成交ID
Open string `json:"o"` // 開盤價(字串,避免浮點誤差)
Close string `json:"c"` // 收盤價(字串)
High string `json:"h"` // 最高價(字串)
Low string `json:"l"` // 最低價(字串)
Volume string `json:"v"` // 交易量Base 資產數量,字串)
TradeCount int64 `json:"n"` // 成交筆數
Final bool `json:"x"` // 是否已收盤true=此根K線已完成false=仍在形成中)
QuoteAssetVolume string `json:"q"` // 交易額Quote 資產成交額,字串)
TakerBuyBaseVolume string `json:"V"` // 主動買單成交量Base字串
TakerBuyQuoteVolume string `json:"Q"` // 主動買單成交額Quote字串
Ignore string `json:"B"` // 保留欄位(可忽略)
}