Coinmarketcap implementation (#243)

* Updates requester package to allow unpacking of zipped files and defaults to warn if no JSON is present

* Initial addition of coinmarketcap functionality

* fix requested changes

* Fix issue with displaying false positive in request.go && reorder plan list

* Rename CurrencyProvider -> CryptocurrencyProvider
Skip seeding currency data if not enabled
Rm line in main.go

* Update test procedures and relevant json files

* Fix const issue within config.go
This commit is contained in:
Ryan O'Hara-Reid
2019-01-31 16:11:42 +11:00
committed by Adrian Gallagher
parent f7810e7eca
commit 82a622294c
10 changed files with 1819 additions and 10 deletions

View File

@@ -56,8 +56,16 @@ const (
WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values."
WarningCurrencyExchangeProvider = "WARNING -- Currency exchange provider invalid valid. Reset to Fixer."
WarningPairsLastUpdatedThresholdExceeded = "WARNING -- Exchange %s: Last manual update of available currency pairs has exceeded %d days. Manual update required!"
APIURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API"
WebsocketURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
)
// Constants here define unset default values displayed in the config.json
// file
const (
APIURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API"
WebsocketURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
DefaultUnsetAPIKey = "Key"
DefaultUnsetAPISecret = "Secret"
DefaultUnsetAccountPlan = "accountPlan"
)
// Variables here are used for configuration
@@ -169,10 +177,20 @@ type BankTransaction struct {
// CurrencyConfig holds all the information needed for currency related manipulation
type CurrencyConfig struct {
ForexProviders []base.Settings `json:"forexProviders"`
Cryptocurrencies string `json:"cryptocurrencies"`
CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat"`
FiatDisplayCurrency string `json:"fiatDisplayCurrency"`
ForexProviders []base.Settings `json:"forexProviders"`
CryptocurrencyProvider CryptocurrencyProvider `json:"cryptocurrencyProvider"`
Cryptocurrencies string `json:"cryptocurrencies"`
CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat"`
FiatDisplayCurrency string `json:"fiatDisplayCurrency"`
}
// CryptocurrencyProvider defines coinmarketcap tools
type CryptocurrencyProvider struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
Verbose bool `json:"verbose"`
APIkey string `json:"apiKey"`
AccountPlan string `json:"accountPlan"`
}
// CommunicationsConfig holds all the information needed for each
@@ -365,6 +383,20 @@ func (c *Config) UpdateCommunicationsConfig(config CommunicationsConfig) {
m.Unlock()
}
// GetCryptocurrencyProviderConfig returns the communications configuration
func (c *Config) GetCryptocurrencyProviderConfig() CryptocurrencyProvider {
m.Lock()
defer m.Unlock()
return c.Currency.CryptocurrencyProvider
}
// UpdateCryptocurrencyProviderConfig returns the communications configuration
func (c *Config) UpdateCryptocurrencyProviderConfig(config CryptocurrencyProvider) {
m.Lock()
c.Currency.CryptocurrencyProvider = config
m.Unlock()
}
// CheckCommunicationsConfig checks to see if the variables are set correctly
// from config.json
func (c *Config) CheckCommunicationsConfig() {
@@ -732,7 +764,9 @@ func (c *Config) CheckExchangeConfigValues() error {
return fmt.Errorf(ErrExchangeBaseCurrenciesEmpty, exch.Name)
}
if exch.AuthenticatedAPISupport { // non-fatal error
if exch.APIKey == "" || exch.APISecret == "" || exch.APIKey == "Key" || exch.APISecret == "Secret" {
if exch.APIKey == "" || exch.APISecret == "" ||
exch.APIKey == DefaultUnsetAPIKey ||
exch.APISecret == DefaultUnsetAPISecret {
c.Exchanges[i].AuthenticatedAPISupport = false
log.Warn(WarningExchangeAuthAPIDefaultOrEmptyValues, exch.Name)
} else if exch.Name == "ITBIT" || exch.Name == "Bitstamp" || exch.Name == "COINUT" || exch.Name == "CoinbasePro" {
@@ -844,7 +878,7 @@ func (c *Config) CheckCurrencyConfigValues() error {
Enabled: false,
Verbose: false,
RESTPollingDelay: 600,
APIKey: "Key",
APIKey: DefaultUnsetAPIKey,
APIKeyLvl: -1,
PrimaryProvider: false,
},
@@ -856,7 +890,7 @@ func (c *Config) CheckCurrencyConfigValues() error {
count := 0
for i := range c.Currency.ForexProviders {
if c.Currency.ForexProviders[i].Enabled {
if c.Currency.ForexProviders[i].APIKey == "Key" {
if c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey {
log.Warnf("%s forex provider API key not set. Please set this in your config.json file", c.Currency.ForexProviders[i].Name)
c.Currency.ForexProviders[i].Enabled = false
c.Currency.ForexProviders[i].PrimaryProvider = false
@@ -881,6 +915,32 @@ func (c *Config) CheckCurrencyConfigValues() error {
}
}
if c.Currency.CryptocurrencyProvider == (CryptocurrencyProvider{}) {
c.Currency.CryptocurrencyProvider.Name = "CoinMarketCap"
c.Currency.CryptocurrencyProvider.Enabled = false
c.Currency.CryptocurrencyProvider.Verbose = false
c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan
c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey
}
if c.Currency.CryptocurrencyProvider.Enabled {
if c.Currency.CryptocurrencyProvider.APIkey == "" ||
c.Currency.CryptocurrencyProvider.APIkey == DefaultUnsetAPIKey {
log.Warnf("CryptocurrencyProvider enabled but api key is unset please set this in your config.json file")
}
if c.Currency.CryptocurrencyProvider.AccountPlan == "" ||
c.Currency.CryptocurrencyProvider.AccountPlan == DefaultUnsetAccountPlan {
log.Warnf("CryptocurrencyProvider enabled but account plan is unset please set this in your config.json file")
}
} else {
if c.Currency.CryptocurrencyProvider.APIkey == "" {
c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey
}
if c.Currency.CryptocurrencyProvider.AccountPlan == "" {
c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan
}
}
if len(c.Currency.Cryptocurrencies) == 0 {
if len(c.Cryptocurrencies) != 0 {
c.Currency.Cryptocurrencies = c.Cryptocurrencies

View File

@@ -181,6 +181,31 @@ func TestUpdateCommunicationsConfig(t *testing.T) {
}
}
func TestGetCryptocurrencyProviderConfig(t *testing.T) {
cfg := GetConfig()
err := cfg.LoadConfig(ConfigTestFile)
if err != nil {
t.Error("Test failed. GetCryptocurrencyProviderConfig LoadConfig error", err)
}
_ = cfg.GetCryptocurrencyProviderConfig()
}
func TestUpdateCryptocurrencyProviderConfig(t *testing.T) {
cfg := GetConfig()
err := cfg.LoadConfig(ConfigTestFile)
if err != nil {
t.Error("Test failed. UpdateCryptocurrencyProviderConfig LoadConfig error", err)
}
orig := cfg.GetCryptocurrencyProviderConfig()
cfg.UpdateCryptocurrencyProviderConfig(CryptocurrencyProvider{Name: "SERIOUS TESTING PROCEDURE!"})
if cfg.Currency.CryptocurrencyProvider.Name != "SERIOUS TESTING PROCEDURE!" {
t.Error("Test failed. UpdateCurrencyProviderConfig LoadConfig error")
}
cfg.UpdateCryptocurrencyProviderConfig(orig)
}
func TestCheckCommunicationsConfig(t *testing.T) {
cfg := GetConfig()
err := cfg.LoadConfig(ConfigTestFile)

View File

@@ -48,6 +48,13 @@
"primaryProvider": false
}
],
"cryptocurrencyProvider": {
"name": "CoinMarketCap",
"enabled": false,
"verbose": false,
"apiKey": "Key",
"accountPlan": "accountPlan"
},
"cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH",
"currencyPairFormat": {
"uppercase": true,

View File

@@ -0,0 +1,764 @@
// Package coinmarketcap connects to a suite of high-performance RESTful JSON
// endpoints that are specifically designed to meet the mission-critical demands
// of application developers, data scientists, and enterprise business
// platforms. Please see https://coinmarketcap.com/api/documentation/v1/# for
// API documentation
package coinmarketcap
import (
"errors"
"fmt"
"log"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/exchanges/request"
)
// Coinmarketcap account plan bitmasks, url and enpoint consts
const (
Basic uint8 = 1 << iota
Hobbyist
Startup
Standard
Professional
Enterprise
baseURL = "https://pro-api.coinmarketcap.com"
sandboxURL = "https://sandbox-api.coinmarketcap.com"
version = "/v1/"
endpointCryptocurrencyInfo = "cryptocurrency/info"
endpointCryptocurrencyMap = "cryptocurrency/map"
endpointCryptocurrencyHistoricalListings = "cryptocurrency/listings/historical"
endpointCryptocurrencyLatestListings = "cryptocurrency/listings/latest"
endpointCryptocurrencyMarketPairs = "cryptocurrency/market-pairs/latest"
endpointOHLCVHistorical = "cryptocurrency/ohlcv/historical"
endpointOHLCVLatest = "cryptocurrency/ohlcv/latest"
endpointGetMarketQuotesHistorical = "cryptocurrency/quotes/historical"
endpointGetMarketQuotesLatest = "cryptocurrency/quotes/latest"
endpointExchangeInfo = "exchange/info"
endpointExchangeMap = "exchange/map"
endpointExchangeMarketPairsLatest = "exchange/market-pairs/latest"
endpointExchangeMarketQuoteHistorical = "exchange/quotes/historical"
endpointExchangeMarketQuoteLatest = "exchange/quotes/latest"
endpointGlobalQuoteHistorical = "global-metrics/quotes/historical"
endpointGlobalQuoteLatest = "global-metrics/quotes/latest"
endpointPriceConversion = "tools/price-conversion"
authrate = 0
defaultTimeOut = time.Second * 15
)
// Coinmarketcap is the overarching type across this package
type Coinmarketcap struct {
Verbose bool
Enabled bool
Name string
APIkey string
APIUrl string
APIVersion string
Plan uint8
Requester *request.Requester
}
// SetDefaults sets default values for the exchange
func (c *Coinmarketcap) SetDefaults() {
c.Name = "CoinMarketCap"
c.Enabled = false
c.Verbose = false
c.APIUrl = baseURL
c.APIVersion = version
c.Requester = request.New(c.Name,
request.NewRateLimit(time.Second*10, authrate),
request.NewRateLimit(time.Second*10, authrate),
common.NewHTTPClientWithTimeout(defaultTimeOut))
}
// Setup sets user configuration
func (c *Coinmarketcap) Setup(conf Settings) {
if !conf.Enabled {
c.Enabled = false
} else {
c.Enabled = true
c.Verbose = conf.Verbose
c.APIkey = conf.APIkey
err := c.SetAccountPlan(conf.AccountPlan)
if err != nil {
log.Fatal(err)
}
}
}
// GetCryptocurrencyInfo returns all static metadata for one or more
// cryptocurrencies including name, symbol, logo, and its various registered
// URLs
//
// currencyID = digit code generated by coinmarketcap
func (c *Coinmarketcap) GetCryptocurrencyInfo(currencyID ...int64) (CryptoCurrencyInfo, error) {
resp := struct {
Data CryptoCurrencyInfo `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Basic)
if err != nil {
return resp.Data, err
}
var currStr []string
for _, d := range currencyID {
currStr = append(currStr, strconv.FormatInt(d, 10))
}
val := url.Values{}
val.Set("id", strings.Join(currStr, ","))
err = c.SendHTTPRequest("GET", endpointCryptocurrencyInfo, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetCryptocurrencyIDMap returns a paginated list of all cryptocurrencies by
// CoinMarketCap ID.
func (c *Coinmarketcap) GetCryptocurrencyIDMap() ([]CryptoCurrencyMap, error) {
resp := struct {
Data []CryptoCurrencyMap `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Basic)
if err != nil {
return resp.Data, err
}
err = c.SendHTTPRequest("GET", endpointCryptocurrencyMap, nil, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetCryptocurrencyHistoricalListings returns a paginated list of all
// cryptocurrencies with market data for a given historical time.
func (c *Coinmarketcap) GetCryptocurrencyHistoricalListings() ([]CryptocurrencyHistoricalListings, error) {
return nil, errors.New("this endpoint is not yet available")
// NOTE unreachable code but will be utilised at a later date
// resp := struct {
// Data []CryptocurrencyHistoricalListings `json:"data"`
// Status Status `json:"status"`
// }{}
// err := c.CheckAccountPlan(0)
// if err != nil {
// return resp.Data, err
// }
// err = c.SendHTTPRequest("GET", endpointCryptocurrencyHistoricalListings, nil, &resp)
// if err != nil {
// return resp.Data, err
// }
// if resp.Status.ErrorCode != 0 {
// return resp.Data, errors.New(resp.Status.ErrorMessage)
// }
// return resp.Data, nil
}
// GetCryptocurrencyLatestListing returns a paginated list of all
// cryptocurrencies with latest market data.
//
// Start - optionally offsets the paginated items
// limit - optionally sets return limit on items [1..5000]
func (c *Coinmarketcap) GetCryptocurrencyLatestListing(start, limit int64) ([]CryptocurrencyLatestListings, error) {
resp := struct {
Data []CryptocurrencyLatestListings `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Basic)
if err != nil {
return resp.Data, err
}
val := url.Values{}
if start >= 1 {
val.Set("start", strconv.FormatInt(start, 10))
}
if limit > 0 {
val.Set("limit", strconv.FormatInt(limit, 10))
}
err = c.SendHTTPRequest("GET", endpointCryptocurrencyLatestListings, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetCryptocurrencyLatestMarketPairs returns all market pairs across all
// exchanges for the specified cryptocurrency with associated stats.
//
// currencyID - refers to the coinmarketcap currency id
// Start - optionally offsets the paginated items
// limit - optionally sets return limit on items [1..5000]
func (c *Coinmarketcap) GetCryptocurrencyLatestMarketPairs(currencyID, start, limit int64) (CryptocurrencyLatestMarketPairs, error) {
resp := struct {
Data CryptocurrencyLatestMarketPairs `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Standard)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("id", strconv.FormatInt(currencyID, 10))
if start >= 1 {
val.Set("start", strconv.FormatInt(start, 10))
}
if limit > 0 {
val.Set("limit", strconv.FormatInt(limit, 10))
}
err = c.SendHTTPRequest("GET", endpointCryptocurrencyMarketPairs, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetCryptocurrencyOHLCHistorical return an interval of historic OHLCV
// (Open, High, Low, Close, Volume) market quotes for a cryptocurrency.
// Currently daily and hourly OHLCV periods are supported.
//
// currencyID - refers to the coinmarketcap currency id
// tStart - refers to the start time of historic value
// tEnd - refers to the end of the time block if zero will default to time.Now()
func (c *Coinmarketcap) GetCryptocurrencyOHLCHistorical(currencyID int64, tStart, tEnd time.Time) (CryptocurrencyOHLCHistorical, error) {
resp := struct {
Data CryptocurrencyOHLCHistorical `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Standard)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("id", strconv.FormatInt(currencyID, 10))
val.Set("time_start", strconv.FormatInt(tStart.Unix(), 10))
if !tEnd.IsZero() {
val.Set("time_end", strconv.FormatInt(tEnd.Unix(), 10))
}
err = c.SendHTTPRequest("GET", endpointOHLCVHistorical, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetCryptocurrencyOHLCLatest return the latest OHLCV
// (Open, High, Low, Close, Volume) market values for one or more
// cryptocurrencies in the currently UTC day. Since the current UTC day is still
// active these values are updated frequently. You can find the final calculated
// OHLCV values for the last completed UTC day along with all historic days
// using /cryptocurrency/ohlcv/historical.
//
// currencyID - refers to the coinmarketcap currency id
func (c *Coinmarketcap) GetCryptocurrencyOHLCLatest(currencyID int64) (CryptocurrencyOHLCLatest, error) {
resp := struct {
Data CryptocurrencyOHLCLatest `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Startup)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("id", strconv.FormatInt(currencyID, 10))
err = c.SendHTTPRequest("GET", endpointOHLCVLatest, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetCryptocurrencyLatestQuotes returns the latest market quote for 1 or more
// cryptocurrencies.
//
// currencyID - refers to the coinmarketcap currency id
func (c *Coinmarketcap) GetCryptocurrencyLatestQuotes(currencyID ...int64) (CryptocurrencyLatestQuotes, error) {
resp := struct {
Data CryptocurrencyLatestQuotes `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Basic)
if err != nil {
return resp.Data, err
}
var currStr []string
for _, d := range currencyID {
currStr = append(currStr, strconv.FormatInt(d, 10))
}
val := url.Values{}
val.Set("id", strings.Join(currStr, ","))
err = c.SendHTTPRequest("GET", endpointGetMarketQuotesLatest, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetCryptocurrencyHistoricalQuotes returns an interval of historic market
// quotes for any cryptocurrency based on time and interval parameters.
//
// currencyID - refers to the coinmarketcap currency id
// tStart - refers to the start time of historic value
// tEnd - refers to the end of the time block if zero will default to time.Now()
func (c *Coinmarketcap) GetCryptocurrencyHistoricalQuotes(currencyID int64, tStart, tEnd time.Time) (CryptocurrencyHistoricalQuotes, error) {
resp := struct {
Data CryptocurrencyHistoricalQuotes `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Standard)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("id", strconv.FormatInt(currencyID, 10))
val.Set("time_start", strconv.FormatInt(tStart.Unix(), 10))
if !tEnd.IsZero() {
val.Set("time_end", strconv.FormatInt(tEnd.Unix(), 10))
}
err = c.SendHTTPRequest("GET", endpointGetMarketQuotesHistorical, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetExchangeInfo returns all static metadata for one or more exchanges
// including logo and homepage URL.
//
// exchangeID - refers to coinmarketcap exchange id
func (c *Coinmarketcap) GetExchangeInfo(exchangeID ...int64) (ExchangeInfo, error) {
resp := struct {
Data ExchangeInfo `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Startup)
if err != nil {
return resp.Data, err
}
var exchStr []string
for _, d := range exchangeID {
exchStr = append(exchStr, strconv.FormatInt(d, 10))
}
val := url.Values{}
val.Set("id", strings.Join(exchStr, ","))
err = c.SendHTTPRequest("GET", endpointExchangeInfo, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetExchangeMap returns a paginated list of all cryptocurrency exchanges by
// CoinMarketCap ID. Recommend using this convenience endpoint to lookup and
// utilize the unique exchange id across all endpoints as typical exchange
// identifiers may change over time. ie huobi -> hadax -> global -> who knows
// what else
//
// Start - optionally offsets the paginated items
// limit - optionally sets return limit on items [1..5000]
func (c *Coinmarketcap) GetExchangeMap(start, limit int64) ([]ExchangeMap, error) {
resp := struct {
Data []ExchangeMap `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Startup)
if err != nil {
return resp.Data, err
}
val := url.Values{}
if start >= 1 {
val.Set("start", strconv.FormatInt(start, 10))
}
if limit != 0 {
val.Set("limit", strconv.FormatInt(start, 10))
}
err = c.SendHTTPRequest("GET", endpointExchangeMap, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetExchangeHistoricalListings returns a paginated list of all cryptocurrency
// exchanges with historical market data for a given point in time.
func (c *Coinmarketcap) GetExchangeHistoricalListings() ([]ExchangeHistoricalListings, error) {
resp := struct {
Data []ExchangeHistoricalListings `json:"data"`
Status Status `json:"status"`
}{}
return resp.Data, errors.New("this endpoint is not yet available")
}
// GetExchangeLatestListings returns a paginated list of all cryptocurrency
// exchanges with historical market data for a given point in time.
func (c *Coinmarketcap) GetExchangeLatestListings() ([]ExchangeLatestListings, error) {
resp := struct {
Data []ExchangeLatestListings `json:"data"`
Status Status `json:"status"`
}{}
return resp.Data, errors.New("this endpoint is not yet available")
}
// GetExchangeLatestMarketPairs returns a list of active market pairs for an
// exchange. Active means the market pair is open for trading.
//
// exchangeID - refers to coinmarketcap exchange id
// Start - optionally offsets the paginated items
// limit - optionally sets return limit on items [1..5000]
func (c *Coinmarketcap) GetExchangeLatestMarketPairs(exchangeID, start, limit int64) (ExchangeLatestMarketPairs, error) {
resp := struct {
Data ExchangeLatestMarketPairs `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Standard)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("id", strconv.FormatInt(exchangeID, 10))
if start >= 1 {
val.Set("start", strconv.FormatInt(start, 10))
}
if limit != 0 {
val.Set("limit", strconv.FormatInt(start, 10))
}
err = c.SendHTTPRequest("GET", endpointExchangeMarketPairsLatest, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetExchangeLatestQuotes returns the latest aggregate market data for 1 or
// more exchanges.
//
// exchangeID - refers to coinmarketcap exchange id
func (c *Coinmarketcap) GetExchangeLatestQuotes(exchangeID ...int64) (ExchangeLatestQuotes, error) {
resp := struct {
Data ExchangeLatestQuotes `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Standard)
if err != nil {
return resp.Data, err
}
var exchStr []string
for _, d := range exchangeID {
exchStr = append(exchStr, strconv.FormatInt(d, 10))
}
val := url.Values{}
val.Set("id", strings.Join(exchStr, ","))
err = c.SendHTTPRequest("GET", endpointExchangeMarketQuoteLatest, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetExchangeHistoricalQuotes returns an interval of historic quotes for any
// exchange based on time and interval parameters.
//
// exchangeID - refers to coinmarketcap exchange id
// tStart - refers to the start time of historic value
// tEnd - refers to the end of the time block if zero will default to time.Now()
func (c *Coinmarketcap) GetExchangeHistoricalQuotes(exchangeID int64, tStart, tEnd time.Time) (ExchangeHistoricalQuotes, error) {
resp := struct {
Data ExchangeHistoricalQuotes `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Standard)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("id", strconv.FormatInt(exchangeID, 10))
val.Set("time_start", strconv.FormatInt(tStart.Unix(), 10))
if !tEnd.IsZero() {
val.Set("time_end", strconv.FormatInt(tEnd.Unix(), 10))
}
err = c.SendHTTPRequest("GET", endpointExchangeMarketQuoteHistorical, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetGlobalMeticLatestQuotes returns the latest quote of aggregate market
// metrics.
func (c *Coinmarketcap) GetGlobalMeticLatestQuotes() (GlobalMeticLatestQuotes, error) {
resp := struct {
Data GlobalMeticLatestQuotes `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Basic)
if err != nil {
return resp.Data, err
}
err = c.SendHTTPRequest("GET", endpointGlobalQuoteLatest, nil, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetGlobalMeticHistoricalQuotes returns an interval of aggregate 24 hour
// volume and market cap data globally based on time and interval parameters.
//
// tStart - refers to the start time of historic value
// tEnd - refers to the end of the time block if zero will default to time.Now()
func (c *Coinmarketcap) GetGlobalMeticHistoricalQuotes(tStart, tEnd time.Time) (GlobalMeticHistoricalQuotes, error) {
resp := struct {
Data GlobalMeticHistoricalQuotes `json:"data"`
Status Status `json:"status"`
}{}
err := c.CheckAccountPlan(Standard)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("time_start", strconv.FormatInt(tStart.Unix(), 10))
if !tEnd.IsZero() {
val.Set("time_end", strconv.FormatInt(tEnd.Unix(), 10))
}
err = c.SendHTTPRequest("GET", endpointGlobalQuoteHistorical, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// GetPriceConversion converts an amount of one currency into multiple
// cryptocurrencies or fiat currencies at the same time using the latest market
// averages. Optionally pass a historical timestamp to convert values based on
// historic averages.
//
// amount - An amount of currency to convert. Example: 10.43
// currencyID - refers to the coinmarketcap currency id
// atHistoricTime - [Optional] timestamp to reference historical pricing during
// conversion.
func (c *Coinmarketcap) GetPriceConversion(amount float64, currencyID int64, atHistoricTime time.Time) (PriceConversion, error) {
resp := struct {
Data PriceConversion `json:"data"`
Status
}{}
err := c.CheckAccountPlan(Hobbyist)
if err != nil {
return resp.Data, err
}
val := url.Values{}
val.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
val.Set("id", strconv.FormatInt(currencyID, 10))
if !atHistoricTime.IsZero() {
val.Set("time", strconv.FormatInt(atHistoricTime.Unix(), 10))
}
err = c.SendHTTPRequest("GET", endpointPriceConversion, val, &resp)
if err != nil {
return resp.Data, err
}
if resp.Status.ErrorCode != 0 {
return resp.Data, errors.New(resp.Status.ErrorMessage)
}
return resp.Data, nil
}
// SendHTTPRequest sends a valid HTTP request
func (c *Coinmarketcap) SendHTTPRequest(method, endpoint string, v url.Values, result interface{}) error {
headers := make(map[string]string)
headers["Accept"] = "application/json"
headers["Accept-Encoding"] = "deflate, gzip"
headers["X-CMC_PRO_API_KEY"] = c.APIkey
path := c.APIUrl + c.APIVersion + endpoint
if v != nil {
path = path + "?" + v.Encode()
}
return c.Requester.SendPayload(method,
path,
headers,
strings.NewReader(""),
result,
false,
c.Verbose)
}
// CheckAccountPlan checks your current account plan to the minimal account
// needed to send http request, this is used to minimize requests for lower
// account privileges
func (c *Coinmarketcap) CheckAccountPlan(minAllowable uint8) error {
if c.Plan < minAllowable {
return errors.New("function use not allowed, higher plan needed")
}
return nil
}
// SetAccountPlan sets account plan
func (c *Coinmarketcap) SetAccountPlan(s string) error {
switch s {
case "basic":
c.Plan = Basic
case "hobbyist":
c.Plan = Hobbyist
case "startup":
c.Plan = Startup
case "standard":
c.Plan = Standard
case "professional":
c.Plan = Professional
case "enterprise":
c.Plan = Enterprise
default:
return fmt.Errorf("account plan %s not found", s)
}
return nil
}

View File

@@ -0,0 +1,415 @@
package coinmarketcap
import (
"testing"
"time"
log "github.com/thrasher-/gocryptotrader/logger"
)
var c Coinmarketcap
// Please set API keys to test endpoint
const (
apikey = ""
apiAccountPlanLevel = ""
)
// Checks credentials but also checks to see if the function can take the
// required account plan level
func areAPICredtionalsSet(minAllowable uint8) bool {
if apiAccountPlanLevel != "" && apikey != "" {
if err := c.CheckAccountPlan(minAllowable); err != nil {
log.Warn("coinmarketpcap test suite - account plan not allowed for function, please review or upgrade plan to test")
return false
}
return true
}
return false
}
func TestSetDefaults(t *testing.T) {
c.SetDefaults()
}
func TestSetup(t *testing.T) {
c.SetDefaults()
cfg := Settings{}
cfg.APIkey = apikey
cfg.AccountPlan = apiAccountPlanLevel
cfg.Enabled = true
cfg.AccountPlan = "basic"
c.Setup(cfg)
}
func TestCheckAccountPlan(t *testing.T) {
c.SetDefaults()
TestSetup(t)
if areAPICredtionalsSet(Basic) {
err := c.CheckAccountPlan(Enterprise)
if err == nil {
t.Error("Test Failed - CheckAccountPlan() error cannot be nil")
}
err = c.CheckAccountPlan(Professional)
if err == nil {
t.Error("Test Failed - CheckAccountPlan() error cannot be nil")
}
err = c.CheckAccountPlan(Standard)
if err == nil {
t.Error("Test Failed - CheckAccountPlan() error cannot be nil")
}
err = c.CheckAccountPlan(Hobbyist)
if err == nil {
t.Error("Test Failed - CheckAccountPlan() error cannot be nil")
}
err = c.CheckAccountPlan(Startup)
if err == nil {
t.Error("Test Failed - CheckAccountPlan() error cannot be nil")
}
err = c.CheckAccountPlan(Basic)
if err != nil {
t.Error("Test Failed - CheckAccountPlan() error", err)
}
}
}
func TestGetCryptocurrencyInfo(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyInfo(1)
if areAPICredtionalsSet(Basic) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyInfo() error", err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyInfo() error cannot be nil")
}
}
}
func TestGetCryptocurrencyIDMap(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyIDMap()
if areAPICredtionalsSet(Basic) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyIDMap() error", err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyIDMap() error cannot be nil")
}
}
}
func TestGetCryptocurrencyHistoricalListings(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyHistoricalListings()
if err == nil {
t.Error("Test Failed - GetCryptocurrencyHistoricalListings() error cannot be nil")
}
}
func TestGetCryptocurrencyLatestListing(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyLatestListing(0, 0)
if areAPICredtionalsSet(Basic) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyLatestListing() error", err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyLatestListing() error cannot be nil")
}
}
}
func TestGetCryptocurrencyLatestMarketPairs(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyLatestMarketPairs(1, 0, 0)
if areAPICredtionalsSet(Standard) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyLatestMarketPairs() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyLatestMarketPairs() error cannot be nil")
}
}
}
func TestGetCryptocurrencyOHLCHistorical(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyOHLCHistorical(1, time.Now(), time.Now())
if areAPICredtionalsSet(Standard) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyOHLCHistorical() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyOHLCHistorical() error cannot be nil")
}
}
}
func TestGetCryptocurrencyOHLCLatest(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyOHLCLatest(1)
if areAPICredtionalsSet(Startup) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyOHLCLatest() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyOHLCLatest() error cannot be nil")
}
}
}
func TestGetCryptocurrencyLatestQuotes(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyLatestQuotes(1)
if areAPICredtionalsSet(Basic) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyLatestQuotes() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyLatestQuotes() error cannot be nil")
}
}
}
func TestGetCryptocurrencyHistoricalQuotes(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetCryptocurrencyHistoricalQuotes(1, time.Now(), time.Now())
if areAPICredtionalsSet(Standard) {
if err != nil {
t.Error("Test Failed - GetCryptocurrencyHistoricalQuotes() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetCryptocurrencyHistoricalQuotes() error cannot be nil")
}
}
}
func TestGetExchangeInfo(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetExchangeInfo(1)
if areAPICredtionalsSet(Startup) {
if err != nil {
t.Error("Test Failed - GetExchangeInfo() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetExchangeInfo() error cannot be nil")
}
}
}
func TestGetExchangeMap(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetExchangeMap(0, 0)
if areAPICredtionalsSet(Startup) {
if err != nil {
t.Error("Test Failed - GetExchangeMap() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetExchangeMap() error cannot be nil")
}
}
}
func TestGetExchangeHistoricalListings(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetExchangeHistoricalListings()
if areAPICredtionalsSet(Basic) {
if err == nil {
t.Error("Test Failed - GetExchangeHistoricalListings() error cannot be nil")
}
} else {
if err == nil {
t.Error("Test Failed - GetExchangeHistoricalListings() error cannot be nil")
}
}
}
func TestGetExchangeLatestListings(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetExchangeLatestListings()
if areAPICredtionalsSet(Basic) {
if err == nil {
t.Error("Test Failed - GetExchangeLatestListings() error cannot be nil")
}
} else {
if err == nil {
t.Error("Test Failed - GetExchangeLatestListings() error cannot be nil")
}
}
}
func TestGetExchangeLatestMarketPairs(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetExchangeLatestMarketPairs(1, 0, 0)
if areAPICredtionalsSet(Standard) {
if err != nil {
t.Error("Test Failed - GetExchangeLatestMarketPairs() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetExchangeLatestMarketPairs() error cannot be nil")
}
}
}
func TestGetExchangeLatestQuotes(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetExchangeLatestQuotes(1)
if areAPICredtionalsSet(Standard) {
if err != nil {
t.Error("Test Failed - GetExchangeLatestQuotes() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetExchangeLatestQuotes() error cannot be nil")
}
}
}
func TestGetExchangeHistoricalQuotes(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetExchangeHistoricalQuotes(1, time.Now(), time.Now())
if areAPICredtionalsSet(Standard) {
if err != nil {
t.Error("Test Failed - GetExchangeHistoricalQuotes() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetExchangeHistoricalQuotes() error cannot be nil")
}
}
}
func TestGetGlobalMeticLatestQuotes(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetGlobalMeticLatestQuotes()
if areAPICredtionalsSet(Basic) {
if err != nil {
t.Error("Test Failed - GetGlobalMeticLatestQuotes() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetGlobalMeticLatestQuotes() error cannot be nil")
}
}
}
func TestGetGlobalMeticHistoricalQuotes(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetGlobalMeticHistoricalQuotes(time.Now(), time.Now())
if areAPICredtionalsSet(Standard) {
if err != nil {
t.Error("Test Failed - GetGlobalMeticHistoricalQuotes() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetGlobalMeticHistoricalQuotes() error cannot be nil")
}
}
}
func TestGetPriceConversion(t *testing.T) {
c.SetDefaults()
TestSetup(t)
_, err := c.GetPriceConversion(0, 1, time.Now())
if areAPICredtionalsSet(Hobbyist) {
if err != nil {
t.Error("Test Failed - GetPriceConversion() error",
err)
}
} else {
if err == nil {
t.Error("Test Failed - GetPriceConversion() error cannot be nil")
}
}
}
func TestSetAccountPlan(t *testing.T) {
accPlans := []string{"basic", "startup", "hobbyist", "standard", "professional", "enterprise"}
for _, plan := range accPlans {
err := c.SetAccountPlan(plan)
if err != nil {
t.Error("Test Failed - SetAccountPlan() error", err)
}
switch plan {
case "basic":
if c.Plan != Basic {
t.Error("Test Failed - SetAccountPlan() error basic plan not set correctly")
}
case "startup":
if c.Plan != Startup {
t.Error("Test Failed - SetAccountPlan() error startup plan not set correctly")
}
case "hobbyist":
if c.Plan != Hobbyist {
t.Error("Test Failed - SetAccountPlan() error hobbyist plan not set correctly")
}
case "standard":
if c.Plan != Standard {
t.Error("Test Failed - SetAccountPlan() error standard plan not set correctly")
}
case "professional":
if c.Plan != Professional {
t.Error("Test Failed - SetAccountPlan() error professional plan not set correctly")
}
case "enterprise":
if c.Plan != Enterprise {
t.Error("Test Failed - SetAccountPlan() error enterprise plan not set correctly")
}
}
}
if err := c.SetAccountPlan("bra"); err == nil {
t.Error("Test Failed - SetAccountPlan() error cannot be nil")
}
}

View File

@@ -0,0 +1,363 @@
package coinmarketcap
import "time"
// Settings defines the current settings from configuration file
type Settings struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
Verbose bool `json:"verbose"`
APIkey string `json:"apiKey"`
AccountPlan string `json:"accountPlan"`
}
// Status defines a response status JSON struct that is received with every
// HTTP request
type Status struct {
Timestamp string `json:"timestamp"`
ErrorCode int64 `json:"error_code"`
ErrorMessage string `json:"error_message"`
Elapsed int64 `json:"elapsed"`
CreditCount int64 `json:"credit_count"`
}
// Currency defines a generic sub type to capture currency data
type Currency struct {
Price float64 `json:"price"`
Volume24H float64 `json:"volume_24h"`
Volume24HAdjusted float64 `json:"volume_24h_adjusted"`
Volume7D float64 `json:"volume_7d"`
Volume30D float64 `json:"volume_30d"`
PercentChange1H float64 `json:"percent_change_1h"`
PercentChangeVolume24H float64 `json:"percent_change_volume_24h"`
PercentChangeVolume7D float64 `json:"percent_change_volume_7d"`
PercentChangeVolume30D float64 `json:"percent_change_volume_30d"`
MarketCap float64 `json:"market_cap"`
TotalMarketCap float64 `json:"total_market_cap"`
LastUpdated time.Time `json:"last_updated"`
}
// OHLC defines a generic sub type for OHLC currency data
type OHLC struct {
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Close float64 `json:"close"`
Volume float64 `json:"volume"`
Timestamp time.Time `json:"timestamp"`
}
// CryptoCurrencyInfo defines cryptocurrency information
type CryptoCurrencyInfo map[string]struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Category string `json:"category"`
Slug string `json:"slug"`
Logo string `json:"logo"`
Tags []string `json:"tags"`
Platform interface{} `json:"platform"`
Urls struct {
Website []string `json:"website"`
Explorer []string `json:"explorer"`
SourceCode []string `json:"source_code"`
MessageBoard []string `json:"message_board"`
Chat []interface{} `json:"chat"`
Announcement []interface{} `json:"announcement"`
Reddit []string `json:"reddit"`
Twitter []string `json:"twitter"`
} `json:"urls"`
}
// CryptoCurrencyMap defines a cryptocurrency struct
type CryptoCurrencyMap struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Slug string `json:"slug"`
IsActive int `json:"is_active"`
FirstHistoricalData time.Time `json:"first_historical_data"`
LastHistoricalData time.Time `json:"last_historical_data"`
Platform interface{} `json:"platform"`
}
// CryptocurrencyHistoricalListings defines a historical listing data
type CryptocurrencyHistoricalListings struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Slug string `json:"slug"`
CmcRank int `json:"cmc_rank"`
NumMarketPairs int `json:"num_market_pairs"`
CirculatingSupply float64 `json:"circulating_supply"`
TotalSupply float64 `json:"total_supply"`
MaxSupply float64 `json:"max_supply"`
LastUpdated time.Time `json:"last_updated"`
Quote struct {
USD Currency `json:"USD"`
BTC Currency `json:"BTC"`
} `json:"quote"`
}
// CryptocurrencyLatestListings defines latest cryptocurrency listing data
type CryptocurrencyLatestListings struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Slug string `json:"slug"`
CmcRank int `json:"cmc_rank"`
NumMarketPairs int `json:"num_market_pairs"`
CirculatingSupply float64 `json:"circulating_supply"`
TotalSupply float64 `json:"total_supply"`
MaxSupply float64 `json:"max_supply"`
LastUpdated time.Time `json:"last_updated"`
DateAdded time.Time `json:"date_added"`
Tags []string `json:"tags"`
Platform interface{} `json:"platform"`
Quote struct {
USD Currency `json:"USD"`
BTC Currency `json:"BTC"`
} `json:"quote"`
}
// CryptocurrencyLatestMarketPairs defines the latest cryptocurrency pairs
type CryptocurrencyLatestMarketPairs struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
NumMarketPairs int `json:"num_market_pairs"`
MarketPairs []struct {
Exchange struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
} `json:"exchange"`
MarketPair string `json:"market_pair"`
MarketPairBase struct {
CurrencyID int `json:"currency_id"`
CurrencySymbol string `json:"currency_symbol"`
CurrencyType string `json:"currency_type"`
} `json:"market_pair_base"`
MarketPairQuote struct {
CurrencyID int `json:"currency_id"`
CurrencySymbol string `json:"currency_symbol"`
CurrencyType string `json:"currency_type"`
} `json:"market_pair_quote"`
Quote struct {
ExchangeReported struct {
Price float64 `json:"price"`
Volume24HBase float64 `json:"volume_24h_base"`
Volume24HQuote float64 `json:"volume_24h_quote"`
LastUpdated time.Time `json:"last_updated"`
} `json:"exchange_reported"`
USD Currency `json:"USD"`
} `json:"quote"`
} `json:"market_pairs"`
}
// CryptocurrencyOHLCHistorical defines open high low close historical data
type CryptocurrencyOHLCHistorical struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Quotes []struct {
TimeOpen time.Time `json:"time_open"`
TimeClose time.Time `json:"time_close"`
Quote struct {
USD OHLC `json:"USD"`
} `json:"quote"`
} `json:"quotes"`
}
// CryptocurrencyOHLCLatest defines open high low close latest data
type CryptocurrencyOHLCLatest map[string]struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
LastUpdated time.Time `json:"last_updated"`
TimeOpen time.Time `json:"time_open"`
TimeClose interface{} `json:"time_close"`
Quote struct {
USD OHLC `json:"USD"`
} `json:"quote"`
}
// CryptocurrencyLatestQuotes defines latest cryptocurrency quotation data
type CryptocurrencyLatestQuotes map[string]struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Slug string `json:"slug"`
CirculatingSupply float64 `json:"circulating_supply"`
TotalSupply float64 `json:"total_supply"`
MaxSupply float64 `json:"max_supply"`
DateAdded time.Time `json:"date_added"`
NumMarketPairs int `json:"num_market_pairs"`
CmcRank int `json:"cmc_rank"`
LastUpdated time.Time `json:"last_updated"`
Tags []string `json:"tags"`
Platform interface{} `json:"platform"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
}
// CryptocurrencyHistoricalQuotes defines historical cryptocurrency quotation
// data
type CryptocurrencyHistoricalQuotes struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Quotes []struct {
Timestamp time.Time `json:"timestamp"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
} `json:"quotes"`
}
// ExchangeInfo defines exchange information
type ExchangeInfo map[string]struct {
Urls struct {
Website []string `json:"website"`
Twitter []string `json:"twitter"`
Blog []interface{} `json:"blog"`
Chat []string `json:"chat"`
Fee []string `json:"fee"`
} `json:"urls"`
Logo string `json:"logo"`
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
}
// ExchangeMap defines a data for an exchange
type ExchangeMap struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
IsActive int `json:"is_active"`
FirstHistoricalData time.Time `json:"first_historical_data"`
LastHistoricalData time.Time `json:"last_historical_data"`
}
// ExchangeHistoricalListings defines historical exchange listings
type ExchangeHistoricalListings struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
CmcRank int `json:"cmc_rank"`
NumMarketPairs int `json:"num_market_pairs"`
Timestamp time.Time `json:"timestamp"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
}
// ExchangeLatestListings defines latest exchange listings
type ExchangeLatestListings struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
NumMarketPairs int `json:"num_market_pairs"`
LastUpdated time.Time `json:"last_updated"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
}
// ExchangeLatestMarketPairs defines latest market pairs
type ExchangeLatestMarketPairs struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
NumMarketPairs int `json:"num_market_pairs"`
MarketPairs []struct {
MarketPair string `json:"market_pair"`
MarketPairBase struct {
CurrencyID int `json:"currency_id"`
CurrencySymbol string `json:"currency_symbol"`
CurrencyType string `json:"currency_type"`
} `json:"market_pair_base"`
MarketPairQuote struct {
CurrencyID int `json:"currency_id"`
CurrencySymbol string `json:"currency_symbol"`
CurrencyType string `json:"currency_type"`
} `json:"market_pair_quote"`
Quote struct {
ExchangeReported struct {
Price float64 `json:"price"`
Volume24HBase float64 `json:"volume_24h_base"`
Volume24HQuote float64 `json:"volume_24h_quote"`
LastUpdated time.Time `json:"last_updated"`
} `json:"exchange_reported"`
USD Currency `json:"USD"`
} `json:"quote"`
} `json:"market_pairs"`
}
// ExchangeLatestQuotes defines latest exchange quotations
type ExchangeLatestQuotes struct {
Binance struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
NumMarketPairs int `json:"num_market_pairs"`
LastUpdated time.Time `json:"last_updated"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
} `json:"binance"`
}
// ExchangeHistoricalQuotes defines historical exchange quotations
type ExchangeHistoricalQuotes struct {
ID int `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Quotes []struct {
Timestamp time.Time `json:"timestamp"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
NumMarketPairs int `json:"num_market_pairs"`
} `json:"quotes"`
}
// GlobalMeticLatestQuotes defines latest global metric quotations
type GlobalMeticLatestQuotes struct {
BtcDominance float64 `json:"btc_dominance"`
EthDominance float64 `json:"eth_dominance"`
ActiveCryptocurrencies int `json:"active_cryptocurrencies"`
ActiveMarketPairs int `json:"active_market_pairs"`
ActiveExchanges int `json:"active_exchanges"`
LastUpdated time.Time `json:"last_updated"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
}
// GlobalMeticHistoricalQuotes defines historical global metric quotations
type GlobalMeticHistoricalQuotes struct {
Quotes []struct {
Timestamp time.Time `json:"timestamp"`
BtcDominance float64 `json:"btc_dominance"`
Quote struct {
USD Currency `json:"USD"`
} `json:"quote"`
} `json:"quotes"`
}
// PriceConversion defines price conversion data
type PriceConversion struct {
Symbol string `json:"symbol"`
ID string `json:"id"`
Name string `json:"name"`
Amount float64 `json:"amount"`
LastUpdated time.Time `json:"last_updated"`
Quote struct {
GBP Currency `json:"GBP"`
LTC Currency `json:"LTC"`
USD Currency `json:"USD"`
} `json:"quote"`
}

View File

@@ -1,9 +1,12 @@
package currency
import (
"errors"
"fmt"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/coinmarketcap"
"github.com/thrasher-/gocryptotrader/currency/forexprovider"
"github.com/thrasher-/gocryptotrader/currency/pair"
log "github.com/thrasher-/gocryptotrader/logger"
@@ -27,6 +30,10 @@ var (
BaseCurrency string
FXProviders *forexprovider.ForexProviders
CryptocurrencyProvider *coinmarketcap.Coinmarketcap
TotalCryptocurrencies []Data
TotalExchanges []Data
)
// SetDefaults sets the default currency provider and settings for
@@ -201,3 +208,113 @@ func ConvertCurrency(amount float64, from, to string) (float64, error) {
return converted * resultTo, nil
}
// Data defines information pertaining to exchange or a cryptocurrency from
// coinmarketcap
type Data struct {
ID int
Name string
Symbol string `json:",omitempty"`
Slug string
Active bool
LastUpdated time.Time
}
// SeedCryptocurrencyMarketData seeds cryptocurrency market data
func SeedCryptocurrencyMarketData(settings coinmarketcap.Settings) error {
if !settings.Enabled {
return errors.New("not enabled please set in config.json with apikey and account levels")
}
if CryptocurrencyProvider == nil {
err := setupCryptoProvider(settings)
if err != nil {
return err
}
}
cryptoData, err := CryptocurrencyProvider.GetCryptocurrencyIDMap()
if err != nil {
return err
}
for _, data := range cryptoData {
var active bool
if data.IsActive == 1 {
active = true
}
TotalCryptocurrencies = append(TotalCryptocurrencies, Data{
ID: data.ID,
Name: data.Name,
Symbol: data.Symbol,
Slug: data.Slug,
Active: active,
LastUpdated: time.Now(),
})
}
return nil
}
// SeedExchangeMarketData seeds exchange market data
func SeedExchangeMarketData(settings coinmarketcap.Settings) error {
if !settings.Enabled {
return errors.New("not enabled please set in config.json with apikey and account levels")
}
if CryptocurrencyProvider == nil {
err := setupCryptoProvider(settings)
if err != nil {
return err
}
}
exchangeData, err := CryptocurrencyProvider.GetExchangeMap(0, 0)
if err != nil {
return err
}
for _, data := range exchangeData {
var active bool
if data.IsActive == 1 {
active = true
}
TotalExchanges = append(TotalExchanges, Data{
ID: data.ID,
Name: data.Name,
Slug: data.Slug,
Active: active,
LastUpdated: time.Now(),
})
}
return nil
}
func setupCryptoProvider(settings coinmarketcap.Settings) error {
if settings.APIkey == "" ||
settings.APIkey == "key" ||
settings.AccountPlan == "" ||
settings.AccountPlan == "accountPlan" {
return errors.New("currencyprovider error api key or plan not set in config.json")
}
CryptocurrencyProvider = new(coinmarketcap.Coinmarketcap)
CryptocurrencyProvider.SetDefaults()
CryptocurrencyProvider.Setup(settings)
return nil
}
// GetTotalMarketCryptocurrencies returns the total seeded market
// cryptocurrencies
func GetTotalMarketCryptocurrencies() []Data {
return TotalCryptocurrencies
}
// GetTotalMarketExchanges returns the total seeded market exchanges
func GetTotalMarketExchanges() []Data {
return TotalExchanges
}

View File

@@ -1,6 +1,7 @@
package request
import (
"compress/gzip"
"errors"
"fmt"
"io"
@@ -290,7 +291,31 @@ func (r *Requester) DoRequest(req *http.Request, method, path string, headers ma
return errors.New("resp is nil")
}
contents, err := ioutil.ReadAll(resp.Body)
var reader io.ReadCloser
switch resp.Header.Get("Content-Encoding") {
case "gzip":
reader, err = gzip.NewReader(resp.Body)
defer reader.Close()
if err != nil {
return err
}
case "json":
reader = resp.Body
default:
switch {
case common.StringContains(resp.Header.Get("Content-Type"), "application/json"):
reader = resp.Body
default:
log.Warnf("encoding is not JSON for request response but receieved %v",
resp.Header.Get("Content-Type"))
reader = resp.Body
}
}
contents, err := ioutil.ReadAll(reader)
if err != nil {
return err
}

26
main.go
View File

@@ -14,6 +14,7 @@ import (
"github.com/thrasher-/gocryptotrader/communications"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/currency/coinmarketcap"
"github.com/thrasher-/gocryptotrader/currency/forexprovider"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
log "github.com/thrasher-/gocryptotrader/logger"
@@ -117,6 +118,31 @@ func main() {
bot.comms = communications.NewComm(bot.config.GetCommunicationsConfig())
bot.comms.GetEnabledCommunicationMediums()
if bot.config.GetCryptocurrencyProviderConfig().Enabled {
log.Debug("Seeding full market data...")
err = currency.SeedCryptocurrencyMarketData(coinmarketcap.Settings(bot.config.GetCryptocurrencyProviderConfig()))
if err != nil {
log.Warnf("Failure seeding cryptocurrency market data %s", err)
} else {
if *verbosity {
log.Debugf("Total market cryptocurrencies: %d",
len(currency.GetTotalMarketCryptocurrencies()))
}
}
err = currency.SeedExchangeMarketData(coinmarketcap.Settings(bot.config.GetCryptocurrencyProviderConfig()))
if err != nil {
log.Warnf("Failure seeding exchange market data %s", err)
} else {
if *verbosity {
log.Debugf("Total market exchanges: %d",
len(currency.GetTotalMarketExchanges()))
}
}
} else {
log.Debug("Cryptocurrency provider not enabled.")
}
log.Debugf("Fiat display currency: %s.", bot.config.Currency.FiatDisplayCurrency)
currency.BaseCurrency = bot.config.Currency.FiatDisplayCurrency
currency.FXProviders = forexprovider.StartFXService(bot.config.GetCurrencyConfig().ForexProviders)

View File

@@ -48,6 +48,13 @@
"primaryProvider": false
}
],
"cryptocurrencyProvider": {
"name": "CoinMarketCap",
"enabled": false,
"verbose": false,
"apiKey": "Key",
"accountPlan": "accountPlan"
},
"cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR",
"currencyPairFormat": {
"uppercase": true,