108 lines
2.9 KiB
Go
108 lines
2.9 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type yahooChart struct {
|
|
Chart struct {
|
|
Result []struct {
|
|
Meta struct {
|
|
RegularMarketPrice float64 `json:"regularMarketPrice"`
|
|
Currency string `json:"currency"`
|
|
} `json:"meta"`
|
|
Timestamps []int64 `json:"timestamp"`
|
|
Indicators struct {
|
|
Quote []struct {
|
|
Close []*float64 `json:"close"`
|
|
} `json:"quote"`
|
|
} `json:"indicators"`
|
|
} `json:"result"`
|
|
Error *struct{ Description string } `json:"error"`
|
|
} `json:"chart"`
|
|
}
|
|
|
|
// FetchYahooPrice récupère le dernier prix connu pour un ticker Yahoo Finance.
|
|
func FetchYahooPrice(ctx context.Context, ticker string) (float64, error) {
|
|
url := fmt.Sprintf(
|
|
"https://query1.finance.yahoo.com/v8/finance/chart/%s?interval=1d&range=1d", ticker)
|
|
return fetchYahoo(ctx, url, ticker)
|
|
}
|
|
|
|
// FetchYahooHistory récupère les prix de clôture journaliers entre from et to.
|
|
// Retourne une slice de (time, price) pour chaque jour ayant une clôture non nulle.
|
|
func FetchYahooHistory(ctx context.Context, ticker string, from, to time.Time) ([]PricePoint, error) {
|
|
url := fmt.Sprintf(
|
|
"https://query1.finance.yahoo.com/v8/finance/chart/%s?interval=1d&period1=%d&period2=%d",
|
|
ticker, from.Unix(), to.Unix())
|
|
|
|
chart, err := fetchYahooChart(ctx, url, ticker)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(chart.Chart.Result) == 0 || len(chart.Chart.Result[0].Indicators.Quote) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
res := chart.Chart.Result[0]
|
|
closes := res.Indicators.Quote[0].Close
|
|
var points []PricePoint
|
|
for i, ts := range res.Timestamps {
|
|
if i >= len(closes) || closes[i] == nil || *closes[i] == 0 {
|
|
continue
|
|
}
|
|
points = append(points, PricePoint{
|
|
At: time.Unix(ts, 0).UTC(),
|
|
Price: *closes[i],
|
|
})
|
|
}
|
|
return points, nil
|
|
}
|
|
|
|
func fetchYahoo(ctx context.Context, url, ticker string) (float64, error) {
|
|
chart, err := fetchYahooChart(ctx, url, ticker)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(chart.Chart.Result) == 0 {
|
|
return 0, fmt.Errorf("no result for ticker %s", ticker)
|
|
}
|
|
price := chart.Chart.Result[0].Meta.RegularMarketPrice
|
|
if price == 0 {
|
|
return 0, fmt.Errorf("zero price for ticker %s", ticker)
|
|
}
|
|
return price, nil
|
|
}
|
|
|
|
func fetchYahooChart(ctx context.Context, url, ticker string) (*yahooChart, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("User-Agent", "Mozilla/5.0")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var chart yahooChart
|
|
if err := json.NewDecoder(resp.Body).Decode(&chart); err != nil {
|
|
return nil, fmt.Errorf("decode yahoo response: %w", err)
|
|
}
|
|
if chart.Chart.Error != nil {
|
|
return nil, fmt.Errorf("yahoo error: %s", chart.Chart.Error.Description)
|
|
}
|
|
return &chart, nil
|
|
}
|
|
|
|
// PricePoint est un prix à un instant donné, partagé entre les providers.
|
|
type PricePoint struct {
|
|
At time.Time
|
|
Price float64
|
|
}
|