Currency package update (#247)

* Initial currency overhaul before service system implementation

* Remove redundant currency string in orderbook.Base
Unexport lastupdated field in orderbook.Base as it was being instantiated multiple times
Add error handling for process orderbook

*  Remove redundant currency string in ticker.Price
 Unexport lastupdated field in ticker.Price
 Add error handling for process ticker function and fix tests

* Phase Two Update

* Update translations to use map type - thankyou to kempeng for spotting this

* Change pair method name from Display -> Format for better readability

* Fixes misspelling and tests

* Implement requested changes from GloriousCode

* Remove reduntant function and streamlined return in currency_translation.go

* Revert pair method naming conventions

* Change currency naming conventions

* Changed code type to exported Item type with underlying string to reduce complexity

* Added interim orderbook process method to orderbook.Base type

* Changed feebuilder struct field to currency.Pair

* Adds fall over system for backup fx providers

* deprecate function and children and fix linter issue with btcmarkets

* Fixed requested changes

* Fix bug and move mtx for rates

* Fixed after rebase oopsies

* Fix linter issues

* Fixes race conditions in testing functions

* Final phase coinmarketcap update

* fix linter issues

* Implement requested changes

* Adds configuration variables to increase/decrease time durations between updating currency file and fetching new currency rates

* Add a collection of tests to improve codecov

* After rebase oopsy fixes for btse

* Fix requested changes

* fix after rebase oopsies and add more efficient comparison checks within currency pair

* Fix linter issues
This commit is contained in:
Ryan O'Hara-Reid
2019-03-19 11:49:05 +11:00
committed by Adrian Gallagher
parent ed760e184e
commit 0990f9d118
189 changed files with 11982 additions and 8055 deletions

2102
currency/code.go Normal file

File diff suppressed because it is too large Load Diff

426
currency/code_test.go Normal file
View File

@@ -0,0 +1,426 @@
package currency
import (
"testing"
"github.com/thrasher-/gocryptotrader/common"
)
func TestRoleString(t *testing.T) {
if Unset.String() != UnsetRollString {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
UnsetRollString,
Unset)
}
if Fiat.String() != FiatCurrencyString {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
FiatCurrencyString,
Fiat)
}
if Cryptocurrency.String() != CryptocurrencyString {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
CryptocurrencyString,
Cryptocurrency)
}
if Token.String() != TokenString {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
TokenString,
Token)
}
if Contract.String() != ContractString {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
ContractString,
Contract)
}
var random Role = 1 << 7
if random.String() != "UNKNOWN" {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
"UNKNOWN",
random)
}
}
func TestRoleMarshalJSON(t *testing.T) {
d, err := common.JSONEncode(Fiat)
if err != nil {
t.Error("Test Failed - Role MarshalJSON() error", err)
}
expected := `"fiatCurrency"`
if string(d) != expected {
t.Errorf("Test Failed - Role MarshalJSON() error expected %s but recieved %s",
expected,
string(d))
}
}
func TestRoleUnmarshalJSON(t *testing.T) {
type AllTheRoles struct {
RoleOne Role `json:"RoleOne"`
RoleTwo Role `json:"RoleTwo"`
RoleThree Role `json:"RoleThree"`
RoleFour Role `json:"RoleFour"`
RoleFive Role `json:"RoleFive"`
RoleUnknown Role `json:"RoleUnknown"`
}
var outgoing = AllTheRoles{
RoleOne: Unset,
RoleTwo: Cryptocurrency,
RoleThree: Fiat,
RoleFour: Token,
RoleFive: Contract,
}
e, err := common.JSONEncode(1337)
if err != nil {
t.Fatal("Test Failed - Role UnmarshalJSON() error", err)
}
var incoming AllTheRoles
err = common.JSONDecode(e, &incoming)
if err == nil {
t.Fatal("Test Failed - Role UnmarshalJSON() error", err)
}
e, err = common.JSONEncode(outgoing)
if err != nil {
t.Fatal("Test Failed - Role UnmarshalJSON() error", err)
}
err = common.JSONDecode(e, &incoming)
if err != nil {
t.Fatal("Test Failed - Role UnmarshalJSON() error", err)
}
if incoming.RoleOne != Unset {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
Unset,
incoming.RoleOne)
}
if incoming.RoleTwo != Cryptocurrency {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
Cryptocurrency,
incoming.RoleTwo)
}
if incoming.RoleThree != Fiat {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
Fiat,
incoming.RoleThree)
}
if incoming.RoleFour != Token {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
Token,
incoming.RoleFour)
}
if incoming.RoleFive != Contract {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
Contract,
incoming.RoleFive)
}
if incoming.RoleUnknown != Unset {
t.Errorf("Test Failed - Role String() error expected %s but recieved %s",
incoming.RoleFive,
incoming.RoleUnknown)
}
}
func TestBaseCode(t *testing.T) {
var main BaseCodes
if main.HasData() {
t.Errorf("Test Failed - BaseCode HasData() error expected false but recieved %v",
main.HasData())
}
catsCode := main.Register("CATS")
if !main.HasData() {
t.Errorf("Test Failed - BaseCode HasData() error expected true but recieved %v",
main.HasData())
}
if !main.Register("CATS").Match(catsCode) {
t.Errorf("Test Failed - BaseCode Match() error expected true but recieved %v",
false)
}
if main.Register("DOGS").Match(catsCode) {
t.Errorf("Test Failed - BaseCode Match() error expected false but recieved %v",
true)
}
loadedCurrencies := main.GetCurrencies()
if loadedCurrencies.Contains(main.Register("OWLS")) {
t.Errorf("Test Failed - BaseCode Contains() error expected false but recieved %v",
true)
}
if !loadedCurrencies.Contains(catsCode) {
t.Errorf("Test Failed - BaseCode Contains() error expected true but recieved %v",
false)
}
err := main.UpdateContract("Bitcoin Perpetual", "XBTUSD", "Bitmex")
if err != nil {
t.Error("Test Failed - BaseCode UpdateContract error", err)
}
err = main.UpdateCryptocurrency("Bitcoin", "BTC", 1337)
if err != nil {
t.Error("Test Failed - BaseCode UpdateContract error", err)
}
err = main.UpdateFiatCurrency("Australian Dollar", "AUD", 1336)
if err != nil {
t.Error("Test Failed - BaseCode UpdateContract error", err)
}
err = main.UpdateToken("Populous", "PPT", "ETH", 1335)
if err != nil {
t.Error("Test Failed - BaseCode UpdateContract error", err)
}
contract := main.Register("XBTUSD")
if contract.IsFiatCurrency() {
t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but recieved %v",
true)
}
if contract.IsCryptocurrency() {
t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but recieved %v",
true)
}
if contract.IsDefaultFiatCurrency() {
t.Errorf("Test Failed - BaseCode IsDefaultFiatCurrency() error expected false but recieved %v",
true)
}
if contract.IsDefaultFiatCurrency() {
t.Errorf("Test Failed - BaseCode IsFiatCurrency() error expected false but recieved %v",
true)
}
err = main.LoadItem(Item{
ID: 0,
FullName: "Cardano",
Role: Cryptocurrency,
Symbol: "ADA",
})
if err != nil {
t.Error("Test Failed - BaseCode LoadItem() error", err)
}
full, err := main.GetFullCurrencyData()
if err != nil {
t.Error("Test Failed - BaseCode GetFullCurrencyData error", err)
}
if len(full.Contracts) != 1 {
t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v",
len(full.Contracts))
}
if len(full.Cryptocurrency) != 2 {
t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v",
len(full.Cryptocurrency))
}
if len(full.FiatCurrency) != 1 {
t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v",
len(full.FiatCurrency))
}
if len(full.Token) != 1 {
t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 1 but recieved %v",
len(full.Token))
}
if len(full.UnsetCurrency) != 3 {
t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 3 but recieved %v",
len(full.UnsetCurrency))
}
if !full.LastMainUpdate.IsZero() {
t.Errorf("Test Failed - BaseCode GetFullCurrencyData() error expected 0 but recieved %s",
full.LastMainUpdate)
}
}
func TestCodeString(t *testing.T) {
expected := "TEST"
cc := NewCode("TEST")
if cc.String() != expected {
t.Errorf("Test Failed - Currency Code String() error expected %s but recieved %s",
expected, cc)
}
}
func TestCodeLower(t *testing.T) {
expected := "test"
cc := NewCode("TEST")
if cc.Lower().String() != expected {
t.Errorf("Test Failed - Currency Code Lower() error expected %s but recieved %s",
expected,
cc.Lower())
}
}
func TestCodeUpper(t *testing.T) {
expected := "TEST"
cc := NewCode("test")
if cc.Upper().String() != expected {
t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s",
expected,
cc.Upper())
}
}
func TestCodeUnmarshalJSON(t *testing.T) {
var unmarshalHere Code
expected := "BRO"
encoded, err := common.JSONEncode(expected)
if err != nil {
t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err)
}
if unmarshalHere.String() != expected {
t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s",
expected,
unmarshalHere)
}
}
func TestCodeMarshalJSON(t *testing.T) {
quickstruct := struct {
Codey Code `json:"sweetCodes"`
}{
Codey: NewCode("BRO"),
}
expectedJSON := `{"sweetCodes":"BRO"}`
encoded, err := common.JSONEncode(quickstruct)
if err != nil {
t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err)
}
if string(encoded) != expectedJSON {
t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s",
expectedJSON,
string(encoded))
}
quickstruct = struct {
Codey Code `json:"sweetCodes"`
}{
Codey: Code{}, // nil code
}
encoded, err = common.JSONEncode(quickstruct)
if err != nil {
t.Fatal("Test Failed - Currency Code UnmarshalJSON error", err)
}
newExpectedJSON := `{"sweetCodes":""}`
if string(encoded) != newExpectedJSON {
t.Errorf("Test Failed - Currency Code Upper() error expected %s but recieved %s",
newExpectedJSON, string(encoded))
}
}
func TestIsDefaultCurrency(t *testing.T) {
if !USD.IsDefaultFiatCurrency() {
t.Errorf("Test Failed. TestIsDefaultCurrency Cannot match currency %s.",
USD)
}
if !AUD.IsDefaultFiatCurrency() {
t.Errorf("Test Failed. TestIsDefaultCurrency Cannot match currency, %s.",
AUD)
}
if LTC.IsDefaultFiatCurrency() {
t.Errorf("Test Failed. TestIsDefaultCurrency Function return is incorrect with, %s.",
LTC)
}
}
func TestIsDefaultCryptocurrency(t *testing.T) {
if !BTC.IsDefaultCryptocurrency() {
t.Errorf("Test Failed. TestIsDefaultCryptocurrency cannot match currency, %s.",
BTC)
}
if !LTC.IsDefaultCryptocurrency() {
t.Errorf("Test Failed. TestIsDefaultCryptocurrency cannot match currency, %s.",
LTC)
}
if AUD.IsDefaultCryptocurrency() {
t.Errorf("Test Failed. TestIsDefaultCryptocurrency function return is incorrect with, %s.",
AUD)
}
}
func TestIsFiatCurrency(t *testing.T) {
if !USD.IsFiatCurrency() {
t.Errorf(
"Test Failed. TestIsFiatCurrency cannot match currency, %s.", USD)
}
if !CNY.IsFiatCurrency() {
t.Errorf(
"Test Failed. TestIsFiatCurrency cannot match currency, %s.", CNY)
}
if LINO.IsFiatCurrency() {
t.Errorf(
"Test Failed. TestIsFiatCurrency cannot match currency, %s.", LINO,
)
}
}
func TestIsCryptocurrency(t *testing.T) {
if !BTC.IsCryptocurrency() {
t.Errorf("Test Failed. TestIsFiatCurrency cannot match currency, %s.",
BTC)
}
if !LTC.IsCryptocurrency() {
t.Errorf("Test Failed. TestIsFiatCurrency cannot match currency, %s.",
LTC)
}
if AUD.IsCryptocurrency() {
t.Errorf("Test Failed. TestIsFiatCurrency cannot match currency, %s.",
AUD)
}
}
func TestItemString(t *testing.T) {
expected := "Hello,World"
newItem := Item{
FullName: expected,
}
if newItem.String() != expected {
t.Errorf("Test Failed - Item String() error expected %s but recieved %s",
expected,
newItem)
}
}

View File

