add list symbols
This commit is contained in:
parent
4e8bc736bf
commit
ba68a8d4c1
|
@ -0,0 +1,21 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
type Kline struct {
|
||||||
|
OpenTime int64 `csv:"open_time"` // 開盤時間(毫秒)
|
||||||
|
Open string `csv:"open"` // 開盤價
|
||||||
|
High string `csv:"high"` // 最高價
|
||||||
|
Low string `csv:"low"` // 最低價
|
||||||
|
Close string `csv:"close"` // 收盤價
|
||||||
|
Volume string `csv:"volume"` // 成交量
|
||||||
|
CloseTime int64 `csv:"close_time"` // 收盤時間(毫秒)
|
||||||
|
QuoteAssetVolume string `csv:"quote_asset_volume"` // 成交額(以報價資產計)
|
||||||
|
NumberOfTrades int `csv:"number_of_trades"` // 交易筆數
|
||||||
|
TakerBuyBaseAssetVolume string `csv:"taker_buy_base_asset_volume"` // 主動買入成交量
|
||||||
|
TakerBuyQuoteAssetVolume string `csv:"taker_buy_quote_asset_volume"` // 主動買入成交額
|
||||||
|
Symbol string // 交易對
|
||||||
|
Interval string // 12h,15m,1d,1h,1m,1s,2h,30m,3m,4h,5m,6h,8h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Kline) TableName() string {
|
||||||
|
return "symbol"
|
||||||
|
}
|
|
@ -7,4 +7,10 @@ import (
|
||||||
|
|
||||||
type DataSourceRepository interface {
|
type DataSourceRepository interface {
|
||||||
GetSymbols(ctx context.Context) ([]*entity.Symbol, error)
|
GetSymbols(ctx context.Context) ([]*entity.Symbol, error)
|
||||||
|
KlineDownloader
|
||||||
|
}
|
||||||
|
|
||||||
|
type KlineDownloader interface {
|
||||||
|
// FetchHistoryKline 抓歷史K線資料,startMillis=0 表示從最早,endMillis=0 表示到最新
|
||||||
|
FetchHistoryKline(ctx context.Context, symbol string, interval string, startMillis, endMillis int64) ([]*entity.Kline, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"blockchain/internal/config"
|
"blockchain/internal/config"
|
||||||
"blockchain/internal/domain/blockchain"
|
"blockchain/internal/domain/blockchain"
|
||||||
"blockchain/internal/domain/entity"
|
"blockchain/internal/domain/entity"
|
||||||
"blockchain/internal/domain/repository"
|
"blockchain/internal/domain/repository"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/adshao/go-binance/v2"
|
"github.com/adshao/go-binance/v2"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
@ -122,3 +131,87 @@ func (repo *BinanceRepository) getSymbolsFromSource(ctx context.Context) ([]bina
|
||||||
|
|
||||||
return info.Symbols, nil
|
return info.Symbols, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *BinanceRepository) FetchHistoryKline(ctx context.Context, symbol string, interval string, startMillis, endMillis int64) ([]*entity.Kline, error) {
|
||||||
|
const baseURL = "https://data.binance.vision/data/spot/daily/klines"
|
||||||
|
// 計算時間範圍
|
||||||
|
var startDate, endDate time.Time
|
||||||
|
if startMillis == 0 {
|
||||||
|
// 若沒指定,直接假設 2009-01-01(比特幣最早有記錄的位置,其他幣不太可能早過這個)
|
||||||
|
startDate = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
} else {
|
||||||
|
startDate = time.UnixMilli(startMillis)
|
||||||
|
}
|
||||||
|
if endMillis == 0 {
|
||||||
|
endDate = time.Now()
|
||||||
|
} else {
|
||||||
|
endDate = time.UnixMilli(endMillis)
|
||||||
|
}
|
||||||
|
symbol = strings.ToUpper(symbol)
|
||||||
|
|
||||||
|
var result []*entity.Kline
|
||||||
|
|
||||||
|
// 逐天下載
|
||||||
|
for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return result, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
dateStr := d.Format("2006-01-02")
|
||||||
|
zipFile := fmt.Sprintf("%s-%s-%s.zip", symbol, interval, dateStr)
|
||||||
|
url := fmt.Sprintf("%s/%s/%s/%s", baseURL, symbol, interval, zipFile)
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil || resp.StatusCode != 200 {
|
||||||
|
continue // 檔案不存在就跳過
|
||||||
|
}
|
||||||
|
tmpPath := filepath.Join(os.TempDir(), zipFile)
|
||||||
|
out, _ := os.Create(tmpPath)
|
||||||
|
io.Copy(out, resp.Body)
|
||||||
|
out.Close()
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
// 解壓縮
|
||||||
|
r, err := zip.OpenReader(tmpPath)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, f := range r.File {
|
||||||
|
rc, _ := f.Open()
|
||||||
|
reader := csv.NewReader(rc)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil || len(record) < 12 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ot, _ := strconv.ParseInt(record[0], 10, 64)
|
||||||
|
ct, _ := strconv.ParseInt(record[6], 10, 64)
|
||||||
|
num, _ := strconv.Atoi(record[8])
|
||||||
|
result = append(result, &entity.Kline{
|
||||||
|
OpenTime: ot,
|
||||||
|
Open: record[1],
|
||||||
|
High: record[2],
|
||||||
|
Low: record[3],
|
||||||
|
Close: record[4],
|
||||||
|
Volume: record[5],
|
||||||
|
CloseTime: ct,
|
||||||
|
QuoteAssetVolume: record[7],
|
||||||
|
NumberOfTrades: num,
|
||||||
|
TakerBuyBaseAssetVolume: record[9],
|
||||||
|
TakerBuyQuoteAssetVolume: record[10],
|
||||||
|
Ignore: record[11],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
rc.Close()
|
||||||
|
}
|
||||||
|
r.Close()
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue