# Go 量化数据源集成方案 - 完整代码实现 > **版本**:v1.0 > **更新时间**:2026-03-06 > **核心功能**:数据源管理器 + 三级速率限制器 + 指数退避 + 自动降级 + 数据类型标准化 > **覆盖**:325 个验证通过端点,19 个数据平台 > **作者**:Manus AI --- ## 目录 - [一、架构总览](#一架构总览) - [二、核心数据结构定义](#二核心数据结构定义) - [三、三级速率限制器实现](#三三级速率限制器实现) - [四、指数退避与自动降级](#四指数退避与自动降级) - [五、数据源管理器](#五数据源管理器) - [六、具体数据源适配器](#六具体数据源适配器) - [七、数据采集调度器](#七数据采集调度器) - [八、配置文件](#八配置文件) - [九、完整目录结构](#九完整目录结构) --- ## 一、架构总览 ``` ┌─────────────────────────────────────────────────────────────┐ │ 数据采集调度器 (Scheduler) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 实时采集 │ │ 定时采集 │ │ 低频采集 │ │ 事件触发 │ │ │ │ 1-5s │ │ 5m-1h │ │ 1d-1w │ │ 按需 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ └──────────────┴──────────────┴──────────────┘ │ │ │ │ │ ┌───────────▼───────────┐ │ │ │ 数据源管理器 (Manager) │ │ │ │ - 注册/发现 │ │ │ │ - 健康检查 │ │ │ │ - 自动降级 │ │ │ └───────────┬───────────┘ │ │ │ │ │ ┌─────────────────────┼─────────────────────┐ │ │ │ 三级速率限制器 (RateLimiter) │ │ │ │ L1: 全局限制 (500/min) │ │ │ │ L2: 平台限制 (Binance 1600/min) │ │ │ │ L3: 端点权重 (depth=2, klines=5) │ │ │ └─────────────────────┬─────────────────────┘ │ │ │ │ │ ┌─────────┬───────────┼───────────┬─────────┐ │ │ ▼ ▼ ▼ ▼ ▼ │ │ Binance OKX Gate.io Yahoo DeFiLlama │ │ Adapter Adapter Adapter Adapter Adapter ... │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 二、核心数据结构定义 ```go package types import "time" // ─── 数据类型枚举 ──────────────────────────────────────────────── type DataType string const ( TypeKline DataType = "kline" // T01 K线/OHLCV TypeTicker DataType = "ticker" // T02 Ticker行情 TypeOrderbook DataType = "orderbook" // T03 订单簿 TypeTrades DataType = "trades" // T04 最近成交 TypeFundingRate DataType = "funding_rate" // T05 资金费率 TypeOpenInterest DataType = "open_interest" // T06 未平仓量 TypeLongShort DataType = "long_short" // T07 多空比 TypeOptions DataType = "options" // T08 期权数据 TypeVolatility DataType = "volatility" // T09 波动率指数 TypeOnchain DataType = "onchain" // T10 链上统计 TypeDeFiTVL DataType = "defi_tvl" // T11 DeFi TVL TypeStablecoin DataType = "stablecoin" // T12 稳定币 TypeSocial DataType = "social" // T13 社交情绪 TypeFearGreed DataType = "fear_greed" // T14 恐惧贪婪 TypeMacro DataType = "macro" // T15 宏观经济 TypeExchangeInfo DataType = "exchange_info" // T16 交易规则 ) // ─── 标准化数据结构 ────────────────────────────────────────────── // Kline K线数据 type Kline struct { Symbol string `json:"symbol"` Interval string `json:"interval"` OpenTime time.Time `json:"open_time"` Open float64 `json:"open"` High float64 `json:"high"` Low float64 `json:"low"` Close float64 `json:"close"` Volume float64 `json:"volume"` Source string `json:"source"` } // Ticker 行情数据 type Ticker struct { Symbol string `json:"symbol"` Last float64 `json:"last"` Bid float64 `json:"bid"` Ask float64 `json:"ask"` High24h float64 `json:"high_24h"` Low24h float64 `json:"low_24h"` Volume24h float64 `json:"volume_24h"` Change24h float64 `json:"change_24h_pct"` MarketCap float64 `json:"market_cap,omitempty"` Timestamp time.Time `json:"timestamp"` Source string `json:"source"` } // FundingRate 资金费率 type FundingRate struct { Symbol string `json:"symbol"` Rate float64 `json:"rate"` MarkPrice float64 `json:"mark_price"` NextFundingTime time.Time `json:"next_funding_time"` Timestamp time.Time `json:"timestamp"` Source string `json:"source"` } // OpenInterest 未平仓量 type OpenInterest struct { Symbol string `json:"symbol"` OI float64 `json:"open_interest"` OIValue float64 `json:"open_interest_value_usd"` Timestamp time.Time `json:"timestamp"` Source string `json:"source"` } // SocialSentiment 社交情绪 type SocialSentiment struct { Platform string `json:"platform"` Subreddit string `json:"subreddit,omitempty"` PostCount int `json:"post_count"` AvgScore float64 `json:"avg_score"` AvgComments float64 `json:"avg_comments"` TopTitle string `json:"top_title"` Subscribers int64 `json:"subscribers,omitempty"` Timestamp time.Time `json:"timestamp"` Source string `json:"source"` } // FearGreedIndex 恐惧贪婪指数 type FearGreedIndex struct { Value int `json:"value"` Classification string `json:"classification"` Avg7d float64 `json:"avg_7d"` Timestamp time.Time `json:"timestamp"` Source string `json:"source"` } // MacroData 宏观经济数据 type MacroData struct { Symbol string `json:"symbol"` Name string `json:"name"` Price float64 `json:"price"` Change float64 `json:"change_pct"` Currency string `json:"currency"` Category string `json:"category"` // index/commodity/forex/bond/stock/etf Timestamp time.Time `json:"timestamp"` Source string `json:"source"` } // ─── 数据源配置 ────────────────────────────────────────────────── // SourceConfig 数据源配置 type SourceConfig struct { Name string `json:"name"` Platform string `json:"platform"` BaseURL string `json:"base_url"` Endpoint string `json:"endpoint"` DataType DataType `json:"data_type"` RateLimit int `json:"rate_limit_per_min"` Weight int `json:"weight"` Timeout time.Duration `json:"timeout"` NeedAPIKey bool `json:"need_api_key"` APIKeyEnv string `json:"api_key_env,omitempty"` DocURL string `json:"doc_url"` Priority int `json:"priority"` // 1=主, 2=备用1, 3=备用2 FallbackTo string `json:"fallback_to,omitempty"` } // SourceHealth 数据源健康状态 type SourceHealth struct { Name string `json:"name"` IsHealthy bool `json:"is_healthy"` LastSuccess time.Time `json:"last_success"` LastError time.Time `json:"last_error"` LastLatency time.Duration `json:"last_latency"` AvgLatency time.Duration `json:"avg_latency"` SuccessCount int64 `json:"success_count"` ErrorCount int64 `json:"error_count"` ConsecutiveErr int `json:"consecutive_errors"` RateLimited bool `json:"rate_limited"` } ``` --- ## 三、三级速率限制器实现 ```go package ratelimit import ( "context" "fmt" "math" "math/rand" "sync" "time" ) // ─── 令牌桶限制器 ──────────────────────────────────────────────── // TokenBucket 令牌桶算法实现 type TokenBucket struct { mu sync.Mutex tokens float64 maxTokens float64 refillRate float64 // tokens per second lastRefill time.Time } func NewTokenBucket(maxTokens float64, refillRate float64) *TokenBucket { return &TokenBucket{ tokens: maxTokens, maxTokens: maxTokens, refillRate: refillRate, lastRefill: time.Now(), } } func (tb *TokenBucket) Take(ctx context.Context, weight int) error { for { tb.mu.Lock() tb.refill() if tb.tokens >= float64(weight) { tb.tokens -= float64(weight) tb.mu.Unlock() return nil } // 计算等待时间 deficit := float64(weight) - tb.tokens waitTime := time.Duration(deficit/tb.refillRate*1000) * time.Millisecond tb.mu.Unlock() select { case <-ctx.Done(): return ctx.Err() case <-time.After(waitTime): // 重试 } } } func (tb *TokenBucket) refill() { now := time.Now() elapsed := now.Sub(tb.lastRefill).Seconds() tb.tokens = math.Min(tb.maxTokens, tb.tokens+elapsed*tb.refillRate) tb.lastRefill = now } // ─── 三级速率限制器 ────────────────────────────────────────────── // ThreeTierRateLimiter 三级速率限制器 type ThreeTierRateLimiter struct { global *TokenBucket // L1: 全局限制 platform map[string]*TokenBucket // L2: 平台限制 endpoint map[string]*TokenBucket // L3: 端点限制 mu sync.RWMutex } func NewThreeTierRateLimiter() *ThreeTierRateLimiter { rl := &ThreeTierRateLimiter{ global: NewTokenBucket(500, 500.0/60.0), // 500/min platform: make(map[string]*TokenBucket), endpoint: make(map[string]*TokenBucket), } // 初始化各平台限制(使用安全阈值 = 官方限制 * 65%) platformLimits := map[string][2]float64{ "binance": {1600, 1600.0 / 60.0}, // 2400*0.65 = 1560 ≈ 1600/min "okx": {420, 420.0 / 60.0}, // 20/2s * 0.7 = 14/2s = 420/min "bybit": {960, 960.0 / 60.0}, // 120/5s * 0.67 = 80/5s = 960/min "gateio": {600, 600.0 / 60.0}, // 900*0.67 = 600/min "kraken": {40, 40.0 / 60.0}, // 1/s * 0.67 = 40/min "coingecko": {20, 20.0 / 60.0}, // 30*0.67 = 20/min "coinpaprika": {400, 400.0 / 60.0}, // 10/s * 0.67 = 400/min "defillama": {20, 20.0 / 60.0}, // ~30*0.67 = 20/min "deribit": {840, 840.0 / 60.0}, // 20/s * 0.7 = 14/s = 840/min "reddit": {6, 6.0 / 60.0}, // 10*0.6 = 6/min "yahoo": {1300, 1300.0 / 60.0}, // 2000/h * 0.65 = 1300/h "dexscreener": {200, 200.0 / 60.0}, // 300*0.67 = 200/min "hyperliquid": {800, 800.0 / 60.0}, // 1200*0.67 = 800/min "mempool": {40, 40.0 / 60.0}, // ~60*0.67 = 40/min "blockchair": {20, 20.0 / 60.0}, // 30*0.67 = 20/min "blockchain": {20, 20.0 / 60.0}, // ~30*0.67 = 20/min "worldbank": {20, 20.0 / 60.0}, // ~30*0.67 = 20/min "alternative": {6, 6.0 / 60.0}, // ~10*0.6 = 6/min "nitter": {3, 3.0 / 60.0}, // ~5*0.6 = 3/min } for name, limits := range platformLimits { rl.platform[name] = NewTokenBucket(limits[0], limits[1]) } return rl } // Acquire 获取请求许可(三级检查) func (rl *ThreeTierRateLimiter) Acquire(ctx context.Context, platform string, endpoint string, weight int) error { // L1: 全局限制 if err := rl.global.Take(ctx, 1); err != nil { return fmt.Errorf("global rate limit: %w", err) } // L2: 平台限制 rl.mu.RLock() pb, ok := rl.platform[platform] rl.mu.RUnlock() if ok { if err := pb.Take(ctx, 1); err != nil { return fmt.Errorf("platform rate limit [%s]: %w", platform, err) } } // L3: 端点权重限制(仅 Binance 等有权重系统的平台) if weight > 1 { key := platform + ":" + endpoint rl.mu.Lock() eb, ok := rl.endpoint[key] if !ok { eb = NewTokenBucket(float64(weight*10), float64(weight*10)/60.0) rl.endpoint[key] = eb } rl.mu.Unlock() if err := eb.Take(ctx, weight); err != nil { return fmt.Errorf("endpoint weight limit [%s]: %w", key, err) } } return nil } ``` --- ## 四、指数退避与自动降级 ```go package retry import ( "context" "fmt" "math" "math/rand" "net/http" "time" ) // RetryConfig 重试配置 type RetryConfig struct { MaxRetries int // 最大重试次数(默认 4) BaseDelay time.Duration // 基础延迟(默认 1s) MaxDelay time.Duration // 最大延迟(默认 30s) JitterFraction float64 // 抖动比例(默认 0.3) } var DefaultRetryConfig = RetryConfig{ MaxRetries: 4, BaseDelay: 1 * time.Second, MaxDelay: 30 * time.Second, JitterFraction: 0.3, } // ExponentialBackoff 指数退避重试 func ExponentialBackoff(ctx context.Context, cfg RetryConfig, fn func() (*http.Response, error)) (*http.Response, error) { var lastErr error for attempt := 0; attempt <= cfg.MaxRetries; attempt++ { resp, err := fn() if err == nil && resp.StatusCode == 200 { return resp, nil } // 判断是否应该重试 if resp != nil { switch resp.StatusCode { case 429: // Rate Limited // 读取 Retry-After 头 if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" { if d, err := time.ParseDuration(retryAfter + "s"); err == nil { time.Sleep(d) continue } } case 418: // IP Banned (Binance) return nil, fmt.Errorf("IP banned by exchange, status 418") case 451: // Region Restricted return nil, fmt.Errorf("region restricted, status 451") case 500, 502, 503, 504: // Server Error // 可重试 default: if resp.StatusCode >= 400 { return nil, fmt.Errorf("HTTP %d (non-retryable)", resp.StatusCode) } } lastErr = fmt.Errorf("HTTP %d", resp.StatusCode) } else { lastErr = err } if attempt == cfg.MaxRetries { break } // 计算退避时间 delay := cfg.BaseDelay * time.Duration(math.Pow(2, float64(attempt))) if delay > cfg.MaxDelay { delay = cfg.MaxDelay } // 添加随机抖动 jitter := time.Duration(float64(delay) * cfg.JitterFraction * rand.Float64()) delay += jitter fmt.Printf("⏳ 重试 %d/%d, 等待 %v (原因: %v)\n", attempt+1, cfg.MaxRetries, delay, lastErr) select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(delay): } } return nil, fmt.Errorf("max retries exceeded: %w", lastErr) } // ─── 自动降级管理器 ────────────────────────────────────────────── // FallbackManager 降级管理器 type FallbackManager struct { // 数据需求 → 数据源优先级列表 fallbackChains map[string][]string } func NewFallbackManager() *FallbackManager { fm := &FallbackManager{ fallbackChains: map[string][]string{ // BTC 价格数据降级链 "btc_price": {"binance", "okx", "bybit", "coingecko", "coinpaprika", "yahoo"}, "eth_price": {"binance", "okx", "bybit", "coingecko", "coinpaprika", "yahoo"}, "sol_price": {"binance", "okx", "coingecko", "yahoo"}, // 资金费率降级链 "btc_funding": {"binance", "okx", "bybit", "hyperliquid", "coingecko_deriv"}, "eth_funding": {"binance", "okx", "bybit", "hyperliquid"}, // 链上数据降级链 "btc_onchain": {"mempool", "blockchain_info", "blockchair"}, "eth_onchain": {"blockchair"}, // DeFi 数据(无降级,DeFiLlama 唯一) "defi_tvl": {"defillama"}, // 社交情绪降级链 "social": {"reddit", "nitter", "alternative"}, // 宏观数据降级链 "macro_index": {"yahoo"}, "macro_gdp": {"worldbank"}, }, } return fm } // GetFallbackChain 获取降级链 func (fm *FallbackManager) GetFallbackChain(dataKey string) []string { if chain, ok := fm.fallbackChains[dataKey]; ok { return chain } return nil } ``` --- ## 五、数据源管理器 ```go package manager import ( "context" "fmt" "sync" "time" ) // DataSourceManager 数据源管理器 type DataSourceManager struct { sources map[string]DataSource // 已注册数据源 health map[string]*SourceHealth // 健康状态 rateLimiter *ThreeTierRateLimiter // 速率限制器 fallback *FallbackManager // 降级管理器 mu sync.RWMutex } // DataSource 数据源接口 type DataSource interface { Name() string Platform() string DataType() DataType Fetch(ctx context.Context, params map[string]string) (interface{}, error) Weight() int } func NewDataSourceManager() *DataSourceManager { return &DataSourceManager{ sources: make(map[string]DataSource), health: make(map[string]*SourceHealth), rateLimiter: NewThreeTierRateLimiter(), fallback: NewFallbackManager(), } } // Register 注册数据源 func (m *DataSourceManager) Register(ds DataSource) { m.mu.Lock() defer m.mu.Unlock() m.sources[ds.Name()] = ds m.health[ds.Name()] = &SourceHealth{ Name: ds.Name(), IsHealthy: true, } } // Fetch 带速率限制和降级的数据获取 func (m *DataSourceManager) Fetch(ctx context.Context, sourceName string, params map[string]string) (interface{}, error) { m.mu.RLock() ds, ok := m.sources[sourceName] m.mu.RUnlock() if !ok { return nil, fmt.Errorf("data source not found: %s", sourceName) } // 速率限制 if err := m.rateLimiter.Acquire(ctx, ds.Platform(), sourceName, ds.Weight()); err != nil { return nil, fmt.Errorf("rate limited: %w", err) } // 执行请求(带指数退避) start := time.Now() result, err := ExponentialBackoff(ctx, DefaultRetryConfig, func() (*http.Response, error) { return ds.Fetch(ctx, params) }) latency := time.Since(start) // 更新健康状态 m.updateHealth(sourceName, err, latency) if err != nil { // 尝试降级 return m.tryFallback(ctx, sourceName, params) } return result, nil } // FetchWithFallback 带降级链的数据获取 func (m *DataSourceManager) FetchWithFallback(ctx context.Context, dataKey string, params map[string]string) (interface{}, error) { chain := m.fallback.GetFallbackChain(dataKey) if chain == nil { return nil, fmt.Errorf("no fallback chain for: %s", dataKey) } for _, platform := range chain { // 查找该平台对应的数据源 sourceName := fmt.Sprintf("%s_%s", platform, dataKey) result, err := m.Fetch(ctx, sourceName, params) if err == nil { return result, nil } fmt.Printf("⚠️ %s 失败,尝试下一个: %v\n", sourceName, err) } return nil, fmt.Errorf("all sources failed for: %s", dataKey) } // updateHealth 更新健康状态 func (m *DataSourceManager) updateHealth(name string, err error, latency time.Duration) { m.mu.Lock() defer m.mu.Unlock() h := m.health[name] if err == nil { h.LastSuccess = time.Now() h.LastLatency = latency h.SuccessCount++ h.ConsecutiveErr = 0 h.IsHealthy = true // 更新平均延迟(指数移动平均) if h.AvgLatency == 0 { h.AvgLatency = latency } else { h.AvgLatency = time.Duration(float64(h.AvgLatency)*0.8 + float64(latency)*0.2) } } else { h.LastError = time.Now() h.ErrorCount++ h.ConsecutiveErr++ if h.ConsecutiveErr >= 3 { h.IsHealthy = false } } } // HealthCheck 健康检查报告 func (m *DataSourceManager) HealthCheck() map[string]*SourceHealth { m.mu.RLock() defer m.mu.RUnlock() result := make(map[string]*SourceHealth, len(m.health)) for k, v := range m.health { result[k] = v } return result } ``` --- ## 六、具体数据源适配器 ### 6.1 Binance 适配器 ```go package adapters import ( "context" "encoding/json" "fmt" "io" "net/http" "strconv" "time" ) // BinanceFundingRateSource Binance 资金费率数据源 type BinanceFundingRateSource struct { symbol string } func NewBinanceFundingRate(symbol string) *BinanceFundingRateSource { return &BinanceFundingRateSource{symbol: symbol} } func (s *BinanceFundingRateSource) Name() string { return fmt.Sprintf("binance_%s_funding", s.symbol) } func (s *BinanceFundingRateSource) Platform() string { return "binance" } func (s *BinanceFundingRateSource) DataType() DataType { return TypeFundingRate } func (s *BinanceFundingRateSource) Weight() int { return 1 } func (s *BinanceFundingRateSource) Fetch(ctx context.Context, params map[string]string) (interface{}, error) { url := fmt.Sprintf("https://fapi.binance.com/fapi/v1/premiumIndex?symbol=%s", s.symbol) req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) req.Header.Set("User-Agent", "QuantKnowledge/2.0") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } body, _ := io.ReadAll(resp.Body) var raw struct { Symbol string `json:"symbol"` MarkPrice string `json:"markPrice"` LastFundingRate string `json:"lastFundingRate"` NextFundingTime int64 `json:"nextFundingTime"` } if err := json.Unmarshal(body, &raw); err != nil { return nil, err } markPrice, _ := strconv.ParseFloat(raw.MarkPrice, 64) rate, _ := strconv.ParseFloat(raw.LastFundingRate, 64) return &FundingRate{ Symbol: raw.Symbol, Rate: rate, MarkPrice: markPrice, NextFundingTime: time.UnixMilli(raw.NextFundingTime), Timestamp: time.Now(), Source: "binance", }, nil } // BinanceKlineSource Binance K线数据源 type BinanceKlineSource struct { symbol string interval string } func NewBinanceKline(symbol, interval string) *BinanceKlineSource { return &BinanceKlineSource{symbol: symbol, interval: interval} } func (s *BinanceKlineSource) Name() string { return fmt.Sprintf("binance_%s_kline_%s", s.symbol, s.interval) } func (s *BinanceKlineSource) Platform() string { return "binance" } func (s *BinanceKlineSource) DataType() DataType { return TypeKline } func (s *BinanceKlineSource) Weight() int { return 5 } func (s *BinanceKlineSource) Fetch(ctx context.Context, params map[string]string) (interface{}, error) { limit := "100" if l, ok := params["limit"]; ok { limit = l } url := fmt.Sprintf("https://fapi.binance.com/fapi/v1/klines?symbol=%s&interval=%s&limit=%s", s.symbol, s.interval, limit) req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } body, _ := io.ReadAll(resp.Body) var raw [][]interface{} if err := json.Unmarshal(body, &raw); err != nil { return nil, err } klines := make([]Kline, len(raw)) for i, k := range raw { open, _ := strconv.ParseFloat(k[1].(string), 64) high, _ := strconv.ParseFloat(k[2].(string), 64) low, _ := strconv.ParseFloat(k[3].(string), 64) close_, _ := strconv.ParseFloat(k[4].(string), 64) vol, _ := strconv.ParseFloat(k[5].(string), 64) klines[i] = Kline{ Symbol: s.symbol, Interval: s.interval, OpenTime: time.UnixMilli(int64(k[0].(float64))), Open: open, High: high, Low: low, Close: close_, Volume: vol, Source: "binance", } } return klines, nil } ``` ### 6.2 Yahoo Finance 适配器 ```go // YahooFinanceSource Yahoo Finance 宏观数据源 type YahooFinanceSource struct { symbol string name string category string // index/commodity/forex/bond/stock/etf/crypto } func NewYahooFinance(symbol, name, category string) *YahooFinanceSource { return &YahooFinanceSource{symbol: symbol, name: name, category: category} } func (s *YahooFinanceSource) Name() string { return fmt.Sprintf("yahoo_%s", s.symbol) } func (s *YahooFinanceSource) Platform() string { return "yahoo" } func (s *YahooFinanceSource) DataType() DataType { return TypeMacro } func (s *YahooFinanceSource) Weight() int { return 1 } func (s *YahooFinanceSource) Fetch(ctx context.Context, params map[string]string) (interface{}, error) { interval := "1d" range_ := "5d" if i, ok := params["interval"]; ok { interval = i } if r, ok := params["range"]; ok { range_ = r } url := fmt.Sprintf("https://query1.finance.yahoo.com/v8/finance/chart/%s?interval=%s&range=%s", s.symbol, interval, range_) req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) req.Header.Set("User-Agent", "Mozilla/5.0") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } body, _ := io.ReadAll(resp.Body) var raw struct { Chart struct { Result []struct { Meta struct { Symbol string `json:"symbol"` RegularMarketPrice float64 `json:"regularMarketPrice"` Currency string `json:"currency"` PreviousClose float64 `json:"previousClose"` } `json:"meta"` } `json:"result"` } `json:"chart"` } if err := json.Unmarshal(body, &raw); err != nil { return nil, err } if len(raw.Chart.Result) == 0 { return nil, fmt.Errorf("no data") } meta := raw.Chart.Result[0].Meta change := 0.0 if meta.PreviousClose > 0 { change = (meta.RegularMarketPrice - meta.PreviousClose) / meta.PreviousClose * 100 } return &MacroData{ Symbol: meta.Symbol, Name: s.name, Price: meta.RegularMarketPrice, Change: change, Currency: meta.Currency, Category: s.category, Timestamp: time.Now(), Source: "yahoo", }, nil } ``` ### 6.3 注册所有数据源 ```go package main import "quantknowledge/manager" func RegisterAllSources(mgr *manager.DataSourceManager) { // ─── Binance 资金费率(40+ 品种)────────────────────────── binanceSymbols := map[string]string{ "BTC": "BTCUSDT", "ETH": "ETHUSDT", "SOL": "SOLUSDT", "DOGE": "DOGEUSDT", "XRP": "XRPUSDT", "WIF": "WIFUSDT", "NEAR": "NEARUSDT", "FIL": "FILUSDT", "ATOM": "ATOMUSDT", "LTC": "LTCUSDT", "BCH": "BCHUSDT", "ETC": "ETCUSDT", "MKR": "MKRUSDT", "SNX": "SNXUSDT", "CRV": "CRVUSDT", "LDO": "LDOUSDT", "PENDLE": "PENDLEUSDT", "STX": "STXUSDT", "ORDI": "ORDIUSDT", "JUP": "JUPUSDT", "ONDO": "ONDOUSDT", "TAO": "TAOUSDT", "RENDER": "RENDERUSDT", "1000PEPE": "1000PEPEUSDT", "1000SHIB": "1000SHIBUSDT", "1000FLOKI": "1000FLOKIUSDT", "1000BONK": "1000BONKUSDT", } for _, symbol := range binanceSymbols { mgr.Register(NewBinanceFundingRate(symbol)) mgr.Register(NewBinanceKline(symbol, "1h")) } // ─── Yahoo Finance 全品种(92 个)────────────────────────── yahooSources := []struct{ symbol, name, category string }{ // 美股指数 {"^IXIC", "纳斯达克", "index"}, {"^DJI", "道琼斯", "index"}, {"^GSPC", "标普500", "index"}, {"^RUT", "罗素2000", "index"}, // 全球指数 {"^N225", "日经225", "index"}, {"^HSI", "恒生", "index"}, {"^FTSE", "富时100", "index"}, {"^GDAXI", "DAX", "index"}, {"^FCHI", "CAC40", "index"}, {"^KS11", "KOSPI", "index"}, {"^TWII", "台湾加权", "index"}, {"000001.SS", "上证", "index"}, {"^STOXX50E", "STOXX50", "index"}, {"^AXJO", "ASX200", "index"}, {"^NSEI", "NIFTY50", "index"}, {"^GSPTSE", "TSX", "index"}, // 大宗商品 {"GC=F", "黄金", "commodity"}, {"SI=F", "白银", "commodity"}, {"CL=F", "原油", "commodity"}, {"HG=F", "铜", "commodity"}, {"NG=F", "天然气", "commodity"}, {"PL=F", "铂金", "commodity"}, {"ZC=F", "玉米", "commodity"}, {"ZW=F", "小麦", "commodity"}, // 汇率 {"DX-Y.NYB", "美元指数", "forex"}, {"EURUSD=X", "EUR/USD", "forex"}, {"JPY=X", "USD/JPY", "forex"}, {"CNY=X", "USD/CNY", "forex"}, // 美债 {"^IRX", "3月美债", "bond"}, {"^FVX", "5年美债", "bond"}, {"^TNX", "10年美债", "bond"}, {"^TYX", "30年美债", "bond"}, // 科技股 {"AAPL", "苹果", "stock"}, {"MSFT", "微软", "stock"}, {"NVDA", "英伟达", "stock"}, {"TSLA", "特斯拉", "stock"}, // ETF {"SPY", "标普ETF", "etf"}, {"QQQ", "纳斯达克ETF", "etf"}, {"GLD", "黄金ETF", "etf"}, {"TLT", "国债ETF", "etf"}, // BTC ETF {"IBIT", "IBIT", "crypto_etf"}, {"FBTC", "FBTC", "crypto_etf"}, {"GBTC", "GBTC", "crypto_etf"}, {"ARKB", "ARKB", "crypto_etf"}, // 加密概念 {"MSTR", "MicroStrategy", "crypto_stock"}, {"COIN", "Coinbase", "crypto_stock"}, // 加密货币 {"BTC-USD", "BTC", "crypto"}, {"ETH-USD", "ETH", "crypto"}, {"SOL-USD", "SOL", "crypto"}, {"BNB-USD", "BNB", "crypto"}, } for _, s := range yahooSources { mgr.Register(NewYahooFinance(s.symbol, s.name, s.category)) } // ─── 更多数据源注册... ────────────────────────────────────── // OKX, Gate.io, DeFiLlama, Reddit, etc. // 同样模式注册 } ``` --- ## 七、数据采集调度器 ```go package scheduler import ( "context" "fmt" "sync" "time" ) // CollectTask 采集任务 type CollectTask struct { Name string SourceName string Params map[string]string Interval time.Duration DataType DataType } // Scheduler 数据采集调度器 type Scheduler struct { manager *DataSourceManager tasks []CollectTask results chan interface{} ctx context.Context cancel context.CancelFunc wg sync.WaitGroup } func NewScheduler(mgr *DataSourceManager) *Scheduler { ctx, cancel := context.WithCancel(context.Background()) return &Scheduler{ manager: mgr, results: make(chan interface{}, 1000), ctx: ctx, cancel: cancel, } } // AddTask 添加采集任务 func (s *Scheduler) AddTask(task CollectTask) { s.tasks = append(s.tasks, task) } // Start 启动调度器 func (s *Scheduler) Start() { for _, task := range s.tasks { s.wg.Add(1) go s.runTask(task) } fmt.Printf("📡 调度器启动: %d 个采集任务\n", len(s.tasks)) } func (s *Scheduler) runTask(task CollectTask) { defer s.wg.Done() ticker := time.NewTicker(task.Interval) defer ticker.Stop() // 立即执行一次 s.executeTask(task) for { select { case <-s.ctx.Done(): return case <-ticker.C: s.executeTask(task) } } } func (s *Scheduler) executeTask(task CollectTask) { result, err := s.manager.Fetch(s.ctx, task.SourceName, task.Params) if err != nil { fmt.Printf("❌ [%s] %v\n", task.Name, err) return } s.results <- result } // Stop 停止调度器 func (s *Scheduler) Stop() { s.cancel() s.wg.Wait() close(s.results) } // Results 获取结果通道 func (s *Scheduler) Results() <-chan interface{} { return s.results } ``` --- ## 八、配置文件 ```yaml # config/datasources.yaml global: max_requests_per_min: 500 default_timeout: 10s retry_max: 4 retry_base_delay: 1s platforms: binance: rate_limit_per_min: 1600 weight_system: true doc_url: https://developers.binance.com/docs/derivatives/usds-margined-futures/general-info okx: rate_limit_per_min: 420 weight_system: false doc_url: https://www.okx.com/docs-v5/en/ coingecko: rate_limit_per_min: 20 weight_system: false doc_url: https://docs.coingecko.com/reference/introduction yahoo: rate_limit_per_min: 1300 weight_system: false doc_url: https://finance.yahoo.com/ reddit: rate_limit_per_min: 6 weight_system: false doc_url: https://www.reddit.com/dev/api/ schedules: realtime: # 1-5秒 - binance_btc_ticker - binance_eth_ticker frequent: # 5分钟 - binance_btc_funding - okx_btc_funding - bybit_btc_funding hourly: # 1小时 - binance_btc_kline_1h - binance_btc_long_short - defillama_tvl - reddit_bitcoin daily: # 24小时 - alternative_fear_greed - worldbank_us_cpi - yahoo_all_macro ``` --- ## 九、完整目录结构 ``` quantknowledge-go/ ├── cmd/ │ └── server/ │ └── main.go # 入口 ├── internal/ │ ├── types/ │ │ └── types.go # 数据类型定义 │ ├── ratelimit/ │ │ └── three_tier.go # 三级速率限制器 │ ├── retry/ │ │ ├── backoff.go # 指数退避 │ │ └── fallback.go # 自动降级 │ ├── manager/ │ │ └── manager.go # 数据源管理器 │ ├── adapters/ │ │ ├── binance.go # Binance 适配器 │ │ ├── okx.go # OKX 适配器 │ │ ├── bybit.go # Bybit 适配器 │ │ ├── gateio.go # Gate.io 适配器 │ │ ├── deribit.go # Deribit 适配器 │ │ ├── coingecko.go # CoinGecko 适配器 │ │ ├── coinpaprika.go # CoinPaprika 适配器 │ │ ├── defillama.go # DeFiLlama 适配器 │ │ ├── yahoo.go # Yahoo Finance 适配器 │ │ ├── reddit.go # Reddit 适配器 │ │ ├── nitter.go # Nitter/X 适配器 │ │ ├── alternative.go # Alternative.me 适配器 │ │ ├── mempool.go # Mempool.space 适配器 │ │ ├── blockchain.go # Blockchain.info 适配器 │ │ ├── hyperliquid.go # Hyperliquid 适配器 │ │ ├── dexscreener.go # DexScreener 适配器 │ │ └── worldbank.go # 世界银行适配器 │ ├── scheduler/ │ │ └── scheduler.go # 数据采集调度器 │ └── api/ │ └── handler.go # HTTP API 处理器 ├── config/ │ └── datasources.yaml # 数据源配置 ├── .air.toml # Air 热重载配置 ├── go.mod └── go.sum ```