@@ -71,14 +71,20 @@ type CryptoCurrencyInfo map[string]struct {
// 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"`
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 struct {
ID int `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Slug string `json:"slug"`
TokenAddress string `json:"token_address"`
} `json:"platform"`
}
// CryptocurrencyHistoricalListings defines a historical listing data

361
currency/conversion.go Normal file
View File

@@ -0,0 +1,361 @@
package currency
import (
"errors"
"fmt"
"sync"
log "github.com/thrasher-/gocryptotrader/logger"
)
// ConversionRates defines protected conversion rate map for concurrent updating
// and retrieval of foreign exchange rates for mainly fiat currencies
type ConversionRates struct {
m map[*Item]map[*Item]*float64
mtx sync.Mutex
}
// HasData returns if conversion rates are present
func (c *ConversionRates) HasData() bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if c.m == nil {
return false
}
return len(c.m) != 0
}
// GetRate returns a rate from the conversion rate list
func (c *ConversionRates) GetRate(from, to Code) (float64, error) {
if from.Item == USDT.Item {
from = USD
}
if to.Item == USDT.Item {
to = USD
}
if from.Item == RUR.Item {
from = RUB
}
if to.Item == RUR.Item {
to = RUB
}
if from.Item == to.Item {
return 1, nil
}
c.mtx.Lock()
defer c.mtx.Unlock()
p, ok := c.m[from.Item][to.Item]
if !ok {
return 0, fmt.Errorf("rate not found for from %s to %s conversion",
from,
to)
}
return *p, nil
}
// Register registers a new conversion rate if not found adds it and allows for
// quick updates
func (c *ConversionRates) Register(from, to Code) (Conversion, error) {
if from.IsCryptocurrency() {
return Conversion{}, errors.New("from currency is a cryptocurrency value")
}
if to.IsCryptocurrency() {
return Conversion{}, errors.New("to currency is a cryptocurrency value")
}
c.mtx.Lock()
defer c.mtx.Unlock()
p, ok := c.m[from.Item][to.Item]
if !ok {
log.Errorf("currency conversion rate not found from %s to %s", from, to)
return Conversion{}, errors.New("no rate found")
}
i, ok := c.m[to.Item][from.Item]
if !ok {
log.Errorf("currency conversion inversion rate not found from %s to %s",
to,
from)
return Conversion{}, errors.New("no rate found")
}
return Conversion{From: from, To: to, rate: p, mtx: &c.mtx, inverseRate: i},
nil
}
// Update updates the full conversion rate values including inversion and
// cross rates
func (c *ConversionRates) Update(m map[string]float64) error {
if len(m) == 0 {
return errors.New("no data given")
}
if storage.IsVerbose() {
log.Debug("Conversion rates are being updated.")
}
solidvalues := make(map[Code]map[Code]float64)
var list []Code // Verification list, cross check all currencies coming in
var mainBaseCurrency Code
for key, val := range m {
code1, err := storage.ValidateFiatCode(key[:3])
if err != nil {
return err
}
if mainBaseCurrency == (Code{}) {
mainBaseCurrency = code1
}
code2, err := storage.ValidateFiatCode(key[3:])
if err != nil {
return err
}
if code1 == code2 { // Get rid of same conversions
continue
}
var codeOneFound, codeTwoFound bool
// Check and add to our funky list
for i := range list {
if list[i] == code1 {
codeOneFound = true
if codeTwoFound {
break
}
}
if list[i] == code2 {
codeTwoFound = true
if codeOneFound {
break
}
}
}
if !codeOneFound {
list = append(list, code1)
}
if !codeTwoFound {
list = append(list, code2)
}
if solidvalues[code1] == nil {
solidvalues[code1] = make(map[Code]float64)
}
solidvalues[code1][code2] = val
// Input inverse values 1/val to swap from -> to and vice versa
if solidvalues[code2] == nil {
solidvalues[code2] = make(map[Code]float64)
}
solidvalues[code2][code1] = 1 / val
}
for _, base := range list {
for _, term := range list {
if base == term {
continue
}
_, ok := solidvalues[base][term]
if !ok {
var crossRate float64
// Check inversion to speed things up
v, ok := solidvalues[term][base]
if !ok {
v1, ok := solidvalues[mainBaseCurrency][base]
if !ok {
return fmt.Errorf("value not found base %s term %s",
mainBaseCurrency,
base)
}
v2, ok := solidvalues[mainBaseCurrency][term]
if !ok {
return fmt.Errorf("value not found base %s term %s",
mainBaseCurrency,
term)
}
crossRate = v2 / v1
} else {
crossRate = 1 / v
}
if storage.IsVerbose() {
log.Debugf("Conversion from %s to %s deriving cross rate value %f",
base,
term,
crossRate)
}
solidvalues[base][term] = crossRate
}
}
}
c.m = nil
for key, val := range solidvalues {
for key2, val2 := range val {
if c.m == nil {
c.m = make(map[*Item]map[*Item]*float64)
}
if c.m[key.Item] == nil {
c.m[key.Item] = make(map[*Item]*float64)
}
p := c.m[key.Item][key2.Item]
if p == nil {
newPalsAndFriends := val2
c.m[key.Item][key2.Item] = &newPalsAndFriends
} else {
*p = val2
}
}
}
return nil
}
// GetFullRates returns the full conversion list
func (c *ConversionRates) GetFullRates() Conversions {
var conversions Conversions
c.mtx.Lock()
for key, val := range c.m {
for key2, val2 := range val {
conversions = append(conversions, Conversion{
From: Code{Item: key},
To: Code{Item: key2},
rate: val2,
mtx: &c.mtx,
})
}
}
c.mtx.Unlock()
return conversions
}
// Conversions define a list of conversion data
type Conversions []Conversion
// Slice exposes the underlying Conversion slice type
func (c Conversions) Slice() []Conversion {
return c
}
// NewConversionFromString splits a string from a foreign exchange provider
func NewConversionFromString(p string) (Conversion, error) {
return NewConversionFromStrings(p[:3], p[3:])
}
// NewConversion returns a conversion rate object that allows for
// obtaining efficient rate values when needed
func NewConversion(from, to Code) (Conversion, error) {
return storage.NewConversion(from, to)
}
// NewConversionFromStrings assigns or finds a new conversion unit
func NewConversionFromStrings(from, to string) (Conversion, error) {
return NewConversion(NewCode(from), NewCode(to))
}
// Conversion defines a specific currency conversion for a rate
type Conversion struct {
From Code
To Code
rate *float64
inverseRate *float64
mtx *sync.Mutex
}
// IsInvalid returns true if both from and to currencies are the same
func (c Conversion) IsInvalid() bool {
if c.From.Item == nil || c.To.Item == nil {
return true
}
return c.From.Item == c.To.Item
}
// IsFiat checks to see if the from and to currency is a fiat e.g. EURUSD
func (c Conversion) IsFiat() bool {
return storage.IsFiatCurrency(c.From) && storage.IsFiatCurrency(c.To)
}
// String returns the stringed fields
func (c Conversion) String() string {
return c.From.String() + c.To.String()
}
// GetRate returns system rate if availabled
func (c Conversion) GetRate() (float64, error) {
c.mtx.Lock()
defer c.mtx.Unlock()
if c.rate == nil {
return 0, errors.New("rate undefined")
}
return *c.rate, nil
}
// GetInversionRate returns the rate of the inversion of the conversion pair
func (c Conversion) GetInversionRate() (float64, error) {
if c.mtx == nil {
return 0, errors.New("mutex copy failure")
}
c.mtx.Lock()
defer c.mtx.Unlock()
if c.rate == nil {
return 0, errors.New("rate undefined")
}
return *c.inverseRate, nil
}
// Convert for example converts $1 USD to the equivalent Japanese Yen or vice
// versa.
func (c Conversion) Convert(fromAmount float64) (float64, error) {
if c.IsInvalid() {
return fromAmount, nil
}
if !c.IsFiat() {
return 0, errors.New("not fiat pair")
}
r, err := c.GetRate()
if err != nil {
return 0, err
}
return r * fromAmount, nil
}
// ConvertInverse converts backwards if needed
func (c Conversion) ConvertInverse(fromAmount float64) (float64, error) {
if c.IsInvalid() {
return fromAmount, nil
}
if !c.IsFiat() {
return 0, errors.New("not fiat pair")
}
r, err := c.GetInversionRate()
if err != nil {
return 0, err
}
return r * fromAmount, nil
}

177
currency/conversion_test.go Normal file
View File

@@ -0,0 +1,177 @@
package currency
import (
"testing"
"github.com/thrasher-/gocryptotrader/common"
)
func TestNewConversionFromString(t *testing.T) {
expected := "AUDUSD"
conv, err := NewConversionFromString(expected)
if err != nil {
t.Error("Test Failed - NewConversionFromString() error", err)
}
if conv.String() != expected {
t.Errorf("Test Failed - NewConversion() error expected %s but received %s",
expected,
conv)
}
newexpected := common.StringToLower(expected)
conv, err = NewConversionFromString(newexpected)
if err != nil {
t.Error("Test Failed - NewConversionFromString() error", err)
}
if conv.String() != newexpected {
t.Errorf("Test Failed - NewConversion() error expected %s but received %s",
newexpected,
conv)
}
}
func TestNewConversionFromStrings(t *testing.T) {
from := "AUD"
to := "USD"
expected := "AUDUSD"
conv, err := NewConversionFromStrings(from, to)
if err != nil {
t.Error("Test Failed - NewConversionFromString() error", err)
}
if conv.String() != expected {
t.Errorf("Test Failed - NewConversion() error expected %s but received %s",
expected,
conv)
}
}
func TestNewConversion(t *testing.T) {
from := NewCode("AUD")
to := NewCode("USD")
expected := "AUDUSD"
conv, err := NewConversion(from, to)
if err != nil {
t.Error("Test Failed - NewConversionFromCode() error", err)
}
if conv.String() != expected {
t.Errorf("Test Failed - NewConversion() error expected %s but received %s",
expected,
conv)
}
}
func TestConversionIsInvalid(t *testing.T) {
from := AUD
to := USD
conv, err := NewConversion(from, to)
if err != nil {
t.Fatal("Test Failed - NewConversion() error", err)
}
if conv.IsInvalid() {
t.Errorf("Test Failed - IsInvalid() error expected false but received %v",
conv.IsInvalid())
}
to = AUD
conv, err = NewConversion(from, to)
if err == nil {
t.Fatal("Test Failed - NewConversion() error", err)
}
}
func TestConversionIsFiatPair(t *testing.T) {
from := AUD
to := USD
conv, err := NewConversion(from, to)
if err != nil {
t.Fatal("Test Failed - NewConversion() error", err)
}
if !conv.IsFiat() {
t.Errorf("Test Failed - IsFiatPair() error expected true but received %v",
conv.IsFiat())
}
to = LTC
conv, err = NewConversion(from, to)
if err == nil {
t.Fatal("Test Failed - NewConversion() error", err)
}
}
func TestConversionsRatesSystem(t *testing.T) {
var SuperDuperConversionSystem ConversionRates
if SuperDuperConversionSystem.HasData() {
t.Fatalf("Test Failed - HasData() error expected false but recieved %v",
SuperDuperConversionSystem.HasData())
}
testmap := map[string]float64{
"USDAUD": 1.3969317581,
"USDBRL": 3.7047257979,
"USDCAD": 1.3186386881,
"USDCHF": 1,
"USDCNY": 6.7222712044,
"USDCZK": 22.6406277552,
"USDDKK": 6.5785575736,
"USDEUR": 0.8816787163,
"USDGBP": 0.7665755599,
"USDHKD": 7.8492329395,
"USDILS": 3.6152354082,
"USDINR": 71.154558279,
"USDJPY": 110.7476635514,
"USDKRW": 1122.7913948157,
"USDMXN": 19.1589666725,
"USDNOK": 8.5818197849,
"USDNZD": 1.4559160642,
"USDPLN": 3.8304531829,
"USDRUB": 65.7533062952,
"USDSEK": 9.3196085346,
"USDSGD": 1.3512608006,
"USDTHB": 31.0950449656,
"USDZAR": 14.138070887,
}
err := SuperDuperConversionSystem.Update(testmap)
if err != nil {
t.Fatal("Test Failed - Update() error", err)
}
err = SuperDuperConversionSystem.Update(nil)
if err == nil {
t.Fatal("Test Failed - Update() error cannnot be nil")
}
if !SuperDuperConversionSystem.HasData() {
t.Fatalf("Test Failed - HasData() error expected true but recieved %v",
SuperDuperConversionSystem.HasData())
}
// * to a rate
p := SuperDuperConversionSystem.m[USD.Item][AUD.Item]
// inverse * to a rate
pi := SuperDuperConversionSystem.m[AUD.Item][USD.Item]
r := *p * 1000
expectedRate := 1396.9317581
if r != expectedRate {
t.Errorf("Test Failed - Convert() error expected %.13f but recieved %.13f",
expectedRate,
r)
}
inverseR := *pi * expectedRate
expectedInverseRate := float64(1000)
if inverseR != expectedInverseRate {
t.Errorf("Test Failed - Convert() error expected %.13f but recieved %.13f",
expectedInverseRate,
inverseR)
}
}

95
currency/currencies.go Normal file
View File

@@ -0,0 +1,95 @@
package currency
import "github.com/thrasher-/gocryptotrader/common"
// NewCurrenciesFromStringArray returns a Currencies object from strings
func NewCurrenciesFromStringArray(currencies []string) Currencies {
var list Currencies
for i := range currencies {
if currencies[i] == "" {
continue
}
list = append(list, NewCode(currencies[i]))
}
return list
}
// Currencies define a range of supported currency codes
type Currencies []Code
// Strings returns an array of currency strings
func (c Currencies) Strings() []string {
var list []string
for _, d := range c {
list = append(list, d.String())
}
return list
}
// Contains checks to see if a currency code is contained in the currency list
func (c Currencies) Contains(cc Code) bool {
for i := range c {
if c[i].Item == cc.Item {
return true
}
}
return false
}
// Join returns a comma serparated string
func (c Currencies) Join() string {
return common.JoinStrings(c.Strings(), ",")
}
// UnmarshalJSON comforms type to the umarshaler interface
func (c *Currencies) UnmarshalJSON(d []byte) error {
var configCurrencies string
err := common.JSONDecode(d, &configCurrencies)
if err != nil {
return err
}
var allTheCurrencies Currencies
for _, data := range common.SplitStrings(configCurrencies, ",") {
allTheCurrencies = append(allTheCurrencies, NewCode(data))
}
*c = allTheCurrencies
return nil
}
// MarshalJSON conforms type to the marshaler interface
func (c Currencies) MarshalJSON() ([]byte, error) {
return common.JSONEncode(c.Join())
}
// Match returns if the full list equals the supplied list
func (c Currencies) Match(other Currencies) bool {
if len(c) != len(other) {
return false
}
for _, d := range c {
var found bool
for i := range other {
if d == other[i] {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// Slice exposes the underlying type
func (c Currencies) Slice() []Code {
return c
}
// HasData checks to see if Currencies type has actual currencies
func (c Currencies) HasData() bool {
return len(c) != 0
}

View File

@@ -0,0 +1,50 @@
package currency
import (
"testing"
"github.com/thrasher-/gocryptotrader/common"
)
func TestCurrenciesUnmarshalJSON(t *testing.T) {
var unmarshalHere Currencies
expected := "btc,usd,ltc,bro,things"
encoded, err := common.JSONEncode(expected)
if err != nil {
t.Fatal("Test Failed - Currencies UnmarshalJSON() error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Currencies UnmarshalJSON() error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Currencies UnmarshalJSON() error", err)
}
if unmarshalHere.Join() != expected {
t.Errorf("Test Failed - Currencies UnmarshalJSON() error expected %s but received %s",
expected, unmarshalHere.Join())
}
}
func TestCurrenciesMarshalJSON(t *testing.T) {
quickStruct := struct {
C Currencies `json:"amazingCurrencies"`
}{
C: NewCurrenciesFromStringArray([]string{"btc", "usd", "ltc", "bro", "things"}),
}
encoded, err := common.JSONEncode(quickStruct)
if err != nil {
t.Fatal("Test Failed - Currencies MarshalJSON() error", err)
}
expected := `{"amazingCurrencies":"btc,usd,ltc,bro,things"}`
if string(encoded) != expected {
t.Errorf("Test Failed - Currencies MarshalJSON() error expected %s but received %s",
expected, string(encoded))
}
}

View File

@@ -1,320 +1,119 @@
package currency
import (
"errors"
"fmt"
"time"
// GetDefaultExchangeRates returns the currency exchange rates based off the
// default fiat values
func GetDefaultExchangeRates() (Conversions, error) {
return storage.GetDefaultForeignExchangeRates()
}
"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"
)
// GetExchangeRates returns the full fiat currency exchange rates base off
// configuration parameters supplied to the currency storage
func GetExchangeRates() (Conversions, error) {
return storage.GetExchangeRates()
}
const (
// DefaultBaseCurrency is the base currency used for conversion
DefaultBaseCurrency = "USD"
// DefaultCurrencies has the default minimum of FIAT values
DefaultCurrencies = "USD,AUD,EUR,CNY"
// DefaultCryptoCurrencies has the default minimum of crytpocurrency values
DefaultCryptoCurrencies = "BTC,LTC,ETH,DOGE,DASH,XRP,XMR"
)
// UpdateBaseCurrency updates storage base currency
func UpdateBaseCurrency(c Code) error {
return storage.UpdateBaseCurrency(c)
}
// Manager is the overarching type across this package
var (
FXRates map[string]float64
// GetBaseCurrency returns the storage base currency
func GetBaseCurrency() Code {
return storage.GetBaseCurrency()
}
FiatCurrencies []string
CryptoCurrencies []string
// GetDefaultBaseCurrency returns storage default base currency
func GetDefaultBaseCurrency() Code {
return storage.GetDefaultBaseCurrency()
}
BaseCurrency string
FXProviders *forexprovider.ForexProviders
// GetCryptocurrencies returns the storage enabled cryptocurrencies
func GetCryptocurrencies() Currencies {
return storage.GetCryptocurrencies()
}
CryptocurrencyProvider *coinmarketcap.Coinmarketcap
TotalCryptocurrencies []Data
TotalExchanges []Data
)
// GetDefaultCryptocurrencies returns a list of default cryptocurrencies
func GetDefaultCryptocurrencies() Currencies {
return storage.GetDefaultCryptocurrencies()
}
// SetDefaults sets the default currency provider and settings for
// currency conversion used outside of the bot setting
func SetDefaults() {
FXRates = make(map[string]float64)
BaseCurrency = DefaultBaseCurrency
// GetFiatCurrencies returns the storage enabled fiat currencies
func GetFiatCurrencies() Currencies {
return storage.GetFiatCurrencies()
}
FXProviders = forexprovider.NewDefaultFXProvider()
err := SeedCurrencyData(DefaultCurrencies)
if err != nil {
log.Errorf("Failed to seed currency data. Err: %s", err)
// GetDefaultFiatCurrencies returns a list of default fiat currencies
func GetDefaultFiatCurrencies() Currencies {
return storage.GetDefaultFiatCurrencies()
}
// UpdateCurrencies updates the local cryptocurrency or fiat currency store
func UpdateCurrencies(c Currencies, isCryptocurrency bool) {
if isCryptocurrency {
storage.UpdateEnabledCryptoCurrencies(c)
return
}
storage.UpdateEnabledFiatCurrencies(c)
}
// SeedCurrencyData returns rates correlated with suported currencies
func SeedCurrencyData(currencies string) error {
if FXRates == nil {
FXRates = make(map[string]float64)
}
if FXProviders == nil {
FXProviders = forexprovider.NewDefaultFXProvider()
}
newRates, err := FXProviders.GetCurrencyData(BaseCurrency, currencies)
if err != nil {
return err
}
for key, value := range newRates {
FXRates[key] = value
}
return nil
// ConvertCurrency converts an amount from one currency to another
func ConvertCurrency(amount float64, from, to Code) (float64, error) {
return storage.ConvertCurrency(amount, from, to)
}
// GetExchangeRates returns the currency exchange rates
func GetExchangeRates() map[string]float64 {
return FXRates
// SeedForeignExchangeData seeds FX data with the currencies supplied
func SeedForeignExchangeData(c Currencies) error {
return storage.SeedForeignExchangeRatesByCurrencies(c)
}
// IsDefaultCurrency checks if the currency passed in matches the default fiat
// currency
func IsDefaultCurrency(currency string) bool {
defaultCurrencies := common.SplitStrings(DefaultCurrencies, ",")
return common.StringDataCompare(defaultCurrencies, common.StringToUpper(currency))
// GetTotalMarketCryptocurrencies returns the full market cryptocurrencies
func GetTotalMarketCryptocurrencies() ([]Code, error) {
return storage.GetTotalMarketCryptocurrencies()
}
// IsDefaultCryptocurrency checks if the currency passed in matches the default
// cryptocurrency
func IsDefaultCryptocurrency(currency string) bool {
cryptoCurrencies := common.SplitStrings(DefaultCryptoCurrencies, ",")
return common.StringDataCompare(cryptoCurrencies, common.StringToUpper(currency))
// RunStorageUpdater runs a new foreign exchange updater instance
func RunStorageUpdater(o BotOverrides, m MainConfiguration, filepath string, v bool) error {
return storage.RunUpdater(o, m, filepath, v)
}
// IsFiatCurrency checks if the currency passed is an enabled fiat currency
func IsFiatCurrency(currency string) bool {
return common.StringDataCompare(FiatCurrencies, common.StringToUpper(currency))
}
// IsCryptocurrency checks if the currency passed is an enabled CRYPTO currency.
func IsCryptocurrency(currency string) bool {
return common.StringDataCompare(CryptoCurrencies, common.StringToUpper(currency))
}
// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC
func IsCryptoPair(p pair.CurrencyPair) bool {
return IsCryptocurrency(p.FirstCurrency.String()) &&
IsCryptocurrency(p.SecondCurrency.String())
}
// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD
func IsCryptoFiatPair(p pair.CurrencyPair) bool {
return IsCryptocurrency(p.FirstCurrency.String()) && !IsCryptocurrency(p.SecondCurrency.String()) ||
!IsCryptocurrency(p.FirstCurrency.String()) && IsCryptocurrency(p.SecondCurrency.String())
}
// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD
func IsFiatPair(p pair.CurrencyPair) bool {
return IsFiatCurrency(p.FirstCurrency.String()) &&
IsFiatCurrency(p.SecondCurrency.String())
}
// Update updates the local crypto currency or base currency store
func Update(input []string, cryptos bool) {
for x := range input {
if cryptos {
if !common.StringDataCompare(CryptoCurrencies, input[x]) {
CryptoCurrencies = append(CryptoCurrencies, common.StringToUpper(input[x]))
// CopyPairFormat copies the pair format from a list of pairs once matched
func CopyPairFormat(p Pair, pairs []Pair, exact bool) Pair {
for x := range pairs {
if exact {
if p.Equal(pairs[x]) {
return pairs[x]
}
}
if p.EqualIncludeReciprocal(pairs[x]) {
return pairs[x]
}
}
return Pair{Base: NewCode(""), Quote: NewCode("")}
}
// FormatPairs formats a string array to a list of currency pairs with the
// supplied currency pair format
func FormatPairs(pairs []string, delimiter, index string) (Pairs, error) {
var result Pairs
for x := range pairs {
if pairs[x] == "" {
continue
}
var p Pair
if delimiter != "" {
p = NewPairDelimiter(pairs[x], delimiter)
} else {
if !common.StringDataCompare(FiatCurrencies, input[x]) {
FiatCurrencies = append(FiatCurrencies, common.StringToUpper(input[x]))
if index != "" {
var err error
p, err = NewPairFromIndex(pairs[x], index)
if err != nil {
return Pairs{}, err
}
} else {
p = NewPairFromStrings(pairs[x][0:3], pairs[x][3:])
}
}
result = append(result, p)
}
}
func extractBaseCurrency() string {
for k := range FXRates {
return k[0:3]
}
return ""
}
// ConvertCurrency for example converts $1 USD to the equivalent Japanese Yen
// or vice versa.
func ConvertCurrency(amount float64, from, to string) (float64, error) {
if FXProviders == nil {
SetDefaults()
}
from = common.StringToUpper(from)
to = common.StringToUpper(to)
if from == to {
return amount, nil
}
if from == "RUR" {
from = "RUB"
}
if to == "RUR" {
to = "RUB"
}
if len(FXRates) == 0 {
SeedCurrencyData(from + "," + to)
}
// Need to extract the base currency to see if we actually got it from the Forex API
// Fixer free API sets the base currency to EUR
baseCurr := extractBaseCurrency()
var resultFrom float64
var resultTo float64
// check to see if we're converting from the base currency
if to == baseCurr {
var ok bool
resultFrom, ok = FXRates[baseCurr+from]
if !ok {
return 0, fmt.Errorf("currency conversion failed. Unable to find %s in currency map [%s -> %s]", from, from, to)
}
return amount / resultFrom, nil
}
// Check to see if we're converting from the base currency
if from == baseCurr {
var ok bool
resultTo, ok = FXRates[baseCurr+to]
if !ok {
return 0, fmt.Errorf("currency conversion failed. Unable to find %s in currency map [%s -> %s]", to, from, to)
}
return resultTo * amount, nil
}
// Otherwise convert to base currency, then to the target currency
resultFrom, ok := FXRates[baseCurr+from]
if !ok {
return 0, fmt.Errorf("currency conversion failed. Unable to find %s in currency map [%s -> %s]", from, from, to)
}
converted := amount / resultFrom
resultTo, ok = FXRates[baseCurr+to]
if !ok {
return 0, fmt.Errorf("currency conversion failed. Unable to find %s in currency map [%s -> %s]", to, from, to)
}
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 x := range cryptoData {
var active bool
if cryptoData[x].IsActive == 1 {
active = true
}
TotalCryptocurrencies = append(TotalCryptocurrencies, Data{
ID: cryptoData[x].ID,
Name: cryptoData[x].Name,
Symbol: cryptoData[x].Symbol,
Slug: cryptoData[x].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
return result, nil
}

View File

@@ -2,259 +2,125 @@ package currency
import (
"testing"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/currency/symbol"
)
func TestSetDefaults(t *testing.T) {
FXRates = nil
BaseCurrency = "BLAH"
FXProviders = nil
SetDefaults()
if FXRates == nil {
t.Fatal("Expected FXRates to be non-nil")
}
if BaseCurrency != DefaultBaseCurrency {
t.Fatal("Expected BaseCurrency to be 'USD'")
}
if FXProviders == nil {
t.Fatal("Expected FXRates to be non-nil")
}
}
func TestSeedCurrencyData(t *testing.T) {
err := SeedCurrencyData("AUD")
func TestGetDefaultExchangeRates(t *testing.T) {
rates, err := GetDefaultExchangeRates()
if err != nil {
t.Fatal(err)
t.Error("Test failed - GetDefaultExchangeRates() err", err)
}
for _, val := range rates {
if !val.IsFiat() {
t.Errorf("Test failed - GetDefaultExchangeRates() %s is not fiat pair",
val)
}
}
}
func TestGetExchangeRates(t *testing.T) {
result := make(map[string]float64)
for k, v := range GetExchangeRates() {
result[k] = v
}
backup := FXRates
FXRates = nil
result = GetExchangeRates()
if result != nil {
t.Fatal("Expected nil map")
rates, err := GetExchangeRates()
if err != nil {
t.Error("Test failed - GetExchangeRates() err", err)
}
FXRates = backup
}
func TestIsDefaultCurrency(t *testing.T) {
t.Parallel()
var str1, str2, str3 string = "USD", "usd", "cats123"
if !IsDefaultCurrency(str1) {
t.Errorf(
"Test Failed. TestIsDefaultCurrency: \nCannot match currency, %s.", str1,
)
}
if !IsDefaultCurrency(str2) {
t.Errorf(
"Test Failed. TestIsDefaultCurrency: \nCannot match currency, %s.", str2,
)
}
if IsDefaultCurrency(str3) {
t.Errorf(
"Test Failed. TestIsDefaultCurrency: \nFunction return is incorrect with, %s.",
str3,
)
for _, val := range rates {
if !val.IsFiat() {
t.Errorf("Test failed - GetExchangeRates() %s is not fiat pair",
val)
}
}
}
func TestIsDefaultCryptocurrency(t *testing.T) {
t.Parallel()
var str1, str2, str3 string = symbol.BTC, symbol.BTC, "dogs123"
if !IsDefaultCryptocurrency(str1) {
t.Errorf(
"Test Failed. TestIsDefaultCryptocurrency: \nCannot match currency, %s.",
str1,
)
func TestUpdateBaseCurrency(t *testing.T) {
err := UpdateBaseCurrency(AUD)
if err != nil {
t.Error("Test failed - UpdateBaseCurrency() err", err)
}
if !IsDefaultCryptocurrency(str2) {
t.Errorf(
"Test Failed. TestIsDefaultCryptocurrency: \nCannot match currency, %s.",
str2,
)
err = UpdateBaseCurrency(LTC)
if err == nil {
t.Error("Test failed - UpdateBaseCurrency() cannot be nil")
}
if IsDefaultCryptocurrency(str3) {
t.Errorf(
"Test Failed. TestIsDefaultCryptocurrency: \nFunction return is incorrect with, %s.",
str3,
)
if GetBaseCurrency() != AUD {
t.Errorf("Test failed - GetBaseCurrency() expected %s but recieved %s",
AUD, GetBaseCurrency())
}
}
func TestIsFiatCurrency(t *testing.T) {
if IsFiatCurrency("") {
t.Error("Test failed. TestIsFiatCurrency returned true on an empty string")
}
FiatCurrencies = []string{"USD", "AUD"}
var str1, str2, str3 string = symbol.BTC, "USD", "birds123"
if IsFiatCurrency(str1) {
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str1,
)
}
if !IsFiatCurrency(str2) {
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str2,
)
}
if IsFiatCurrency(str3) {
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str3,
)
func TestGetDefaultBaseCurrency(t *testing.T) {
if GetDefaultBaseCurrency() != USD {
t.Errorf("Test failed - GetDefaultBaseCurrency() expected %s but recieved %s",
USD, GetDefaultBaseCurrency())
}
}
func TestIsCryptocurrency(t *testing.T) {
if IsCryptocurrency("") {
t.Error("Test failed. TestIsCryptocurrency returned true on an empty string")
}
CryptoCurrencies = []string{symbol.BTC, symbol.LTC, symbol.DASH}
var str1, str2, str3 string = "USD", symbol.BTC, "pterodactyl123"
if IsCryptocurrency(str1) {
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str1,
)
}
if !IsCryptocurrency(str2) {
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str2,
)
}
if IsCryptocurrency(str3) {
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str3,
)
func TestGetDefaulCryptoCurrencies(t *testing.T) {
expected := Currencies{BTC, LTC, ETH, DOGE, DASH, XRP, XMR}
if !GetDefaultCryptocurrencies().Match(expected) {
t.Errorf("Test failed - GetDefaultCryptocurrencies() expected %s but recieved %s",
expected, GetDefaultCryptocurrencies())
}
}
func TestIsCryptoPair(t *testing.T) {
if IsCryptocurrency("") {
t.Error("Test failed. TestIsCryptocurrency returned true on an empty string")
}
CryptoCurrencies = []string{symbol.BTC, symbol.LTC, symbol.DASH}
FiatCurrencies = []string{"USD"}
if !IsCryptoPair(pair.NewCurrencyPair(symbol.BTC, symbol.LTC)) {
t.Error("Test Failed. TestIsCryptoPair. Expected true result")
}
if IsCryptoPair(pair.NewCurrencyPair(symbol.BTC, "USD")) {
t.Error("Test Failed. TestIsCryptoPair. Expected false result")
func TestGetDefaultFiatCurrencies(t *testing.T) {
expected := Currencies{USD, AUD, EUR, CNY}
if !GetDefaultFiatCurrencies().Match(expected) {
t.Errorf("Test failed - GetDefaultFiatCurrencies() expected %s but recieved %s",
expected, GetDefaultFiatCurrencies())
}
}
func TestIsCryptoFiatPair(t *testing.T) {
if IsCryptocurrency("") {
t.Error("Test failed. TestIsCryptocurrency returned true on an empty string")
func TestUpdateCurrencies(t *testing.T) {
fiat := Currencies{HKN, JPY}
UpdateCurrencies(fiat, false)
rFiat := GetFiatCurrencies()
if !rFiat.Contains(HKN) || !rFiat.Contains(JPY) {
t.Error("Test failed - UpdateCurrencies() currencies did not update")
}
CryptoCurrencies = []string{symbol.BTC, symbol.LTC, symbol.DASH}
FiatCurrencies = []string{"USD"}
if !IsCryptoFiatPair(pair.NewCurrencyPair(symbol.BTC, "USD")) {
t.Error("Test Failed. TestIsCryptoPair. Expected true result")
}
if IsCryptoFiatPair(pair.NewCurrencyPair(symbol.BTC, symbol.LTC)) {
t.Error("Test Failed. TestIsCryptoPair. Expected false result")
crypto := Currencies{ZAR, ZCAD, B2}
UpdateCurrencies(crypto, true)
rCrypto := GetCryptocurrencies()
if !rCrypto.Contains(ZAR) || !rCrypto.Contains(ZCAD) || !rCrypto.Contains(B2) {
t.Error("Test failed - UpdateCurrencies() currencies did not update")
}
}
func TestIsFiatPair(t *testing.T) {
CryptoCurrencies = []string{symbol.BTC, symbol.LTC, symbol.DASH}
FiatCurrencies = []string{"USD", "AUD", "EUR"}
if !IsFiatPair(pair.NewCurrencyPair("AUD", "USD")) {
t.Error("Test Failed. TestIsFiatPair. Expected true result")
}
if IsFiatPair(pair.NewCurrencyPair(symbol.BTC, "AUD")) {
t.Error("Test Failed. TestIsFiatPair. Expected false result")
}
}
func TestUpdate(t *testing.T) {
CryptoCurrencies = []string{symbol.BTC, symbol.LTC, symbol.DASH}
FiatCurrencies = []string{"USD", "AUD"}
Update([]string{"ETH"}, true)
Update([]string{"JPY"}, false)
if !IsCryptocurrency("ETH") {
t.Error(
"Test Failed. TestUpdate: \nCannot match currency: ETH",
)
}
if !IsFiatCurrency("JPY") {
t.Errorf(
"Test Failed. TestUpdate: \nCannot match currency: JPY",
)
}
}
func TestExtractBaseCurrency(t *testing.T) {
backup := FXRates
FXRates = nil
FXRates = make(map[string]float64)
if extractBaseCurrency() != "" {
t.Fatalf("Test failed. Expected '' as base currency")
}
FXRates["USDAUD"] = 120
if extractBaseCurrency() != "USD" {
t.Fatalf("Test failed. Expected 'USD' as base currency")
}
FXRates = backup
}
func TestConvertCurrency(t *testing.T) {
_, err := ConvertCurrency(100, "AUD", "USD")
_, err := ConvertCurrency(100, AUD, USD)
if err != nil {
t.Fatal(err)
}
_, err = ConvertCurrency(100, "USD", "AUD")
r, err := ConvertCurrency(100, AUD, AUD)
if err != nil {
t.Fatal(err)
}
_, err = ConvertCurrency(100, "CNY", "AUD")
if r != 100 {
t.Errorf("Test Failed - ConvertCurrency error, incorrect rate return %2.f but received %2.f",
100.00, r)
}
_, err = ConvertCurrency(100, USD, AUD)
if err != nil {
t.Fatal(err)
}
_, err = ConvertCurrency(100, "meow", "USD")
_, err = ConvertCurrency(100, CNY, AUD)
if err != nil {
t.Fatal(err)
}
_, err = ConvertCurrency(100, LTC, USD)
if err == nil {
t.Fatal("Expected err on non-existent currency")
}
_, err = ConvertCurrency(100, "USD", "meow")
_, err = ConvertCurrency(100, USD, LTC)
if err == nil {
t.Fatal("Expected err on non-existent currency")
}
}

View File

@@ -0,0 +1,62 @@
package currency
import (
"time"
"github.com/thrasher-/gocryptotrader/currency/coinmarketcap"
)
// MainConfiguration is the main configuration from the config.json file
type MainConfiguration struct {
ForexProviders []FXSettings
CryptocurrencyProvider coinmarketcap.Settings
Cryptocurrencies Currencies
CurrencyPairFormat interface{}
FiatDisplayCurrency Code
CurrencyDelay time.Duration
FxRateDelay time.Duration
}
// BotOverrides defines a bot overriding factor for quick running currency
// subsystems
type BotOverrides struct {
Coinmarketcap bool
FxCurrencyConverter bool
FxCurrencyLayer bool
FxFixer bool
FxOpenExchangeRates bool
}
// CoinmarketcapSettings refers to settings
type CoinmarketcapSettings coinmarketcap.Settings
// SystemsSettings defines incoming system settings
type SystemsSettings struct {
Coinmarketcap coinmarketcap.Settings
Currencyconverter FXSettings
Currencylayer FXSettings
Fixer FXSettings
Openexchangerates FXSettings
}
// FXSettings defines foreign exchange requester settings
type FXSettings struct {
Name string `json:"name"`
Enabled bool `json:"enabled"`
Verbose bool `json:"verbose"`
RESTPollingDelay time.Duration `json:"restPollingDelay"`
APIKey string `json:"apiKey"`
APIKeyLvl int `json:"apiKeyLvl"`
PrimaryProvider bool `json:"primaryProvider"`
}
// File defines a full currency file generated by the currency storage
// analysis system
type File struct {
LastMainUpdate time.Time `json:"lastMainUpdate"`
Cryptocurrency []Item `json:"cryptocurrencies"`
FiatCurrency []Item `json:"fiatCurrencies"`
UnsetCurrency []Item `json:"unsetCurrencies"`
Contracts []Item `json:"contracts"`
Token []Item `json:"tokens"`
}

View File

@@ -4,6 +4,9 @@ import (
"time"
)
// DefaultTimeOut is the default timeout for foreign exchange providers
const DefaultTimeOut = time.Second * 15
// Settings enforces standard variables across the provider packages
type Settings struct {
Name string `json:"name"`

View File

@@ -3,44 +3,153 @@ package base
import (
"errors"
"fmt"
"sync"
log "github.com/thrasher-/gocryptotrader/logger"
"github.com/thrasher-/gocryptotrader/common"
)
// IFXProviders contains an array of foreign exchange interfaces
type IFXProviders []IFXProvider
// IFXProvider enforces standard functions for all foreign exchange providers
// supported in GoCryptoTrader
type IFXProvider interface {
Setup(config Settings)
Setup(config Settings) error
GetRates(baseCurrency, symbols string) (map[string]float64, error)
GetName() string
IsEnabled() bool
IsPrimaryProvider() bool
GetSupportedCurrencies() ([]string, error)
}
// FXHandler defines a full suite of FX data providers with failure backup with
// unsupported currency shunt procedure
type FXHandler struct {
Primary Provider
Support []Provider
mtx sync.Mutex
}
// Provider defines a singular foreign exchange provider with its supported
// currencies to cross reference request currencies and if not supported shunt
// request traffic to and from other providers so that we can maintain full
// currency list integration
type Provider struct {
Provider IFXProvider
SupportedCurrencies []string
}
// GetNewRate access rates by predetermined logic based on how a provider
// handles requests
func (p *Provider) GetNewRate(base string, currencies []string) (map[string]float64, error) {
if !p.Provider.IsEnabled() {
return nil, fmt.Errorf("provider %s is not enabled",
p.Provider.GetName())
}
switch p.Provider.GetName() {
case "ExchangeRates":
return p.Provider.GetRates(base, "") // Zero value to get all rates
default:
return p.Provider.GetRates(base, common.JoinStrings(currencies, ","))
}
}
// CheckCurrencies cross references supplied currencies with exchange supported
// currencies, if there are any currencies not supported it returns a list
// to pass on to the next provider
func (p Provider) CheckCurrencies(currencies []string) []string {
var spillOver []string
for _, c := range currencies {
if !common.StringDataCompareUpper(p.SupportedCurrencies, c) {
spillOver = append(spillOver, c)
}
}
return spillOver
}
// GetCurrencyData returns currency data from enabled FX providers
func (fxp IFXProviders) GetCurrencyData(baseCurrency, symbols string) (map[string]float64, error) {
for x := range fxp {
if fxp[x].IsPrimaryProvider() && fxp[x].IsEnabled() {
rates, err := fxp[x].GetRates(baseCurrency, symbols)
if err != nil {
log.Error(err)
for y := range fxp {
if !fxp[y].IsPrimaryProvider() && fxp[x].IsEnabled() {
rates, err = fxp[y].GetRates(baseCurrency, symbols)
if err != nil {
log.Error(err)
continue
}
return rates, nil
}
}
return nil, fmt.Errorf("forex provider %s unable to acquire rates data", fxp[x].GetName())
}
return rates, nil
}
func (f *FXHandler) GetCurrencyData(baseCurrency string, currencies []string) (map[string]float64, error) {
var fullRange = currencies
if !common.StringDataCompareUpper(currencies, baseCurrency) {
fullRange = append(fullRange, baseCurrency)
}
return nil, errors.New("no forex providers enabled")
f.mtx.Lock()
defer f.mtx.Unlock()
if f.Primary.Provider == nil {
return nil, errors.New("primary foreign exchange provider details not set")
}
shunt := f.Primary.CheckCurrencies(fullRange)
rates, err := f.Primary.GetNewRate(baseCurrency, currencies)
if err != nil {
return f.backupGetRate(baseCurrency, currencies)
}
if len(shunt) != 0 {
rateNew, err := f.backupGetRate(baseCurrency, shunt)
if err != nil {
return nil, fmt.Errorf("failed to update rate map for currencies %v %v",
shunt,
err)
}
for key, val := range rateNew {
rates[key] = val
}
return rates, nil
}
return rates, nil
}
// backupGetRate uses the currencies that are supported and falls through, and
// errors when unsupported currency found
func (f *FXHandler) backupGetRate(base string, currencies []string) (map[string]float64, error) {
if f.Support == nil {
return nil, errors.New("no supporting foreign exchange providers set")
}
var shunt []string
rate := make(map[string]float64)
for i := range f.Support {
if len(shunt) != 0 {
shunt = f.Support[i].CheckCurrencies(shunt)
newRate, err := f.Support[i].GetNewRate(base, shunt)
if err != nil {
continue
}
for k, v := range newRate {
rate[k] = v
}
if len(shunt) != 0 {
continue
}
return rate, nil
}
shunt = f.Support[i].CheckCurrencies(currencies)
newRate, err := f.Support[i].GetNewRate(base, currencies)
if err != nil {
continue
}
for k, v := range newRate {
rate[k] = v
}
if len(shunt) != 0 {
continue
}
return rate, nil
}
return nil, fmt.Errorf("currencies %s not supported", shunt)
}

View File

@@ -1 +0,0 @@
package base

View File

@@ -3,10 +3,13 @@ package currencyconverter
import (
"errors"
"fmt"
"net/http"
"net/url"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
"github.com/thrasher-/gocryptotrader/exchanges/request"
log "github.com/thrasher-/gocryptotrader/logger"
)
@@ -22,15 +25,19 @@ const (
APIEndpointUsage = "usage"
defaultAPIKey = "Key"
authRate = 0
unAuthRate = 0
)
// CurrencyConverter stores the struct for the CurrencyConverter API
type CurrencyConverter struct {
base.Base
Requester *request.Requester
}
// Setup sets appropriate values for CurrencyLayer
func (c *CurrencyConverter) Setup(config base.Settings) {
func (c *CurrencyConverter) Setup(config base.Settings) error {
c.Name = config.Name
c.APIKey = config.APIKey
c.APIKeyLvl = config.APIKeyLvl
@@ -38,6 +45,11 @@ func (c *CurrencyConverter) Setup(config base.Settings) {
c.RESTPollingDelay = config.RESTPollingDelay
c.Verbose = config.Verbose
c.PrimaryProvider = config.PrimaryProvider
c.Requester = request.New(c.Name,
request.NewRateLimit(time.Second*10, authRate),
request.NewRateLimit(time.Second*10, unAuthRate),
common.NewHTTPClientWithTimeout(base.DefaultTimeOut))
return nil
}
// GetRates is a wrapper function to return rates
@@ -128,8 +140,8 @@ func (c *CurrencyConverter) Convert(from, to string) (map[string]float64, error)
return result, nil
}
// GetCurrencies returns a list of the supported currencies
func (c *CurrencyConverter) GetCurrencies() (map[string]CurrencyItem, error) {
// GetSupportedCurrencies returns a list of the supported currencies
func (c *CurrencyConverter) GetSupportedCurrencies() ([]string, error) {
var result Currencies
err := c.SendHTTPRequest(APIEndpointCurrencies, url.Values{}, &result)
@@ -137,7 +149,12 @@ func (c *CurrencyConverter) GetCurrencies() (map[string]CurrencyItem, error) {
return nil, err
}
return result.Results, nil
var currencies []string
for key := range result.Results {
currencies = append(currencies, key)
}
return currencies, nil
}
// GetCountries returns a list of the supported countries and
@@ -157,16 +174,23 @@ func (c *CurrencyConverter) GetCountries() (map[string]CountryItem, error) {
// upgrades request to SSL.
func (c *CurrencyConverter) SendHTTPRequest(endPoint string, values url.Values, result interface{}) error {
var path string
var auth bool
if c.APIKey == "" || c.APIKey == defaultAPIKey {
path = fmt.Sprintf("%s%s/%s?", APIEndpointFreeURL, APIEndpointVersion, endPoint)
auth = true
} else {
path = fmt.Sprintf("%s%s%s?", APIEndpointURL, APIEndpointVersion, endPoint)
values.Set("apiKey", c.APIKey)
}
path += values.Encode()
err := common.SendHTTPGetRequest(path, true, c.Verbose, &result)
err := c.Requester.SendPayload(http.MethodGet,
path,
nil,
nil,
&result,
auth,
c.Verbose)
if err != nil {
return fmt.Errorf("currency converter API SendHTTPRequest error %s with path %s",
err,

View File

@@ -80,12 +80,12 @@ func TestConvert(t *testing.T) {
}
}
func TestGetCurrencies(t *testing.T) {
func TestGetSupportedCurrencies(t *testing.T) {
if !IsAPIKeysSet() {
t.Skip()
}
_, err := c.GetCurrencies()
_, err := c.GetSupportedCurrencies()
if err != nil {
t.Fatal(err)
}

View File

@@ -14,11 +14,15 @@ package currencylayer
import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
"github.com/thrasher-/gocryptotrader/exchanges/request"
log "github.com/thrasher-/gocryptotrader/logger"
)
// const declarations consist of endpoints and APIKey privileges
@@ -36,6 +40,9 @@ const (
APIEndpointConversion = "convert"
APIEndpointTimeframe = "timeframe"
APIEndpointChange = "change"
authRate = 0
unAuthRate = 0
)
// CurrencyLayer is a foreign exchange rate provider at
@@ -43,10 +50,17 @@ const (
// account. Has automatic upgrade to a SSL connection.
type CurrencyLayer struct {
base.Base
Requester *request.Requester
}
// Setup sets appropriate values for CurrencyLayer
func (c *CurrencyLayer) Setup(config base.Settings) {
func (c *CurrencyLayer) Setup(config base.Settings) error {
if config.APIKeyLvl < 0 || config.APIKeyLvl > 3 {
log.Errorf("apikey incorrectly set in config.json for %s, please set appropriate account levels",
config.Name)
return errors.New("apikey set failure")
}
c.Name = config.Name
c.APIKey = config.APIKey
c.APIKeyLvl = config.APIKeyLvl
@@ -54,6 +68,12 @@ func (c *CurrencyLayer) Setup(config base.Settings) {
c.RESTPollingDelay = config.RESTPollingDelay
c.Verbose = config.Verbose
c.PrimaryProvider = config.PrimaryProvider
c.Requester = request.New(c.Name,
request.NewRateLimit(time.Second*10, authRate),
request.NewRateLimit(time.Second*10, unAuthRate),
common.NewHTTPClientWithTimeout(base.DefaultTimeOut))
return nil
}
// GetRates is a wrapper function to return rates for GoCryptoTrader
@@ -62,7 +82,7 @@ func (c *CurrencyLayer) GetRates(baseCurrency, symbols string) (map[string]float
}
// GetSupportedCurrencies returns supported currencies
func (c *CurrencyLayer) GetSupportedCurrencies() (map[string]string, error) {
func (c *CurrencyLayer) GetSupportedCurrencies() ([]string, error) {
var resp SupportedCurrencies
if err := c.SendHTTPRequest(APIEndpointList, url.Values{}, &resp); err != nil {
@@ -72,7 +92,13 @@ func (c *CurrencyLayer) GetSupportedCurrencies() (map[string]string, error) {
if !resp.Success {
return nil, errors.New(resp.Error.Info)
}
return resp.Currencies, nil
var currencies []string
for key := range resp.Currencies {
currencies = append(currencies, key)
}
return currencies, nil
}
// GetliveData returns live quotes for foreign exchange currencies
@@ -198,12 +224,20 @@ func (c *CurrencyLayer) SendHTTPRequest(endPoint string, values url.Values, resu
var path string
values.Set("access_key", c.APIKey)
var auth bool
if c.APIKeyLvl == AccountFree {
path = fmt.Sprintf("%s%s%s", APIEndpointURL, endPoint, "?")
} else {
auth = true
path = fmt.Sprintf("%s%s%s", APIEndpointURLSSL, endPoint, "?")
}
path += values.Encode()
return common.SendHTTPGetRequest(path, true, c.Verbose, result)
return c.Requester.SendPayload(http.MethodGet,
path,
nil,
nil,
&result,
auth,
c.Verbose)
}

View File

@@ -2,6 +2,8 @@ package currencylayer
import (
"testing"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
)
var c CurrencyLayer
@@ -10,54 +12,126 @@ var c CurrencyLayer
// minimize your API calls using this test.
const (
APIkey = ""
Apilevel = 3
Apilevel = 0
)
var isSet bool
func setup() error {
if !isSet {
defaultCfg := base.Settings{
Name: "CurrencyLayer",
Enabled: true,
}
if APIkey != "" {
defaultCfg.APIKey = APIkey
}
if Apilevel > -2 && Apilevel < 4 {
defaultCfg.APIKeyLvl = Apilevel
}
err := c.Setup(defaultCfg)
if err != nil {
return err
}
isSet = true
}
return nil
}
func areAPIKeysSet() bool {
return APIkey != "" && Apilevel != -1
}
func TestGetRates(t *testing.T) {
_, err := c.GetRates("USD", "AUD")
if err == nil {
err := setup()
if err != nil {
t.Skip("Test Failed - CurrencyLayer GetRates error", err)
}
_, err = c.GetRates("USD", "AUD")
if areAPIKeysSet() && err != nil {
t.Error("test error - currencylayer GetRates() error", err)
} else if !areAPIKeysSet() && err == nil {
t.Error("test error - currencylayer GetRates() error cannot be nil")
}
}
func TestGetSupportedCurrencies(t *testing.T) {
_, err := c.GetSupportedCurrencies()
if err == nil {
err := setup()
if err != nil {
t.Fatal("Test Failed - CurrencyLayer GetSupportedCurrencies error", err)
}
_, err = c.GetSupportedCurrencies()
if areAPIKeysSet() && err != nil {
t.Error("test error - currencylayer GetSupportedCurrencies() error", err)
} else if !areAPIKeysSet() && err == nil {
t.Error("test error - currencylayer GetSupportedCurrencies() error cannot be nil")
}
}
func TestGetliveData(t *testing.T) {
_, err := c.GetliveData("AUD", "USD")
if err == nil {
err := setup()
if err != nil {
t.Fatal("Test Failed - CurrencyLayer GetliveData error", err)
}
_, err = c.GetliveData("AUD", "USD")
if areAPIKeysSet() && err != nil {
t.Error("test error - currencylayer GetliveData() error", err)
} else if !areAPIKeysSet() && err == nil {
t.Error("test error - currencylayer GetliveData() error cannot be nil")
}
}
func TestGetHistoricalData(t *testing.T) {
_, err := c.GetHistoricalData("2016-12-15", []string{"AUD"}, "USD")
if err == nil {
err := setup()
if err != nil {
t.Fatal("Test Failed - CurrencyLayer GetHistoricalData error", err)
}
_, err = c.GetHistoricalData("2016-12-15", []string{"AUD"}, "USD")
if areAPIKeysSet() && err != nil {
t.Error("test error - currencylayer GetHistoricalData() error", err)
} else if !areAPIKeysSet() && err == nil {
t.Error("test error - currencylayer GetHistoricalData() error cannot be nil")
}
}
func TestConvert(t *testing.T) {
_, err := c.Convert("USD", "AUD", "", 1)
if err == nil {
t.Error("test error - currencylayer Convert() error")
err := setup()
if err != nil {
t.Fatal("Test Failed - CurrencyLayer Convert error", err)
}
_, err = c.Convert("USD", "AUD", "", 1)
if areAPIKeysSet() && err != nil && c.APIKeyLvl >= AccountBasic {
t.Error("test error - currencylayer Convert() error", err)
} else if !areAPIKeysSet() && err == nil {
t.Error("test error - currencylayer Convert() error cannot be nil")
}
}
func TestQueryTimeFrame(t *testing.T) {
_, err := c.QueryTimeFrame("2010-12-0", "2010-12-5", "USD", []string{"AUD"})
if err == nil {
t.Error("test error - currencylayer QueryTimeFrame() error")
err := setup()
if err != nil {
t.Fatal("Test Failed - CurrencyLayer QueryTimeFrame error", err)
}
_, err = c.QueryTimeFrame("2010-12-0", "2010-12-5", "USD", []string{"AUD"})
if areAPIKeysSet() && err != nil && c.APIKeyLvl >= AccountPro {
t.Error("test error - currencylayer QueryTimeFrame() error", err)
} else if !areAPIKeysSet() && err == nil {
t.Error("test error - currencylayer QueryTimeFrame() error cannot be nil")
}
}
func TestQueryCurrencyChange(t *testing.T) {
_, err := c.QueryCurrencyChange("2010-12-0", "2010-12-5", "USD", []string{"AUD"})
if err == nil {
t.Error("test error - currencylayer QueryCurrencyChange() error")
err := setup()
if err != nil {
t.Fatal("Test Failed - CurrencyLayer QueryCurrencyChange() error", err)
}
_, err = c.QueryCurrencyChange("2010-12-0", "2010-12-5", "USD", []string{"AUD"})
if areAPIKeysSet() && err != nil && c.APIKeyLvl == AccountEnterprise {
t.Error("test error - currencylayer QueryCurrencyChange() error", err)
} else if !areAPIKeysSet() && err == nil {
t.Error("test error - currencylayer QueryCurrencyChange() error cannot be nil")
}
}

View File

@@ -1,12 +1,15 @@
package currencylayer
// Error Defines the response error if an error occurred
type Error struct {
Code int `json:"code"`
Info string `json:"info"`
}
// LiveRates is a response type holding rates priced now.
type LiveRates struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error Error `json:"error"`
Terms string `json:"terms"`
Privacy string `json:"privacy"`
Timestamp int64 `json:"timestamp"`
@@ -16,11 +19,8 @@ type LiveRates struct {
// SupportedCurrencies holds supported currency information
type SupportedCurrencies struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error Error `json:"error"`
Terms string `json:"terms"`
Privacy string `json:"privacy"`
Currencies map[string]string `json:"currencies"`
@@ -28,11 +28,8 @@ type SupportedCurrencies struct {
// HistoricalRates is a response type holding rates priced from the past.
type HistoricalRates struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error Error `json:"error"`
Terms string `json:"terms"`
Privacy string `json:"privacy"`
Historical bool `json:"historical"`
@@ -44,11 +41,8 @@ type HistoricalRates struct {
// ConversionRate is a response type holding a converted rate.
type ConversionRate struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error Error `json:"error"`
Privacy string `json:"privacy"`
Terms string `json:"terms"`
Query struct {
@@ -67,11 +61,8 @@ type ConversionRate struct {
// TimeFrame is a response type holding exchange rates for a time period
type TimeFrame struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error Error `json:"error"`
Terms string `json:"terms"`
Privacy string `json:"privacy"`
Timeframe bool `json:"timeframe"`
@@ -83,11 +74,8 @@ type TimeFrame struct {
// ChangeRate is the response type that holds rate change data.
type ChangeRate struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error Error `json:"error"`
Terms string `json:"terms"`
Privacy string `json:"privacy"`
Change bool `json:"change"`

View File

@@ -3,11 +3,14 @@ package exchangerates
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
"github.com/thrasher-/gocryptotrader/exchanges/request"
log "github.com/thrasher-/gocryptotrader/logger"
)
@@ -15,23 +18,32 @@ const (
exchangeRatesAPI = "https://api.exchangeratesapi.io"
exchangeRatesLatest = "latest"
exchangeRatesHistory = "history"
exchangeRatesSupportedCurrencies = "USD,ISK,CAD,MXN,CHF,AUD,CNY,GBP,SEK,NOK,TRY,IDR,ZAR," +
"HRK,EUR,HKD,ILS,NZD,MYR,JPY,CZK,JPY,CZK,SGD,RUB,RON,HUF,BGN,INR,KRW," +
"DKK,THB,PHP,PLN,BRL"
exchangeRatesSupportedCurrencies = "EUR,CHF,USD,BRL,ISK,PHP,KRW,BGN,MXN," +
"RON,CAD,SGD,NZD,THB,HKD,JPY,NOK,HRK,ILS,GBP,DKK,HUF,MYR,RUB,TRY,IDR," +
"ZAR,INR,AUD,CZK,SEK,CNY,PLN"
authRate = 0
unAuthRate = 0
)
// ExchangeRates stores the struct for the ExchangeRatesAPI API
type ExchangeRates struct {
base.Base
Requester *request.Requester
}
// Setup sets appropriate values for CurrencyLayer
func (e *ExchangeRates) Setup(config base.Settings) {
func (e *ExchangeRates) Setup(config base.Settings) error {
e.Name = config.Name
e.Enabled = config.Enabled
e.RESTPollingDelay = config.RESTPollingDelay
e.Verbose = config.Verbose
e.PrimaryProvider = config.PrimaryProvider
e.Requester = request.New(e.Name,
request.NewRateLimit(time.Second*10, authRate),
request.NewRateLimit(time.Second*10, unAuthRate),
common.NewHTTPClientWithTimeout(base.DefaultTimeOut))
return nil
}
func cleanCurrencies(baseCurrency, symbols string) string {
@@ -150,10 +162,21 @@ func (e *ExchangeRates) GetRates(baseCurrency, symbols string) (map[string]float
return standardisedRates, nil
}
// GetSupportedCurrencies returns the supported currency list
func (e *ExchangeRates) GetSupportedCurrencies() ([]string, error) {
return common.SplitStrings(exchangeRatesSupportedCurrencies, ","), nil
}
// SendHTTPRequest sends a HTTPS request to the desired endpoint and returns the result
func (e *ExchangeRates) SendHTTPRequest(endPoint string, values url.Values, result interface{}) error {
path := common.EncodeURLValues(exchangeRatesAPI+"/"+endPoint, values)
err := common.SendHTTPGetRequest(path, true, e.Verbose, &result)
err := e.Requester.SendPayload(http.MethodGet,
path,
nil,
nil,
&result,
false,
e.Verbose)
if err != nil {
return fmt.Errorf("exchangeRatesAPI SendHTTPRequest error %s with path %s",
err,

View File

@@ -2,12 +2,26 @@ package exchangerates
import (
"testing"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
)
var e ExchangeRates
var initialSetup bool
func setup() {
e.Setup(base.Settings{
Name: "ExchangeRates",
Enabled: true,
})
initialSetup = true
}
func TestGetLatestRates(t *testing.T) {
e.Verbose = true
if !initialSetup {
setup()
}
result, err := e.GetLatestRates("USD", "")
if err != nil {
t.Fatalf("failed to GetLatestRates. Err: %s", err)
@@ -40,6 +54,9 @@ func TestGetLatestRates(t *testing.T) {
}
func TestCleanCurrencies(t *testing.T) {
if !initialSetup {
setup()
}
result := cleanCurrencies("USD", "USD,AUD")
if result != "AUD" {
t.Fatalf("unexpected result. AUD should be the only symbol")
@@ -60,6 +77,9 @@ func TestCleanCurrencies(t *testing.T) {
}
func TestGetRates(t *testing.T) {
if !initialSetup {
setup()
}
_, err := e.GetRates("USD", "AUD")
if err != nil {
t.Fatalf("failed to GetRates. Err: %s", err)
@@ -67,7 +87,9 @@ func TestGetRates(t *testing.T) {
}
func TestGetHistoricalRates(t *testing.T) {
e.Verbose = true
if !initialSetup {
setup()
}
_, err := e.GetHistoricalRates("-1", "USD", []string{"AUD"})
if err == nil {
t.Fatalf("unexpected result. Invalid date should throw an error")
@@ -80,6 +102,9 @@ func TestGetHistoricalRates(t *testing.T) {
}
func TestGetTimeSeriesRates(t *testing.T) {
if !initialSetup {
setup()
}
_, err := e.GetTimeSeriesRates("", "", "USD", []string{"EUR", "USD"})
if err == nil {
t.Fatal("unexpected result. Empty startDate endDate params should throw an error")

View File

@@ -10,11 +10,15 @@ package fixer
import (
"errors"
"net/http"
"net/url"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
"github.com/thrasher-/gocryptotrader/exchanges/request"
log "github.com/thrasher-/gocryptotrader/logger"
)
const (
@@ -24,22 +28,32 @@ const (
fixerAPIProfessionalPlus
fixerAPIEnterprise
fixerAPI = "http://data.fixer.io/api/"
fixerAPISSL = "https://data.fixer.io/api/"
fixerAPILatest = "latest"
fixerAPIConvert = "convert"
fixerAPITimeSeries = "timeseries"
fixerAPIFluctuation = "fluctuation"
fixerAPI = "http://data.fixer.io/api/"
fixerAPISSL = "https://data.fixer.io/api/"
fixerAPILatest = "latest"
fixerAPIConvert = "convert"
fixerAPITimeSeries = "timeseries"
fixerAPIFluctuation = "fluctuation"
fixerSupportedCurrencies = "symbols"
authRate = 0
unAuthRate = 0
)
// Fixer is a foreign exchange rate provider at https://fixer.io/
// NOTE DEFAULT BASE CURRENCY IS EUR upgrade to basic to change
type Fixer struct {
base.Base
Requester *request.Requester
}
// Setup sets appropriate values for fixer object
func (f *Fixer) Setup(config base.Settings) {
func (f *Fixer) Setup(config base.Settings) error {
if config.APIKeyLvl < 0 || config.APIKeyLvl > 4 {
log.Errorf("apikey incorrectly set in config.json for %s, please set appropriate account levels",
config.Name)
return errors.New("apikey set failure")
}
f.APIKey = config.APIKey
f.APIKeyLvl = config.APIKeyLvl
f.Enabled = config.Enabled
@@ -47,6 +61,32 @@ func (f *Fixer) Setup(config base.Settings) {
f.RESTPollingDelay = config.RESTPollingDelay
f.Verbose = config.Verbose
f.PrimaryProvider = config.PrimaryProvider
f.Requester = request.New(f.Name,
request.NewRateLimit(time.Second*10, authRate),
request.NewRateLimit(time.Second*10, unAuthRate),
common.NewHTTPClientWithTimeout(base.DefaultTimeOut))
return nil
}
// GetSupportedCurrencies returns supported currencies
func (f *Fixer) GetSupportedCurrencies() ([]string, error) {
var resp Symbols
err := f.SendOpenHTTPRequest(fixerSupportedCurrencies, nil, &resp)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, errors.New(resp.Error.Type + resp.Error.Info)
}
var currencies []string
for key := range resp.Map {
currencies = append(currencies, key)
}
return currencies, nil
}
// GetRates is a wrapper function to return rates
@@ -209,10 +249,19 @@ func (f *Fixer) SendOpenHTTPRequest(endpoint string, v url.Values, result interf
var path string
v.Set("access_key", f.APIKey)
var auth bool
if f.APIKeyLvl == fixerAPIFree {
path = fixerAPI + endpoint + "?" + v.Encode()
} else {
path = fixerAPISSL + endpoint + "?" + v.Encode()
auth = true
}
return common.SendHTTPGetRequest(path, true, f.Verbose, result)
return f.Requester.SendPayload(http.MethodGet,
path,
nil,
nil,
result,
auth,
f.Verbose)
}

View File

@@ -2,12 +2,8 @@ package fixer
// Rates contains the data fields for the currencies you have requested.
type Rates struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Type string `json:"type"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error RespError `json:"error"`
Historical bool `json:"historical"`
Timestamp int64 `json:"timestamp"`
Base string `json:"base"`
@@ -17,13 +13,9 @@ type Rates struct {
// Conversion contains data for currency conversion
type Conversion struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Type string `json:"type"`
Info string `json:"info"`
} `json:"error"`
Query struct {
Success bool `json:"success"`
Error RespError `json:"error"`
Query struct {
From string `json:"from"`
To string `json:"to"`
Amount float64 `json:"amount"`
@@ -39,12 +31,8 @@ type Conversion struct {
// TimeSeries holds timeseries data
type TimeSeries struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Type string `json:"type"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error RespError `json:"error"`
Timeseries bool `json:"timeseries"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
@@ -54,12 +42,8 @@ type TimeSeries struct {
// Fluctuation holds fluctuation data
type Fluctuation struct {
Success bool `json:"success"`
Error struct {
Code int `json:"code"`
Type string `json:"type"`
Info string `json:"info"`
} `json:"error"`
Success bool `json:"success"`
Error RespError `json:"error"`
Fluctuation bool `json:"fluctuation"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
@@ -74,3 +58,17 @@ type Flux struct {
Change float64 `json:"change"`
ChangePCT float64 `json:"change_pct"`
}
// RespError defines a general resp error sub type
type RespError struct {
Code int `json:"code"`
Type string `json:"type"`
Info string `json:"info"`
}
// Symbols defines a symbols list
type Symbols struct {
Success bool `json:"success"`
Error RespError `json:"error"`
Map map[string]string `json:"symbols"`
}

View File

@@ -3,68 +3,135 @@
package forexprovider
import (
"errors"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
currencyconverter "github.com/thrasher-/gocryptotrader/currency/forexprovider/currencyconverterapi"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/currencylayer"
exchangerates "github.com/thrasher-/gocryptotrader/currency/forexprovider/exchangeratesapi.io"
fixer "github.com/thrasher-/gocryptotrader/currency/forexprovider/fixer.io"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/openexchangerates"
log "github.com/thrasher-/gocryptotrader/logger"
)
// ForexProviders is an array of foreign exchange interfaces
// ForexProviders is a foreign exchange handler type
type ForexProviders struct {
base.IFXProviders
base.FXHandler
}
// GetAvailableForexProviders returns a list of supported forex providers
func GetAvailableForexProviders() []string {
return []string{"CurrencyConverter", "CurrencyLayer", "ExchangeRates", "Fixer", "OpenExchangeRates"}
return []string{"CurrencyConverter",
"CurrencyLayer",
"ExchangeRates",
"Fixer",
"OpenExchangeRates"}
}
// NewDefaultFXProvider returns the default forex provider (currencyconverterAPI)
func NewDefaultFXProvider() *ForexProviders {
fxp := new(ForexProviders)
currencyC := new(exchangerates.ExchangeRates)
currencyC.PrimaryProvider = true
currencyC.Enabled = true
currencyC.Name = "ExchangeRates"
fxp.IFXProviders = append(fxp.IFXProviders, currencyC)
return fxp
handler := new(ForexProviders)
provider := new(exchangerates.ExchangeRates)
err := provider.Setup(base.Settings{
PrimaryProvider: true,
Enabled: true,
Name: "ExchangeRates",
})
if err != nil {
panic(err)
}
currencies, _ := provider.GetSupportedCurrencies()
providerBase := base.Provider{
Provider: provider,
SupportedCurrencies: currencies,
}
handler.FXHandler = base.FXHandler{
Primary: providerBase,
}
return handler
}
// SetProvider sets provider to the FX handler
func (f *ForexProviders) SetProvider(b base.IFXProvider) error {
currencies, err := b.GetSupportedCurrencies()
if err != nil {
return err
}
providerBase := base.Provider{
Provider: b,
SupportedCurrencies: currencies,
}
if b.IsPrimaryProvider() {
f.FXHandler = base.FXHandler{
Primary: providerBase,
}
return nil
}
f.FXHandler.Support = append(f.FXHandler.Support, providerBase)
return nil
}
// StartFXService starts the forex provider service and returns a pointer to it
func StartFXService(fxProviders []base.Settings) *ForexProviders {
fxp := new(ForexProviders)
func StartFXService(fxProviders []base.Settings) (*ForexProviders, error) {
handler := new(ForexProviders)
for i := range fxProviders {
if fxProviders[i].Name == "CurrencyConverter" && fxProviders[i].Enabled {
currencyC := new(currencyconverter.CurrencyConverter)
currencyC.Setup(fxProviders[i])
fxp.IFXProviders = append(fxp.IFXProviders, currencyC)
}
if fxProviders[i].Name == "CurrencyLayer" && fxProviders[i].Enabled {
currencyLayerP := new(currencylayer.CurrencyLayer)
currencyLayerP.Setup(fxProviders[i])
fxp.IFXProviders = append(fxp.IFXProviders, currencyLayerP)
}
if fxProviders[i].Name == "ExchangeRates" && fxProviders[i].Enabled {
exchangeRatesP := new(exchangerates.ExchangeRates)
exchangeRatesP.Setup(fxProviders[i])
fxp.IFXProviders = append(fxp.IFXProviders, exchangeRatesP)
}
if fxProviders[i].Name == "Fixer" && fxProviders[i].Enabled {
fixerP := new(fixer.Fixer)
fixerP.Setup(fxProviders[i])
fxp.IFXProviders = append(fxp.IFXProviders, fixerP)
}
if fxProviders[i].Name == "OpenExchangeRates" && fxProviders[i].Enabled {
OpenExchangeRatesP := new(openexchangerates.OXR)
OpenExchangeRatesP.Setup(fxProviders[i])
fxp.IFXProviders = append(fxp.IFXProviders, OpenExchangeRatesP)
switch {
case fxProviders[i].Name == "CurrencyConverter" && fxProviders[i].Enabled:
provider := new(currencyconverter.CurrencyConverter)
err := provider.Setup(fxProviders[i])
if err != nil {
return nil, err
}
handler.SetProvider(provider)
case fxProviders[i].Name == "CurrencyLayer" && fxProviders[i].Enabled:
provider := new(currencylayer.CurrencyLayer)
err := provider.Setup(fxProviders[i])
if err != nil {
return nil, err
}
handler.SetProvider(provider)
case fxProviders[i].Name == "ExchangeRates" && fxProviders[i].Enabled:
provider := new(exchangerates.ExchangeRates)
err := provider.Setup(fxProviders[i])
if err != nil {
return nil, err
}
handler.SetProvider(provider)
case fxProviders[i].Name == "Fixer" && fxProviders[i].Enabled:
provider := new(fixer.Fixer)
err := provider.Setup(fxProviders[i])
if err != nil {
return nil, err
}
handler.SetProvider(provider)
case fxProviders[i].Name == "OpenExchangeRates" && fxProviders[i].Enabled:
provider := new(openexchangerates.OXR)
err := provider.Setup(fxProviders[i])
if err != nil {
return nil, err
}
handler.SetProvider(provider)
}
}
if len(fxp.IFXProviders) == 0 {
log.Error("No foreign exchange providers enabled")
if handler.Primary.Provider == nil {
return nil, errors.New("no foreign exchange providers enabled")
}
return fxp
return handler, nil
}

View File

@@ -14,9 +14,12 @@ import (
"net/http"
"net/url"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
"github.com/thrasher-/gocryptotrader/exchanges/request"
log "github.com/thrasher-/gocryptotrader/logger"
)
// These consts contain endpoint information
@@ -33,6 +36,21 @@ const (
APIEndpointConvert = "convert/%s/%s/%s"
APIEndpointOHLC = "ohlc.json"
APIEndpointUsage = "usage.json"
oxrSupportedCurrencies = "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD," +
"BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTC,BTN,BWP,BYN,BYR,BZD,CAD,CDF," +
"CHF,CLF,CLP,CNH,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EEK,EGP," +
"ERN,ETB,EUR,FJD,FKP,GBP,GEL,GGP,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK," +
"HTG,HUF,IDR,ILS,IMP,INR,IQD,IRR,ISK,JEP,JMD,JOD,JPY,KES,KGS,KHR,KMF," +
"KPW,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT," +
"MOP,MRO,MRU,MTL,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR," +
"PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK," +
"SGD,SHP,SLL,SOS,SRD,SSP,STD,STN,SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY," +
"TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VEF,VND,VUV,WST,XAF,XAG,XAU,XCD,XDR," +
"XOF,XPD,XPF,XPT,YER,ZAR,ZMK,ZMW"
authRate = 0
unAuthRate = 0
)
// OXR is a foreign exchange rate provider at https://openexchangerates.org/
@@ -40,10 +58,16 @@ const (
// DOCs : https://docs.openexchangerates.org/docs
type OXR struct {
base.Base
Requester *request.Requester
}
// Setup sets values for the OXR object
func (o *OXR) Setup(config base.Settings) {
func (o *OXR) Setup(config base.Settings) error {
if config.APIKeyLvl < 0 || config.APIKeyLvl > 2 {
log.Errorf("apikey incorrectly set in config.json for %s, please set appropriate account levels",
config.Name)
return errors.New("apikey set failure")
}
o.APIKey = config.APIKey
o.APIKeyLvl = config.APIKeyLvl
o.Enabled = config.Enabled
@@ -51,6 +75,11 @@ func (o *OXR) Setup(config base.Settings) {
o.RESTPollingDelay = config.RESTPollingDelay
o.Verbose = config.Verbose
o.PrimaryProvider = config.PrimaryProvider
o.Requester = request.New(o.Name,
request.NewRateLimit(time.Second*10, authRate),
request.NewRateLimit(time.Second*10, unAuthRate),
common.NewHTTPClientWithTimeout(base.DefaultTimeOut))
return nil
}
// GetRates is a wrapper function to return rates
@@ -125,6 +154,11 @@ func (o *OXR) GetCurrencies(showInactive, prettyPrint, showAlternative bool) (ma
return resp, o.SendHTTPRequest(APIEndpointCurrencies, v, &resp)
}
// GetSupportedCurrencies returns a list of supported currencies
func (o *OXR) GetSupportedCurrencies() ([]string, error) {
return common.SplitStrings(oxrSupportedCurrencies, ","), nil
}
// GetTimeSeries returns historical exchange rates for a given time period,
// where available.
func (o *OXR) GetTimeSeries(baseCurrency, startDate, endDate string, symbols []string, prettyPrint, showAlternative bool) (map[string]interface{}, error) {
@@ -223,9 +257,11 @@ func (o *OXR) SendHTTPRequest(endpoint string, values url.Values, result interfa
headers["Authorization"] = "Token " + o.APIKey
path := APIURL + endpoint + "?" + values.Encode()
resp, err := common.SendHTTPRequest(http.MethodGet, path, headers, nil)
if err != nil {
return err
}
return common.JSONDecode([]byte(resp), result)
return o.Requester.SendPayload(http.MethodGet,
path,
headers,
nil,
result,
false,
o.Verbose)
}

View File

@@ -2,6 +2,8 @@ package openexchangerates
import (
"testing"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
)
// please set apikey for due diligence testing NOTE testing uses your allocated
@@ -13,7 +15,20 @@ const (
var o OXR
var initialSetup bool
func setup() {
o.Setup(base.Settings{
Name: "OpenExchangeRates",
Enabled: true,
})
initialSetup = true
}
func TestGetRates(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.GetRates("USD", "AUD")
if err == nil {
t.Error("test failed - GetRates() error", err)
@@ -21,6 +36,9 @@ func TestGetRates(t *testing.T) {
}
func TestGetLatest(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.GetLatest("USD", "AUD", false, false)
if err == nil {
t.Error("test failed - GetLatest() error", err)
@@ -28,6 +46,9 @@ func TestGetLatest(t *testing.T) {
}
func TestGetHistoricalRates(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.GetHistoricalRates("2017-12-01", "USD", []string{"CNH", "AUD", "ANG"}, false, false)
if err == nil {
t.Error("test failed - GetRates() error", err)
@@ -35,6 +56,9 @@ func TestGetHistoricalRates(t *testing.T) {
}
func TestGetCurrencies(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.GetCurrencies(true, true, true)
if err != nil {
t.Error("test failed - GetCurrencies() error", err)
@@ -42,6 +66,9 @@ func TestGetCurrencies(t *testing.T) {
}
func TestGetTimeSeries(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.GetTimeSeries("USD", "2017-12-01", "2017-12-02", []string{"CNH", "AUD", "ANG"}, false, false)
if err == nil {
t.Error("test failed - GetTimeSeries() error", err)
@@ -49,6 +76,9 @@ func TestGetTimeSeries(t *testing.T) {
}
func TestConvertCurrency(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.ConvertCurrency(1337, "USD", "AUD")
if err == nil {
t.Error("test failed - ConvertCurrency() error", err)
@@ -56,6 +86,9 @@ func TestConvertCurrency(t *testing.T) {
}
func TestGetOHLC(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.GetOHLC("2017-07-17T08:30:00Z", "1m", "USD", []string{"AUD"}, false)
if err == nil {
t.Error("test failed - GetOHLC() error", err)
@@ -63,6 +96,9 @@ func TestGetOHLC(t *testing.T) {
}
func TestGetUsageStats(t *testing.T) {
if !initialSetup {
setup()
}
_, err := o.GetUsageStats(false)
if err == nil {
t.Error("test failed - GetUsageStats() error", err)

190
currency/pair.go Normal file
View File

@@ -0,0 +1,190 @@
package currency
import (
"fmt"
"strings"
"github.com/thrasher-/gocryptotrader/common"
)
// NewPairDelimiter splits the desired currency string at delimeter, the returns
// a Pair struct
func NewPairDelimiter(currencyPair, delimiter string) Pair {
result := strings.Split(currencyPair, delimiter)
return Pair{
Delimiter: delimiter,
Base: NewCode(result[0]),
Quote: NewCode(result[1]),
}
}
// NewPairFromStrings returns a CurrencyPair without a delimiter
func NewPairFromStrings(baseCurrency, quoteCurrency string) Pair {
return Pair{
Base: NewCode(baseCurrency),
Quote: NewCode(quoteCurrency),
}
}
// NewPair returns a currency pair from currency codes
func NewPair(baseCurrency, quoteCurrency Code) Pair {
return Pair{
Base: baseCurrency,
Quote: quoteCurrency,
}
}
// NewPairWithDelimiter returns a CurrencyPair with a delimiter
func NewPairWithDelimiter(base, quote, delimiter string) Pair {
return Pair{
Base: NewCode(base),
Quote: NewCode(quote),
Delimiter: delimiter,
}
}
// NewPairFromIndex returns a CurrencyPair via a currency string and specific
// index
func NewPairFromIndex(currencyPair, index string) (Pair, error) {
i := strings.Index(currencyPair, index)
if i == -1 {
return Pair{},
fmt.Errorf("index %s not found in currency pair string", index)
}
if i == 0 {
return NewPairFromStrings(currencyPair[0:len(index)],
currencyPair[len(index):]),
nil
}
return NewPairFromStrings(currencyPair[0:i], currencyPair[i:]), nil
}
// NewPairFromString converts currency string into a new CurrencyPair
// with or without delimeter
func NewPairFromString(currencyPair string) Pair {
delimiters := []string{"_", "-"}
var delimiter string
for _, x := range delimiters {
if strings.Contains(currencyPair, x) {
delimiter = x
return NewPairDelimiter(currencyPair, delimiter)
}
}
return NewPairFromStrings(currencyPair[0:3], currencyPair[3:])
}
// Pair holds currency pair information
type Pair struct {
Delimiter string `json:"delimiter"`
Base Code `json:"base"`
Quote Code `json:"quote"`
}
// String returns a currency pair string
func (p Pair) String() string {
return p.Base.String() + p.Delimiter + p.Quote.String()
}
// Lower converts the pair object to lowercase
func (p Pair) Lower() Pair {
return Pair{
Delimiter: p.Delimiter,
Base: p.Base.Lower(),
Quote: p.Quote.Lower(),
}
}
// Upper converts the pair object to uppercase
func (p Pair) Upper() Pair {
return Pair{
Delimiter: p.Delimiter,
Base: p.Base.Upper(),
Quote: p.Quote.Upper(),
}
}
// UnmarshalJSON comforms type to the umarshaler interface
func (p *Pair) UnmarshalJSON(d []byte) error {
var pair string
err := common.JSONDecode(d, &pair)
if err != nil {
return err
}
*p = NewPairFromString(pair)
return nil
}
// MarshalJSON conforms type to the marshaler interface
func (p Pair) MarshalJSON() ([]byte, error) {
return common.JSONEncode(p.String())
}
// Format changes the currency based on user preferences overriding the default
// String() display
func (p Pair) Format(delimiter string, uppercase bool) Pair {
p.Delimiter = delimiter
if uppercase {
return p.Upper()
}
return p.Lower()
}
// Equal compares two currency pairs and returns whether or not they are equal
func (p Pair) Equal(cPair Pair) bool {
return p.Base.Item == cPair.Base.Item && p.Quote.Item == cPair.Quote.Item
}
// EqualIncludeReciprocal compares two currency pairs and returns whether or not
// they are the same including reciprocal currencies.
func (p Pair) EqualIncludeReciprocal(cPair Pair) bool {
if p.Base.Item == cPair.Base.Item &&
p.Quote.Item == cPair.Quote.Item ||
p.Base.Item == cPair.Quote.Item &&
p.Quote.Item == cPair.Base.Item {
return true
}
return false
}
// IsCryptoPair checks to see if the pair is a crypto pair e.g. BTCLTC
func (p Pair) IsCryptoPair() bool {
return storage.IsCryptocurrency(p.Base) &&
storage.IsCryptocurrency(p.Quote)
}
// IsCryptoFiatPair checks to see if the pair is a crypto fiat pair e.g. BTCUSD
func (p Pair) IsCryptoFiatPair() bool {
return storage.IsCryptocurrency(p.Base) &&
storage.IsFiatCurrency(p.Quote) ||
storage.IsFiatCurrency(p.Base) &&
storage.IsCryptocurrency(p.Quote)
}
// IsFiatPair checks to see if the pair is a fiat pair e.g. EURUSD
func (p Pair) IsFiatPair() bool {
return storage.IsFiatCurrency(p.Base) && storage.IsFiatCurrency(p.Quote)
}
// IsInvalid checks invalid pair if base and quote are the same
func (p Pair) IsInvalid() bool {
return p.Base.Item == p.Quote.Item
}
// Swap turns the currency pair into its reciprocal
func (p Pair) Swap() Pair {
p.Base, p.Quote = p.Quote, p.Base
return p
}
// IsEmpty returns whether or not the pair is empty or is missing a currency
// code
func (p Pair) IsEmpty() bool {
return p.Base.IsEmpty() || p.Quote.IsEmpty()
}
// ContainsCurrency checks to see if a pair contains a specific currency
func (p Pair) ContainsCurrency(c Code) bool {
return p.Base.Item == c.Item || p.Quote.Item == c.Item
}

View File

@@ -1,57 +0,0 @@
# GoCryptoTrader package Pair
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/currency/pair)
[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
This pair package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY)
## Current Features for pair
+ Provides a new data structure for a currency pair
+ Methods to manipulate, create and retrieve different parts of the currency pair
+ Example below:
```go
import "github.com/thrasher-/gocryptotrader/currency/pair"
// Create new pair
newPair := pair.NewCurrencyPair("BTC", "USD")
// Retrieve different parts of the pair
bitcoinString := newPair.GetFirstCurrency
```
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
Please feel free to submit any pull requests or suggest any desired features to be added.
When submitting a PR, please abide by our coding guidelines:
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Donations
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***

View File

@@ -1,246 +0,0 @@
package pair
import (
"math/rand"
"strings"
"github.com/thrasher-/gocryptotrader/common"
)
// CurrencyItem is an exported string with methods to manipulate the data instead
// of using array/slice access modifiers
type CurrencyItem string
// Lower converts the CurrencyItem object c to lowercase
func (c CurrencyItem) Lower() CurrencyItem {
return CurrencyItem(strings.ToLower(string(c)))
}
// Upper converts the CurrencyItem object c to uppercase
func (c CurrencyItem) Upper() CurrencyItem {
return CurrencyItem(strings.ToUpper(string(c)))
}
// String converts the CurrencyItem object c to string
func (c CurrencyItem) String() string {
return string(c)
}
// CurrencyPair holds currency pair information
type CurrencyPair struct {
Delimiter string `json:"delimiter"`
FirstCurrency CurrencyItem `json:"first_currency"`
SecondCurrency CurrencyItem `json:"second_currency"`
}
// Pair returns a currency pair string
func (c CurrencyPair) Pair() CurrencyItem {
return c.FirstCurrency + CurrencyItem(c.Delimiter) + c.SecondCurrency
}
// Display formats and returns the currency based on user preferences,
// overriding the default Pair() display
func (c CurrencyPair) Display(delimiter string, uppercase bool) CurrencyItem {
var pair CurrencyItem
if delimiter != "" {
pair = c.FirstCurrency + CurrencyItem(delimiter) + c.SecondCurrency
} else {
pair = c.FirstCurrency + c.SecondCurrency
}
if uppercase {
return pair.Upper()
}
return pair.Lower()
}
// Equal compares two currency pairs and returns whether or not they are equal
func (c CurrencyPair) Equal(p CurrencyPair, exact bool) bool {
if !exact {
if c.FirstCurrency.Upper() == p.FirstCurrency.Upper() &&
c.SecondCurrency.Upper() == p.SecondCurrency.Upper() ||
c.FirstCurrency.Upper() == p.SecondCurrency.Upper() &&
c.SecondCurrency.Upper() == p.FirstCurrency.Upper() {
return true
}
} else {
if c.FirstCurrency.Upper() == p.FirstCurrency.Upper() &&
c.SecondCurrency.Upper() == p.SecondCurrency.Upper() {
return true
}
}
return false
}
// Swap swaps the pairs first and second currencies
func (c CurrencyPair) Swap() CurrencyPair {
p := c
p.FirstCurrency = c.SecondCurrency
p.SecondCurrency = c.FirstCurrency
return p
}
// Empty returns whether or not the pair is empty
func (c CurrencyPair) Empty() bool {
if c.FirstCurrency == "" || c.SecondCurrency == "" {
return true
}
return false
}
// NewCurrencyPairDelimiter splits the desired currency string at delimeter,
// the returns a CurrencyPair struct
func NewCurrencyPairDelimiter(currency, delimiter string) CurrencyPair {
result := strings.Split(currency, delimiter)
return CurrencyPair{
Delimiter: delimiter,
FirstCurrency: CurrencyItem(result[0]),
SecondCurrency: CurrencyItem(result[1]),
}
}
// NewCurrencyPair returns a CurrencyPair without a delimiter
func NewCurrencyPair(firstCurrency, secondCurrency string) CurrencyPair {
return CurrencyPair{
FirstCurrency: CurrencyItem(firstCurrency),
SecondCurrency: CurrencyItem(secondCurrency),
}
}
// NewCurrencyPairWithDelimiter returns a CurrencyPair with a delimiter
func NewCurrencyPairWithDelimiter(firstCurrency, secondCurrency, delimiter string) CurrencyPair {
return CurrencyPair{
FirstCurrency: CurrencyItem(firstCurrency),
SecondCurrency: CurrencyItem(secondCurrency),
Delimiter: delimiter,
}
}
// NewCurrencyPairFromIndex returns a CurrencyPair via a currency string and
// specific index
func NewCurrencyPairFromIndex(currency, index string) CurrencyPair {
i := strings.Index(currency, index)
if i == 0 {
return NewCurrencyPair(currency[0:len(index)], currency[len(index):])
}
return NewCurrencyPair(currency[0:i], currency[i:])
}
// NewCurrencyPairFromString converts currency string into a new CurrencyPair
// with or without delimeter
func NewCurrencyPairFromString(currency string) CurrencyPair {
delimiters := []string{"_", "-"}
var delimiter string
for _, x := range delimiters {
if strings.Contains(currency, x) {
delimiter = x
return NewCurrencyPairDelimiter(currency, delimiter)
}
}
return NewCurrencyPair(currency[0:3], currency[3:])
}
// Contains checks to see if a specified pair exists inside a currency pair
// array
func Contains(pairs []CurrencyPair, p CurrencyPair, exact bool) bool {
for x := range pairs {
if pairs[x].Equal(p, exact) {
return true
}
}
return false
}
// ContainsCurrency checks to see if a pair contains a specific currency
func ContainsCurrency(p CurrencyPair, c string) bool {
return p.FirstCurrency.Upper().String() == common.StringToUpper(c) ||
p.SecondCurrency.Upper().String() == common.StringToUpper(c)
}
// RemovePairsByFilter checks to see if a pair contains a specific currency
// and removes it from the list of pairs
func RemovePairsByFilter(p []CurrencyPair, filter string) []CurrencyPair {
var pairs []CurrencyPair
for x := range p {
if ContainsCurrency(p[x], filter) {
continue
}
pairs = append(pairs, p[x])
}
return pairs
}
// FormatPairs formats a string array to a list of currency pairs with the
// supplied currency pair format
func FormatPairs(pairs []string, delimiter, index string) []CurrencyPair {
var result []CurrencyPair
for x := range pairs {
if pairs[x] == "" {
continue
}
var p CurrencyPair
if delimiter != "" {
p = NewCurrencyPairDelimiter(pairs[x], delimiter)
} else {
if index != "" {
p = NewCurrencyPairFromIndex(pairs[x], index)
} else {
p = NewCurrencyPair(pairs[x][0:3], pairs[x][3:])
}
}
result = append(result, p)
}
return result
}
// CopyPairFormat copies the pair format from a list of pairs once matched
func CopyPairFormat(p CurrencyPair, pairs []CurrencyPair, exact bool) CurrencyPair {
for x := range pairs {
if p.Equal(pairs[x], exact) {
return pairs[x]
}
}
return CurrencyPair{}
}
// FindPairDifferences returns pairs which are new or have been removed
func FindPairDifferences(oldPairs, newPairs []string) (newPs, removedPs []string) {
for x := range newPairs {
if newPairs[x] == "" {
continue
}
if !common.StringDataCompareUpper(oldPairs, newPairs[x]) {
newPs = append(newPs, newPairs[x])
}
}
for x := range oldPairs {
if oldPairs[x] == "" {
continue
}
if !common.StringDataCompareUpper(newPairs, oldPairs[x]) {
removedPs = append(removedPs, oldPairs[x])
}
}
return newPs, removedPs
}
// PairsToStringArray returns a list of pairs as a string array
func PairsToStringArray(pairs []CurrencyPair) []string {
var p []string
for x := range pairs {
p = append(p, pairs[x].Pair().String())
}
return p
}
// RandomPairFromPairs returns a random pair from a list of pairs
func RandomPairFromPairs(pairs []CurrencyPair) CurrencyPair {
pairsLen := len(pairs)
if pairsLen == 0 {
return CurrencyPair{}
}
return pairs[rand.Intn(pairsLen)]
}

View File

@@ -1,440 +0,0 @@
package pair
import (
"testing"
)
func TestLower(t *testing.T) {
t.Parallel()
pair := CurrencyItem("BTCUSD")
actual := pair.Lower()
expected := CurrencyItem("btcusd")
if actual != expected {
t.Errorf("Test failed. Lower(): %s was not equal to expected value: %s",
actual, expected)
}
}
func TestUpper(t *testing.T) {
t.Parallel()
pair := CurrencyItem("btcusd")
actual := pair.Upper()
expected := CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf("Test failed. Upper(): %s was not equal to expected value: %s",
actual, expected)
}
}
func TestString(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
actual := "BTCUSD"
expected := pair.Pair().String()
if actual != expected {
t.Errorf("Test failed. String(): %s was not equal to expected value: %s",
actual, expected)
}
}
func TestFirstCurrency(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
actual := pair.FirstCurrency
expected := CurrencyItem("BTC")
if actual != expected {
t.Errorf(
"Test failed. GetFirstCurrency(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestSecondCurrency(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
actual := pair.SecondCurrency
expected := CurrencyItem("USD")
if actual != expected {
t.Errorf(
"Test failed. GetSecondCurrency(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestPair(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
actual := pair.Pair()
expected := CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestDisplay(t *testing.T) {
t.Parallel()
pair := NewCurrencyPairDelimiter("BTC-USD", "-")
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Display("", false)
expected = CurrencyItem("btcusd")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Display("~", true)
expected = CurrencyItem("BTC~USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestEqual(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
secondPair := NewCurrencyPair("btc", "uSd")
actual := pair.Equal(secondPair, false)
expected := true
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
secondPair.SecondCurrency = "ETH"
actual = pair.Equal(secondPair, false)
expected = false
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
secondPair = NewCurrencyPair("USD", "BTC")
actual = pair.Equal(secondPair, false)
expected = true
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
}
func TestSwap(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
actual := pair.Swap().Pair()
expected := CurrencyItem("USDBTC")
if actual != expected {
t.Errorf(
"Test failed. TestSwap: %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestEmpty(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
if pair.Empty() {
t.Error("Test failed. Empty() returned true when the pair was initialised")
}
var p CurrencyPair
if !p.Empty() {
t.Error("Test failed. Empty() returned true when the pair wasn't initialised")
}
}
func TestNewCurrencyPair(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
actual := pair.Pair()
expected := CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestNewCurrencyPairWithDelimiter(t *testing.T) {
t.Parallel()
pair := NewCurrencyPairWithDelimiter("BTC", "USD", "-test-")
actual := pair.Pair()
expected := CurrencyItem("BTC-test-USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
pair = NewCurrencyPairWithDelimiter("BTC", "USD", "")
actual = pair.Pair()
expected = CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestNewCurrencyPairDelimiter(t *testing.T) {
t.Parallel()
pair := NewCurrencyPairDelimiter("BTC-USD", "-")
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = CurrencyItem(pair.Delimiter)
expected = "-"
if actual != expected {
t.Errorf(
"Test failed. Delmiter: %s was not equal to expected value: %s",
actual, expected,
)
}
}
// TestNewCurrencyPairFromIndex returns a CurrencyPair via a currency string and
// specific index
func TestNewCurrencyPairFromIndex(t *testing.T) {
t.Parallel()
currency := "BTCUSD"
index := "BTC"
pair := NewCurrencyPairFromIndex(currency, index)
pair.Delimiter = "-"
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
currency = "DOGEBTC"
pair = NewCurrencyPairFromIndex(currency, index)
pair.Delimiter = "-"
actual = pair.Pair()
expected = CurrencyItem("DOGE-BTC")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestNewCurrencyPairFromString(t *testing.T) {
t.Parallel()
pairStr := "BTC-USD"
pair := NewCurrencyPairFromString(pairStr)
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
pairStr = "BTCUSD"
pair = NewCurrencyPairFromString(pairStr)
actual = pair.Pair()
expected = CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestContains(t *testing.T) {
pairOne := NewCurrencyPair("BTC", "USD")
var pairs []CurrencyPair
pairs = append(pairs, pairOne, NewCurrencyPair("LTC", "USD"))
if !Contains(pairs, pairOne, true) {
t.Errorf("Test failed. TestContains: Expected pair was not found")
}
if Contains(pairs, NewCurrencyPair("ETH", "USD"), false) {
t.Errorf("Test failed. TestContains: Non-existent pair was found")
}
}
func TestContainsCurrency(t *testing.T) {
p := NewCurrencyPair("BTC", "USD")
if !ContainsCurrency(p, "BTC") {
t.Error("Test failed. TestContainsCurrency: Expected currency was not found")
}
if ContainsCurrency(p, "ETH") {
t.Error("Test failed. TestContainsCurrency: Non-existent currency was found")
}
}
func TestRemovePairsByFilter(t *testing.T) {
var pairs []CurrencyPair
pairs = append(pairs, NewCurrencyPair("BTC", "USD"),
NewCurrencyPair("LTC", "USD"),
NewCurrencyPair("LTC", "USDT"))
pairs = RemovePairsByFilter(pairs, "USDT")
if Contains(pairs, NewCurrencyPair("LTC", "USDT"), true) {
t.Error("Test failed. TestRemovePairsByFilter unexpected result")
}
}
func TestFormatPairs(t *testing.T) {
if len(FormatPairs([]string{""}, "-", "")) > 0 {
t.Error("Test failed. TestFormatPairs: Empty string returned a valid pair")
}
if FormatPairs([]string{"BTC-USD"}, "-", "")[0].Pair().String() != "BTC-USD" {
t.Error("Test failed. TestFormatPairs: Expected pair was not found")
}
if FormatPairs([]string{"BTCUSD"}, "", "BTC")[0].Pair().String() != "BTCUSD" {
t.Error("Test failed. TestFormatPairs: Expected pair was not found")
}
if FormatPairs([]string{"ETHUSD"}, "", "")[0].Pair().String() != "ETHUSD" {
t.Error("Test failed. TestFormatPairs: Expected pair was not found")
}
}
func TestCopyPairFormat(t *testing.T) {
pairOne := NewCurrencyPair("BTC", "USD")
pairOne.Delimiter = "-"
var pairs []CurrencyPair
pairs = append(pairs, pairOne, NewCurrencyPair("LTC", "USD"))
testPair := NewCurrencyPair("BTC", "USD")
testPair.Delimiter = "~"
result := CopyPairFormat(testPair, pairs, false)
if result.Pair().String() != "BTC-USD" {
t.Error("Test failed. TestCopyPairFormat: Expected pair was not found")
}
result = CopyPairFormat(NewCurrencyPair("ETH", "USD"), pairs, true)
if result.Pair().String() != "" {
t.Error("Test failed. TestCopyPairFormat: Unexpected non empty pair returned")
}
}
func TestFindPairDifferences(t *testing.T) {
pairList := []string{"BTC-USD", "ETH-USD", "LTC-USD"}
// Test new pair update
newPairs, removedPairs := FindPairDifferences(pairList, []string{"DASH-USD"})
if len(newPairs) != 1 && len(removedPairs) != 3 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
// Test that we don't allow empty strings for new pairs
newPairs, removedPairs = FindPairDifferences(pairList, []string{""})
if len(newPairs) != 0 && len(removedPairs) != 3 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
// Test that we don't allow empty strings for new pairs
newPairs, removedPairs = FindPairDifferences([]string{""}, pairList)
if len(newPairs) != 3 && len(removedPairs) != 0 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
// Test that the supplied pair lists are the same, so
// no newPairs or removedPairs
newPairs, removedPairs = FindPairDifferences(pairList, pairList)
if len(newPairs) != 0 && len(removedPairs) != 0 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
}
func TestPairsToStringArray(t *testing.T) {
var pairs []CurrencyPair
pairs = append(pairs, NewCurrencyPair("BTC", "USD"))
expected := []string{"BTCUSD"}
actual := PairsToStringArray(pairs)
if actual[0] != expected[0] {
t.Error("Test failed. TestPairsToStringArray: Unexpected values")
}
}
func TestRandomPairFromPairs(t *testing.T) {
// Test that an empty pairs array returns an empty currency pair
result := RandomPairFromPairs([]CurrencyPair{})
if !result.Empty() {
t.Error("Test failed. TestRandomPairFromPairs: Unexpected values")
}
// Test that a populated pairs array returns a non-empty currency pair
var pairs []CurrencyPair
pairs = append(pairs, NewCurrencyPair("BTC", "USD"))
result = RandomPairFromPairs(pairs)
if result.Empty() {
t.Error("Test failed. TestRandomPairFromPairs: Unexpected values")
}
// Test that a populated pairs array over a number of attempts returns ALL
// currency pairs
pairs = append(pairs, NewCurrencyPair("ETH", "USD"))
expectedResults := make(map[string]bool)
for i := 0; i < 50; i++ {
p := RandomPairFromPairs(pairs).Pair().String()
_, ok := expectedResults[p]
if !ok {
expectedResults[p] = true
}
}
for x := range pairs {
_, ok := expectedResults[pairs[x].Pair().String()]
if !ok {
t.Error("Test failed. TestRandomPairFromPairs: Unexpected values")
}
}
}

563
currency/pair_test.go Normal file
View File

@@ -0,0 +1,563 @@
package currency
import (
"testing"
"github.com/thrasher-/gocryptotrader/common"
)
const (
defaultPair = "BTCUSD"
defaultPairWDelimiter = "BTC-USD"
)
func TestLower(t *testing.T) {
t.Parallel()
pair := NewPairFromString(defaultPair)
actual := pair.Lower()
expected := NewPairFromString(defaultPair).Lower()
if actual != expected {
t.Errorf("Test failed. Lower(): %s was not equal to expected value: %s",
actual, expected)
}
}
func TestUpper(t *testing.T) {
t.Parallel()
pair := NewPairFromString(defaultPair)
actual := pair.Upper()
expected := NewPairFromString(defaultPair)
if actual != expected {
t.Errorf("Test failed. Upper(): %s was not equal to expected value: %s",
actual, expected)
}
}
func TestPairUnmarshalJSON(t *testing.T) {
var unmarshalHere Pair
configPair := NewPairDelimiter("btc_usd", "_")
encoded, err := common.JSONEncode(configPair)
if err != nil {
t.Fatal("Test Failed - Pair UnmarshalJSON() error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Pair UnmarshalJSON() error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Pair UnmarshalJSON() error", err)
}
if !unmarshalHere.Equal(configPair) {
t.Errorf("Test Failed - Pairs UnmarshalJSON() error expected %s but received %s",
configPair, unmarshalHere)
}
}
func TestPairMarshalJSON(t *testing.T) {
quickstruct := struct {
Pair Pair `json:"superPair"`
}{
Pair{Base: BTC, Quote: USD, Delimiter: "-"},
}
encoded, err := common.JSONEncode(quickstruct)
if err != nil {
t.Fatal("Test Failed - Pair MarshalJSON() error", err)
}
expected := `{"superPair":"BTC-USD"}`
if string(encoded) != expected {
t.Errorf("Test Failed - Pair MarshalJSON() error expected %s but received %s",
expected, string(encoded))
}
}
func TestIsCryptoPair(t *testing.T) {
if !NewPair(BTC, LTC).IsCryptoPair() {
t.Error("Test Failed. TestIsCryptoPair. Expected true result")
}
if NewPair(BTC, USD).IsCryptoPair() {
t.Error("Test Failed. TestIsCryptoPair. Expected false result")
}
}
func TestIsCryptoFiatPair(t *testing.T) {
if !NewPair(BTC, USD).IsCryptoFiatPair() {
t.Error("Test Failed. TestIsCryptoPair. Expected true result")
}
if NewPair(BTC, LTC).IsCryptoFiatPair() {
t.Error("Test Failed. TestIsCryptoPair. Expected false result")
}
}
func TestIsFiatPair(t *testing.T) {
if !NewPair(AUD, USD).IsFiatPair() {
t.Error("Test Failed. TestIsFiatPair. Expected true result")
}
if NewPair(BTC, AUD).IsFiatPair() {
t.Error("Test Failed. TestIsFiatPair. Expected false result")
}
}
func TestString(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
actual := defaultPair
expected := pair.String()
if actual != expected {
t.Errorf("Test failed. String(): %s was not equal to expected value: %s",
actual, expected)
}
}
func TestFirstCurrency(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
actual := pair.Base
expected := BTC
if actual != expected {
t.Errorf(
"Test failed. GetFirstCurrency(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestSecondCurrency(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
actual := pair.Quote
expected := USD
if actual != expected {
t.Errorf(
"Test failed. GetSecondCurrency(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestPair(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
actual := pair.String()
expected := defaultPair
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestDisplay(t *testing.T) {
t.Parallel()
pair := NewPairDelimiter(defaultPairWDelimiter, "-")
actual := pair.String()
expected := defaultPairWDelimiter
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Format("", false).String()
expected = "btcusd"
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Format("~", true).String()
expected = "BTC~USD"
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestEquall(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
secondPair := NewPair(BTC, USD)
actual := pair.Equal(secondPair)
expected := true
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
secondPair.Quote = ETH
actual = pair.Equal(secondPair)
expected = false
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
secondPair = NewPair(USD, BTC)
actual = pair.Equal(secondPair)
expected = false
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
}
func TestEqualIncludeReciprocal(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
secondPair := NewPair(BTC, USD)
actual := pair.EqualIncludeReciprocal(secondPair)
expected := true
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
secondPair.Quote = ETH
actual = pair.EqualIncludeReciprocal(secondPair)
expected = false
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
secondPair = NewPair(USD, BTC)
actual = pair.EqualIncludeReciprocal(secondPair)
expected = true
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
}
func TestSwap(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
actual := pair.Swap().String()
expected := "USDBTC"
if actual != expected {
t.Errorf(
"Test failed. TestSwap: %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestEmpty(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
if pair.IsEmpty() {
t.Error("Test failed. Empty() returned true when the pair was initialised")
}
p := NewPair(NewCode(""), NewCode(""))
if !p.IsEmpty() {
t.Error("Test failed. Empty() returned true when the pair wasn't initialised")
}
}
func TestNewPair(t *testing.T) {
t.Parallel()
pair := NewPair(BTC, USD)
actual := pair.String()
expected := defaultPair
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestNewPairWithDelimiter(t *testing.T) {
t.Parallel()
pair := NewPairWithDelimiter("BTC", "USD", "-test-")
actual := pair.String()
expected := "BTC-test-USD"
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
pair = NewPairWithDelimiter("BTC", "USD", "")
actual = pair.String()
expected = defaultPair
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestNewPairDelimiter(t *testing.T) {
t.Parallel()
pair := NewPairDelimiter(defaultPairWDelimiter, "-")
actual := pair.String()
expected := defaultPairWDelimiter
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Delimiter
expected = "-"
if actual != expected {
t.Errorf(
"Test failed. Delmiter: %s was not equal to expected value: %s",
actual, expected,
)
}
}
// TestNewPairFromIndex returns a CurrencyPair via a currency string and
// specific index
func TestNewPairFromIndex(t *testing.T) {
t.Parallel()
currency := defaultPair
index := "BTC"
pair, err := NewPairFromIndex(currency, index)
if err != nil {
t.Error("test failed - NewPairFromIndex() error", err)
}
pair.Delimiter = "-"
actual := pair.String()
expected := defaultPairWDelimiter
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
currency = "DOGEBTC"
pair, err = NewPairFromIndex(currency, index)
if err != nil {
t.Error("test failed - NewPairFromIndex() error", err)
}
pair.Delimiter = "-"
actual = pair.String()
expected = "DOGE-BTC"
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestNewPairFromString(t *testing.T) {
t.Parallel()
pairStr := defaultPairWDelimiter
pair := NewPairFromString(pairStr)
actual := pair.String()
expected := defaultPairWDelimiter
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
pairStr = defaultPair
pair = NewPairFromString(pairStr)
actual = pair.String()
expected = defaultPair
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestContainsCurrency(t *testing.T) {
p := NewPair(BTC, USD)
if !p.ContainsCurrency(BTC) {
t.Error("Test failed. TestContainsCurrency: Expected currency was not found")
}
if p.ContainsCurrency(ETH) {
t.Error("Test failed. TestContainsCurrency: Non-existent currency was found")
}
}
func TestFormatPairs(t *testing.T) {
newP, err := FormatPairs([]string{""}, "-", "")
if err != nil {
t.Error("Test Failed - FormatPairs() error", err)
}
if len(newP) > 0 {
t.Error("Test failed. TestFormatPairs: Empty string returned a valid pair")
}
newP, err = FormatPairs([]string{defaultPairWDelimiter}, "-", "")
if err != nil {
t.Error("Test Failed - FormatPairs() error", err)
}
if newP[0].String() != defaultPairWDelimiter {
t.Error("Test failed. TestFormatPairs: Expected pair was not found")
}
newP, err = FormatPairs([]string{defaultPair}, "", "BTC")
if err != nil {
t.Error("Test Failed - FormatPairs() error", err)
}
if newP[0].String() != defaultPair {
t.Error("Test failed. TestFormatPairs: Expected pair was not found")
}
newP, err = FormatPairs([]string{"ETHUSD"}, "", "")
if err != nil {
t.Error("Test Failed - FormatPairs() error", err)
}
if newP[0].String() != "ETHUSD" {
t.Error("Test failed. TestFormatPairs: Expected pair was not found")
}
}
func TestCopyPairFormat(t *testing.T) {
pairOne := NewPair(BTC, USD)
pairOne.Delimiter = "-"
var pairs []Pair
pairs = append(pairs, pairOne, NewPair(LTC, USD))
testPair := NewPair(BTC, USD)
testPair.Delimiter = "~"
result := CopyPairFormat(testPair, pairs, false)
if result.String() != defaultPairWDelimiter {
t.Error("Test failed. TestCopyPairFormat: Expected pair was not found")
}
result = CopyPairFormat(NewPair(ETH, USD), pairs, true)
if result.String() != "" {
t.Error("Test failed. TestCopyPairFormat: Unexpected non empty pair returned")
}
}
func TestFindPairDifferences(t *testing.T) {
pairList := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"})
// Test new pair update
newPairs, removedPairs := pairList.FindDifferences(NewPairsFromStrings([]string{"DASH-USD"}))
if len(newPairs) != 1 && len(removedPairs) != 3 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
// Test that we don't allow empty strings for new pairs
newPairs, removedPairs = pairList.FindDifferences(NewPairsFromStrings([]string{""}))
if len(newPairs) != 0 && len(removedPairs) != 3 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
// Test that we don't allow empty strings for new pairs
newPairs, removedPairs = NewPairsFromStrings([]string{""}).FindDifferences(pairList)
if len(newPairs) != 3 && len(removedPairs) != 0 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
// Test that the supplied pair lists are the same, so
// no newPairs or removedPairs
newPairs, removedPairs = pairList.FindDifferences(pairList)
if len(newPairs) != 0 && len(removedPairs) != 0 {
t.Error("Test failed. TestFindPairDifferences: Unexpected values")
}
}
func TestPairsToStringArray(t *testing.T) {
var pairs Pairs
pairs = append(pairs, NewPair(BTC, USD))
expected := []string{defaultPair}
actual := pairs.Strings()
if actual[0] != expected[0] {
t.Error("Test failed. TestPairsToStringArray: Unexpected values")
}
}
func TestRandomPairFromPairs(t *testing.T) {
// Test that an empty pairs array returns an empty currency pair
var emptyPairs Pairs
result := emptyPairs.GetRandomPair()
if !result.IsEmpty() {
t.Error("Test failed. TestRandomPairFromPairs: Unexpected values")
}
// Test that a populated pairs array returns a non-empty currency pair
var pairs Pairs
pairs = append(pairs, NewPair(BTC, USD))
result = pairs.GetRandomPair()
if result.IsEmpty() {
t.Error("Test failed. TestRandomPairFromPairs: Unexpected values")
}
// Test that a populated pairs array over a number of attempts returns ALL
// currency pairs
pairs = append(pairs, NewPair(ETH, USD))
expectedResults := make(map[string]bool)
for i := 0; i < 50; i++ {
p := pairs.GetRandomPair().String()
_, ok := expectedResults[p]
if !ok {
expectedResults[p] = true
}
}
for x := range pairs {
_, ok := expectedResults[pairs[x].String()]
if !ok {
t.Error("Test failed. TestRandomPairFromPairs: Unexpected values")
}
}
}
func TestIsInvalid(t *testing.T) {
p := NewPair(LTC, LTC)
if !p.IsInvalid() {
t.Error("Test Failed - IsInvalid() error expect true but received false")
}
}

158
currency/pairs.go Normal file
View File

@@ -0,0 +1,158 @@
package currency
import (
"math/rand"
"github.com/thrasher-/gocryptotrader/common"
)
// NewPairsFromStrings takes in currency pair strings and returns a currency
// pair list
func NewPairsFromStrings(pairs []string) Pairs {
var ps Pairs
for _, p := range pairs {
if p == "" {
continue
}
ps = append(ps, NewPairFromString(p))
}
return ps
}
// Pairs defines a list of pairs
type Pairs []Pair
// Strings returns a slice of strings referring to each currency pair
func (p Pairs) Strings() []string {
var list []string
for i := range p {
list = append(list, p[i].String())
}
return list
}
// Join returns a comma separated list of currency pairs
func (p Pairs) Join() string {
return common.JoinStrings(p.Strings(), ",")
}
// Format formats the pair list to the exchange format configuration
func (p Pairs) Format(delimiter, index string, uppercase bool) Pairs {
var pairs Pairs
for i := range p {
var formattedPair Pair
formattedPair.Delimiter = delimiter
formattedPair.Base = p[i].Base
formattedPair.Quote = p[i].Quote
if index != "" {
formattedPair.Quote = NewCode(index)
}
if uppercase {
pairs = append(pairs, formattedPair.Upper())
} else {
pairs = append(pairs, formattedPair)
}
}
return pairs
}
// UnmarshalJSON comforms type to the umarshaler interface
func (p *Pairs) UnmarshalJSON(d []byte) error {
var pairs string
err := common.JSONDecode(d, &pairs)
if err != nil {
return err
}
var allThePairs Pairs
for _, data := range common.SplitStrings(pairs, ",") {
allThePairs = append(allThePairs, NewPairFromString(data))
}
*p = allThePairs
return nil
}
// MarshalJSON conforms type to the marshaler interface
func (p Pairs) MarshalJSON() ([]byte, error) {
return common.JSONEncode(p.Join())
}
// Upper returns an upper formatted pair list
func (p Pairs) Upper() Pairs {
var upper Pairs
for i := range p {
upper = append(upper, p[i].Upper())
}
return upper
}
// Slice exposes the underlying type
func (p Pairs) Slice() []Pair {
return p
}
// Contains checks to see if a specified pair exists inside a currency pair
// array
func (p Pairs) Contains(check Pair, exact bool) bool {
for _, pair := range p.Slice() {
if exact {
if pair.Equal(check) {
return true
}
} else {
if pair.EqualIncludeReciprocal(check) {
return true
}
}
}
return false
}
// RemovePairsByFilter checks to see if a pair contains a specific currency
// and removes it from the list of pairs
func (p Pairs) RemovePairsByFilter(filter Code) Pairs {
var pairs Pairs
for _, pair := range p.Slice() {
if pair.ContainsCurrency(filter) {
continue
}
pairs = append(pairs, pair)
}
return pairs
}
// FindDifferences returns pairs which are new or have been removed
func (p Pairs) FindDifferences(pairs Pairs) (newPairs, removedPairs Pairs) {
for x := range pairs {
if pairs[x].String() == "" {
continue
}
if !p.Contains(pairs[x], true) {
newPairs = append(newPairs, pairs[x])
}
}
for _, oldPair := range p {
if oldPair.String() == "" {
continue
}
if !pairs.Contains(oldPair, true) {
removedPairs = append(removedPairs, oldPair)
}
}
return
}
// GetRandomPair returns a random pair from a list of pairs
func (p Pairs) GetRandomPair() Pair {
pairsLen := len(p)
if pairsLen == 0 {
return Pair{Base: NewCode(""), Quote: NewCode("")}
}
return p[rand.Intn(pairsLen)]
}

133
currency/pairs_test.go Normal file
View File

@@ -0,0 +1,133 @@
package currency
import (
"testing"
"github.com/thrasher-/gocryptotrader/common"
)
func TestPairsUpper(t *testing.T) {
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
expected := "BTC_USD,BTC_AUD,BTC_LTC"
if pairs.Upper().Join() != expected {
t.Errorf("Test Failed - Pairs Join() error expected %s but received %s",
expected, pairs.Join())
}
}
func TestPairsString(t *testing.T) {
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
expected := []string{"btc_usd", "btc_aud", "btc_ltc"}
for i, p := range pairs {
if p.String() != expected[i] {
t.Errorf("Test Failed - Pairs String() error expected %s but received %s",
expected, p.String())
}
}
}
func TestPairsJoin(t *testing.T) {
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
expected := "btc_usd,btc_aud,btc_ltc"
if pairs.Join() != expected {
t.Errorf("Test Failed - Pairs Join() error expected %s but received %s",
expected, pairs.Join())
}
}
func TestPairsFormat(t *testing.T) {
pairs := NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"})
expected := "BTC-USD,BTC-AUD,BTC-LTC"
if pairs.Format("-", "", true).Join() != expected {
t.Errorf("Test Failed - Pairs Join() error expected %s but received %s",
expected, pairs.Format("-", "", true).Join())
}
expected = "btc:usd,btc:aud,btc:ltc"
if pairs.Format(":", "", false).Join() != expected {
t.Errorf("Test Failed - Pairs Join() error expected %s but received %s",
expected, pairs.Format("-", "", true).Join())
}
expected = "btc:krw,btc:krw,btc:krw"
if pairs.Format(":", "krw", false).Join() != expected {
t.Errorf("Test Failed - Pairs Join() error expected %s but received %s",
expected, pairs.Format("-", "", true).Join())
}
}
func TestPairsUnmarshalJSON(t *testing.T) {
var unmarshalHere Pairs
configPairs := "btc_usd,btc_aud,btc_ltc"
encoded, err := common.JSONEncode(configPairs)
if err != nil {
t.Fatal("Test Failed - Pairs UnmarshalJSON() error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Pairs UnmarshalJSON() error", err)
}
err = common.JSONDecode(encoded, &unmarshalHere)
if err != nil {
t.Fatal("Test Failed - Pairs UnmarshalJSON() error", err)
}
if unmarshalHere.Join() != configPairs {
t.Errorf("Test Failed - Pairs UnmarshalJSON() error expected %s but received %s",
configPairs, unmarshalHere.Join())
}
}
func TestPairsMarshalJSON(t *testing.T) {
quickstruct := struct {
Pairs Pairs `json:"soManyPairs"`
}{
Pairs: NewPairsFromStrings([]string{"btc_usd", "btc_aud", "btc_ltc"}),
}
encoded, err := common.JSONEncode(quickstruct)
if err != nil {
t.Fatal("Test Failed - Pairs MarshalJSON() error", err)
}
expected := `{"soManyPairs":"btc_usd,btc_aud,btc_ltc"}`
if string(encoded) != expected {
t.Errorf("Test Failed - Pairs MarshalJSON() error expected %s but received %s",
expected, string(encoded))
}
}
func TestRemovePairsByFilter(t *testing.T) {
var pairs = Pairs{
NewPair(BTC, USD),
NewPair(LTC, USD),
NewPair(LTC, USDT),
}
pairs = pairs.RemovePairsByFilter(USDT)
if pairs.Contains(NewPair(LTC, USDT), true) {
t.Error("Test failed. TestRemovePairsByFilter unexpected result")
}
}
func TestContains(t *testing.T) {
var pairs = Pairs{
NewPair(BTC, USD),
NewPair(LTC, USD),
}
if !pairs.Contains(NewPair(BTC, USD), true) {
t.Errorf("Test failed. TestContains: Expected pair was not found")
}
if pairs.Contains(NewPair(ETH, USD), false) {
t.Errorf("Test failed. TestContains: Non-existent pair was found")
}
}

754
currency/storage.go Normal file
View File

@@ -0,0 +1,754 @@
package currency
import (
"encoding/json"
"errors"
"fmt"
"sync"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/coinmarketcap"
"github.com/thrasher-/gocryptotrader/currency/forexprovider"
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
log "github.com/thrasher-/gocryptotrader/logger"
)
// CurrencyFileUpdateDelay defines the rate at which the currency.json file is
// updated
const (
DefaultCurrencyFileDelay = 168 * time.Hour
DefaultForeignExchangeDelay = 1 * time.Minute
)
func init() {
storage.SetDefaults()
}
// storage is an overarching type that keeps track of and updates currency,
// currency exchange rates and pairs
var storage Storage
// Storage contains the loaded storage currencies supported on available crypto
// or fiat marketplaces
// NOTE: All internal currencies are upper case
type Storage struct {
// FiatCurrencies defines the running fiat currencies in the currency
// storage
fiatCurrencies Currencies
// Cryptocurrencies defines the running cryptocurrencies in the currency
// storage
cryptocurrencies Currencies
// CurrencyCodes is a full basket of currencies either crypto, fiat, ico or
// contract being tracked by the currency storage system
currencyCodes BaseCodes
// Main converting currency
baseCurrency Code
// FXRates defines a protected conversion rate map
fxRates ConversionRates
// DefaultBaseCurrency is the base currency used for conversion
defaultBaseCurrency Code
// DefaultFiatCurrencies has the default minimum of FIAT values
defaultFiatCurrencies Currencies
// DefaultCryptoCurrencies has the default minimum of crytpocurrency values
defaultCryptoCurrencies Currencies
// FiatExchangeMarkets defines an interface to access FX data for fiat
// currency rates
fiatExchangeMarkets *forexprovider.ForexProviders
// CurrencyAnalysis defines a full market analysis suite to receieve and
// define different fiat currencies, cryptocurrencies and markets
currencyAnalysis *coinmarketcap.Coinmarketcap
// Path defines the main folder to dump and find currency JSON
path string
// Update delay variables
currencyFileUpdateDelay time.Duration
foreignExchangeUpdateDelay time.Duration
mtx sync.Mutex
wg sync.WaitGroup
shutdownC chan struct{}
updaterRunning bool
Verbose bool
}
// SetDefaults sets storage defaults for basic package functionality
func (s *Storage) SetDefaults() {
s.defaultBaseCurrency = USD
s.baseCurrency = s.defaultBaseCurrency
s.SetDefaultFiatCurrencies(USD, AUD, EUR, CNY)
s.SetDefaultCryptocurrencies(BTC, LTC, ETH, DOGE, DASH, XRP, XMR)
s.SetupConversionRates()
s.fiatExchangeMarkets = forexprovider.NewDefaultFXProvider()
}
// RunUpdater runs the foreign exchange updater service. This will set up a JSON
// dump file and keep foreign exchange rates updated as fast as possible without
// triggering rate limiters, it will also run a full cryptocurrency check
// through coin market cap and expose analytics for exchange services
func (s *Storage) RunUpdater(overrides BotOverrides, settings MainConfiguration, filePath string, verbose bool) error {
s.mtx.Lock()
if !settings.Cryptocurrencies.HasData() {
s.mtx.Unlock()
return errors.New("currency storage error, no cryptocurrencies loaded")
}
s.cryptocurrencies = settings.Cryptocurrencies
if settings.FiatDisplayCurrency.IsEmpty() {
s.mtx.Unlock()
return errors.New("currency storage error, no fiat display currency set in config")
}
s.baseCurrency = settings.FiatDisplayCurrency
log.Debugf("Fiat display currency: %s.", s.baseCurrency)
if settings.CryptocurrencyProvider.Enabled {
log.Debugf("Setting up currency analysis system with Coinmarketcap...")
c := &coinmarketcap.Coinmarketcap{}
c.SetDefaults()
c.Setup(coinmarketcap.Settings{
Name: settings.CryptocurrencyProvider.Name,
Enabled: true,
AccountPlan: settings.CryptocurrencyProvider.AccountPlan,
APIkey: settings.CryptocurrencyProvider.APIkey,
Verbose: settings.CryptocurrencyProvider.Verbose,
})
s.currencyAnalysis = c
}
if filePath == "" {
s.mtx.Unlock()
return errors.New("currency package runUpdater error filepath not set")
}
s.path = filePath + common.GetOSPathSlash() + "currency.json"
if settings.CurrencyDelay.Nanoseconds() == 0 {
s.currencyFileUpdateDelay = DefaultCurrencyFileDelay
} else {
s.currencyFileUpdateDelay = settings.CurrencyDelay
}
if settings.FxRateDelay.Nanoseconds() == 0 {
s.foreignExchangeUpdateDelay = DefaultForeignExchangeDelay
} else {
s.foreignExchangeUpdateDelay = settings.FxRateDelay
}
var fxSettings []base.Settings
for i := range settings.ForexProviders {
switch settings.ForexProviders[i].Name {
case "CurrencyConverter":
if overrides.FxCurrencyConverter ||
settings.ForexProviders[i].Enabled {
settings.ForexProviders[i].Enabled = true
fxSettings = append(fxSettings,
base.Settings(settings.ForexProviders[i]))
}
case "CurrencyLayer":
if overrides.FxCurrencyLayer || settings.ForexProviders[i].Enabled {
settings.ForexProviders[i].Enabled = true
fxSettings = append(fxSettings,
base.Settings(settings.ForexProviders[i]))
}
case "Fixer":
if overrides.FxFixer || settings.ForexProviders[i].Enabled {
settings.ForexProviders[i].Enabled = true
fxSettings = append(fxSettings,
base.Settings(settings.ForexProviders[i]))
}
case "OpenExchangeRates":
if overrides.FxOpenExchangeRates ||
settings.ForexProviders[i].Enabled {
settings.ForexProviders[i].Enabled = true
fxSettings = append(fxSettings,
base.Settings(settings.ForexProviders[i]))
}
case "ExchangeRates":
// TODO ADD OVERRIDE
if settings.ForexProviders[i].Enabled {
settings.ForexProviders[i].Enabled = true
fxSettings = append(fxSettings,
base.Settings(settings.ForexProviders[i]))
}
}
}
if len(fxSettings) != 0 {
var err error
s.fiatExchangeMarkets, err = forexprovider.StartFXService(fxSettings)
if err != nil {
s.mtx.Unlock()
return err
}
log.Debugf("Primary foreign exchange conversion provider %s enabled",
s.fiatExchangeMarkets.Primary.Provider.GetName())
for i := range s.fiatExchangeMarkets.Support {
log.Debugf("Support forex conversion provider %s enabled",
s.fiatExchangeMarkets.Support[i].Provider.GetName())
}
// Mutex present in this go routine to lock down retrieving rate data
// until this system initially updates
go s.ForeignExchangeUpdater()
} else {
log.Warnf("No foreign exchange providers enabled in config.json")
s.mtx.Unlock()
}
return nil
}
// SetupConversionRates sets default conversion rate values
func (s *Storage) SetupConversionRates() {
s.fxRates = ConversionRates{
m: make(map[*Item]map[*Item]*float64),
}
}
// SetDefaultFiatCurrencies assigns the default fiat currency list and adds it
// to the running list
func (s *Storage) SetDefaultFiatCurrencies(c ...Code) {
for _, currency := range c {
s.defaultFiatCurrencies = append(s.defaultFiatCurrencies, currency)
s.fiatCurrencies = append(s.fiatCurrencies, currency)
}
}
// SetDefaultCryptocurrencies assigns the default cryptocurrency list and adds
// it to the running list
func (s *Storage) SetDefaultCryptocurrencies(c ...Code) {
for _, currency := range c {
s.defaultCryptoCurrencies = append(s.defaultCryptoCurrencies, currency)
s.cryptocurrencies = append(s.cryptocurrencies, currency)
}
}
// SetupForexProviders sets up a new instance of the forex providers
func (s *Storage) SetupForexProviders(setting ...base.Settings) error {
addr, err := forexprovider.StartFXService(setting)
if err != nil {
return err
}
s.fiatExchangeMarkets = addr
return nil
}
// ForeignExchangeUpdater is a routine that seeds foreign exchange rate and keeps
// updated as fast as possible
func (s *Storage) ForeignExchangeUpdater() {
log.Debugf("Foreign exchange updater started, seeding FX rate list..")
s.wg.Add(1)
defer s.wg.Done()
err := s.SeedCurrencyAnalysisData()
if err != nil {
log.Error(err)
}
err = s.SeedForeignExchangeRates()
if err != nil {
log.Error(err)
}
// Unlock main rate retrieval mutex so all routines waiting can get access
// to data
s.mtx.Unlock()
// Set tickers to client defined rates or defaults
SeedForeignExchangeTick := time.NewTicker(s.foreignExchangeUpdateDelay)
SeedCurrencyAnalysisTick := time.NewTicker(s.currencyFileUpdateDelay)
for {
select {
case <-s.shutdownC:
return
case <-SeedForeignExchangeTick.C:
err := s.SeedForeignExchangeRates()
if err != nil {
log.Error(err)
}
case <-SeedCurrencyAnalysisTick.C:
err := s.SeedCurrencyAnalysisData()
if err != nil {
log.Error(err)
}
}
}
}
// SeedCurrencyAnalysisData sets a new instance of a coinmarketcap data.
func (s *Storage) SeedCurrencyAnalysisData() error {
b, err := common.ReadFile(s.path)
if err != nil {
err = s.FetchCurrencyAnalysisData()
if err != nil {
return s.WriteCurrencyDataToFile(s.path, false)
}
return s.WriteCurrencyDataToFile(s.path, true)
}
var fromFile File
err = common.JSONDecode(b, &fromFile)
if err != nil {
return err
}
err = s.LoadFileCurrencyData(fromFile)
if err != nil {
return err
}
// Based on update delay update the file
if fromFile.LastMainUpdate.After(fromFile.LastMainUpdate.Add(s.currencyFileUpdateDelay)) ||
fromFile.LastMainUpdate.IsZero() {
err = s.FetchCurrencyAnalysisData()
if err != nil {
return s.WriteCurrencyDataToFile(s.path, false)
}
return s.WriteCurrencyDataToFile(s.path, true)
}
return nil
}
// FetchCurrencyAnalysisData fetches a new fresh batch of currency data and
// loads it into memory
func (s *Storage) FetchCurrencyAnalysisData() error {
if s.currencyAnalysis == nil {
log.Warn("Currency analysis system offline please set api keys for coinmarketcap")
return errors.New("currency analysis system offline")
}
return s.UpdateCurrencies()
}
// WriteCurrencyDataToFile writes the full currency data to a designated file
func (s *Storage) WriteCurrencyDataToFile(path string, mainUpdate bool) error {
data, err := s.currencyCodes.GetFullCurrencyData()
if err != nil {
return err
}
if mainUpdate {
t := time.Now()
data.LastMainUpdate = t
s.currencyCodes.LastMainUpdate = t
}
var encoded []byte
encoded, err = json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
return common.WriteFile(path, encoded)
}
// LoadFileCurrencyData loads currencies into the currency codes
func (s *Storage) LoadFileCurrencyData(f File) error {
for _, contract := range f.Contracts {
err := s.currencyCodes.LoadItem(contract)
if err != nil {
return err
}
}
for _, crypto := range f.Cryptocurrency {
err := s.currencyCodes.LoadItem(crypto)
if err != nil {
return err
}
}
for _, fiat := range f.FiatCurrency {
err := s.currencyCodes.LoadItem(fiat)
if err != nil {
return err
}
}
for _, token := range f.Token {
err := s.currencyCodes.LoadItem(token)
if err != nil {
return err
}
}
for _, unset := range f.UnsetCurrency {
err := s.currencyCodes.LoadItem(unset)
if err != nil {
return err
}
}
s.currencyCodes.LastMainUpdate = f.LastMainUpdate
return nil
}
// UpdateCurrencies updates currency roll and information using coin market cap
func (s *Storage) UpdateCurrencies() error {
m, err := s.currencyAnalysis.GetCryptocurrencyIDMap()
if err != nil {
return err
}
for x := range m {
if m[x].IsActive != 1 {
continue
}
if m[x].Platform.Symbol != "" {
err := s.currencyCodes.UpdateToken(m[x].Name,
m[x].Symbol,
m[x].Platform.Symbol,
m[x].ID)
if err != nil {
return err
}
continue
}
err := s.currencyCodes.UpdateCryptocurrency(m[x].Name,
m[x].Symbol,
m[x].ID)
if err != nil {
return err
}
}
return nil
}
// SeedForeignExchangeRatesByCurrencies seeds the foreign exchange rates by
// currencies supplied
func (s *Storage) SeedForeignExchangeRatesByCurrencies(c Currencies) error {
s.fxRates.mtx.Lock()
defer s.fxRates.mtx.Unlock()
rates, err := s.fiatExchangeMarkets.GetCurrencyData(s.baseCurrency.String(),
c.Strings())
if err != nil {
return err
}
return s.updateExchangeRates(rates)
}
// SeedForeignExchangeRate returns a singular exchange rate
func (s *Storage) SeedForeignExchangeRate(from, to Code) (map[string]float64, error) {
return s.fiatExchangeMarkets.GetCurrencyData(from.String(),
[]string{to.String()})
}
// GetDefaultForeignExchangeRates returns foreign exchange rates based off
// default fiat currencies.
func (s *Storage) GetDefaultForeignExchangeRates() (Conversions, error) {
if !s.updaterRunning {
err := s.SeedDefaultForeignExchangeRates()
if err != nil {
return nil, err
}
}
return s.fxRates.GetFullRates(), nil
}
// SeedDefaultForeignExchangeRates seeds the default foreign exchange rates
func (s *Storage) SeedDefaultForeignExchangeRates() error {
s.fxRates.mtx.Lock()
defer s.fxRates.mtx.Unlock()
rates, err := s.fiatExchangeMarkets.GetCurrencyData(
s.defaultBaseCurrency.String(),
s.defaultFiatCurrencies.Strings())
if err != nil {
return err
}
return s.updateExchangeRates(rates)
}
// GetExchangeRates returns storage seeded exchange rates
func (s *Storage) GetExchangeRates() (Conversions, error) {
if !s.updaterRunning {
err := s.SeedForeignExchangeRates()
if err != nil {
return nil, err
}
}
return s.fxRates.GetFullRates(), nil
}
// SeedForeignExchangeRates seeds the foreign exchange rates from storage config
// currencies
func (s *Storage) SeedForeignExchangeRates() error {
s.fxRates.mtx.Lock()
defer s.fxRates.mtx.Unlock()
rates, err := s.fiatExchangeMarkets.GetCurrencyData(
s.baseCurrency.String(),
s.fiatCurrencies.Strings())
if err != nil {
return err
}
return s.updateExchangeRates(rates)
}
// UpdateForeignExchangeRates sets exchange rates on the FX map
func (s *Storage) updateExchangeRates(m map[string]float64) error {
err := s.fxRates.Update(m)
if err != nil {
return err
}
if s.path != "" {
return s.WriteCurrencyDataToFile(s.path, false)
}
return nil
}
// SetupCryptoProvider sets congiguration parameters and starts a new instance
// of the currency analyser
func (s *Storage) 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")
}
s.currencyAnalysis = new(coinmarketcap.Coinmarketcap)
s.currencyAnalysis.SetDefaults()
s.currencyAnalysis.Setup(settings)
return nil
}
// GetTotalMarketCryptocurrencies returns the total seeded market
// cryptocurrencies
func (s *Storage) GetTotalMarketCryptocurrencies() (Currencies, error) {
if !s.currencyCodes.HasData() {
return nil, errors.New("market currency codes not populated")
}
return s.currencyCodes.GetCurrencies(), nil
}
// IsDefaultCurrency returns if a currency is a default currency
func (s *Storage) IsDefaultCurrency(c Code) bool {
t, _ := GetTranslation(c)
for _, d := range s.defaultFiatCurrencies {
if d.Match(c) || d.Match(t) {
return true
}
}
return false
}
// IsDefaultCryptocurrency returns if a cryptocurrency is a default
// cryptocurrency
func (s *Storage) IsDefaultCryptocurrency(c Code) bool {
t, _ := GetTranslation(c)
for _, d := range s.defaultCryptoCurrencies {
if d.Match(c) || d.Match(t) {
return true
}
}
return false
}
// IsFiatCurrency returns if a currency is part of the enabled fiat currency
// list
func (s *Storage) IsFiatCurrency(c Code) bool {
if c.Item.Role != Unset {
return c.Item.Role == Fiat
}
t, _ := GetTranslation(c)
for _, d := range s.fiatCurrencies {
if d.Match(c) || d.Match(t) {
return true
}
}
return false
}
// IsCryptocurrency returns if a cryptocurrency is part of the enabled
// cryptocurrency list
func (s *Storage) IsCryptocurrency(c Code) bool {
if c.Item.Role != Unset {
return c.Item.Role == Cryptocurrency
}
t, _ := GetTranslation(c)
for _, d := range s.cryptocurrencies {
if d.Match(c) || d.Match(t) {
return true
}
}
return false
}
// ValidateCode validates string against currency list and returns a currency
// code
func (s *Storage) ValidateCode(newCode string) Code {
return s.currencyCodes.Register(newCode)
}
// ValidateFiatCode validates a fiat currency string and returns a currency
// code
func (s *Storage) ValidateFiatCode(newCode string) (Code, error) {
c, err := s.currencyCodes.RegisterFiat(newCode)
if err != nil {
return c, err
}
if !s.fiatCurrencies.Contains(c) {
s.fiatCurrencies = append(s.fiatCurrencies, c)
}
return c, nil
}
// ValidateCryptoCode validates a cryptocurrency string and returns a currency
// code
// TODO: Update and add in RegisterCrypto member func
func (s *Storage) ValidateCryptoCode(newCode string) Code {
c := s.currencyCodes.Register(newCode)
if !s.cryptocurrencies.Contains(c) {
s.cryptocurrencies = append(s.cryptocurrencies, c)
}
return c
}
// UpdateBaseCurrency changes base currency
func (s *Storage) UpdateBaseCurrency(c Code) error {
if c.IsFiatCurrency() {
s.baseCurrency = c
return nil
}
return fmt.Errorf("currency %s not fiat failed to set currency", c)
}
// GetCryptocurrencies returns the cryptocurrency list
func (s *Storage) GetCryptocurrencies() Currencies {
return s.cryptocurrencies
}
// GetDefaultCryptocurrencies returns a list of default cryptocurrencies
func (s *Storage) GetDefaultCryptocurrencies() Currencies {
return s.defaultCryptoCurrencies
}
// GetFiatCurrencies returns the fiat currencies list
func (s *Storage) GetFiatCurrencies() Currencies {
return s.fiatCurrencies
}
// GetDefaultFiatCurrencies returns the default fiat currencies list
func (s *Storage) GetDefaultFiatCurrencies() Currencies {
return s.defaultFiatCurrencies
}
// GetDefaultBaseCurrency returns the default base currency
func (s *Storage) GetDefaultBaseCurrency() Code {
return s.defaultBaseCurrency
}
// GetBaseCurrency returns the current storage base currency
func (s *Storage) GetBaseCurrency() Code {
return s.baseCurrency
}
// UpdateEnabledCryptoCurrencies appends new cryptocurrencies to the enabled
// currency list
func (s *Storage) UpdateEnabledCryptoCurrencies(c Currencies) {
for _, i := range c {
if !s.cryptocurrencies.Contains(i) {
s.cryptocurrencies = append(s.cryptocurrencies, i)
}
}
}
// UpdateEnabledFiatCurrencies appends new fiat currencies to the enabled
// currency list
func (s *Storage) UpdateEnabledFiatCurrencies(c Currencies) {
for _, i := range c {
if !s.fiatCurrencies.Contains(i) && !s.cryptocurrencies.Contains(i) {
s.fiatCurrencies = append(s.fiatCurrencies, i)
}
}
}
// ConvertCurrency for example converts $1 USD to the equivalent Japanese Yen
// or vice versa.
func (s *Storage) ConvertCurrency(amount float64, from, to Code) (float64, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
if !s.fxRates.HasData() {
err := s.SeedDefaultForeignExchangeRates()
if err != nil {
return 0, err
}
}
r, err := s.fxRates.GetRate(from, to)
if err != nil {
return 0, err
}
return r * amount, nil
}
// GetStorageRate returns the rate of the conversion value
func (s *Storage) GetStorageRate(from, to Code) (float64, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
if !s.fxRates.HasData() {
err := s.SeedDefaultForeignExchangeRates()
if err != nil {
return 0, err
}
}
return s.fxRates.GetRate(from, to)
}
// NewConversion returns a new conversion object that has a pointer to a related
// rate with its inversion.
func (s *Storage) NewConversion(from, to Code) (Conversion, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
if !s.fxRates.HasData() {
err := storage.SeedDefaultForeignExchangeRates()
if err != nil {
return Conversion{}, err
}
}
return s.fxRates.Register(from, to)
}
// IsVerbose returns if the storage is in verbose mode
func (s *Storage) IsVerbose() bool {
return s.Verbose
}

27
currency/storage_test.go Normal file
View File

@@ -0,0 +1,27 @@
package currency
import "testing"
func TestRunUpdater(t *testing.T) {
var newStorage Storage
err := newStorage.RunUpdater(BotOverrides{}, MainConfiguration{}, "", false)
if err == nil {
t.Fatal("Test Failed storage RunUpdater() error cannot be nil")
}
mainConfig := MainConfiguration{
Cryptocurrencies: NewCurrenciesFromStringArray([]string{"BTC"}),
FiatDisplayCurrency: USD,
}
err = newStorage.RunUpdater(BotOverrides{}, mainConfig, "", false)
if err == nil {
t.Fatal("Test Failed storage RunUpdater() error cannot be nil")
}
err = newStorage.RunUpdater(BotOverrides{}, mainConfig, "/bla", false)
if err != nil {
t.Fatal("Test Failed storage RunUpdater() error", err)
}
}

127
currency/symbol.go Normal file
View File

@@ -0,0 +1,127 @@
package currency
import "errors"
// symbols map holds the currency name and symbol mappings
var symbols = map[*Item]string{
ALL.Item: "Lek",
AFN.Item: "؋",
ARS.Item: "$",
AWG.Item: "ƒ",
AUD.Item: "$",
AZN.Item: "ман",
BSD.Item: "$",
BBD.Item: "$",
BYN.Item: "Br",
BZD.Item: "BZ$",
BMD.Item: "$",
BOB.Item: "$b",
BAM.Item: "KM",
BWP.Item: "P",
BGN.Item: "лв",
BRL.Item: "R$",
BND.Item: "$",
KHR.Item: "៛",
CAD.Item: "$",
KYD.Item: "$",
CLP.Item: "$",
CNY.Item: "¥",
COP.Item: "$",
CRC.Item: "₡",
HRK.Item: "kn",
CUP.Item: "₱",
CZK.Item: "Kč",
DKK.Item: "kr",
DOP.Item: "RD$",
XCD.Item: "$",
EGP.Item: "£",
SVC.Item: "$",
EUR.Item: "€",
FKP.Item: "£",
FJD.Item: "$",
GHS.Item: "¢",
GIP.Item: "£",
GTQ.Item: "Q",
GGP.Item: "£",
GYD.Item: "$",
HNL.Item: "L",
HKD.Item: "$",
HUF.Item: "Ft",
ISK.Item: "kr",
INR.Item: "₹",
IDR.Item: "Rp",
IRR.Item: "﷼",
IMP.Item: "£",
ILS.Item: "₪",
JMD.Item: "J$",
JPY.Item: "¥",
JEP.Item: "£",
KZT.Item: "лв",
KPW.Item: "₩",
KRW.Item: "₩",
KGS.Item: "лв",
LAK.Item: "₭",
LBP.Item: "£",
LRD.Item: "$",
MKD.Item: "ден",
MYR.Item: "RM",
MUR.Item: "₨",
MXN.Item: "$",
MNT.Item: "₮",
MZN.Item: "MT",
NAD.Item: "$",
NPR.Item: "₨",
ANG.Item: "ƒ",
NZD.Item: "$",
NIO.Item: "C$",
NGN.Item: "₦",
NOK.Item: "kr",
OMR.Item: "﷼",
PKR.Item: "₨",
PAB.Item: "B/.",
PYG.Item: "Gs",
PEN.Item: "S/.",
PHP.Item: "₱",
PLN.Item: "zł",
QAR.Item: "﷼",
RON.Item: "lei",
RUB.Item: "₽",
RUR.Item: "₽",
SHP.Item: "£",
SAR.Item: "﷼",
RSD.Item: "Дин.",
SCR.Item: "₨",
SGD.Item: "$",
SBD.Item: "$",
SOS.Item: "S",
ZAR.Item: "R",
LKR.Item: "₨",
SEK.Item: "kr",
CHF.Item: "CHF",
SRD.Item: "$",
SYP.Item: "£",
TWD.Item: "NT$",
THB.Item: "฿",
TTD.Item: "TT$",
TRY.Item: "₺",
TVD.Item: "$",
UAH.Item: "₴",
GBP.Item: "£",
USD.Item: "$",
USDT.Item: "$",
UYU.Item: "$U",
UZS.Item: "лв",
VEF.Item: "Bs",
VND.Item: "₫",
YER.Item: "﷼",
ZWD.Item: "Z$",
}
// GetSymbolByCurrencyName returns a currency symbol
func GetSymbolByCurrencyName(currency Code) (string, error) {
result, ok := symbols[currency.Item]
if !ok {
return "", errors.New("currency symbol not found")
}
return result, nil
}

View File

@@ -1,56 +0,0 @@
# GoCryptoTrader package Symbol
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/currency/symbol)
[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
This symbol package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY)
## Current Features for symbol
+ This package services the currency package by providing symbol mapping.
+ Example below:
```go
import "github.com/thrasher-/gocryptotrader/currency/symbol"
// Get the string of the symbol by the currency
chineseYen := "CNY"
symbol := symbol.GetSymbolByCurrencyName(chineseYen)
// symbol == "¥"
```
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
Please feel free to submit any pull requests or suggest any desired features to be added.
When submitting a PR, please abide by our coding guidelines:
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Donations
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
package symbol
package currency
import "testing"
func TestGetSymbolByCurrencyName(t *testing.T) {
expected := "₩"
actual, err := GetSymbolByCurrencyName("KPW")
actual, err := GetSymbolByCurrencyName(KPW)
if err != nil {
t.Errorf("Test failed. TestGetSymbolByCurrencyName error: %s", err)
}
@@ -13,7 +13,7 @@ func TestGetSymbolByCurrencyName(t *testing.T) {
t.Errorf("Test failed. TestGetSymbolByCurrencyName differing values")
}
_, err = GetSymbolByCurrencyName("BLAH")
_, err = GetSymbolByCurrencyName(Code{})
if err == nil {
t.Errorf("Test failed. TestGetSymbolByCurrencyNam returned nil on non-existent currency")
}

22
currency/translation.go Normal file
View File

@@ -0,0 +1,22 @@
package currency
var translations = map[Code]Code{
BTC: XBT,
ETH: XETH,
DOGE: XDG,
USD: USDT,
XBT: BTC,
XETH: ETH,
XDG: DOGE,
USDT: USD,
}
// GetTranslation returns similar strings for a particular currency if not found
// returns the code back
func GetTranslation(currency Code) (Code, bool) {
val, ok := translations[currency]
if !ok {
return currency, ok
}
return val, ok
}

View File

@@ -1,55 +0,0 @@
# GoCryptoTrader package Translation
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader/currency/translation)
[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
This translation package is part of the GoCryptoTrader codebase.
## This is still in active development
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTQyYjIxNGVhMWU5MDZlOGYzMmE0NTJmM2MzYWY5NGMzMmM4MzUwNTBjZTEzNjIwODM5NDcxODQwZDljMGQyNGY)
## Current Features for translation
+ This package services the currency package with translation functions.
+ Example below:
```go
import "github.com/thrasher-/gocryptotrader/currency/translation"
// Is translatable
b := translation.HasTranslation("BTC")
// b == true; translation = XBT
```
### Please click GoDocs chevron above to view current GoDoc information for this package
## Contribution
Please feel free to submit any pull requests or suggest any desired features to be added.
When submitting a PR, please abide by our coding guidelines:
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Donations
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***

View File

@@ -1,34 +0,0 @@
package translation
import (
"errors"
"github.com/thrasher-/gocryptotrader/currency/pair"
)
var translations = map[pair.CurrencyItem]pair.CurrencyItem{
"BTC": "XBT",
"ETH": "XETH",
"DOGE": "XDG",
"USD": "USDT",
}
// GetTranslation returns similar strings for a particular currency
func GetTranslation(currency pair.CurrencyItem) (pair.CurrencyItem, error) {
for k, v := range translations {
if k == currency {
return v, nil
}
if v == currency {
return k, nil
}
}
return "", errors.New("no translation found for specified currency")
}
// HasTranslation returns whether or not a particular currency has a translation
func HasTranslation(currency pair.CurrencyItem) bool {
_, err := GetTranslation(currency)
return (err == nil)
}

View File

@@ -1,61 +0,0 @@
package translation
import (
"testing"
"github.com/thrasher-/gocryptotrader/currency/pair"
)
func TestGetTranslation(t *testing.T) {
currencyPair := pair.NewCurrencyPair("BTC", "USD")
expected := pair.CurrencyItem("XBT")
actual, err := GetTranslation(currencyPair.FirstCurrency)
if err != nil {
t.Error("GetTranslation: failed to retrieve translation for BTC")
}
if expected != actual {
t.Error("GetTranslation: translation result was different to expected result")
}
currencyPair.FirstCurrency = "NEO"
_, err = GetTranslation(currencyPair.FirstCurrency)
if err == nil {
t.Error("GetTranslation: no error on non translatable currency")
}
expected = "BTC"
currencyPair.FirstCurrency = "XBT"
actual, err = GetTranslation(currencyPair.FirstCurrency)
if err != nil {
t.Error("GetTranslation: failed to retrieve translation for BTC")
}
if expected != actual {
t.Error("GetTranslation: translation result was different to expected result")
}
}
func TestHasTranslation(t *testing.T) {
currencyPair := pair.NewCurrencyPair("BTC", "USD")
expected := true
actual := HasTranslation(currencyPair.FirstCurrency)
if expected != actual {
t.Error("HasTranslation: translation result was different to expected result")
}
currencyPair.FirstCurrency = "XBT"
expected = true
actual = HasTranslation(currencyPair.FirstCurrency)
if expected != actual {
t.Error("HasTranslation: translation result was different to expected result")
}
currencyPair.FirstCurrency = "NEO"
expected = false
actual = HasTranslation(currencyPair.FirstCurrency)
if expected != actual {
t.Error("HasTranslation: translation result was different to expected result")
}
}

View File

@@ -0,0 +1,34 @@
package currency
import "testing"
func TestGetTranslation(t *testing.T) {
currencyPair := NewPair(BTC, USD)
expected := XBT
actual, ok := GetTranslation(currencyPair.Base)
if !ok {
t.Error("GetTranslation: failed to retrieve translation for BTC")
}
if expected != actual {
t.Error("GetTranslation: translation result was different to expected result")
}
currencyPair.Base = NEO
_, ok = GetTranslation(currencyPair.Base)
if ok {
t.Error("GetTranslation: no error on non translatable currency")
}
expected = BTC
currencyPair.Base = XBT
actual, ok = GetTranslation(currencyPair.Base)
if !ok {
t.Error("GetTranslation: failed to retrieve translation for BTC")
}
if expected != actual {
t.Error("GetTranslation: translation result was different to expected result")
}
}