account/internal/pipeline/coingecko.go
GnomeZworc 28e082f0fc
add correct backfile
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-06-14 13:41:42 +02:00

131 lines
3.5 KiB
Go

package pipeline
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
// ResolveCryptoID résout un ticker crypto (ex: "BTC") en ID CoinGecko (ex: "bitcoin").
func ResolveCryptoID(ctx context.Context, apiKey, ticker string) (string, error) {
url := fmt.Sprintf("https://api.coingecko.com/api/v3/search?query=%s", ticker)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", err
}
if apiKey != "" {
req.Header.Set("x-cg-pro-api-key", apiKey)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var result struct {
Coins []struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
} `json:"coins"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("decode coingecko search: %w", err)
}
// Le premier résultat dont le symbol correspond exactement est le bon
upper := strings.ToUpper(ticker)
for _, coin := range result.Coins {
if strings.ToUpper(coin.Symbol) == upper {
return coin.ID, nil
}
}
return "", fmt.Errorf("no coingecko match for ticker %s", ticker)
}
// FetchCoinGeckoHistory récupère les prix EUR journaliers entre from et to pour un coin.
func FetchCoinGeckoHistory(ctx context.Context, apiKey, coinID string, from, to time.Time) ([]PricePoint, error) {
baseURL := "https://api.coingecko.com/api/v3"
if apiKey != "" {
baseURL = "https://pro-api.coingecko.com/api/v3"
}
url := fmt.Sprintf("%s/coins/%s/market_chart/range?vs_currency=eur&from=%d&to=%d",
baseURL, coinID, from.Unix(), to.Unix())
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
if apiKey != "" {
req.Header.Set("x-cg-pro-api-key", apiKey)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Réponse : {"prices": [[timestamp_ms, price], ...]}
var raw struct {
Prices [][2]float64 `json:"prices"`
}
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
return nil, fmt.Errorf("decode coingecko history: %w", err)
}
points := make([]PricePoint, 0, len(raw.Prices))
for _, p := range raw.Prices {
points = append(points, PricePoint{
At: time.UnixMilli(int64(p[0])).UTC(),
Price: p[1],
})
}
return points, nil
}
// FetchCoinGeckoPrices récupère les prix EUR pour une liste d'IDs CoinGecko.
// Le ticker stocké dans instrument_ticker_cache doit être l'ID CoinGecko (ex: "bitcoin", "ethereum").
func FetchCoinGeckoPrices(ctx context.Context, apiKey string, coinIDs []string) (map[string]float64, error) {
if len(coinIDs) == 0 {
return map[string]float64{}, nil
}
baseURL := "https://api.coingecko.com/api/v3/simple/price"
if apiKey != "" {
baseURL = "https://pro-api.coingecko.com/api/v3/simple/price"
}
url := fmt.Sprintf("%s?ids=%s&vs_currencies=eur", baseURL, strings.Join(coinIDs, ","))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
if apiKey != "" {
req.Header.Set("x-cg-pro-api-key", apiKey)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Réponse : {"bitcoin": {"eur": 45000.0}, "ethereum": {"eur": 2500.0}}
var raw map[string]map[string]float64
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
return nil, fmt.Errorf("decode coingecko response: %w", err)
}
prices := make(map[string]float64, len(raw))
for id, currencies := range raw {
prices[id] = currencies["eur"]
}
return prices, nil
}