Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bex/entrys.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/banbox/banexg/errs"
"github.com/banbox/banexg/okx"
"github.com/banbox/banexg/utils"
"github.com/banbox/banexg/yahoo"
)

func init() {
Expand All @@ -16,6 +17,7 @@ func init() {
"bybit": WrapNew(bybit.New),
"china": china.NewExchange,
"okx": okx.NewExchange,
"yahoo": yahoo.NewExchange,
}
}

Expand Down
210 changes: 210 additions & 0 deletions yahoo/biz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package yahoo

import (
"context"
"strconv"
"strings"
"time"

"github.com/banbox/banexg"
"github.com/banbox/banexg/errs"
"github.com/banbox/banexg/utils"
)

func (e *Yahoo) Init() *errs.Error {
if err := e.Exchange.Init(); err != nil {
return err
}
if e.UserAgent == "" {
e.UserAgent = defaultUserAgent
}
e.TimeFrames = map[string]string{
"1m": "1m",
"2m": "2m",
"5m": "5m",
"15m": "15m",
"30m": "30m",
"60m": "60m",
"1h": "60m",
"1H": "60m",
"90m": "90m",
// 4h has no native Yahoo interval; FetchOHLCV aggregates from 1h.
"4h": "4h",
"4H": "4h",
"1d": "1d",
"1D": "1d",
"5d": "5d",
"1w": "1wk",
"1W": "1wk",
"1wk": "1wk",
"1mo": "1mo",
"1M": "1mo",
"3mo": "3mo",
}
e.ExgInfo.Min1mHole = 1
e.MarketsLock.Lock()
if e.Markets == nil {
e.Markets = banexg.MarketMap{}
}
e.MarketsLock.Unlock()
e.MarketsByIdLock.Lock()
if e.MarketsById == nil {
e.MarketsById = banexg.MarketArrMap{}
}
e.MarketsByIdLock.Unlock()
return nil
}

// LoadMarkets is lazy: Yahoo has no "list all instruments" endpoint.
// If params[ParamSymbols] is provided, build Market entries for those tickers;
// otherwise return whatever has accumulated via earlier MapMarket calls.
func (e *Yahoo) LoadMarkets(reload bool, params map[string]interface{}) (banexg.MarketMap, *errs.Error) {
var symbols []string
if params != nil {
if v, ok := params[banexg.ParamSymbols]; ok && v != nil {
symbols, _ = v.([]string)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
for _, s := range symbols {
if s == "" {
continue
}
e.registerMarket(buildMarket(s))
}
e.MarketsLock.Lock()
out := e.Markets
e.MarketsLock.Unlock()
return out, nil
}

// MapMarket builds a Market on demand for a Yahoo ticker.
func (e *Yahoo) MapMarket(rawID string, year int) (*banexg.Market, *errs.Error) {
if rawID == "" {
return nil, errs.NewMsg(errs.CodeParamRequired, "symbol required")
}
if mar := e.GetMarketById(rawID, ""); mar != nil {
return mar, nil
}
m := buildMarket(rawID)
e.registerMarket(m)
return m, nil
}

func (e *Yahoo) registerMarket(m *banexg.Market) {
e.MarketsLock.Lock()
e.Markets[m.Symbol] = m
e.MarketsLock.Unlock()
e.MarketsByIdLock.Lock()
e.MarketsById[m.ID] = []*banexg.Market{m}
e.MarketsByIdLock.Unlock()
}

func (e *Yahoo) FetchOHLCV(symbol, timeframe string, since int64, limit int, params map[string]interface{}) ([]*banexg.Kline, *errs.Error) {
if symbol == "" {
return nil, errs.NewMsg(errs.CodeParamRequired, "symbol required")
}
interval := e.GetTimeFrame(timeframe)
if interval == "" {
return nil, errs.NewMsg(errs.CodeInvalidTimeFrame, "unsupported timeframe: %s", timeframe)
}
// 4h has no native Yahoo interval; fetch 1h and aggregate.
if interval == "4h" {
baseLimit := 0
if limit > 0 {
baseLimit = limit * 4
}
base, err := e.FetchOHLCV(symbol, "1h", since, baseLimit, params)
if err != nil {
return nil, err
}
out := aggregate(base, 4*60*60*1000)
if limit > 0 && len(out) > limit {
out = out[len(out)-limit:]
}
return out, nil
}
args := utils.SafeParams(params)
args["symbol"] = symbol
args[banexg.ParamInterval] = interval
args["events"] = "history"

until := utils.PopMapVal(args, banexg.ParamUntil, int64(0))
if since > 0 {
args["period1"] = strconv.FormatInt(since/1000, 10)
if until > 0 {
args["period2"] = strconv.FormatInt(until/1000, 10)
} else {
args["period2"] = strconv.FormatInt(time.Now().Unix(), 10)
}
} else {
args["range"] = chooseRange(interval, limit)
}

tryNum := e.GetRetryNum("FetchOHLCV", 1)
rsp := e.RequestApiRetry(context.Background(), MidChartGet, args, tryNum)
if rsp.Error != nil {
return nil, rsp.Error
}
klines, err := parseChart(rsp.Content)
if err != nil {
return nil, err
}
if limit > 0 && len(klines) > limit {
// Yahoo doesn't accept a `limit` parameter, so trim from the oldest end.
klines = klines[len(klines)-limit:]
}
return klines, nil
}

func (e *Yahoo) FetchTickers(symbols []string, params map[string]interface{}) ([]*banexg.Ticker, *errs.Error) {
if len(symbols) == 0 {
return nil, errs.NewMsg(errs.CodeParamRequired, "symbols required")
}
args := utils.SafeParams(params)
args["symbols"] = strings.Join(symbols, ",")

tryNum := e.GetRetryNum("FetchTickers", 1)
rsp := e.RequestApiRetry(context.Background(), MidQuoteGet, args, tryNum)
if rsp.Error != nil {
return nil, rsp.Error
}
return parseQuote(rsp.Content)
}

func (e *Yahoo) FetchTicker(symbol string, params map[string]interface{}) (*banexg.Ticker, *errs.Error) {
list, err := e.FetchTickers([]string{symbol}, params)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, errs.NewMsg(errs.CodeDataNotFound, "no ticker for %s", symbol)
}
return list[0], nil
}

func (e *Yahoo) FetchTickerPrice(symbol string, params map[string]interface{}) (map[string]float64, *errs.Error) {
var symbols []string
if symbol == "" {
if v, ok := params[banexg.ParamSymbols]; ok && v != nil {
symbols, _ = v.([]string)
}
} else {
symbols = []string{symbol}
}
if len(symbols) == 0 {
return nil, errs.NewMsg(errs.CodeParamRequired, "symbol(s) required")
}
list, err := e.FetchTickers(symbols, params)
if err != nil {
return nil, err
}
out := make(map[string]float64, len(list))
for _, t := range list {
out[t.Symbol] = t.Last
}
return out, nil
}

func (e *Yahoo) Close() *errs.Error {
return nil
}
Loading