You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cointop/pkg/api/impl/coinmarketcap/coinmarketcap.go

272 lines
6.2 KiB
Go

package coinmarketcap
import (
"errors"
"fmt"
"os"
"strings"
"time"
apitypes "github.com/miguelmota/cointop/pkg/api/types"
util "github.com/miguelmota/cointop/pkg/api/util"
cmc "github.com/miguelmota/go-coinmarketcap/pro/v1"
cmcv2 "github.com/miguelmota/go-coinmarketcap/v2"
)
// ErrQuoteNotFound is the error for when a quote is not found
var ErrQuoteNotFound = errors.New("Quote not found")
// ErrPingFailed is the error for when pinging the API fails
var ErrPingFailed = errors.New("Failed to ping")
// Service service
type Service struct {
client *cmc.Client
}
// NewCMC new service
func NewCMC(apiKey string) *Service {
if apiKey == "" {
apiKey = os.Getenv("CMC_PRO_API_KEY")
}
client := cmc.NewClient(&cmc.Config{
ProAPIKey: apiKey,
})
return &Service{
client: client,
}
}
// Ping ping API
func (s *Service) Ping() error {
info, err := s.client.Cryptocurrency.Info(&cmc.InfoOptions{
Symbol: "BTC",
})
if err != nil {
return ErrPingFailed
}
if info == nil {
return ErrPingFailed
}
return nil
}
func (s *Service) getPaginatedCoinData(convert string, offset int) ([]apitypes.Coin, error) {
var ret []apitypes.Coin
max := 100
listings, err := s.client.Cryptocurrency.LatestListings(&cmc.ListingOptions{
Limit: max,
Convert: convert,
Start: max * offset,
})
if err != nil {
return nil, err
}
for _, v := range listings {
quote, ok := v.Quote[convert]
if !ok {
return nil, ErrQuoteNotFound
}
ret = append(ret, apitypes.Coin{
ID: util.FormatID(v.Name),
Name: util.FormatName(v.Name),
Symbol: util.FormatSymbol(v.Symbol),
Rank: util.FormatRank(v.CMCRank),
AvailableSupply: util.FormatSupply(v.CirculatingSupply),
TotalSupply: util.FormatSupply(v.TotalSupply),
MarketCap: util.FormatMarketCap(quote.MarketCap),
Price: util.FormatPrice(v.Quote[convert].Price, convert),
PercentChange1H: util.FormatPercentChange(quote.PercentChange1H),
PercentChange24H: util.FormatPercentChange(quote.PercentChange24H),
PercentChange7D: util.FormatPercentChange(quote.PercentChange7D),
Volume24H: util.FormatVolume(v.Quote[convert].Volume24H),
LastUpdated: util.FormatLastUpdated(v.LastUpdated),
})
}
return ret, nil
}
// GetAllCoinData gets all coin data. Need to paginate through all pages
func (s *Service) GetAllCoinData(convert string, ch chan []apitypes.Coin) error {
go func() {
maxPages := 10
defer close(ch)
for i := 0; i < maxPages; i++ {
if i > 0 {
time.Sleep(1 * time.Second)
}
coins, err := s.getPaginatedCoinData(convert, i)
if err != nil {
return
}
ch <- coins
}
}()
return nil
}
// GetCoinData gets all data of a coin.
func (s *Service) GetCoinData(name string, convert string) (apitypes.Coin, error) {
ret := apitypes.Coin{}
coins, err := s.getPaginatedCoinData(convert, 0)
if err != nil {
return ret, err
}
for _, coin := range coins {
if coin.Name == name {
return coin, nil
}
}
return ret, nil
}
// GetCoinDataBatch gets all data of specified coins.
func (s *Service) GetCoinDataBatch(names []string, convert string) ([]apitypes.Coin, error) {
ret := []apitypes.Coin{}
coins, err := s.getPaginatedCoinData(convert, 0)
if err != nil {
return ret, err
}
for _, coin := range coins {
for _, name := range names {
if coin.Name == name {
ret = append(ret, coin)
break
}
}
}
return ret, nil
}
// GetCoinGraphData gets coin graph data
func (s *Service) GetCoinGraphData(convert, symbol string, name string, start int64, end int64) (apitypes.CoinGraph, error) {
ret := apitypes.CoinGraph{}
graphData, err := cmcv2.TickerGraph(&cmcv2.TickerGraphOptions{
Symbol: symbol,
Start: start,
End: end,
})
if err != nil {
return ret, err
}
ret.MarketCapByAvailableSupply = graphData.MarketCapByAvailableSupply
ret.PriceBTC = graphData.PriceBTC
ret.Price = graphData.PriceUSD
ret.Volume = graphData.VolumeUSD
return ret, nil
}
// GetGlobalMarketGraphData gets global market graph data
func (s *Service) GetGlobalMarketGraphData(convert string, start int64, end int64) (apitypes.MarketGraph, error) {
ret := apitypes.MarketGraph{}
graphData, err := cmcv2.GlobalMarketGraph(&cmcv2.GlobalMarketGraphOptions{
Start: start,
End: end,
})
if err != nil {
return ret, err
}
ret.MarketCapByAvailableSupply = graphData.MarketCapByAvailableSupply
ret.VolumeUSD = graphData.VolumeUSD
return ret, nil
}
// GetGlobalMarketData gets global market data
func (s *Service) GetGlobalMarketData(convert string) (apitypes.GlobalMarketData, error) {
ret := apitypes.GlobalMarketData{}
market, err := s.client.GlobalMetrics.LatestQuotes(&cmc.QuoteOptions{
Convert: convert,
})
if err != nil {
return ret, err
}
ret = apitypes.GlobalMarketData{
TotalMarketCapUSD: market.Quote[convert].TotalMarketCap,
Total24HVolumeUSD: market.Quote[convert].TotalVolume24H,
BitcoinPercentageOfMarketCap: market.BTCDominance,
ActiveCurrencies: int(market.ActiveCryptocurrencies),
ActiveAssets: 0,
ActiveMarkets: int(market.ActiveMarketPairs),
}
return ret, nil
}
// Price returns the current price of the coin
func (s *Service) Price(name string, convert string) (float64, error) {
convert = strings.ToUpper(convert)
symbol, err := cmcv2.CoinSymbol(util.NameToSlug(name))
if err != nil {
return 0, err
}
price, err := cmcv2.Price(&cmcv2.PriceOptions{
Symbol: symbol,
Convert: convert,
})
if err != nil {
return 0, err
}
return util.FormatPrice(price, convert), nil
}
// CoinLink returns the URL link for the coin
func (s *Service) CoinLink(name string) string {
slug := util.NameToSlug(name)
return fmt.Sprintf("https://coinmarketcap.com/currencies/%s", slug)
}
// SupportedCurrencies returns a list of supported currencies
func (s *Service) SupportedCurrencies() []string {
// keep these in alphabetical order
return []string{
"AUD",
"BRL",
"BTC",
"CAD",
"CFH",
"CLP",
"CNY",
"CZK",
"DKK",
"ETH",
"EUR",
"GBP",
"HKD",
"HUF",
"IDR",
"ILS",
"INR",
"JPY",
"KRW",
"MXN",
"MYR",
"NOK",
"NZD",
"PHP",
"PKR",
"PLN",
"RUB",
"SEK",
"SGD",
"THB",
"TRY",
"TWD",
"USD",
"VND",
"ZAR",
}
}