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 }