added
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
package yahoo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const baseURL = "https://query1.finance.yahoo.com/v8/finance/chart"
|
||||
const summaryURL = "https://query1.finance.yahoo.com/v10/finance/quoteSummary"
|
||||
|
||||
type Client struct {
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
Date time.Time
|
||||
Open float64
|
||||
High float64
|
||||
Low float64
|
||||
Close float64
|
||||
Volume int64
|
||||
}
|
||||
|
||||
type Quote struct {
|
||||
Symbol string
|
||||
Price float64
|
||||
PrevClose float64
|
||||
ChangePct float64
|
||||
}
|
||||
|
||||
type chartResponse struct {
|
||||
Chart struct {
|
||||
Result []struct {
|
||||
Meta struct {
|
||||
Symbol string `json:"symbol"`
|
||||
RegularMarketPrice float64 `json:"regularMarketPrice"`
|
||||
PreviousClose float64 `json:"previousClose"`
|
||||
} `json:"meta"`
|
||||
Timestamps []int64 `json:"timestamp"`
|
||||
Indicators struct {
|
||||
Quote []struct {
|
||||
Open []float64 `json:"open"`
|
||||
High []float64 `json:"high"`
|
||||
Low []float64 `json:"low"`
|
||||
Close []float64 `json:"close"`
|
||||
Volume []int64 `json:"volume"`
|
||||
} `json:"quote"`
|
||||
} `json:"indicators"`
|
||||
} `json:"result"`
|
||||
Error *struct {
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
} `json:"error"`
|
||||
} `json:"chart"`
|
||||
}
|
||||
|
||||
func New() *Client {
|
||||
return &Client{
|
||||
http: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) History(symbol string, days int) ([]Bar, error) {
|
||||
rangeStr := "3mo"
|
||||
if days > 90 {
|
||||
rangeStr = "6mo"
|
||||
}
|
||||
url := fmt.Sprintf("%s/%s?interval=1d&range=%s", baseURL, symbol, rangeStr)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("yahoo: HTTP %d for %s", resp.StatusCode, symbol)
|
||||
}
|
||||
|
||||
var data chartResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data.Chart.Error != nil {
|
||||
return nil, fmt.Errorf("yahoo: %s — %s", data.Chart.Error.Code, data.Chart.Error.Description)
|
||||
}
|
||||
if len(data.Chart.Result) == 0 {
|
||||
return nil, fmt.Errorf("yahoo: no data for %s", symbol)
|
||||
}
|
||||
|
||||
result := data.Chart.Result[0]
|
||||
quotes := result.Indicators.Quote
|
||||
if len(quotes) == 0 {
|
||||
return nil, fmt.Errorf("yahoo: empty quotes for %s", symbol)
|
||||
}
|
||||
q := quotes[0]
|
||||
|
||||
bars := make([]Bar, 0, len(result.Timestamps))
|
||||
for i, ts := range result.Timestamps {
|
||||
if i >= len(q.Close) || q.Close[i] == 0 {
|
||||
continue
|
||||
}
|
||||
bar := Bar{
|
||||
Date: time.Unix(ts, 0).UTC(),
|
||||
Close: safeFloat(q.Close, i),
|
||||
Open: safeFloat(q.Open, i),
|
||||
High: safeFloat(q.High, i),
|
||||
Low: safeFloat(q.Low, i),
|
||||
Volume: safeInt(q.Volume, i),
|
||||
}
|
||||
bars = append(bars, bar)
|
||||
}
|
||||
return bars, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetQuote(symbol string) (*Quote, error) {
|
||||
url := fmt.Sprintf("%s/%s?interval=1d&range=5d", baseURL, symbol)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var data chartResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data.Chart.Result) == 0 {
|
||||
return nil, fmt.Errorf("yahoo: no result for %s", symbol)
|
||||
}
|
||||
|
||||
meta := data.Chart.Result[0].Meta
|
||||
changePct := 0.0
|
||||
if meta.PreviousClose > 0 {
|
||||
changePct = (meta.RegularMarketPrice - meta.PreviousClose) / meta.PreviousClose * 100
|
||||
}
|
||||
|
||||
return &Quote{
|
||||
Symbol: meta.Symbol,
|
||||
Price: meta.RegularMarketPrice,
|
||||
PrevClose: meta.PreviousClose,
|
||||
ChangePct: changePct,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarketCapInfo contient les données fondamentales clés.
|
||||
type MarketCapInfo struct {
|
||||
MarketCap int64 // en USD
|
||||
FloatShares int64
|
||||
ShortRatio float64
|
||||
ForwardPE float64
|
||||
}
|
||||
|
||||
type quoteSummaryResponse struct {
|
||||
QuoteSummary struct {
|
||||
Result []struct {
|
||||
SummaryDetail struct {
|
||||
MarketCap struct {
|
||||
Raw int64 `json:"raw"`
|
||||
} `json:"marketCap"`
|
||||
ForwardPE struct {
|
||||
Raw float64 `json:"raw"`
|
||||
} `json:"forwardPE"`
|
||||
} `json:"summaryDetail"`
|
||||
DefaultKeyStatistics struct {
|
||||
FloatShares struct {
|
||||
Raw int64 `json:"raw"`
|
||||
} `json:"floatShares"`
|
||||
ShortRatio struct {
|
||||
Raw float64 `json:"raw"`
|
||||
} `json:"shortRatio"`
|
||||
} `json:"defaultKeyStatistics"`
|
||||
} `json:"result"`
|
||||
} `json:"quoteSummary"`
|
||||
}
|
||||
|
||||
// GetMarketCap retourne les données fondamentales d'un ticker.
|
||||
func (c *Client) GetMarketCap(symbol string) (*MarketCapInfo, error) {
|
||||
url := fmt.Sprintf("%s/%s?modules=summaryDetail,defaultKeyStatistics", summaryURL, symbol)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("yahoo quoteSummary: HTTP %d for %s", resp.StatusCode, symbol)
|
||||
}
|
||||
|
||||
var data quoteSummaryResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := data.QuoteSummary.Result
|
||||
if len(results) == 0 {
|
||||
return nil, fmt.Errorf("yahoo: no summary for %s", symbol)
|
||||
}
|
||||
|
||||
r := results[0]
|
||||
return &MarketCapInfo{
|
||||
MarketCap: r.SummaryDetail.MarketCap.Raw,
|
||||
FloatShares: r.DefaultKeyStatistics.FloatShares.Raw,
|
||||
ShortRatio: r.DefaultKeyStatistics.ShortRatio.Raw,
|
||||
ForwardPE: r.SummaryDetail.ForwardPE.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func safeFloat(s []float64, i int) float64 {
|
||||
if i < len(s) {
|
||||
return s[i]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func safeInt(s []int64, i int) int64 {
|
||||
if i < len(s) {
|
||||
return s[i]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user