mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-07 07:26:48 +00:00
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:
committed by
Adrian Gallagher
parent
ed760e184e
commit
0990f9d118
2102
currency/code.go
Normal file
2102
currency/code.go
Normal file
File diff suppressed because it is too large
Load Diff
426
currency/code_test.go
Normal file
426
currency/code_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
361
currency/conversion.go
Normal 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
177
currency/conversion_test.go
Normal 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
95
currency/currencies.go
Normal 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
|
||||
}
|
||||
50
currency/currencies_test.go
Normal file
50
currency/currencies_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
62
currency/currency_types.go
Normal file
62
currency/currency_types.go
Normal 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"`
|
||||
}
|
||||
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package base
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
190
currency/pair.go
Normal 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
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-/gocryptotrader)
|
||||
[](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-/gocryptotrader/currency/pair)
|
||||
[](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
|
||||
[](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***
|
||||
|
||||
@@ -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)]
|
||||
}
|
||||
@@ -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
563
currency/pair_test.go
Normal 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
158
currency/pairs.go
Normal 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
133
currency/pairs_test.go
Normal 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
754
currency/storage.go
Normal 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
27
currency/storage_test.go
Normal 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
127
currency/symbol.go
Normal 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
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-/gocryptotrader)
|
||||
[](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-/gocryptotrader/currency/symbol)
|
||||
[](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
|
||||
[](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
@@ -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
22
currency/translation.go
Normal 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
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-/gocryptotrader)
|
||||
[](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-/gocryptotrader/currency/translation)
|
||||
[](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
|
||||
[](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***
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
34
currency/translation_test.go
Normal file
34
currency/translation_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user