mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-18 09:54:35 +08:00
Dev backtest (#1134)
This commit is contained in:
104
market/historical.go
Normal file
104
market/historical.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
binanceFuturesKlinesURL = "https://fapi.binance.com/fapi/v1/klines"
|
||||
binanceMaxKlineLimit = 1500
|
||||
)
|
||||
|
||||
// GetKlinesRange 拉取指定时间范围内的 K 线序列(闭区间),返回按时间升序排列的数据。
|
||||
func GetKlinesRange(symbol string, timeframe string, start, end time.Time) ([]Kline, error) {
|
||||
symbol = Normalize(symbol)
|
||||
normTF, err := NormalizeTimeframe(timeframe)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !end.After(start) {
|
||||
return nil, fmt.Errorf("end time must be after start time")
|
||||
}
|
||||
|
||||
startMs := start.UnixMilli()
|
||||
endMs := end.UnixMilli()
|
||||
|
||||
var all []Kline
|
||||
cursor := startMs
|
||||
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
|
||||
for cursor < endMs {
|
||||
req, err := http.NewRequest("GET", binanceFuturesKlinesURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Set("symbol", symbol)
|
||||
q.Set("interval", normTF)
|
||||
q.Set("limit", fmt.Sprintf("%d", binanceMaxKlineLimit))
|
||||
q.Set("startTime", fmt.Sprintf("%d", cursor))
|
||||
q.Set("endTime", fmt.Sprintf("%d", endMs))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("binance klines api returned status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var raw [][]interface{}
|
||||
if err := json.Unmarshal(body, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
batch := make([]Kline, len(raw))
|
||||
for i, item := range raw {
|
||||
openTime := int64(item[0].(float64))
|
||||
open, _ := parseFloat(item[1])
|
||||
high, _ := parseFloat(item[2])
|
||||
low, _ := parseFloat(item[3])
|
||||
close, _ := parseFloat(item[4])
|
||||
volume, _ := parseFloat(item[5])
|
||||
closeTime := int64(item[6].(float64))
|
||||
|
||||
batch[i] = Kline{
|
||||
OpenTime: openTime,
|
||||
Open: open,
|
||||
High: high,
|
||||
Low: low,
|
||||
Close: close,
|
||||
Volume: volume,
|
||||
CloseTime: closeTime,
|
||||
}
|
||||
}
|
||||
|
||||
all = append(all, batch...)
|
||||
|
||||
last := batch[len(batch)-1]
|
||||
cursor = last.CloseTime + 1
|
||||
|
||||
// 若返回数量少于请求上限,说明已到达末尾,可提前退出。
|
||||
if len(batch) < binanceMaxKlineLimit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return all, nil
|
||||
}
|
||||
Reference in New Issue
Block a user