131 lines
3.5 KiB
Go
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
|
|
}
|