diff --git a/README.md b/README.md index 4c48fe1f..49a38e3a 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Download and install Go from [Go Downloads](https://golang.org/dl/) go get github.com/thrasher-/gocryptotrader cd $GOPATH/src/github.com/thrasher-/gocryptotrader go install -cp $GOPATH/src/github.com/thrasher-/gocryptotrader/config_example.dat $GOPATH/bin/config.dat +cp $GOPATH/src/github.com/thrasher-/gocryptotrader/config_example.json $GOPATH/bin/config.json ``` Make any neccessary changes to the config file. diff --git a/config/config.go b/config/config.go index 5a156b51..ec87ed57 100644 --- a/config/config.go +++ b/config/config.go @@ -19,9 +19,9 @@ import ( // Constants declared here are filename strings and test strings const ( - ConfigFile = "config.dat" - OldConfigFile = "config.json" - ConfigTestFile = "../testdata/configtest.dat" + EncryptedConfigFile = "config.dat" + ConfigFile = "config.json" + ConfigTestFile = "../testdata/configtest.json" configFileEncryptionPrompt = 0 configFileEncryptionEnabled = 1 configFileEncryptionDisabled = -1 @@ -47,7 +47,6 @@ var ( WarningWebserverRootWebFolderNotFound = "WARNING -- Webserver support disabled due to missing web folder." WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values." WarningCurrencyExchangeProvider = "WARNING -- Currency exchange provider invalid valid. Reset to Fixer." - RenamingConfigFile = "Renaming config file %s to %s." Cfg Config ) @@ -104,6 +103,7 @@ type ExchangeConfig struct { Enabled bool Verbose bool Websocket bool + UseSandbox bool RESTPollingDelay time.Duration AuthenticatedAPISupport bool APIKey string @@ -316,34 +316,37 @@ func GetFilePath(file string) string { return file } if flag.Lookup("test.v") == nil { - return ConfigFile + data, err := common.ReadFile(EncryptedConfigFile) + if err == nil { + if ConfirmECS(data) { + return EncryptedConfigFile + } + err = os.Rename(EncryptedConfigFile, ConfigFile) + if err != nil { + log.Fatalf("Unable to rename config file: %s", err) + } + log.Printf("Renaming non-encrypted config file from %s to %s", + EncryptedConfigFile, ConfigFile) + return ConfigFile + } + if !ConfirmECS(data) { + return ConfigFile + } + err = os.Rename(ConfigFile, EncryptedConfigFile) + if err != nil { + log.Fatalf("Unable to rename config file: %s", err) + } + log.Printf("Renaming encrypted config file from %s to %s", ConfigFile, + EncryptedConfigFile) + return EncryptedConfigFile } return ConfigTestFile } -// CheckConfig checks to see if there is an old configuration filename and path -// if found it will change it to correct filename. -func CheckConfig() error { - _, err := common.ReadFile(OldConfigFile) - if err == nil { - err = os.Rename(OldConfigFile, ConfigFile) - if err != nil { - return err - } - log.Printf(RenamingConfigFile+"\n", OldConfigFile, ConfigFile) - } - return nil -} - // ReadConfig verifies and checks for encryption and verifies the unencrypted // file contains JSON. func (c *Config) ReadConfig(configPath string) error { defaultPath := GetFilePath(configPath) - err := CheckConfig() - if err != nil { - return err - } - file, err := common.ReadFile(defaultPath) if err != nil { return err diff --git a/config_example.dat b/config_example.json similarity index 100% rename from config_example.dat rename to config_example.json diff --git a/currency/currency.go b/currency/currency.go index 0201f529..4e00dd7e 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -68,7 +68,7 @@ var ( ErrCurrencyNotFound = errors.New("unable to find specified currency") ErrQueryingYahoo = errors.New("unable to query Yahoo currency values") ErrQueryingYahooZeroCount = errors.New("yahoo returned zero currency data") - YahooEnabled = true + YahooEnabled = false ) // SetProvider sets the currency exchange service used by the currency diff --git a/currency/currency_test.go b/currency/currency_test.go index bee21e73..c5b74e1f 100644 --- a/currency/currency_test.go +++ b/currency/currency_test.go @@ -323,35 +323,37 @@ func TestCheckAndAddCurrency(t *testing.T) { } func TestSeedCurrencyData(t *testing.T) { - SetProvider(true) - currencyRequestDefault := "" - currencyRequestUSDAUD := "USD,AUD" - currencyRequestObtuse := "WigWham" + // SetProvider(true) + if YahooEnabled { + currencyRequestDefault := "" + currencyRequestUSDAUD := "USD,AUD" + currencyRequestObtuse := "WigWham" - err := SeedCurrencyData(currencyRequestDefault) - if err != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err, currencyRequestDefault, - ) - } - err2 := SeedCurrencyData(currencyRequestUSDAUD) - if err2 != nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err2, currencyRequestUSDAUD, - ) - } - err3 := SeedCurrencyData(currencyRequestObtuse) - if err3 == nil { - t.Errorf( - "Test Failed. SeedCurrencyData: Error %s with currency as %s.", - err3, currencyRequestObtuse, - ) + err := SeedCurrencyData(currencyRequestDefault) + if err != nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err, currencyRequestDefault, + ) + } + err2 := SeedCurrencyData(currencyRequestUSDAUD) + if err2 != nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err2, currencyRequestUSDAUD, + ) + } + err3 := SeedCurrencyData(currencyRequestObtuse) + if err3 == nil { + t.Errorf( + "Test Failed. SeedCurrencyData: Error %s with currency as %s.", + err3, currencyRequestObtuse, + ) + } } - SetProvider(false) - err = SeedCurrencyData("") + //SetProvider(false) + err := SeedCurrencyData("") if err != nil { t.Errorf("Test failed. SeedCurrencyData via Fixer. Error: %s", err) } @@ -371,29 +373,31 @@ func TestMakecurrencyPairs(t *testing.T) { } func TestConvertCurrency(t *testing.T) { - SetProvider(true) - fiatCurrencies := DefaultCurrencies - for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { - for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { - floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo) - if err != nil { - t.Errorf( - "Test Failed. ConvertCurrency: Error %s with return: %.2f Currency 1: %s Currency 2: %s", - err, floatyMcfloat, currencyFrom, currencyTo, - ) - } - if reflect.TypeOf(floatyMcfloat).String() != "float64" { - t.Error("Test Failed. ConvertCurrency: Error, incorrect return type") - } - if floatyMcfloat <= 0 { - t.Error( - "Test Failed. ConvertCurrency: Error, negative return or a serious issue with current fiat", - ) + // SetProvider(true) + if YahooEnabled { + fiatCurrencies := DefaultCurrencies + for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") { + for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") { + floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo) + if err != nil { + t.Errorf( + "Test Failed. ConvertCurrency: Error %s with return: %.2f Currency 1: %s Currency 2: %s", + err, floatyMcfloat, currencyFrom, currencyTo, + ) + } + if reflect.TypeOf(floatyMcfloat).String() != "float64" { + t.Error("Test Failed. ConvertCurrency: Error, incorrect return type") + } + if floatyMcfloat <= 0 { + t.Error( + "Test Failed. ConvertCurrency: Error, negative return or a serious issue with current fiat", + ) + } } } } - SetProvider(false) + // SetProvider(false) _, err := ConvertCurrency(1000, "USD", "AUD") if err != nil { t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) @@ -409,26 +413,26 @@ func TestConvertCurrency(t *testing.T) { t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err) } - // Test non-existant currencies + // Test non-existent currencies _, err = ConvertCurrency(1000, "ASDF", "USD") if err == nil { - t.Errorf("Test failed. ConvertCurrency non-existant currency -> USD. Error %s", err) + t.Errorf("Test failed. ConvertCurrency non-existent currency -> USD. Error %s", err) } _, err = ConvertCurrency(1000, "USD", "ASDF") if err == nil { - t.Errorf("Test failed. ConvertCurrency USD -> non-existant currency. Error %s", err) + t.Errorf("Test failed. ConvertCurrency USD -> non-existent currency. Error %s", err) } _, err = ConvertCurrency(1000, "CNY", "UAHF") if err == nil { - t.Errorf("Test failed. ConvertCurrency non-USD currency CNY -> non-existant currency. Error %s", err) + t.Errorf("Test failed. ConvertCurrency non-USD currency CNY -> non-existent currency. Error %s", err) } _, err = ConvertCurrency(1000, "UASF", "UAHF") if err == nil { - t.Errorf("Test failed. ConvertCurrency non-existant currency -> non-existant currency. Error %s", err) + t.Errorf("Test failed. ConvertCurrency non-existent currency -> non-existent currency. Error %s", err) } } @@ -440,6 +444,10 @@ func TestFetchFixerCurrencyData(t *testing.T) { } func TestFetchYahooCurrencyData(t *testing.T) { + if !YahooEnabled { + t.Skip() + } + t.Parallel() var fetchData []string fiatCurrencies := DefaultCurrencies @@ -460,6 +468,10 @@ func TestFetchYahooCurrencyData(t *testing.T) { } func TestQueryYahooCurrencyValues(t *testing.T) { + if !YahooEnabled { + t.Skip() + } + err := QueryYahooCurrencyValues(DefaultCurrencies) if err != nil { t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err) diff --git a/currency/symbol/symbol_test.go b/currency/symbol/symbol_test.go index 5975cb67..5c026c9c 100644 --- a/currency/symbol/symbol_test.go +++ b/currency/symbol/symbol_test.go @@ -15,7 +15,7 @@ func TestGetSymbolByCurrencyName(t *testing.T) { _, err = GetSymbolByCurrencyName("BLAH") if err == nil { - t.Errorf("Test failed. TestGetSymbolByCurrencyNam returned nil on non-existant currency") + t.Errorf("Test failed. TestGetSymbolByCurrencyNam returned nil on non-existent currency") } } diff --git a/exchanges/alphapoint/alphapoint_types.go b/exchanges/alphapoint/alphapoint_types.go index 549b48c4..f00cedf2 100644 --- a/exchanges/alphapoint/alphapoint_types.go +++ b/exchanges/alphapoint/alphapoint_types.go @@ -43,7 +43,7 @@ type Trades struct { Trades []Trade `json:"trades"` } -// Trade is a sub-type which holds the singular trade that occured in the past +// Trade is a sub-type which holds the singular trade that occurred in the past type Trade struct { TID int64 `json:"tid"` Price float64 `json:"px"` diff --git a/exchanges/anx/anx_test.go b/exchanges/anx/anx_test.go index c5ede53e..cb7ff28f 100644 --- a/exchanges/anx/anx_test.go +++ b/exchanges/anx/anx_test.go @@ -37,7 +37,7 @@ func TestSetup(t *testing.T) { setup := ANX{} setup.Name = "ANX" anxSetupConfig := config.GetConfig() - anxSetupConfig.LoadConfig("../../testdata/configtest.dat") + anxSetupConfig.LoadConfig("../../testdata/configtest.json") anxConfig, err := anxSetupConfig.GetExchangeConfig("ANX") if err != nil { t.Error("Test Failed - ANX Setup() init error") diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index b25ad80f..7ecc324c 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -140,9 +140,11 @@ func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) { return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } -// GetOrderbook retieves the entire orderbook bid and ask price on a currency -// pair +// GetOrderbook retieves the orderbook bid and ask price points for a currency +// pair - By default the response will return 25 bid and 25 ask price points. // CurrencyPair - Example "BTCUSD" +// Values can contain limit amounts for both the asks and bids - Example +// "limit_bids" = 1000 func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbook, error) { response := Orderbook{} path := common.EncodeURLValues( @@ -153,7 +155,10 @@ func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbo } // GetTrades returns a list of the most recent trades for the given curencyPair +// By default the response will return 100 trades // CurrencyPair - Example "BTCUSD" +// Values can contain limit amounts for the number of trades returned - Example +// "limit_trades" = 1000 func (b *Bitfinex) GetTrades(currencyPair string, values url.Values) ([]TradeStructure, error) { response := []TradeStructure{} path := common.EncodeURLValues( @@ -188,7 +193,7 @@ func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) { return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } -// GetSymbols returns the avaliable currency pairs on the exchange +// GetSymbols returns the available currency pairs on the exchange func (b *Bitfinex) GetSymbols() ([]string, error) { products := []string{} path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbols) @@ -444,7 +449,7 @@ func (b *Bitfinex) GetBalanceHistory(symbol string, timeSince, timeUntil time.Ti b.SendAuthenticatedHTTPRequest("POST", bitfinexHistory, request, &response) } -// GetMovementHistory returns an array of past deposits and withdrawels +// GetMovementHistory returns an array of past deposits and withdrawals func (b *Bitfinex) GetMovementHistory(symbol, method string, timeSince, timeUntil time.Time, limit int) ([]MovementHistory, error) { response := []MovementHistory{} request := make(map[string]interface{}) diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 03363c5d..f81f439c 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -32,7 +32,7 @@ func TestSetup(t *testing.T) { setup := Bitfinex{} setup.Name = "Bitfinex" cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") bfxConfig, err := cfg.GetExchangeConfig("Bitfinex") if err != nil { t.Error("Test Failed - Bitfinex Setup() init error") diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index 976c6b02..fbd532cb 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -40,7 +40,7 @@ type TradeStructure struct { Type string `json:"sell"` } -// Lendbook holds most recent funding data for a relevent currency +// Lendbook holds most recent funding data for a relevant currency type Lendbook struct { Bids []Book `json:"bids"` Asks []Book `json:"asks"` @@ -86,7 +86,7 @@ type AccountInfo struct { } `json:"fees"` } -// AccountFees stores withdrawel account fee data from Bitfinex +// AccountFees stores withdrawal account fee data from Bitfinex type AccountFees struct { Withdraw struct { BTC float64 `json:"BTC,string"` @@ -186,7 +186,7 @@ type WalletTransfer struct { Message string `json:"message"` } -// Withdrawal holds withdrawel status information +// Withdrawal holds withdrawal status information type Withdrawal struct { Status string `json:"status"` Message string `json:"message"` @@ -300,7 +300,7 @@ type Offer struct { ExecutedAmount float64 `json:"executed_amount,string"` } -// MarginFunds holds active funding information used in a margin positon +// MarginFunds holds active funding information used in a margin position type MarginFunds struct { ID int64 `json:"id"` PositionID int64 `json:"position_id"` diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index a2033f31..cb432330 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -2,6 +2,7 @@ package bitfinex import ( "log" + "net/url" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" @@ -78,7 +79,10 @@ func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderb // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Bitfinex) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) { var orderBook orderbook.Base - orderbookNew, err := b.GetOrderbook(p.Pair().String(), nil) + urlVals := url.Values{} + urlVals.Set("limit_bids", "100") + urlVals.Set("limit_asks", "100") + orderbookNew, err := b.GetOrderbook(p.Pair().String(), urlVals) if err != nil { return orderBook, err } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index fa385413..e391ec53 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -350,7 +350,7 @@ func (b *Bitstamp) PlaceOrder(currencyPair string, price float64, amount float64 b.SendAuthenticatedHTTPRequest(path, true, req, &response) } -// GetWithdrawalRequests returns withdrawl requests for the account +// GetWithdrawalRequests returns withdrawal requests for the account // timedelta - positive integer with max value 50000000 which returns requests // from number of seconds ago to now. func (b *Bitstamp) GetWithdrawalRequests(timedelta int64) ([]WithdrawalRequests, error) { diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index ce298211..3eb2ad3d 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -42,7 +42,7 @@ func TestSetup(t *testing.T) { b := Bitstamp{} b.Name = "Bitstamp" cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") bConfig, err := cfg.GetExchangeConfig("Bitstamp") if err != nil { t.Error("Test Failed - Bitstamp Setup() init error") diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 99e5e209..f16fc2c4 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -27,7 +27,7 @@ func TestSetup(t *testing.T) { b := Bittrex{} b.Name = "Bittrex" cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") bConfig, err := cfg.GetExchangeConfig("Bittrex") if err != nil { t.Error("Test Failed - Bittrex Setup() init error") diff --git a/exchanges/btcc/btcc_test.go b/exchanges/btcc/btcc_test.go index 56826455..ba52fdff 100644 --- a/exchanges/btcc/btcc_test.go +++ b/exchanges/btcc/btcc_test.go @@ -23,7 +23,7 @@ func TestSetup(t *testing.T) { t.Parallel() b.Name = "BTCC" cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") bConfig, err := cfg.GetExchangeConfig("BTCC") if err != nil { t.Error("Test Failed - BTCC Setup() init error") diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 1f6bc8e8..304852ce 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -60,7 +60,7 @@ func (b *BTCMarkets) SetDefaults() { b.AssetTypes = []string{ticker.Spot} } -// Setup takes in an exchange configuration and sets all paramaters +// Setup takes in an exchange configuration and sets all parameters func (b *BTCMarkets) Setup(exch config.ExchangeConfig) { if !exch.Enabled { b.SetEnabled(false) diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index bc0e9776..7740f253 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -25,7 +25,7 @@ func TestSetup(t *testing.T) { b := BTCMarkets{} b.Name = "BTC Markets" cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") bConfig, err := cfg.GetExchangeConfig("BTC Markets") if err != nil { t.Error("Test Failed - BTC Markets Setup() init error") diff --git a/exchanges/exchange.go b/exchanges/exchange.go index f66e382b..95a706ee 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -15,7 +15,7 @@ import ( const ( warningBase64DecryptSecretKeyFailed = "WARNING -- Exchange %s unable to base64 decode secret key.. Disabling Authenticated API support." - // WarningAuthenticatedRequestWithoutCredentialsSet error message for authenticated request without credentails set + // WarningAuthenticatedRequestWithoutCredentialsSet error message for authenticated request without credentials set WarningAuthenticatedRequestWithoutCredentialsSet = "WARNING -- Exchange %s authenticated HTTP request called but not supported due to unset/default API keys." // ErrExchangeNotFound is a constant for an error message ErrExchangeNotFound = "Exchange not found in dataset." diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index 1a553bfb..39c97f72 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -22,7 +22,7 @@ func TestSetAssetTypes(t *testing.T) { err = b.SetAssetTypes() if err == nil { - t.Fatal("Test failed. TestSetAssetTypes returned nil error for a non-existant exchange") + t.Fatal("Test failed. TestSetAssetTypes returned nil error for a non-existent exchange") } b.Name = "ANX" @@ -77,9 +77,9 @@ func TestGetExchangeAssetTypes(t *testing.T) { t.Fatal("Test failed. Bitfinex does not contain default asset type 'SPOT'") } - _, err = GetExchangeAssetTypes("non-existant-exchange") + _, err = GetExchangeAssetTypes("non-existent-exchange") if err == nil { - t.Fatal("Test failed. Got asset types for non-existant exchange") + t.Fatal("Test failed. Got asset types for non-existent exchange") } } @@ -96,7 +96,7 @@ func TestSetCurrencyPairFormat(t *testing.T) { err = b.SetCurrencyPairFormat() if err == nil { - t.Fatal("Test failed. TestSetCurrencyPairFormat returned nil error for a non-existant exchange") + t.Fatal("Test failed. TestSetCurrencyPairFormat returned nil error for a non-existent exchange") } b.Name = "ANX" @@ -349,9 +349,9 @@ func TestGetAndFormatExchangeCurrencies(t *testing.T) { actual, expected) } - _, err = GetAndFormatExchangeCurrencies("non-existant", pairs) + _, err = GetAndFormatExchangeCurrencies("non-existent", pairs) if err == nil { - t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies returned nil error on non-existant exchange") + t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies returned nil error on non-existent exchange") } } diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index 0749ade9..9f9cf279 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -16,6 +16,7 @@ import ( const ( gdaxAPIURL = "https://api.gdax.com/" + gdaxSandboxAPIURL = "https://public.sandbox.gdax.com" gdaxAPIVersion = "0" gdaxProducts = "products" gdaxOrderbook = "book" @@ -69,9 +70,10 @@ func (g *GDAX) SetDefaults() { g.ConfigCurrencyPairFormat.Delimiter = "" g.ConfigCurrencyPairFormat.Uppercase = true g.AssetTypes = []string{ticker.Spot} + g.APIUrl = gdaxAPIURL } -// Setup initialises the exchange paramaters with the current configuration +// Setup initialises the exchange parameters with the current configuration func (g *GDAX) Setup(exch config.ExchangeConfig) { if !exch.Enabled { g.SetEnabled(false) @@ -85,6 +87,9 @@ func (g *GDAX) Setup(exch config.ExchangeConfig) { g.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") g.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") g.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + if exch.UseSandbox { + g.APIUrl = gdaxSandboxAPIURL + } err := g.SetCurrencyPairFormat() if err != nil { log.Fatal(err) @@ -110,17 +115,17 @@ func (g *GDAX) GetProducts() ([]Product, error) { products := []Product{} return products, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, g.Verbose, &products) + common.SendHTTPGetRequest(g.APIUrl+gdaxProducts, true, g.Verbose, &products) } // GetOrderbook returns orderbook by currency pair and level func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { orderbook := OrderbookResponse{} - path := fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook) + path := fmt.Sprintf("%s/%s/%s", g.APIUrl+gdaxProducts, symbol, gdaxOrderbook) if level > 0 { levelStr := strconv.Itoa(level) - path = fmt.Sprintf("%s/%s/%s?level=%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook, levelStr) + path = fmt.Sprintf("%s/%s/%s?level=%s", g.APIUrl+gdaxProducts, symbol, gdaxOrderbook, levelStr) } if err := common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook); err != nil { @@ -190,7 +195,7 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { func (g *GDAX) GetTicker(currencyPair string) (Ticker, error) { ticker := Ticker{} path := fmt.Sprintf( - "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTicker) + "%s/%s/%s", g.APIUrl+gdaxProducts, currencyPair, gdaxTicker) log.Println(path) return ticker, common.SendHTTPGetRequest(path, true, g.Verbose, &ticker) @@ -201,7 +206,7 @@ func (g *GDAX) GetTicker(currencyPair string) (Ticker, error) { func (g *GDAX) GetTrades(currencyPair string) ([]Trade, error) { trades := []Trade{} path := fmt.Sprintf( - "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTrades) + "%s/%s/%s", g.APIUrl+gdaxProducts, currencyPair, gdaxTrades) return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } @@ -226,7 +231,7 @@ func (g *GDAX) GetHistoricRates(currencyPair string, start, end, granularity int } path := common.EncodeURLValues( - fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxHistory), + fmt.Sprintf("%s/%s/%s", g.APIUrl+gdaxProducts, currencyPair, gdaxHistory), values) if err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp); err != nil { @@ -253,7 +258,7 @@ func (g *GDAX) GetHistoricRates(currencyPair string, start, end, granularity int func (g *GDAX) GetStats(currencyPair string) (Stats, error) { stats := Stats{} path := fmt.Sprintf( - "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxStats) + "%s/%s/%s", g.APIUrl+gdaxProducts, currencyPair, gdaxStats) return stats, common.SendHTTPGetRequest(path, true, g.Verbose, &stats) } @@ -264,7 +269,7 @@ func (g *GDAX) GetCurrencies() ([]Currency, error) { currencies := []Currency{} return currencies, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, g.Verbose, ¤cies) + common.SendHTTPGetRequest(g.APIUrl+gdaxCurrencies, true, g.Verbose, ¤cies) } // GetServerTime returns the API server time @@ -272,7 +277,7 @@ func (g *GDAX) GetServerTime() (ServerTime, error) { serverTime := ServerTime{} return serverTime, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, g.Verbose, &serverTime) + common.SendHTTPGetRequest(g.APIUrl+gdaxTime, true, g.Verbose, &serverTime) } // GetAccounts returns a list of trading accounts associated with the APIKEYS @@ -487,7 +492,7 @@ func (g *GDAX) GetOrders(status []string, currencyPair string) ([]GeneralizedOrd params.Set("product_id", currencyPair) } - path := common.EncodeURLValues(gdaxAPIURL+gdaxOrders, params) + path := common.EncodeURLValues(g.APIUrl+gdaxOrders, params) path = common.GetURIPath(path) return resp, @@ -514,10 +519,10 @@ func (g *GDAX) GetFills(orderID, currencyPair string) ([]FillResponse, error) { params.Set("product_id", currencyPair) } if len(params.Get("order_id")) == 0 && len(params.Get("product_id")) == 0 { - return resp, errors.New("no paramaters set") + return resp, errors.New("no parameters set") } - path := common.EncodeURLValues(gdaxAPIURL+gdaxFills, params) + path := common.EncodeURLValues(g.APIUrl+gdaxFills, params) uri := common.GetURIPath(path) return resp, @@ -533,7 +538,7 @@ func (g *GDAX) GetFundingRecords(status string) ([]Funding, error) { params := url.Values{} params.Set("status", status) - path := common.EncodeURLValues(gdaxAPIURL+gdaxFunding, params) + path := common.EncodeURLValues(g.APIUrl+gdaxFunding, params) uri := common.GetURIPath(path) return resp, @@ -708,7 +713,7 @@ func (g *GDAX) GetCoinbaseAccounts() ([]CoinbaseAccounts, error) { // E.g. BTC-USD. *Required* if type is fills // accountID - ID of the account to generate an account report for. *Required* // if type is account -// format - pdf or csv (defualt is pdf) +// format - pdf or csv (default is pdf) // email - [optional] Email address to send the report to func (g *GDAX) GetReport(reportType, startDate, endDate, currencyPair, accountID, format, email string) (Report, error) { resp := Report{} @@ -782,7 +787,7 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri headers["CB-ACCESS-PASSPHRASE"] = g.ClientID headers["Content-Type"] = "application/json" - resp, err := common.SendHTTPRequest(method, gdaxAPIURL+path, headers, bytes.NewBuffer(payload)) + resp, err := common.SendHTTPRequest(method, g.APIUrl+path, headers, bytes.NewBuffer(payload)) if err != nil { return err } diff --git a/exchanges/gdax/gdax_test.go b/exchanges/gdax/gdax_test.go index fd2ae99a..e4ec5d58 100644 --- a/exchanges/gdax/gdax_test.go +++ b/exchanges/gdax/gdax_test.go @@ -21,7 +21,7 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") gdxConfig, err := cfg.GetExchangeConfig("Bitfinex") if err != nil { t.Error("Test Failed - GDAX Setup() init error") diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 33c54dc6..eefd5320 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -111,7 +111,7 @@ func (g *Gemini) SetDefaults() { g.AssetTypes = []string{ticker.Spot} } -// Setup sets exchange configuration paramaters +// Setup sets exchange configuration parameters func (g *Gemini) Setup(exch config.ExchangeConfig) { if !exch.Enabled { g.SetEnabled(false) @@ -125,6 +125,9 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { g.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") g.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") g.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + if exch.UseSandbox { + g.APIUrl = geminiSandboxAPIURL + } err := g.SetCurrencyPairFormat() if err != nil { log.Fatal(err) @@ -139,7 +142,7 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) { // GetSymbols returns all available symbols for trading func (g *Gemini) GetSymbols() ([]string, error) { symbols := []string{} - path := fmt.Sprintf("%s/v%s/%s", geminiAPIURL, geminiAPIVersion, geminiSymbols) + path := fmt.Sprintf("%s/v%s/%s", g.APIUrl, geminiAPIVersion, geminiSymbols) return symbols, common.SendHTTPGetRequest(path, true, g.Verbose, &symbols) } @@ -156,7 +159,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { ticker := Ticker{} resp := TickerResponse{} - path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTicker, currencyPair) + path := fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiTicker, currencyPair) err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp) if err != nil { @@ -182,7 +185,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { // params - limit_bids or limit_asks [OPTIONAL] default 50, 0 returns all Values // Type is an integer ie "params.Set("limit_asks", 30)" func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiOrderbook, currencyPair), params) + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiOrderbook, currencyPair), params) orderbook := Orderbook{} return orderbook, common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook) @@ -198,15 +201,15 @@ func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook // include_breaks boolean Optional. Whether to display broken trades. False by // default. Can be '1' or 'true' to activate func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTrades, currencyPair), params) + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiTrades, currencyPair), params) trades := []Trade{} return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } -// GetAuction returns auction infomation +// GetAuction returns auction information func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { - path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair) + path := fmt.Sprintf("%s/v%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiAuction, currencyPair) auction := Auction{} return auction, common.SendHTTPGetRequest(path, true, g.Verbose, &auction) @@ -224,7 +227,7 @@ func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { // include_indicative - [bool] Whether to include publication of // indicative prices and quantities. func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]AuctionHistory, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) + path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", g.APIUrl, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) auctionHist := []AuctionHistory{} return auctionHist, common.SendHTTPGetRequest(path, true, g.Verbose, &auctionHist) @@ -348,8 +351,8 @@ func (g *Gemini) GetDepositAddress(depositAddlabel, currency string) (DepositAdd } // WithdrawCrypto withdraws crypto currency to a whitelisted address -func (g *Gemini) WithdrawCrypto(address, currency string, amount float64) (WithdrawelAddress, error) { - response := WithdrawelAddress{} +func (g *Gemini) WithdrawCrypto(address, currency string, amount float64) (WithdrawalAddress, error) { + response := WithdrawalAddress{} request := make(map[string]interface{}) request["address"] = address request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index 1f139f3a..5b583c1e 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -47,7 +47,7 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") geminiConfig, err := cfg.GetExchangeConfig("Gemini") if err != nil { t.Error("Test Failed - Gemini Setup() init error") diff --git a/exchanges/gemini/gemini_types.go b/exchanges/gemini/gemini_types.go index a6cd4aed..dfa5c64b 100644 --- a/exchanges/gemini/gemini_types.go +++ b/exchanges/gemini/gemini_types.go @@ -150,8 +150,8 @@ type DepositAddress struct { Label string `json:"label"` } -// WithdrawelAddress holds withdrawel information -type WithdrawelAddress struct { +// WithdrawalAddress holds withdrawal information +type WithdrawalAddress struct { Address string `json:"address"` Amount float64 `json:"amount"` TXHash string `json:"txHash"` diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 9d31a34a..d1b52cf4 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -15,14 +15,16 @@ import ( ) const ( - HUOBI_API_URL = "https://api.huobi.com/apiv2.php" - HUOBI_API_VERSION = "2" + huobiAPIURL = "https://api.huobi.com/apiv2.php" + huobiAPIVersion = "2" ) +// HUOBI is the overarching type across this package type HUOBI struct { exchange.Base } +// SetDefaults sets default values for the exchange func (h *HUOBI) SetDefaults() { h.Name = "Huobi" h.Enabled = false @@ -37,6 +39,7 @@ func (h *HUOBI) SetDefaults() { h.AssetTypes = []string{ticker.Spot} } +// Setup sets user configuration func (h *HUOBI) Setup(exch config.ExchangeConfig) { if !exch.Enabled { h.SetEnabled(false) @@ -61,31 +64,28 @@ func (h *HUOBI) Setup(exch config.ExchangeConfig) { } } +// GetFee returns Huobi fee func (h *HUOBI) GetFee() float64 { return h.Fee } -func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { - resp := HuobiTickerResponse{} +// GetTicker returns the Huobi ticker +func (h *HUOBI) GetTicker(symbol string) (Ticker, error) { + resp := TickerResponse{} path := fmt.Sprintf("https://api.huobi.com/staticmarket/ticker_%s_json.js", symbol) - err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) - if err != nil { - return HuobiTicker{}, err - } - return resp.Ticker, nil + return resp.Ticker, common.SendHTTPGetRequest(path, true, h.Verbose, &resp) } -func (h *HUOBI) GetOrderBook(symbol string) (HuobiOrderbook, error) { +// GetOrderBook returns the Huobi current orderbook for a currency pair +func (h *HUOBI) GetOrderBook(symbol string) (Orderbook, error) { path := fmt.Sprintf("https://api.huobi.com/staticmarket/depth_%s_json.js", symbol) - resp := HuobiOrderbook{} - err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) - if err != nil { - return resp, err - } - return resp, nil + resp := Orderbook{} + + return resp, common.SendHTTPGetRequest(path, true, h.Verbose, &resp) } +// GetAccountInfo returns account information func (h *HUOBI) GetAccountInfo() { err := h.SendAuthenticatedRequest("get_account_info", url.Values{}) @@ -94,6 +94,7 @@ func (h *HUOBI) GetAccountInfo() { } } +// GetOrders returns full list of orders func (h *HUOBI) GetOrders(coinType int) { values := url.Values{} values.Set("coin_type", strconv.Itoa(coinType)) @@ -104,6 +105,7 @@ func (h *HUOBI) GetOrders(coinType int) { } } +// GetOrderInfo returns specific info on an order func (h *HUOBI) GetOrderInfo(orderID, coinType int) { values := url.Values{} values.Set("id", strconv.Itoa(orderID)) @@ -115,6 +117,7 @@ func (h *HUOBI) GetOrderInfo(orderID, coinType int) { } } +// Trade opens a trade on the Huobi exchange func (h *HUOBI) Trade(orderType string, coinType int, price, amount float64) { values := url.Values{} if orderType != "buy" { @@ -130,6 +133,7 @@ func (h *HUOBI) Trade(orderType string, coinType int, price, amount float64) { } } +// MarketTrade initiates a market trade func (h *HUOBI) MarketTrade(orderType string, coinType int, price, amount float64) { values := url.Values{} if orderType != "buy_market" { @@ -145,6 +149,7 @@ func (h *HUOBI) MarketTrade(orderType string, coinType int, price, amount float6 } } +// CancelOrder cancels order by order ID func (h *HUOBI) CancelOrder(orderID, coinType int) { values := url.Values{} values.Set("coin_type", strconv.Itoa(coinType)) @@ -156,6 +161,7 @@ func (h *HUOBI) CancelOrder(orderID, coinType int) { } } +// ModifyOrder modifies an order func (h *HUOBI) ModifyOrder(orderType string, coinType, orderID int, price, amount float64) { values := url.Values{} values.Set("coin_type", strconv.Itoa(coinType)) @@ -169,6 +175,7 @@ func (h *HUOBI) ModifyOrder(orderType string, coinType, orderID int, price, amou } } +// GetNewDealOrders creates a new deal func (h *HUOBI) GetNewDealOrders(coinType int) { values := url.Values{} values.Set("coin_type", strconv.Itoa(coinType)) @@ -179,6 +186,7 @@ func (h *HUOBI) GetNewDealOrders(coinType int) { } } +// GetOrderIDByTradeID returns ORDERID by Trade ID func (h *HUOBI) GetOrderIDByTradeID(coinType, orderID int) { values := url.Values{} values.Set("coin_type", strconv.Itoa(coinType)) @@ -190,6 +198,7 @@ func (h *HUOBI) GetOrderIDByTradeID(coinType, orderID int) { } } +// SendAuthenticatedRequest sends an autheticated HTTP request to Huobi func (h *HUOBI) SendAuthenticatedRequest(method string, v url.Values) error { if !h.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name) @@ -203,13 +212,13 @@ func (h *HUOBI) SendAuthenticatedRequest(method string, v url.Values) error { encoded := v.Encode() if h.Verbose { - log.Printf("Sending POST request to %s with params %s\n", HUOBI_API_URL, encoded) + log.Printf("Sending POST request to %s with params %s\n", huobiAPIURL, encoded) } headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" - resp, err := common.SendHTTPRequest("POST", HUOBI_API_URL, headers, strings.NewReader(encoded)) + resp, err := common.SendHTTPRequest("POST", huobiAPIURL, headers, strings.NewReader(encoded)) if err != nil { return err diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go new file mode 100644 index 00000000..4f2d3c93 --- /dev/null +++ b/exchanges/huobi/huobi_test.go @@ -0,0 +1,50 @@ +package huobi + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var h HUOBI + +// Please supply your own APIKEYS here for due diligence testing + +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + h.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + huobiConfig, err := cfg.GetExchangeConfig("Huobi") + if err != nil { + t.Error("Test Failed - Huobi Setup() init error") + } + + huobiConfig.AuthenticatedAPISupport = true + huobiConfig.APIKey = apiKey + huobiConfig.APISecret = apiSecret + + h.Setup(huobiConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if h.GetFee() != 0 { + t.Error("test failed - Huobi GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := h.GetTicker("btcusd") + if err == nil { + t.Error("test failed - Huobi GetTicker() error", err) + } +} diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index 144d7d63..5cd8c7f6 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -1,6 +1,7 @@ package huobi -type HuobiTicker struct { +// Ticker holds ticker information +type Ticker struct { High float64 Low float64 Last float64 @@ -9,12 +10,14 @@ type HuobiTicker struct { Sell float64 } -type HuobiTickerResponse struct { +// TickerResponse holds the initial response type +type TickerResponse struct { Time string - Ticker HuobiTicker + Ticker Ticker } -type HuobiOrderbook struct { +// Orderbook holds the order book information +type Orderbook struct { ID float64 TS float64 Bids [][]float64 `json:"bids"` diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 49fc986a..7efc3d11 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -51,7 +51,7 @@ func (i *ItBit) SetDefaults() { i.AssetTypes = []string{ticker.Spot} } -// Setup sets the exchange paramaters from exchange config +// Setup sets the exchange parameters from exchange config func (i *ItBit) Setup(exch config.ExchangeConfig) { if !exch.Enabled { i.SetEnabled(false) diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go index 88c51689..2ae18ab7 100644 --- a/exchanges/itbit/itbit_test.go +++ b/exchanges/itbit/itbit_test.go @@ -22,7 +22,7 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") itbitConfig, err := cfg.GetExchangeConfig("ITBIT") if err != nil { t.Error("Test Failed - Gemini Setup() init error") @@ -124,6 +124,7 @@ func TestGetOrder(t *testing.T) { } func TestCancelOrder(t *testing.T) { + t.Skip() err := i.CancelOrder("1337", "1337order") if err == nil { t.Error("Test Failed - CancelOrder() error", err) diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 418881ef..6550535b 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -1,7 +1,6 @@ package kraken import ( - "errors" "fmt" "log" "net/url" @@ -16,37 +15,39 @@ import ( ) const ( - KRAKEN_API_URL = "https://api.kraken.com" - KRAKEN_API_VERSION = "0" - KRAKEN_SERVER_TIME = "Time" - KRAKEN_ASSETS = "Assets" - KRAKEN_ASSET_PAIRS = "AssetPairs" - KRAKEN_TICKER = "Ticker" - KRAKEN_OHLC = "OHLC" - KRAKEN_DEPTH = "Depth" - KRAKEN_TRADES = "Trades" - KRAKEN_SPREAD = "Spread" - KRAKEN_BALANCE = "Balance" - KRAKEN_TRADE_BALANCE = "TradeBalance" - KRAKEN_OPEN_ORDERS = "OpenOrders" - KRAKEN_CLOSED_ORDERS = "ClosedOrders" - KRAKEN_QUERY_ORDERS = "QueryOrders" - KRAKEN_TRADES_HISTORY = "TradesHistory" - KRAKEN_QUERY_TRADES = "QueryTrades" - KRAKEN_OPEN_POSITIONS = "OpenPositions" - KRAKEN_LEDGERS = "Ledgers" - KRAKEN_QUERY_LEDGERS = "QueryLedgers" - KRAKEN_TRADE_VOLUME = "TradeVolume" - KRAKEN_ORDER_CANCEL = "CancelOrder" - KRAKEN_ORDER_PLACE = "AddOrder" + krakenAPIURL = "https://api.kraken.com" + krakenAPIVersion = "0" + krakenServerTime = "Time" + krakenAssets = "Assets" + krakenAssetPairs = "AssetPairs" + krakenTicker = "Ticker" + krakenOHLC = "OHLC" + krakenDepth = "Depth" + krakenTrades = "Trades" + krakenSpread = "Spread" + krakenBalance = "Balance" + krakenTradeBalance = "TradeBalance" + krakenOpenOrders = "OpenOrders" + krakenClosedOrders = "ClosedOrders" + krakenQueryOrders = "QueryOrders" + krakenTradeHistory = "TradesHistory" + krakenQueryTrades = "QueryTrades" + krakenOpenPositions = "OpenPositions" + krakenLedgers = "Ledgers" + krakenQueryLedgers = "QueryLedgers" + krakenTradeVolume = "TradeVolume" + krakenOrderCancel = "CancelOrder" + krakenOrderPlace = "AddOrder" ) +// Kraken is the overarching type across the alphapoint package type Kraken struct { exchange.Base CryptoFee, FiatFee float64 - Ticker map[string]KrakenTicker + Ticker map[string]Ticker } +// SetDefaults sets current default settings func (k *Kraken) SetDefaults() { k.Name = "Kraken" k.Enabled = false @@ -55,7 +56,7 @@ func (k *Kraken) SetDefaults() { k.Verbose = false k.Websocket = false k.RESTPollingDelay = 10 - k.Ticker = make(map[string]KrakenTicker) + k.Ticker = make(map[string]Ticker) k.RequestCurrencyPairFormat.Delimiter = "" k.RequestCurrencyPairFormat.Uppercase = true k.RequestCurrencyPairFormat.Separator = "," @@ -64,6 +65,7 @@ func (k *Kraken) SetDefaults() { k.AssetTypes = []string{ticker.Spot} } +// Setup sets current exchange configuration func (k *Kraken) Setup(exch config.ExchangeConfig) { if !exch.Enabled { k.SetEnabled(false) @@ -88,81 +90,75 @@ func (k *Kraken) Setup(exch config.ExchangeConfig) { } } +// GetFee returns current fee for either crypto or fiat func (k *Kraken) GetFee(cryptoTrade bool) float64 { if cryptoTrade { return k.CryptoFee - } else { - return k.FiatFee } + return k.FiatFee } -func (k *Kraken) GetServerTime() error { - var result interface{} - path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SERVER_TIME) +// GetServerTime returns current server time +func (k *Kraken) GetServerTime(unixTime bool) (interface{}, error) { + var result GeneralResponse + path := fmt.Sprintf("%s/%s/public/%s", krakenAPIURL, krakenAPIVersion, krakenServerTime) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) - if err != nil { - return err + return nil, fmt.Errorf("getServerTime() error %s", err) } - log.Println(result) - return nil -} - -func (k *Kraken) GetAssets() error { - var result interface{} - path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSETS) - err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) - - if err != nil { - return err + if unixTime { + return result.Result["unixtime"], nil } - - log.Println(result) - return nil + return result.Result["rfc1123"], nil } -func (k *Kraken) GetAssetPairs() (map[string]KrakenAssetPairs, error) { +// GetAssets returns a full asset list +func (k *Kraken) GetAssets() (interface{}, error) { + var result GeneralResponse + path := fmt.Sprintf("%s/%s/public/%s", krakenAPIURL, krakenAPIVersion, krakenAssets) + + return result.Result, common.SendHTTPGetRequest(path, true, k.Verbose, &result) +} + +// GetAssetPairs returns a full asset pair list +func (k *Kraken) GetAssetPairs() (map[string]AssetPairs, error) { type Response struct { - Result map[string]KrakenAssetPairs `json:"result"` - Error []interface{} `json:"error"` + Result map[string]AssetPairs `json:"result"` + Error []interface{} `json:"error"` } response := Response{} - path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSET_PAIRS) - err := common.SendHTTPGetRequest(path, true, k.Verbose, &response) + path := fmt.Sprintf("%s/%s/public/%s", krakenAPIURL, krakenAPIVersion, krakenAssetPairs) - if err != nil { - return nil, err - } - - return response.Result, nil + return response.Result, common.SendHTTPGetRequest(path, true, k.Verbose, &response) } -func (k *Kraken) GetTicker(symbol string) error { +// GetTicker returns ticker information from kraken +func (k *Kraken) GetTicker(symbol string) (Ticker, error) { + ticker := Ticker{} values := url.Values{} values.Set("pair", symbol) type Response struct { - Error []interface{} `json:"error"` - Data map[string]KrakenTickerResponse `json:"result"` + Error []interface{} `json:"error"` + Data map[string]TickerResponse `json:"result"` } resp := Response{} - path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TICKER, values.Encode()) - err := common.SendHTTPGetRequest(path, true, k.Verbose, &resp) + path := fmt.Sprintf("%s/%s/public/%s?%s", krakenAPIURL, krakenAPIVersion, krakenTicker, values.Encode()) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &resp) if err != nil { - return err + return ticker, err } if len(resp.Error) > 0 { - return errors.New(fmt.Sprintf("Kraken error: %s", resp.Error)) + return ticker, fmt.Errorf("Kraken error: %s", resp.Error) } - for x, y := range resp.Data { - x = x[1:4] + x[5:] - ticker := KrakenTicker{} + for _, y := range resp.Data { ticker.Ask, _ = strconv.ParseFloat(y.Ask[0], 64) ticker.Bid, _ = strconv.ParseFloat(y.Bid[0], 64) ticker.Last, _ = strconv.ParseFloat(y.Last[0], 64) @@ -172,25 +168,59 @@ func (k *Kraken) GetTicker(symbol string) error { ticker.Low, _ = strconv.ParseFloat(y.Low[1], 64) ticker.High, _ = strconv.ParseFloat(y.High[1], 64) ticker.Open, _ = strconv.ParseFloat(y.Open, 64) - k.Ticker[x] = ticker } - return nil + return ticker, nil } -func (k *Kraken) GetOHLC(symbol string) error { +// GetOHLC returns an array of open high low close values of a currency pair +func (k *Kraken) GetOHLC(symbol string) ([]OpenHighLowClose, error) { values := url.Values{} values.Set("pair", symbol) - var result interface{} - path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_OHLC, values.Encode()) - err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) - - if err != nil { - return err + type Response struct { + Error []interface{} `json:"error"` + Data map[string]interface{} `json:"result"` } - log.Println(result) - return nil + var OHLC []OpenHighLowClose + var result Response + + path := fmt.Sprintf("%s/%s/public/%s?%s", krakenAPIURL, krakenAPIVersion, krakenOHLC, values.Encode()) + + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) + if err != nil { + return OHLC, err + } + + if len(result.Error) != 0 { + return OHLC, fmt.Errorf("GetOHLC error: %s", result.Error) + } + + for _, y := range result.Data[symbol].([]interface{}) { + o := OpenHighLowClose{} + for i, x := range y.([]interface{}) { + switch i { + case 0: + o.Time = x.(float64) + case 1: + o.Open, _ = strconv.ParseFloat(x.(string), 64) + case 2: + o.High, _ = strconv.ParseFloat(x.(string), 64) + case 3: + o.Low, _ = strconv.ParseFloat(x.(string), 64) + case 4: + o.Close, _ = strconv.ParseFloat(x.(string), 64) + case 5: + o.Vwap, _ = strconv.ParseFloat(x.(string), 64) + case 6: + o.Volume, _ = strconv.ParseFloat(x.(string), 64) + case 7: + o.Count = x.(float64) + } + } + OHLC = append(OHLC, o) + } + return OHLC, nil } // GetDepth returns the orderbook for a particular currency @@ -199,12 +229,13 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { values.Set("pair", symbol) var result interface{} - var ob Orderbook - path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_DEPTH, values.Encode()) - err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) + var orderBook Orderbook + path := fmt.Sprintf("%s/%s/public/%s?%s", krakenAPIURL, krakenAPIVersion, krakenDepth, values.Encode()) + + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { - return ob, err + return orderBook, err } data := result.(map[string]interface{}) @@ -223,14 +254,14 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { for x := range data { entry := data[x].([]interface{}) - price, err := strconv.ParseFloat(entry[0].(string), 64) - if err != nil { - return nil, err + price, priceErr := strconv.ParseFloat(entry[0].(string), 64) + if priceErr != nil { + return nil, priceErr } - amount, err := strconv.ParseFloat(entry[1].(string), 64) - if err != nil { - return nil, err + amount, amountErr := strconv.ParseFloat(entry[1].(string), 64) + if amountErr != nil { + return nil, amountErr } result = append(result, OrderbookBase{Price: price, Amount: amount}) @@ -238,82 +269,116 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { return result, nil } - ob.Bids, err = processOrderbook(bidsData) + orderBook.Bids, err = processOrderbook(bidsData) if err != nil { - return ob, err + return orderBook, err } - ob.Asks, err = processOrderbook(asksData) + orderBook.Asks, err = processOrderbook(asksData) if err != nil { - return ob, err + return orderBook, err } - return ob, nil + return orderBook, nil } -func (k *Kraken) GetTrades(symbol string) error { +// GetTrades returns current trades on Kraken +func (k *Kraken) GetTrades(symbol string) ([]RecentTrades, error) { values := url.Values{} values.Set("pair", symbol) + var recentTrades []RecentTrades var result interface{} - path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TRADES, values.Encode()) - err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) + path := fmt.Sprintf("%s/%s/public/%s?%s", krakenAPIURL, krakenAPIVersion, krakenTrades, values.Encode()) + + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { - return err + return recentTrades, err } - log.Println(result) - return nil + data := result.(map[string]interface{}) + tradeInfo := data["result"].(map[string]interface{}) + + for _, x := range tradeInfo[symbol].([]interface{}) { + r := RecentTrades{} + for i, y := range x.([]interface{}) { + switch i { + case 0: + r.Price, _ = strconv.ParseFloat(y.(string), 64) + case 1: + r.Volume, _ = strconv.ParseFloat(y.(string), 64) + case 2: + r.Time = y.(float64) + case 3: + r.BuyOrSell = y.(string) + case 4: + r.MarketOrLimit = y.(string) + case 5: + r.Miscellaneous = y.(string) + } + } + recentTrades = append(recentTrades, r) + } + return recentTrades, nil } -func (k *Kraken) GetSpread(symbol string) { +// GetSpread returns the full spread on Kraken +func (k *Kraken) GetSpread(symbol string) ([]Spread, error) { values := url.Values{} values.Set("pair", symbol) - var result interface{} - path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SPREAD, values.Encode()) - err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) + var peanutButter []Spread + var response interface{} + path := fmt.Sprintf("%s/%s/public/%s?%s", krakenAPIURL, krakenAPIVersion, krakenSpread, values.Encode()) + + err := common.SendHTTPGetRequest(path, true, k.Verbose, &response) if err != nil { - log.Println(err) - return - } -} - -func (k *Kraken) GetBalance() { - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_BALANCE, url.Values{}) - - if err != nil { - log.Println(err) - return + return peanutButter, err } - log.Println(result) + data := response.(map[string]interface{}) + result := data["result"].(map[string]interface{}) + + for _, x := range result[symbol].([]interface{}) { + s := Spread{} + for i, y := range x.([]interface{}) { + switch i { + case 0: + s.Time = y.(float64) + case 1: + s.Bid, _ = strconv.ParseFloat(y.(string), 64) + case 2: + s.Ask, _ = strconv.ParseFloat(y.(string), 64) + } + } + peanutButter = append(peanutButter, s) + } + return peanutButter, nil } -func (k *Kraken) GetTradeBalance(symbol, asset string) { +// GetBalance returns your balance associated with your keys +func (k *Kraken) GetBalance() (interface{}, error) { + return k.SendAuthenticatedHTTPRequest(krakenBalance, url.Values{}) +} + +// GetTradeBalance returns full information about your trades on Kraken +func (k *Kraken) GetTradeBalance(symbol, asset string) (interface{}, error) { values := url.Values{} if len(symbol) > 0 { values.Set("aclass", symbol) } - if len(asset) > 0 { values.Set("asset", asset) } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_TRADE_BALANCE, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenTradeBalance, values) } -func (k *Kraken) GetOpenOrders(showTrades bool, userref int64) { +// GetOpenOrders returns all current open orders +func (k *Kraken) GetOpenOrders(showTrades bool, userref int64) (interface{}, error) { values := url.Values{} if showTrades { @@ -324,17 +389,11 @@ func (k *Kraken) GetOpenOrders(showTrades bool, userref int64) { values.Set("userref", strconv.FormatInt(userref, 10)) } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_OPEN_ORDERS, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenOpenOrders, values) } -func (k *Kraken) GetClosedOrders(showTrades bool, userref, start, end, offset int64, closetime string) { +// GetClosedOrders returns a list of closed orders +func (k *Kraken) GetClosedOrders(showTrades bool, userref, start, end, offset int64, closetime string) (interface{}, error) { values := url.Values{} if showTrades { @@ -361,17 +420,11 @@ func (k *Kraken) GetClosedOrders(showTrades bool, userref, start, end, offset in values.Set("closetime", closetime) } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_CLOSED_ORDERS, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenClosedOrders, values) } -func (k *Kraken) QueryOrdersInfo(showTrades bool, userref, txid int64) { +// QueryOrdersInfo returns order information +func (k *Kraken) QueryOrdersInfo(showTrades bool, userref, txid int64) (interface{}, error) { values := url.Values{} if showTrades { @@ -386,17 +439,11 @@ func (k *Kraken) QueryOrdersInfo(showTrades bool, userref, txid int64) { values.Set("txid", strconv.FormatInt(userref, 10)) } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_QUERY_ORDERS, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenQueryOrders, values) } -func (k *Kraken) GetTradesHistory(tradeType string, showRelatedTrades bool, start, end, offset int64) { +// GetTradesHistory returns trade history information +func (k *Kraken) GetTradesHistory(tradeType string, showRelatedTrades bool, start, end, offset int64) (interface{}, error) { values := url.Values{} if len(tradeType) > 0 { @@ -419,17 +466,11 @@ func (k *Kraken) GetTradesHistory(tradeType string, showRelatedTrades bool, star values.Set("offset", strconv.FormatInt(offset, 10)) } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_TRADES_HISTORY, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenTradeHistory, values) } -func (k *Kraken) QueryTrades(txid int64, showRelatedTrades bool) { +// QueryTrades returns information on a specific trade +func (k *Kraken) QueryTrades(txid int64, showRelatedTrades bool) (interface{}, error) { values := url.Values{} values.Set("txid", strconv.FormatInt(txid, 10)) @@ -437,17 +478,11 @@ func (k *Kraken) QueryTrades(txid int64, showRelatedTrades bool) { values.Set("trades", "true") } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_QUERY_TRADES, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenQueryTrades, values) } -func (k *Kraken) OpenPositions(txid int64, showPL bool) { +// OpenPositions returns current open positions +func (k *Kraken) OpenPositions(txid int64, showPL bool) (interface{}, error) { values := url.Values{} values.Set("txid", strconv.FormatInt(txid, 10)) @@ -455,17 +490,11 @@ func (k *Kraken) OpenPositions(txid int64, showPL bool) { values.Set("docalcs", "true") } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_OPEN_POSITIONS, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenOpenPositions, values) } -func (k *Kraken) GetLedgers(symbol, asset, ledgerType string, start, end, offset int64) { +// GetLedgers returns current ledgers +func (k *Kraken) GetLedgers(symbol, asset, ledgerType string, start, end, offset int64) (interface{}, error) { values := url.Values{} if len(symbol) > 0 { @@ -492,45 +521,27 @@ func (k *Kraken) GetLedgers(symbol, asset, ledgerType string, start, end, offset values.Set("offset", strconv.FormatInt(offset, 10)) } - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_LEDGERS, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenLedgers, values) } -func (k *Kraken) QueryLedgers(id string) { +// QueryLedgers queries an individual ledger by ID +func (k *Kraken) QueryLedgers(id string) (interface{}, error) { values := url.Values{} values.Set("id", id) - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_QUERY_LEDGERS, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenQueryLedgers, values) } -func (k *Kraken) GetTradeVolume(symbol string) { +// GetTradeVolume returns your trade volume by currency +func (k *Kraken) GetTradeVolume(symbol string) (interface{}, error) { values := url.Values{} values.Set("pair", symbol) - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_TRADE_VOLUME, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenTradeVolume, values) } -func (k *Kraken) AddOrder(symbol, side, orderType string, price, price2, volume, leverage, position float64) { +// AddOrder adds a new order for Kraken exchange +func (k *Kraken) AddOrder(symbol, side, orderType string, price, price2, volume, leverage, position float64) (interface{}, error) { values := url.Values{} values.Set("pairs", symbol) values.Set("type", side) @@ -541,36 +552,24 @@ func (k *Kraken) AddOrder(symbol, side, orderType string, price, price2, volume, values.Set("leverage", strconv.FormatFloat(leverage, 'f', -1, 64)) values.Set("position", strconv.FormatFloat(position, 'f', -1, 64)) - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_ORDER_PLACE, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenOrderPlace, values) } -func (k *Kraken) CancelOrder(orderID int64) { +// CancelOrder cancels order by orderID +func (k *Kraken) CancelOrder(orderID int64) (interface{}, error) { values := url.Values{} values.Set("txid", strconv.FormatInt(orderID, 10)) - result, err := k.SendAuthenticatedHTTPRequest(KRAKEN_ORDER_CANCEL, values) - - if err != nil { - log.Println(err) - return - } - - log.Println(result) + return k.SendAuthenticatedHTTPRequest(krakenOrderCancel, values) } +// SendAuthenticatedHTTPRequest sends an authenticated HTTP request func (k *Kraken) SendAuthenticatedHTTPRequest(method string, values url.Values) (interface{}, error) { if !k.AuthenticatedAPISupport { return nil, fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, k.Name) } - path := fmt.Sprintf("/%s/private/%s", KRAKEN_API_VERSION, method) + path := fmt.Sprintf("/%s/private/%s", krakenAPIVersion, method) if k.Nonce.Get() == 0 { k.Nonce.Set(time.Now().UnixNano()) } else { @@ -578,8 +577,8 @@ func (k *Kraken) SendAuthenticatedHTTPRequest(method string, values url.Values) } values.Set("nonce", k.Nonce.String()) - secret, err := common.Base64Decode(k.APISecret) + secret, err := common.Base64Decode(k.APISecret) if err != nil { return nil, err } @@ -588,22 +587,33 @@ func (k *Kraken) SendAuthenticatedHTTPRequest(method string, values url.Values) signature := common.Base64Encode(common.GetHMAC(common.HashSHA512, append([]byte(path), shasum...), secret)) if k.Verbose { - log.Printf("Sending POST request to %s, path: %s.", KRAKEN_API_URL, path) + log.Printf("Sending POST request to %s, path: %s.", krakenAPIURL, path) } headers := make(map[string]string) headers["API-Key"] = k.APIKey headers["API-Sign"] = signature - resp, err := common.SendHTTPRequest("POST", KRAKEN_API_URL+path, headers, strings.NewReader(values.Encode())) - + rawResp, err := common.SendHTTPRequest("POST", krakenAPIURL+path, headers, strings.NewReader(values.Encode())) if err != nil { return nil, err } if k.Verbose { - log.Printf("Received raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", rawResp) } - return resp, nil + var resp interface{} + + err = common.JSONDecode([]byte(rawResp), &resp) + if err != nil { + return nil, err + } + + data := resp.(map[string]interface{}) + if len(data["error"].([]interface{})) != 0 { + return nil, fmt.Errorf("kraken AuthenticattedHTTPRequest error: %s", data["error"]) + } + + return data["result"].(interface{}), nil } diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go new file mode 100644 index 00000000..000757aa --- /dev/null +++ b/exchanges/kraken/kraken_test.go @@ -0,0 +1,218 @@ +package kraken + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var k Kraken + +// Please add your own APIkeys to do correct due diligence testing. +const ( + apiKey = "" + apiSecret = "" + clientID = "" +) + +func TestSetDefaults(t *testing.T) { + k.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + krakenConfig, err := cfg.GetExchangeConfig("Kraken") + if err != nil { + t.Error("Test Failed - kraken Setup() init error", err) + } + + krakenConfig.AuthenticatedAPISupport = true + krakenConfig.APIKey = apiKey + krakenConfig.APISecret = apiSecret + krakenConfig.ClientID = clientID + + k.Setup(krakenConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if k.GetFee(true) != 0.1 { + t.Error("Test Failed - kraken GetFee() error") + } + if k.GetFee(false) != 0.35 { + t.Error("Test Failed - kraken GetFee() error") + } +} + +func TestGetServerTime(t *testing.T) { + t.Parallel() + _, err := k.GetServerTime(false) + if err != nil { + t.Error("Test Failed - GetServerTime() error", err) + } + _, err = k.GetServerTime(true) + if err != nil { + t.Error("Test Failed - GetServerTime() error", err) + } +} + +func TestGetAssets(t *testing.T) { + t.Parallel() + _, err := k.GetAssets() + if err != nil { + t.Error("Test Failed - GetAssets() error", err) + } +} + +func TestGetAssetPairs(t *testing.T) { + t.Parallel() + _, err := k.GetAssetPairs() + if err != nil { + t.Error("Test Failed - GetAssetPairs() error", err) + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := k.GetTicker("BCHEUR") + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOHLC(t *testing.T) { + t.Parallel() + _, err := k.GetOHLC("BCHEUR") + if err != nil { + t.Error("Test Failed - GetOHLC() error", err) + } +} + +func TestGetDepth(t *testing.T) { + t.Parallel() + _, err := k.GetDepth("BCHEUR") + if err != nil { + t.Error("Test Failed - GetDepth() error", err) + } +} + +func TestGetTrades(t *testing.T) { + t.Parallel() + _, err := k.GetTrades("BCHEUR") + if err != nil { + t.Error("Test Failed - GetTrades() error", err) + } +} + +func TestGetSpread(t *testing.T) { + t.Parallel() + _, err := k.GetSpread("BCHEUR") + if err != nil { + t.Error("Test Failed - GetSpread() error", err) + } +} + +func TestGetBalance(t *testing.T) { + t.Parallel() + _, err := k.GetBalance() + if err == nil { + t.Error("Test Failed - GetBalance() error", err) + } +} + +func TestGetTradeBalance(t *testing.T) { + t.Parallel() + _, err := k.GetTradeBalance("", "") + if err == nil { + t.Error("Test Failed - GetTradeBalance() error", err) + } +} + +func TestGetOpenOrders(t *testing.T) { + t.Parallel() + _, err := k.GetOpenOrders(true, 0) + if err == nil { + t.Error("Test Failed - GetOpenOrders() error", err) + } +} + +func TestGetClosedOrders(t *testing.T) { + t.Parallel() + _, err := k.GetClosedOrders(true, 0, 0, 0, 0, "") + if err == nil { + t.Error("Test Failed - GetClosedOrders() error", err) + } +} + +func TestQueryOrdersInfo(t *testing.T) { + t.Parallel() + _, err := k.QueryOrdersInfo(false, 0, 0) + if err == nil { + t.Error("Test Failed - QueryOrdersInfo() error", err) + } +} + +func TestGetTradesHistory(t *testing.T) { + t.Parallel() + _, err := k.GetTradesHistory("", false, 0, 0, 0) + if err == nil { + t.Error("Test Failed - GetTradesHistory() error", err) + } +} + +func TestQueryTrades(t *testing.T) { + t.Parallel() + _, err := k.QueryTrades(0, false) + if err == nil { + t.Error("Test Failed - QueryTrades() error", err) + } +} + +func TestOpenPositions(t *testing.T) { + t.Parallel() + _, err := k.OpenPositions(0, false) + if err == nil { + t.Error("Test Failed - OpenPositions() error", err) + } +} + +func TestGetLedgers(t *testing.T) { + t.Parallel() + _, err := k.GetLedgers("bla", "bla", "bla", 0, 0, 0) + if err == nil { + t.Error("Test Failed - GetLedgers() error", err) + } +} + +func TestQueryLedgers(t *testing.T) { + t.Parallel() + _, err := k.QueryLedgers("1337") + if err == nil { + t.Error("Test Failed - QueryLedgers() error", err) + } +} + +func TestGetTradeVolume(t *testing.T) { + t.Parallel() + _, err := k.GetTradeVolume("BCHEUR") + if err == nil { + t.Error("Test Failed - GetTradeVolume() error", err) + } +} + +func TestAddOrder(t *testing.T) { + t.Parallel() + _, err := k.AddOrder("bla", "bla", "bla", 0, 0, 0, 0, 0) + if err == nil { + t.Error("Test Failed - AddOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + _, err := k.CancelOrder(1337) + if err == nil { + t.Error("Test Failed - CancelOrder() error", err) + } +} diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index 007009fc..47c438f0 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -1,6 +1,13 @@ package kraken -type KrakenAssetPairs struct { +// GeneralResponse is a generalized response type +type GeneralResponse struct { + Result map[string]interface{} `json:"result"` + Error []interface{} `json:"error"` +} + +// AssetPairs holds asset pair information +type AssetPairs struct { Altname string `json:"altname"` AclassBase string `json:"aclass_base"` Base string `json:"base"` @@ -19,7 +26,8 @@ type KrakenAssetPairs struct { MarginStop int `json:"margin_stop"` } -type KrakenTicker struct { +// Ticker is a standard ticker type +type Ticker struct { Ask float64 Bid float64 Last float64 @@ -31,6 +39,41 @@ type KrakenTicker struct { Open float64 } +// TickerResponse holds ticker information before its put into the Ticker struct +type TickerResponse struct { + Ask []string `json:"a"` + Bid []string `json:"b"` + Last []string `json:"c"` + Volume []string `json:"v"` + VWAP []string `json:"p"` + Trades []int64 `json:"t"` + Low []string `json:"l"` + High []string `json:"h"` + Open string `json:"o"` +} + +// OpenHighLowClose contains ticker event information +type OpenHighLowClose struct { + Time float64 + Open float64 + High float64 + Low float64 + Close float64 + Vwap float64 + Volume float64 + Count float64 +} + +// RecentTrades holds recent trade data +type RecentTrades struct { + Price float64 + Volume float64 + Time float64 + BuyOrSell string + MarketOrLimit string + Miscellaneous interface{} +} + // OrderbookBase stores the orderbook price and amount data type OrderbookBase struct { Price float64 @@ -43,14 +86,9 @@ type Orderbook struct { Asks []OrderbookBase } -type KrakenTickerResponse struct { - Ask []string `json:"a"` - Bid []string `json:"b"` - Last []string `json:"c"` - Volume []string `json:"v"` - VWAP []string `json:"p"` - Trades []int64 `json:"t"` - Low []string `json:"l"` - High []string `json:"h"` - Open string `json:"o"` +// Spread holds the spread between trades +type Spread struct { + Time float64 + Bid float64 + Ask float64 } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 30410649..f94fa218 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -1,8 +1,12 @@ package kraken import ( + "fmt" "log" + "net/url" + "strconv" + "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" @@ -44,7 +48,7 @@ func (k *Kraken) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Pri if err != nil { return tickerPrice, err } - err = k.GetTicker(pairsCollated.String()) + err = k.SetTicker(pairsCollated.String()) if err != nil { return tickerPrice, err } @@ -68,6 +72,44 @@ func (k *Kraken) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Pri return ticker.GetTicker(k.GetName(), p, assetType) } +// SetTicker sets ticker information from kraken +func (k *Kraken) SetTicker(symbol string) error { + values := url.Values{} + values.Set("pair", symbol) + + type Response struct { + Error []interface{} `json:"error"` + Data map[string]TickerResponse `json:"result"` + } + + resp := Response{} + path := fmt.Sprintf("%s/%s/public/%s?%s", krakenAPIURL, krakenAPIVersion, krakenTicker, values.Encode()) + + err := common.SendHTTPGetRequest(path, true, k.Verbose, &resp) + if err != nil { + return err + } + + if len(resp.Error) > 0 { + return fmt.Errorf("Kraken error: %s", resp.Error) + } + + for x, y := range resp.Data { + ticker := Ticker{} + ticker.Ask, _ = strconv.ParseFloat(y.Ask[0], 64) + ticker.Bid, _ = strconv.ParseFloat(y.Bid[0], 64) + ticker.Last, _ = strconv.ParseFloat(y.Last[0], 64) + ticker.Volume, _ = strconv.ParseFloat(y.Volume[1], 64) + ticker.VWAP, _ = strconv.ParseFloat(y.VWAP[1], 64) + ticker.Trades = y.Trades[1] + ticker.Low, _ = strconv.ParseFloat(y.Low[1], 64) + ticker.High, _ = strconv.ParseFloat(y.High[1], 64) + ticker.Open, _ = strconv.ParseFloat(y.Open, 64) + k.Ticker[x] = ticker + } + return nil +} + // GetTickerPrice returns the ticker for a currency pair func (k *Kraken) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) { tickerNew, err := ticker.GetTicker(k.GetName(), p, assetType) diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 639619d2..402ada43 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -15,26 +15,28 @@ import ( ) const ( - LAKEBTC_API_URL = "https://api.lakebtc.com/api_v2" - LAKEBTC_API_VERSION = "2" - LAKEBTC_TICKER = "ticker" - LAKEBTC_ORDERBOOK = "bcorderbook" - LAKEBTC_TRADES = "bctrades" - LAKEBTC_GET_ACCOUNT_INFO = "getAccountInfo" - LAKEBTC_BUY_ORDER = "buyOrder" - LAKEBTC_SELL_ORDER = "sellOrder" - LAKEBTC_OPEN_ORDERS = "openOrders" - LAKEBTC_GET_ORDERS = "getOrders" - LAKEBTC_CANCEL_ORDER = "cancelOrder" - LAKEBTC_GET_TRADES = "getTrades" - LAKEBTC_GET_EXTERNAL_ACCOUNTS = "getExternalAccounts" - LAKEBTC_CREATE_WITHDRAW = "createWithdraw" + lakeBTCAPIURL = "https://api.lakebtc.com/api_v2" + lakeBTCAPIVersion = "2" + lakeBTCTicker = "ticker" + lakeBTCOrderbook = "bcorderbook" + lakeBTCTrades = "bctrades" + lakeBTCGetAccountInfo = "getAccountInfo" + lakeBTCBuyOrder = "buyOrder" + lakeBTCSellOrder = "sellOrder" + lakeBTCOpenOrders = "openOrders" + lakeBTCGetOrders = "getOrders" + lakeBTCCancelOrder = "cancelOrder" + lakeBTCGetTrades = "getTrades" + lakeBTCGetExternalAccounts = "getExternalAccounts" + lakeBTCCreateWithdraw = "createWithdraw" ) +// LakeBTC is the overarching type across the LakeBTC package type LakeBTC struct { exchange.Base } +// SetDefaults sets LakeBTC defaults func (l *LakeBTC) SetDefaults() { l.Name = "LakeBTC" l.Enabled = false @@ -50,6 +52,7 @@ func (l *LakeBTC) SetDefaults() { l.AssetTypes = []string{ticker.Spot} } +// Setup sets exchange configuration profile func (l *LakeBTC) Setup(exch config.ExchangeConfig) { if !exch.Enabled { l.SetEnabled(false) @@ -74,26 +77,28 @@ func (l *LakeBTC) Setup(exch config.ExchangeConfig) { } } +// GetFee returns maker or taker fee func (l *LakeBTC) GetFee(maker bool) float64 { if maker { return l.MakerFee - } else { - return l.TakerFee } + return l.TakerFee } -func (l *LakeBTC) GetTicker() (map[string]LakeBTCTicker, error) { - response := make(map[string]LakeBTCTickerResponse) - path := fmt.Sprintf("%s/%s", LAKEBTC_API_URL, LAKEBTC_TICKER) - err := common.SendHTTPGetRequest(path, true, l.Verbose, &response) - if err != nil { +// GetTicker returns the current ticker from lakeBTC +func (l *LakeBTC) GetTicker() (map[string]Ticker, error) { + response := make(map[string]TickerResponse) + path := fmt.Sprintf("%s/%s", lakeBTCAPIURL, lakeBTCTicker) + + if err := common.SendHTTPGetRequest(path, true, l.Verbose, &response); err != nil { return nil, err } - result := make(map[string]LakeBTCTicker) + + result := make(map[string]Ticker) var addresses []string for k, v := range response { - var ticker LakeBTCTicker + var ticker Ticker key := common.StringToUpper(k) if v.Ask != nil { ticker.Ask, _ = strconv.ParseFloat(v.Ask.(string), 64) @@ -119,18 +124,19 @@ func (l *LakeBTC) GetTicker() (map[string]LakeBTCTicker, error) { return result, nil } -func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { +// GetOrderBook returns the order book from LakeBTC +func (l *LakeBTC) GetOrderBook(currency string) (Orderbook, error) { type Response struct { Bids [][]string `json:"bids"` Asks [][]string `json:"asks"` } - path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_ORDERBOOK, common.StringToLower(currency)) + path := fmt.Sprintf("%s/%s?symbol=%s", lakeBTCAPIURL, lakeBTCOrderbook, common.StringToLower(currency)) resp := Response{} err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { - return LakeBTCOrderbook{}, err + return Orderbook{}, err } - orderbook := LakeBTCOrderbook{} + orderbook := Orderbook{} for _, x := range resp.Bids { price, err := strconv.ParseFloat(x[0], 64) @@ -143,7 +149,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { log.Println(err) continue } - orderbook.Bids = append(orderbook.Bids, LakeBTCOrderbookStructure{price, amount}) + orderbook.Bids = append(orderbook.Bids, OrderbookStructure{price, amount}) } for _, x := range resp.Asks { @@ -157,45 +163,40 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { log.Println(err) continue } - orderbook.Asks = append(orderbook.Asks, LakeBTCOrderbookStructure{price, amount}) + orderbook.Asks = append(orderbook.Asks, OrderbookStructure{price, amount}) } return orderbook, nil } -func (l *LakeBTC) GetTradeHistory(currency string) ([]LakeBTCTradeHistory, error) { - path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_TRADES, common.StringToLower(currency)) - resp := []LakeBTCTradeHistory{} - err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) - if err != nil { - return nil, err - } - return resp, nil +// GetTradeHistory returns the trade history for a given currency pair +func (l *LakeBTC) GetTradeHistory(currency string) ([]TradeHistory, error) { + path := fmt.Sprintf("%s/%s?symbol=%s", lakeBTCAPIURL, lakeBTCTrades, common.StringToLower(currency)) + resp := []TradeHistory{} + + return resp, common.SendHTTPGetRequest(path, true, l.Verbose, &resp) } -func (l *LakeBTC) GetAccountInfo() (LakeBTCAccountInfo, error) { - resp := LakeBTCAccountInfo{} - err := l.SendAuthenticatedHTTPRequest(LAKEBTC_GET_ACCOUNT_INFO, "", &resp) +// GetAccountInfo returns your current account information +func (l *LakeBTC) GetAccountInfo() (AccountInfo, error) { + resp := AccountInfo{} - if err != nil { - return resp, err - } - - return resp, nil + return resp, l.SendAuthenticatedHTTPRequest(lakeBTCGetAccountInfo, "", &resp) } -func (l *LakeBTC) Trade(orderType int, amount, price float64, currency string) (LakeBTCTrade, error) { - resp := LakeBTCTrade{} +// Trade executes an order on the exchange and returns trade inforamtion or an +// error +func (l *LakeBTC) Trade(orderType int, amount, price float64, currency string) (Trade, error) { + resp := Trade{} params := strconv.FormatFloat(price, 'f', -1, 64) + "," + strconv.FormatFloat(amount, 'f', -1, 64) + "," + currency - err := errors.New("") if orderType == 1 { - err = l.SendAuthenticatedHTTPRequest(LAKEBTC_BUY_ORDER, params, &resp) + if err := l.SendAuthenticatedHTTPRequest(lakeBTCBuyOrder, params, &resp); err != nil { + return resp, err + } } else { - err = l.SendAuthenticatedHTTPRequest(LAKEBTC_SELL_ORDER, params, &resp) - } - - if err != nil { - return resp, err + if err := l.SendAuthenticatedHTTPRequest(lakeBTCSellOrder, params, &resp); err != nil { + return resp, err + } } if resp.Result != "order received" { @@ -205,31 +206,26 @@ func (l *LakeBTC) Trade(orderType int, amount, price float64, currency string) ( return resp, nil } -func (l *LakeBTC) GetOpenOrders() ([]LakeBTCOpenOrders, error) { - orders := []LakeBTCOpenOrders{} - err := l.SendAuthenticatedHTTPRequest(LAKEBTC_OPEN_ORDERS, "", &orders) +// GetOpenOrders returns all open orders associated with your account +func (l *LakeBTC) GetOpenOrders() ([]OpenOrders, error) { + orders := []OpenOrders{} - if err != nil { - return nil, err - } - return orders, nil + return orders, l.SendAuthenticatedHTTPRequest(lakeBTCOpenOrders, "", &orders) } -func (l *LakeBTC) GetOrders(orders []int64) ([]LakeBTCOrders, error) { +// GetOrders returns your orders +func (l *LakeBTC) GetOrders(orders []int64) ([]Orders, error) { var ordersStr []string for _, x := range orders { ordersStr = append(ordersStr, strconv.FormatInt(x, 10)) } - resp := []LakeBTCOrders{} - err := l.SendAuthenticatedHTTPRequest(LAKEBTC_GET_ORDERS, common.JoinStrings(ordersStr, ","), &resp) - - if err != nil { - return nil, err - } - return resp, nil + resp := []Orders{} + return resp, + l.SendAuthenticatedHTTPRequest(lakeBTCGetOrders, common.JoinStrings(ordersStr, ","), &resp) } +// CancelOrder cancels an order by ID number and returns an error func (l *LakeBTC) CancelOrder(orderID int64) error { type Response struct { Result bool `json:"Result"` @@ -237,54 +233,45 @@ func (l *LakeBTC) CancelOrder(orderID int64) error { resp := Response{} params := strconv.FormatInt(orderID, 10) - err := l.SendAuthenticatedHTTPRequest(LAKEBTC_CANCEL_ORDER, params, &resp) + err := l.SendAuthenticatedHTTPRequest(lakeBTCCancelOrder, params, &resp) if err != nil { return err } if resp.Result != true { - return errors.New("Unable to cancel order.") + return errors.New("unable to cancel order") } - return nil } -func (l *LakeBTC) GetTrades(timestamp int64) ([]LakeBTCAuthenticaltedTradeHistory, error) { +// GetTrades returns trades associated with your account by timestamp +func (l *LakeBTC) GetTrades(timestamp int64) ([]AuthenticatedTradeHistory, error) { params := "" if timestamp != 0 { params = strconv.FormatInt(timestamp, 10) } - trades := []LakeBTCAuthenticaltedTradeHistory{} - err := l.SendAuthenticatedHTTPRequest(LAKEBTC_GET_TRADES, params, &trades) - if err != nil { - return nil, err - } - - return trades, nil + trades := []AuthenticatedTradeHistory{} + return trades, l.SendAuthenticatedHTTPRequest(lakeBTCGetTrades, params, &trades) } -/* Only for BTC */ -func (l *LakeBTC) GetExternalAccounts() ([]LakeBTCExternalAccounts, error) { - resp := []LakeBTCExternalAccounts{} - err := l.SendAuthenticatedHTTPRequest(LAKEBTC_GET_EXTERNAL_ACCOUNTS, "", &resp) - if err != nil { - return resp, err - } - return resp, nil +// GetExternalAccounts returns your external accounts WARNING: Only for BTC! +func (l *LakeBTC) GetExternalAccounts() ([]ExternalAccounts, error) { + resp := []ExternalAccounts{} + + return resp, l.SendAuthenticatedHTTPRequest(lakeBTCGetExternalAccounts, "", &resp) } -/* Only for BTC */ -func (l *LakeBTC) CreateWithdraw(amount float64, accountID int64) (LakeBTCWithdraw, error) { - resp := LakeBTCWithdraw{} +// CreateWithdraw allows your to withdraw to external account WARNING: Only for +// BTC! +func (l *LakeBTC) CreateWithdraw(amount float64, accountID int64) (Withdraw, error) { + resp := Withdraw{} params := strconv.FormatFloat(amount, 'f', -1, 64) + ",btc," + strconv.FormatInt(accountID, 10) - err := l.SendAuthenticatedHTTPRequest(LAKEBTC_CREATE_WITHDRAW, params, &resp) - if err != nil { - return resp, err - } - return resp, nil + + return resp, l.SendAuthenticatedHTTPRequest(lakeBTCCreateWithdraw, params, &resp) } +// SendAuthenticatedHTTPRequest sends an autheticated HTTP request to a LakeBTC func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result interface{}) (err error) { if !l.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) @@ -300,7 +287,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int hmac := common.GetHMAC(common.HashSHA1, []byte(req), []byte(l.APISecret)) if l.Verbose { - log.Printf("Sending POST request to %s calling method %s with params %s\n", LAKEBTC_API_URL, method, req) + log.Printf("Sending POST request to %s calling method %s with params %s\n", lakeBTCAPIURL, method, req) } postData := make(map[string]interface{}) @@ -318,7 +305,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int headers["Authorization"] = "Basic " + common.Base64Encode([]byte(l.APIKey+":"+common.HexEncodeToString(hmac))) headers["Content-Type"] = "application/json-rpc" - resp, err := common.SendHTTPRequest("POST", LAKEBTC_API_URL, headers, strings.NewReader(string(data))) + resp, err := common.SendHTTPRequest("POST", lakeBTCAPIURL, headers, strings.NewReader(string(data))) if err != nil { return err } @@ -342,10 +329,8 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int } err = common.JSONDecode([]byte(resp), &result) - if err != nil { return errors.New("unable to JSON Unmarshal response") } - return nil } diff --git a/exchanges/lakebtc/lakebtc_test.go b/exchanges/lakebtc/lakebtc_test.go new file mode 100644 index 00000000..02edeeb4 --- /dev/null +++ b/exchanges/lakebtc/lakebtc_test.go @@ -0,0 +1,124 @@ +package lakebtc + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var l LakeBTC + +// Please add your own APIkeys to do correct due diligence testing. +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + l.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + lakebtcConfig, err := cfg.GetExchangeConfig("LakeBTC") + if err != nil { + t.Error("Test Failed - LakeBTC Setup() init error") + } + + lakebtcConfig.AuthenticatedAPISupport = true + lakebtcConfig.APIKey = apiKey + lakebtcConfig.APISecret = apiSecret + + l.Setup(lakebtcConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if l.GetFee(false) != 0.2 { + t.Error("Test Failed - GetFee() error") + } + if l.GetFee(true) != 0.15 { + t.Error("Test Failed - GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := l.GetTicker() + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOrderBook(t *testing.T) { + t.Parallel() + _, err := l.GetOrderBook("BTCUSD") + if err != nil { + t.Error("Test Failed - GetOrderBook() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + t.Parallel() + _, err := l.GetTradeHistory("BTCUSD") + if err != nil { + t.Error("Test Failed - GetTradeHistory() error", err) + } +} + +func TestTrade(t *testing.T) { + t.Parallel() + _, err := l.Trade(0, 0, 0, "USD") + if err == nil { + t.Error("Test Failed - Trade() error", err) + } +} + +func TestGetOpenOrders(t *testing.T) { + t.Parallel() + _, err := l.GetOpenOrders() + if err == nil { + t.Error("Test Failed - GetOpenOrders() error", err) + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + _, err := l.GetOrders([]int64{1, 2}) + if err == nil { + t.Error("Test Failed - GetOrders() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + err := l.CancelOrder(1337) + if err == nil { + t.Error("Test Failed - CancelOrder() error", err) + } +} + +func TestGetTrades(t *testing.T) { + t.Parallel() + _, err := l.GetTrades(1337) + if err == nil { + t.Error("Test Failed - GetTrades() error", err) + } +} + +func TestGetExternalAccounts(t *testing.T) { + t.Parallel() + _, err := l.GetExternalAccounts() + if err == nil { + t.Error("Test Failed - GetExternalAccounts() error", err) + } +} + +func TestCreateWithdraw(t *testing.T) { + t.Parallel() + _, err := l.CreateWithdraw(0, 1337) + if err == nil { + t.Error("Test Failed - CreateWithdraw() error", err) + } +} diff --git a/exchanges/lakebtc/lakebtc_types.go b/exchanges/lakebtc/lakebtc_types.go index ea8a7455..ffbf6fd1 100644 --- a/exchanges/lakebtc/lakebtc_types.go +++ b/exchanges/lakebtc/lakebtc_types.go @@ -1,6 +1,7 @@ package lakebtc -type LakeBTCTicker struct { +// Ticker holds ticker information +type Ticker struct { Last float64 Bid float64 Ask float64 @@ -9,18 +10,21 @@ type LakeBTCTicker struct { Volume float64 } -type LakeBTCOrderbookStructure struct { +// OrderbookStructure stores price and amount for order books +type OrderbookStructure struct { Price float64 Amount float64 } -type LakeBTCOrderbook struct { - Bids []LakeBTCOrderbookStructure `json:"bids"` - Asks []LakeBTCOrderbookStructure `json:"asks"` +// Orderbook contains arrays of orderbook information +type Orderbook struct { + Bids []OrderbookStructure `json:"bids"` + Asks []OrderbookStructure `json:"asks"` } -/* Silly hack due to API returning null instead of strings */ -type LakeBTCTickerResponse struct { +// TickerResponse stores temp response +// Silly hack due to API returning null instead of strings +type TickerResponse struct { Last interface{} Bid interface{} Ask interface{} @@ -29,14 +33,16 @@ type LakeBTCTickerResponse struct { Volume interface{} } -type LakeBTCTradeHistory struct { +// TradeHistory holds trade history data +type TradeHistory struct { Date int64 `json:"data"` Price float64 `json:"price,string"` Amount float64 `json:"amount,string"` TID int64 `json:"tid"` } -type LakeBTCAccountInfo struct { +// AccountInfo contains account information +type AccountInfo struct { Balance map[string]string `json:"balance"` Locked map[string]string `json:"locked"` Profile struct { @@ -46,12 +52,14 @@ type LakeBTCAccountInfo struct { } `json:"profile"` } -type LakeBTCTrade struct { +// Trade holds trade information +type Trade struct { ID int64 `json:"id"` Result string `json:"result"` } -type LakeBTCOpenOrders struct { +// OpenOrders stores full information on your open orders +type OpenOrders struct { ID int64 `json:"id"` Amount float64 `json:"amount,string"` Price float64 `json:"price,string"` @@ -60,7 +68,8 @@ type LakeBTCOpenOrders struct { At int64 `json:"at"` } -type LakeBTCOrders struct { +// Orders holds current order information +type Orders struct { ID int64 `json:"id"` OriginalAmount float64 `json:"original_amount,string"` Amount float64 `json:"amount,string"` @@ -71,14 +80,17 @@ type LakeBTCOrders struct { At int64 `json:"at"` } -type LakeBTCAuthenticaltedTradeHistory struct { +// AuthenticatedTradeHistory is a store of personalised auth trade history +type AuthenticatedTradeHistory struct { Type string `json:"type"` Symbol string `json:"symbol"` Amount float64 `json:"amount,string"` Total float64 `json:"total,string"` At int64 `json:"at"` } -type LakeBTCExternalAccounts struct { + +// ExternalAccounts holds external account information +type ExternalAccounts struct { ID int64 `json:"id,string"` Type string `json:"type"` Address string `json:"address"` @@ -88,7 +100,8 @@ type LakeBTCExternalAccounts struct { UpdatedAt int64 `json:"updated_at,string"` } -type LakeBTCWithdraw struct { +// Withdraw holds withdrawal information +type Withdraw struct { ID int64 `json:"id,string"` Amount float64 `json:"amount,string"` Currency string `json:"currency"` diff --git a/exchanges/liqui/liqui_test.go b/exchanges/liqui/liqui_test.go index eaddeb06..dcb28cbd 100644 --- a/exchanges/liqui/liqui_test.go +++ b/exchanges/liqui/liqui_test.go @@ -20,7 +20,7 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { cfg := config.GetConfig() - cfg.LoadConfig("../../testdata/configtest.dat") + cfg.LoadConfig("../../testdata/configtest.json") liquiConfig, err := cfg.GetExchangeConfig("Liqui") if err != nil { t.Error("Test Failed - liqui Setup() init error") diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index e07bbe1f..6484b20d 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -12,26 +12,101 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/exchanges" - "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( - LOCALBITCOINS_API_URL = "https://localbitcoins.com" - LOCALBITCOINS_API_TICKER = "/bitcoinaverage/ticker-all-currencies/" - LOCALBITCOINS_API_BITCOINCHARTS = "/bitcoincharts/" - LOCALBITCOINS_API_PINCODE = "pincode/" - LOCALBITCOINS_API_WALLET = "wallet/" - LOCALBITCOINS_API_MYSELF = "myself/" - LOCALBITCOINS_API_WALLET_BALANCE = "wallet-balance/" - LOCALBITCOINS_API_WALLET_SEND = "wallet-send/" - LOCALBITCOINS_API_WALLET_SEND_PIN = "wallet-send-pin/" - LOCALBITCOINS_API_WALLET_ADDRESS = "wallet-addr/" + localbitcoinsAPIURL = "https://localbitcoins.com" + + // Autheticated Calls + localbitcoinsAPIAccountInfo = "api/account_info" + localbitcoinsAPIMyself = "myself/" + localbitcoinsAPIAds = "ads/" + localbitcoinsAPIAdGet = "ad-get/" + localbitcoinsAPIAdEdit = "ad/" + localbitcoinsAPIAdCreate = "ad-create/" + localbitcoinsAPIUpdateEquation = "ad-equation/" + localbitcoinsAPIDeleteAd = "ad-delete/" + localbitcoinsAPIRelease = "contact_release/" + localbitcoinsAPIReleaseByPin = "contact_release_pin/" + localbitcoinsAPIMarkAsPaid = "contact_mark_as_paid/" + localbitcoinsAPIMessages = "contact_messages/" + localbitcoinsAPISendMessage = "contact_message_post/" + localbitcoinsAPIDispute = "contact_dispute/" + localbitcoinsAPICancelTrade = "contact_cancel/" + localbitcoinsAPIFundTrade = "contact_fund/" + localbitcoinsAPIConfirmRealName = "contact_mark_realname/" + localbitcoinsAPIVerifyIdentity = "contact_mark_identified/" + localbitcoinsAPIInitiateTrade = "contact_create/" + localbitcoinsAPITradeInfo = "contact_info/" + localbitcoinsAPIDashboard = "dashboard/" + localbitcoinsAPIDashboardReleased = "dashboard/released/" + localbitcoinsAPIDashboardCancelled = "dashboard/canceled/" + localbitcoinsAPIDashboardClosed = "dashboard/closed/" + localbitcoinsAPIFeedback = "feedback/" + localbitcoinsAPILogout = "logout/" + localbitcoinsAPICreateInvoice = "merchant/new_invoice/" + localbitcoinsAPIGetNotification = "notifications/" + localbitcoinsAPIMarkNotification = "notifications/mark_as_read/" + localbitcoinsAPIPinCode = "pincode/" + localbitcoinsAPIVerifyUsername = "real_name_verifiers/" + localbitcoinsAPIWallet = "wallet/" + localbitcoinsAPIWalletBalance = "wallet-balance/" + localbitcoinsAPIWalletSend = "wallet-send/" + localbitcoinsAPIWalletSendPin = "wallet-send-pin/" + localbitcoinsAPIWalletAddress = "wallet-addr/" + + // Un-Autheticated Calls + localbitcoinsAPICountryCodes = "/api/countrycodes/" + localbitcoinsAPICurrencies = "/api/currencies/" + localbitcoinsAPIPaymentMethods = "/api/payment_methods/" + localbitcoinsAPIPlaces = "/api/places/" + localbitcoinsAPITicker = "/bitcoinaverage/ticker-all-currencies/" + localbitcoinsAPIBitcoincharts = "/bitcoincharts/" + localbitcoinsAPICashBuy = "/buy-bitcoins-with-cash/" + localbitcoinsAPIOnlineBuy = "/buy-bitcoins-online/" + + // Trade Types + tradeTypeLocalSell = "LOCAL_SELL" + tradeTypeLocalBuy = "LOCAL_BUY" + tradeTypeOnlineSell = "ONLINE_SELL" + tradeTypeOnlineBuy = "ONLINE_BUY" + + // Reference Types + refTypeShort = "SHORT" + refTypeLong = "LONG" + refTypeNumbers = "NUMBERS" + refTypeLetters = "LETTERS" + + // Feedback Values + feedbackTrust = "trust" + feedbackPositive = "positive" + feedbackNeutral = "neutral" + feedbackBlock = "block" + feedbackBlockWithoutFeedback = "block_without_feedback" + + // State Values + stateNotOpened = "NOT_OPENED" + stateWaitingForPayment = "WAITING_FOR_PAYMENT" + statePaid = "PAID" + stateNotPaid = "DIDNT_PAID" + statePaidLate = "PAID_IN_LATE" + statePartlyPaid = "PAID_PARTLY" + statePaidAndConfirmed = "PAID_AND_CONFIRMED" + statePaidLateConfirmed = "PAID_IN_LATE_AND_CONFIRMED" + statePaidPartlyConfirmed = "PAID_PARTLY_AND_CONFIRMED" ) +var ( + // Payment Methods + paymentMethodOne string +) + +// LocalBitcoins is the overarching type across the localbitcoins package type LocalBitcoins struct { exchange.Base } +// SetDefaults sets the package defaults for localbitcoins func (l *LocalBitcoins) SetDefaults() { l.Name = "LocalBitcoins" l.Enabled = false @@ -43,9 +118,9 @@ func (l *LocalBitcoins) SetDefaults() { l.RequestCurrencyPairFormat.Uppercase = true l.ConfigCurrencyPairFormat.Delimiter = "" l.ConfigCurrencyPairFormat.Uppercase = true - l.AssetTypes = []string{ticker.Spot} } +// Setup sets exchange configuration parameters func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) { if !exch.Enabled { l.SetEnabled(false) @@ -63,107 +138,34 @@ func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) { if err != nil { log.Fatal(err) } - err = l.SetAssetTypes() - if err != nil { - log.Fatal(err) - } } } +// GetFee returns the fee for maker or taker func (l *LocalBitcoins) GetFee(maker bool) float64 { if maker { return l.MakerFee - } else { - return l.TakerFee } + return l.TakerFee } -func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { - result := make(map[string]LocalBitcoinsTicker) - err := common.SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, l.Verbose, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (l *LocalBitcoins) GetTrades(currency string, values url.Values) ([]LocalBitcoinsTrade, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency), values) - result := []LocalBitcoinsTrade{} - err := common.SendHTTPGetRequest(path, true, l.Verbose, &result) - - if err != nil { - return result, err - } - - return result, nil -} - -func (l *LocalBitcoins) GetOrderbook(currency string) (LocalBitcoinsOrderbook, error) { +// GetAccountInfo lets you retrieve the public user information on a +// LocalBitcoins user. The response contains the same information that is found +// on an account's public profile page. +func (l *LocalBitcoins) GetAccountInfo(username string, self bool) (AccountInfo, error) { type response struct { - Bids [][]string `json:"bids"` - Asks [][]string `json:"asks"` - } - - path := fmt.Sprintf("%s/%s/orderbook.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency) - resp := response{} - err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) - - if err != nil { - return LocalBitcoinsOrderbook{}, err - } - - orderbook := LocalBitcoinsOrderbook{} - - for _, x := range resp.Bids { - price, err := strconv.ParseFloat(x[0], 64) - if err != nil { - log.Println(err) - continue - } - amount, err := strconv.ParseFloat(x[1], 64) - if err != nil { - log.Println(err) - continue - } - orderbook.Bids = append(orderbook.Bids, LocalBitcoinsOrderbookStructure{price, amount}) - } - - for _, x := range resp.Asks { - price, err := strconv.ParseFloat(x[0], 64) - if err != nil { - log.Println(err) - continue - } - amount, err := strconv.ParseFloat(x[1], 64) - if err != nil { - log.Println(err) - continue - } - orderbook.Asks = append(orderbook.Asks, LocalBitcoinsOrderbookStructure{price, amount}) - } - - return orderbook, nil -} - -func (l *LocalBitcoins) GetAccountInfo(username string, self bool) (LocalBitcoinsAccountInfo, error) { - type response struct { - Data LocalBitcoinsAccountInfo `json:"data"` + Data AccountInfo `json:"data"` } resp := response{} if self { - err := l.SendAuthenticatedHTTPRequest("GET", LOCALBITCOINS_API_MYSELF, nil, &resp) - + err := l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIMyself, nil, &resp) if err != nil { return resp.Data, err } } else { - path := fmt.Sprintf("%s/api/account_info/%s/", LOCALBITCOINS_API_URL, username) + path := fmt.Sprintf("%s/%s/%s/", localbitcoinsAPIURL, localbitcoinsAPIAccountInfo, username) err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) - if err != nil { return resp.Data, err } @@ -172,6 +174,269 @@ func (l *LocalBitcoins) GetAccountInfo(username string, self bool) (LocalBitcoin return resp.Data, nil } +// Getads returns information of single advertisement based on the ad ID, if +// adID ommited. +// +// adID - [optional] string if ommited returns all ads +func (l *LocalBitcoins) Getads(adID string) (AdData, error) { + type response struct { + Data AdData `json:"data"` + } + resp := response{} + + if len(adID) > 0 { + return resp.Data, + l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIAdGet+adID+"/", nil, &resp) + } + + return resp.Data, + l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIAds, nil, &resp) +} + +// EditAd updates set advertisements +// +// params - see localbitcoins_types.go AdEdit for reference +// adID - string for the ad you already created +func (l *LocalBitcoins) EditAd(params AdEdit, adID string) error { + type response struct { + Data AdData `json:"data"` + } + + resp := response{} + //request := make(map[string]interface{}) + + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIAdEdit+adID+"/", nil, &resp) +} + +// CreateAd creates a new advertisement +// +// params - see localbitcoins_types.go AdCreate for reference +func (l *LocalBitcoins) CreateAd(params AdCreate) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIAdCreate, nil, nil) +} + +// UpdatePriceEquation updates price equation of an advertisement. If there are +// problems with new equation, the price and equation are not updated and +// advertisement remains visible. +// +// equation - string of equation +// adID - string of specific ad identification +func (l *LocalBitcoins) UpdatePriceEquation(equation, adID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIUpdateEquation+adID, nil, nil) +} + +// DeleteAd deletes the advertisement by adID. +// +// adID - string of specific ad identification +func (l *LocalBitcoins) DeleteAd(adID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIDeleteAd+adID, nil, nil) +} + +// ReleaseFunds releases Bitcoin trades specified by ID {contact_id}. If the +// release was successful a message is returned on the data key. +func (l *LocalBitcoins) ReleaseFunds(contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIRelease+contactID, nil, nil) +} + +// ReleaseFundsByPin releases Bitcoin trades specified by ID {contact_id}. if +// the current pincode is provided. If the release was successful a message is +// returned on the data key. +func (l *LocalBitcoins) ReleaseFundsByPin(pin int, contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIReleaseByPin+contactID, nil, nil) +} + +// MarkAsPaid marks a trade as paid. +func (l *LocalBitcoins) MarkAsPaid(contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIMarkAsPaid+contactID, nil, nil) +} + +// GetMessages returns all chat messages from the trade. Messages are on the message_list key. +func (l *LocalBitcoins) GetMessages(contactID string) (Message, error) { + type response struct { + MessageList Message `json:"message_list"` + } + resp := response{} + + return resp.MessageList, + l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIMessages+contactID, nil, &resp) +} + +// SendMessage posts a message and/or uploads an image to the trade. Encode +// images with multipart/form-data encoding. +func (l *LocalBitcoins) SendMessage(msg, contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPISendMessage+contactID, nil, nil) +} + +// Dispute starts a dispute on the specified trade ID if the requirements for +// starting the dispute has been fulfilled. +// +// topic - [optional] String Short description of issue to LocalBitcoins customer support. +func (l *LocalBitcoins) Dispute(topic, contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIDispute+contactID, nil, nil) +} + +// CancelTrade cancels the trade if the token owner is the Bitcoin buyer. +// Bitcoin sellers cannot cancel trades. +func (l *LocalBitcoins) CancelTrade(contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPICancelTrade+contactID, nil, nil) +} + +// FundTrade attempts to fund an unfunded local trade from the token owners +// wallet. Works only if the token owner is the Bitcoin seller in the trade. +func (l *LocalBitcoins) FundTrade(contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIFundTrade+contactID, nil, nil) +} + +// ConfirmRealName creates or updates real name confirmation. +func (l *LocalBitcoins) ConfirmRealName(contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIConfirmRealName+contactID, nil, nil) +} + +// VerifyIdentity marks the identity of trade partner as verified. You must be +// the advertiser in this trade. +func (l *LocalBitcoins) VerifyIdentity(contactID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIVerifyIdentity+contactID, nil, nil) +} + +// InitiateTrade sttempts to start a Bitcoin trade from the specified +// advertisement ID. +func (l *LocalBitcoins) InitiateTrade(amount int, message, adID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIInitiateTrade+adID, nil, nil) +} + +// GetTradeInfo returns information about a single trade that the token owner is +// part in. +func (l *LocalBitcoins) GetTradeInfo(contactID string) error { + return l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPITradeInfo+contactID, nil, nil) +} + +// GetCountryCodes returns a list of valid and recognized countrycodes +func (l *LocalBitcoins) GetCountryCodes() error { + return common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPICountryCodes, true, l.Verbose, nil) +} + +// GetCurrencies returns a list of valid and recognized fiat currencies. Also +// contains human readable name for every currency and boolean that tells if +// currency is an altcoin. +func (l *LocalBitcoins) GetCurrencies() error { + return common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPICurrencies, true, l.Verbose, nil) +} + +// GetDashboardInfo returns a list of trades on the data key contact_list. This +// API end point mirrors the website's dashboard, allowing access to contacts in +// different states. +// In addition all of these listings have buyer/ and seller/ sub-listings to +// view contacts where the token owner is either buying or selling, respectively. +// E.g. /api/dashboard/buyer/. All contacts where the token owner is +// participating are returned. +func (l *LocalBitcoins) GetDashboardInfo() (DashBoardInfo, error) { + resp := DashBoardInfo{} + + return resp, + l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIDashboard, nil, &resp) +} + +// GetDashboardReleasedTrades returns a list of all released trades where the +// token owner is either a buyer or seller. +func (l *LocalBitcoins) GetDashboardReleasedTrades() (DashBoardInfo, error) { + resp := DashBoardInfo{} + + return resp, + l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIDashboardReleased, nil, &resp) +} + +// GetDashboardCancelledTrades returns a list of all canceled trades where the +// token owner is either a buyer or seller. +func (l *LocalBitcoins) GetDashboardCancelledTrades() (DashBoardInfo, error) { + resp := DashBoardInfo{} + + return resp, + l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIDashboardCancelled, nil, &resp) +} + +// GetDashboardClosedTrades returns a list of all closed trades where the token +// owner is either a buyer or seller. +func (l *LocalBitcoins) GetDashboardClosedTrades() (DashBoardInfo, error) { + resp := DashBoardInfo{} + + return resp, + l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIDashboardClosed, nil, &resp) +} + +// SetFeedback gives feedback to user. Possible feedback values are: trust, +// positive, neutral, block, block_without_feedback, (check const values) +// This is only possible to set if there is a trade between the token owner and +// the user specified in {username} that is canceled or released. You may also +// set feedback message using msg field with few exceptions. Feedback +// block_without_feedback clears the message and with block the message is +// mandatory. +// +// feedback - string (use const valuesfor feedback) +// msg - [optional] Feedback message displayed alongside feedback on receivers +// profile page. +// username - username of trade contact +func (l *LocalBitcoins) SetFeedback(msg, feedback, username string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIFeedback, nil, nil) +} + +// Logout expires the current access token immediately. To get a new token +// afterwards, public apps will need to re-authenticate, confidential apps can +// turn in a refresh token. +func (l *LocalBitcoins) Logout() error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPILogout, nil, nil) +} + +// CreateNewInvoice creates a new invoice. +func (l *LocalBitcoins) CreateNewInvoice(currency, description, returnURL string, amount float64, internal bool) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPICreateInvoice, nil, nil) +} + +// GetInvoice returns information about a specific invoice created by the token +// owner. +func (l *LocalBitcoins) GetInvoice(invoiceID string) (Invoice, error) { + resp := Invoice{} + return resp, l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPICreateInvoice, nil, &resp) +} + +// DeleteInvoice deletes a specific invoice. Deleting invoices is possible when +// it is sure that receiver cannot accidentally pay the invoice at the same time +// as the merchant is deleting it. You can use the API request +// /api/merchant/invoice/{invoice_id}/ to check if deleting is possible. +func (l *LocalBitcoins) DeleteInvoice(invoiceID string) (Invoice, error) { + resp := Invoice{} + return resp, l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPICreateInvoice, nil, &resp) +} + +// GetNotifications returns recent notifications. +func (l *LocalBitcoins) GetNotifications() ([]NotificationInfo, error) { + resp := []NotificationInfo{} + return resp, l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIGetNotification, nil, &resp) +} + +// MarkNotifications marks a specific notification as read. +func (l *LocalBitcoins) MarkNotifications(notificationID string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIMarkNotification, nil, nil) +} + +// GetPaymentMethods returns a list of valid payment methods. Also contains name +// and code for payment methods, and possible limitations in currencies and bank +// name choices. +func (l *LocalBitcoins) GetPaymentMethods() error { + return common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPIPaymentMethods, true, l.Verbose, nil) +} + +// GetPaymentMethodsByCountry returns a list of valid payment methods filtered +// by countrycodes. +func (l *LocalBitcoins) GetPaymentMethodsByCountry(countryCode string) error { + return common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPIPaymentMethods+countryCode, true, l.Verbose, nil) +} + +// CheckPincode checks the given PIN code against the token owners currently +// active PIN code. You can use this method to ensure the person using the +// session is the legitimate user. +// Due to only requiring the read scope, the user is not guaranteed to have set +// a PIN code. If you protect your application using this request, please make +// the user has set a PIN code for his account. func (l *LocalBitcoins) CheckPincode(pin int) (bool, error) { type response struct { Data struct { @@ -181,64 +446,91 @@ func (l *LocalBitcoins) CheckPincode(pin int) (bool, error) { resp := response{} values := url.Values{} values.Set("pincode", strconv.Itoa(pin)) - err := l.SendAuthenticatedHTTPRequest("POST", LOCALBITCOINS_API_PINCODE, values, &resp) + err := l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIPinCode, values, &resp) if err != nil { return false, err } if !resp.Data.PinOK { - return false, errors.New("Pin invalid.") + return false, errors.New("pin invalid") } return true, nil } -func (l *LocalBitcoins) GetWalletInfo() (LocalBitcoinsWalletInfo, error) { +// GetPlaces Looks up places near lat, lon and provides full URLs to buy and +// sell listings for each. +func (l *LocalBitcoins) GetPlaces(lat, lon int, location, countryCode string) error { + return common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPIPlaces, true, l.Verbose, nil) +} + +// VerifyUsername returns list of real name verifiers for the user. Returns a +// list only when you have a trade with the user where you are the seller. +func (l *LocalBitcoins) VerifyUsername() error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIVerifyUsername, nil, nil) +} + +// GetRecentMessages returns maximum of 25 newest trade messages. Does not +// return messages older than one month. Messages are ordered by sending time, +// and the newest one is first. +func (l *LocalBitcoins) GetRecentMessages(after string) error { + return l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIVerifyUsername, nil, nil) +} + +// GetWalletInfo gets information about the token owner's wallet balance. +func (l *LocalBitcoins) GetWalletInfo() (WalletInfo, error) { type response struct { - Data LocalBitcoinsWalletInfo `json:"data"` + Data WalletInfo `json:"data"` } resp := response{} - err := l.SendAuthenticatedHTTPRequest("GET", LOCALBITCOINS_API_WALLET, nil, &resp) + err := l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIWallet, nil, &resp) if err != nil { - return LocalBitcoinsWalletInfo{}, err + return WalletInfo{}, err } if resp.Data.Message != "OK" { - return LocalBitcoinsWalletInfo{}, errors.New("Unable to fetch wallet info.") + return WalletInfo{}, errors.New("unable to fetch wallet info") } return resp.Data, nil } -func (l *LocalBitcoins) GetWalletBalance() (LocalBitcoinsWalletBalanceInfo, error) { +// GetWalletBalance Same as GetWalletInfo(), but only returns the message, +// receiving_address and total fields. +// Use this instead if you don't care about transactions at the moment. +func (l *LocalBitcoins) GetWalletBalance() (WalletBalanceInfo, error) { type response struct { - Data LocalBitcoinsWalletBalanceInfo `json:"data"` + Data WalletBalanceInfo `json:"data"` } resp := response{} - err := l.SendAuthenticatedHTTPRequest("GET", LOCALBITCOINS_API_WALLET_BALANCE, nil, &resp) + err := l.SendAuthenticatedHTTPRequest("GET", localbitcoinsAPIWalletBalance, nil, &resp) if err != nil { - return LocalBitcoinsWalletBalanceInfo{}, err + return WalletBalanceInfo{}, err } if resp.Data.Message != "OK" { - return LocalBitcoinsWalletBalanceInfo{}, errors.New("Unable to fetch wallet balance.") + return WalletBalanceInfo{}, errors.New("unable to fetch wallet balance") } return resp.Data, nil } +// WalletSend sends amount of bitcoins from the token owner's wallet to address. +// On success, the response returns a message indicating success. It is highly +// recommended to minimize the lifetime of access tokens with the money +// permission. Use Logout() to make the current token expire instantly. func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int) (bool, error) { values := url.Values{} values.Set("address", address) values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - path := LOCALBITCOINS_API_WALLET_SEND + path := localbitcoinsAPIWalletSend if pin > 0 { values.Set("pincode", strconv.Itoa(pin)) - path = LOCALBITCOINS_API_WALLET_SEND_PIN + path = localbitcoinsAPIWalletSendPin } type response struct { @@ -254,12 +546,15 @@ func (l *LocalBitcoins) WalletSend(address string, amount float64, pin int) (boo } if resp.Data.Message != "Money is being sent" { - return false, errors.New("Unable to send Bitcoins.") + return false, errors.New("unable to send Bitcoins") } return true, nil } +// GetWalletAddress returns an unused receiving address from the token owner's +// wallet. The address is returned in the address key of the response. Note that +// this API may keep returning the same (unused) address if requested repeatedly. func (l *LocalBitcoins) GetWalletAddress() (string, error) { type response struct { Data struct { @@ -268,18 +563,98 @@ func (l *LocalBitcoins) GetWalletAddress() (string, error) { } } resp := response{} - err := l.SendAuthenticatedHTTPRequest("POST", LOCALBITCOINS_API_WALLET_ADDRESS, nil, &resp) + err := l.SendAuthenticatedHTTPRequest("POST", localbitcoinsAPIWalletAddress, nil, &resp) if err != nil { return "", err } if resp.Data.Message != "OK!" { - return "", errors.New("Unable to fetch wallet address.") + return "", errors.New("unable to fetch wallet address") } return resp.Data.Address, nil } +// GetBitcoinsWithCashAd returns buy or sell as cash local advertisements. +func (l *LocalBitcoins) GetBitcoinsWithCashAd(locationID, locationSlug string, BuySide bool) error { + return common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPICashBuy, true, l.Verbose, nil) +} + +// GetBitcoinsOnlineAd this API returns buy or sell Bitcoin online ads. +func (l *LocalBitcoins) GetBitcoinsOnlineAd(countryCode, countryName, paymentMethod string, BuySide bool) error { + return common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPIOnlineBuy, true, l.Verbose, nil) +} + +// GetTicker returns list of all completed trades. +func (l *LocalBitcoins) GetTicker() (map[string]Ticker, error) { + result := make(map[string]Ticker) + + return result, + common.SendHTTPGetRequest(localbitcoinsAPIURL+localbitcoinsAPITicker, true, l.Verbose, &result) +} + +// GetTrades returns all closed trades in online buy and online sell categories, +// updated every 15 minutes. +func (l *LocalBitcoins) GetTrades(currency string, values url.Values) ([]Trade, error) { + path := common.EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", localbitcoinsAPIURL+localbitcoinsAPIBitcoincharts, currency), values) + result := []Trade{} + + return result, common.SendHTTPGetRequest(path, true, l.Verbose, &result) +} + +// GetOrderbook returns buy and sell bitcoin online advertisements. Amount is +// the maximum amount available for the trade request. Price is the hourly +// updated price. The price is based on the price equation and commission % +// entered by the ad author. +func (l *LocalBitcoins) GetOrderbook(currency string) (Orderbook, error) { + type response struct { + Bids [][]string `json:"bids"` + Asks [][]string `json:"asks"` + } + + path := fmt.Sprintf("%s/%s/orderbook.json", localbitcoinsAPIURL+localbitcoinsAPIBitcoincharts, currency) + resp := response{} + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) + + if err != nil { + return Orderbook{}, err + } + + orderbook := Orderbook{} + + for _, x := range resp.Bids { + price, err := strconv.ParseFloat(x[0], 64) + if err != nil { + log.Println(err) + continue + } + amount, err := strconv.ParseFloat(x[1], 64) + if err != nil { + log.Println(err) + continue + } + orderbook.Bids = append(orderbook.Bids, Price{price, amount}) + } + + for _, x := range resp.Asks { + price, err := strconv.ParseFloat(x[0], 64) + if err != nil { + log.Println(err) + continue + } + amount, err := strconv.ParseFloat(x[1], 64) + if err != nil { + log.Println(err) + continue + } + orderbook.Asks = append(orderbook.Asks, Price{price, amount}) + } + + return orderbook, nil +} + +// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to +// localbitcoins func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values url.Values, result interface{}) (err error) { if !l.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) @@ -306,16 +681,29 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values headers["Apiauth-Signature"] = common.StringToUpper(common.HexEncodeToString(hmac)) headers["Content-Type"] = "application/x-www-form-urlencoded" - resp, err := common.SendHTTPRequest(method, LOCALBITCOINS_API_URL+path, headers, bytes.NewBuffer([]byte(payload))) + if l.Verbose { + log.Printf("Raw Path: \n%s\n", path) + } + + resp, err := common.SendHTTPRequest(method, localbitcoinsAPIURL+path, headers, bytes.NewBuffer([]byte(payload))) + if err != nil { + return err + } if l.Verbose { log.Printf("Received raw: \n%s\n", resp) } - err = common.JSONDecode([]byte(resp), &result) + errCapture := GeneralError{} + if err = common.JSONDecode([]byte(resp), &errCapture); err == nil { + if len(errCapture.Error.Message) != 0 { + return errors.New(errCapture.Error.Message) + } + } + err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("unable to JSON Unmarshal response") + return err } return nil diff --git a/exchanges/localbitcoins/localbitcoins_test.go b/exchanges/localbitcoins/localbitcoins_test.go new file mode 100644 index 00000000..41492dec --- /dev/null +++ b/exchanges/localbitcoins/localbitcoins_test.go @@ -0,0 +1,75 @@ +package localbitcoins + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var l LocalBitcoins + +// Please supply your own APIKEYS here for due diligence testing + +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + l.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + localbitcoinsConfig, err := cfg.GetExchangeConfig("LocalBitcoins") + if err != nil { + t.Error("Test Failed - LakeBTC Setup() init error") + } + + localbitcoinsConfig.AuthenticatedAPISupport = true + localbitcoinsConfig.APIKey = apiKey + localbitcoinsConfig.APISecret = apiSecret + + l.Setup(localbitcoinsConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if l.GetFee(false) != 0 || l.GetFee(true) != 0 { + t.Error("Test Failed - GetFee() error") + } +} + +func TestGetAccountInfo(t *testing.T) { + t.Parallel() + _, err := l.GetAccountInfo("", true) + if err == nil { + t.Error("Test Failed - GetAccountInfo() error", err) + } + _, err = l.GetAccountInfo("bitcoinbaron", false) + if err != nil { + t.Error("Test Failed - GetAccountInfo() error", err) + } +} + +func TestGetads(t *testing.T) { + t.Parallel() + _, err := l.Getads("") + if err == nil { + t.Error("Test Failed - Getads() - Full list, error", err) + } + _, err = l.Getads("1337") + if err == nil { + t.Error("Test Failed - Getads() error", err) + } +} + +func TestEditAd(t *testing.T) { + t.Parallel() + edit := AdEdit{} + err := l.EditAd(edit, "1337") + if err == nil { + t.Error("Test Failed - EditAd() error", err) + } +} diff --git a/exchanges/localbitcoins/localbitcoins_types.go b/exchanges/localbitcoins/localbitcoins_types.go index 337c2e00..bd82ecbb 100644 --- a/exchanges/localbitcoins/localbitcoins_types.go +++ b/exchanges/localbitcoins/localbitcoins_types.go @@ -4,7 +4,318 @@ import ( "time" ) -type LocalBitcoinsTicker struct { +// GeneralError is an error capture type +type GeneralError struct { + Error struct { + Message string `json:"message"` + ErrorCode int `json:"error_code"` + } `json:"error"` +} + +// AccountInfo holds public user information +type AccountInfo struct { + Username string `json:"username"` + FeedbackScore int `json:"feedback_score"` + FeedbackCount int `json:"feedback_count"` + RealNameVeriTrusted int `json:"real_name_verifications_trusted"` + TradingPartners int `json:"trading_partners_count"` + URL string `json:"url"` + RealNameVeriUntrusted int `json:"real_name_verifications_untrusted"` + HasFeedback bool `json:"has_feedback"` + IdentityVerifiedAt string `json:"identify_verified_at"` + TrustedCount int `json:"trusted_count"` + FeedbacksUnconfirmed int `json:"feedbacks_unconfirmed_count"` + BlockedCount int `json:"blocked_count"` + TradeVolumeText string `json:"trade_volume_text"` + HasCommonTrades bool `json:"has_common_trades"` + RealNameVeriRejected int `json:"real_name_verifications_rejected"` + AgeText string `json:"age_text"` + ConfirmedTradesText string `json:"confirmed_trade_count_text"` + CreatedAt string `json:"created_at"` +} + +// AdData references the full possible return of ad data +type AdData struct { + AdList []struct { + Data struct { + Visible bool `json:"visible"` + HiddenByOpeningHours bool `json:"hidden_by_opening_hours"` + Location string `json:"location_string"` + CountryCode string `json:"countrycode"` + City string `json:"city"` + TradeType string `json:"trade_type"` + OnlineProvider string `json:"online_provider"` + FirstTimeLimitBTC string `json:"first_time_limit_btc"` + VolumeCoefficientBTC string `json:"volume_coefficient_btc"` + SMSVerficationRequired bool `json:"sms_verification_required"` + ReferenceType string `json:"reference_type"` + DisplayReference bool `json:"display_reference"` + Currency string `json:"currency"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + MinAmount string `json:"min_amount"` + MaxAmount string `json:"max_amount"` + MaXAmountAvailable string `json:"max_amount_available"` + LimitToFiatAmounts string `json:"limit_to_fiat_amounts"` + AdID int64 `json:"ad_id"` + TempPriceUSD string `json:"temp_price_usd"` + Floating bool `json:"floating"` + Profile interface{} `json:"profile"` + RequireFeedBackScore int `json:"require_feedback_score"` + RequireTradeVolume float64 `json:"require_trade_volume"` + RequireTrustedByAdvertiser bool `json:"require_trusted_by_advertiser"` + PaymentWindowMinutes int `json:"payment_window_minutes"` + BankName string `json:"bank_name"` + TrackMaxAmount bool `json:"track_max_amount"` + ATMModel string `json:"atm_model"` + PriceEquation string `json:"price_equation"` + OpeningHours interface{} `json:"opening_hours"` + AccountInfo string `json:"account_info"` + AccountDetails interface{} `json:"account_details"` + } `json:"data"` + Actions struct { + PublicView string `json:"public_view"` + HTMLEdit string `json:"html_edit"` + ChangeForm string `json:"change_form"` + ContactForm string `json:"contact_form"` + } `json:"actions"` + } `json:"ad_list"` + AdCount int `json:"ad_count"` +} + +// AdEdit references an outgoing paramater type for EditAd() method +type AdEdit struct { + // Required Arguments + PriceEquation string `json:"price_equation"` + Latitude int `json:"lat"` + Longitude int `json:"lon"` + City string `json:"city"` + Location string `json:"location_string"` + CountryCode string `json:"countrycode"` + Currency string `json:"currency"` + AccountInfo string `json:"account_info"` + BankName string `json:"bank_name"` + MSG string `json:"msg"` + SMSVerficationRequired bool `json:"sms_verification_required"` + TrackMaxAmount bool `json:"track_max_amount"` + RequireTrustedByAdvertiser bool `json:"require_trusted_by_advertiser"` + RequireIdentification bool `json:"require_identification"` + + // Optional Arguments + MinAmount int `json:"min_amount"` + MaxAmount int `json:"max_amount"` + OpeningHours []string `json:"opening_hours"` + LimitToFiatAmounts string `json:"limit_to_fiat_amounts"` + Visible bool `json:"visible,int"` + + // Optional Arguments ONLINE_SELL ads + RequireTradeVolume int `json:"require_trade_volume"` + RequireFeedBackScore int `json:"require_feedback_score"` + FirstTimeLimitBTC int `json:"first_time_limit_btc"` + VolumeCoefficientBTC int `json:"volume_coefficient_btc"` + ReferenceType string `json:"reference_type"` + DisplayReference bool `json:"display_reference,int"` + + // Optional Arguments ONLINE_BUY + PaymentWindowMinutes int `json:"payment_window_minutes"` + + // Optional Arguments LOCAL_SELL + Floating bool `json:"floating,int"` +} + +// AdCreate references an outgoing paramater type for CreateAd() method +type AdCreate struct { + // Required Arguments + PriceEquation string `json:"price_equation"` + Latitude int `json:"lat"` + Longitude int `json:"lon"` + City string `json:"city"` + Location string `json:"location_string"` + CountryCode string `json:"countrycode"` + Currency string `json:"currency"` + AccountInfo string `json:"account_info"` + BankName string `json:"bank_name"` + MSG string `json:"msg"` + SMSVerficationRequired bool `json:"sms_verification_required"` + TrackMaxAmount bool `json:"track_max_amount"` + RequireTrustedByAdvertiser bool `json:"require_trusted_by_advertiser"` + RequireIdentification bool `json:"require_identification"` + OnlineProvider string `json:"online_provider"` + TradeType string `json:"trade_type"` + + // Optional Arguments + MinAmount int `json:"min_amount"` + MaxAmount int `json:"max_amount"` + OpeningHours []string `json:"opening_hours"` + LimitToFiatAmounts string `json:"limit_to_fiat_amounts"` + Visible bool `json:"visible,int"` + + // Optional Arguments ONLINE_SELL ads + RequireTradeVolume int `json:"require_trade_volume"` + RequireFeedBackScore int `json:"require_feedback_score"` + FirstTimeLimitBTC int `json:"first_time_limit_btc"` + VolumeCoefficientBTC int `json:"volume_coefficient_btc"` + ReferenceType string `json:"reference_type"` + DisplayReference bool `json:"display_reference,int"` + + // Optional Arguments ONLINE_BUY + PaymentWindowMinutes int `json:"payment_window_minutes"` + + // Optional Arguments LOCAL_SELL + Floating bool `json:"floating,int"` +} + +// Message holds the returned message data from a contact +type Message struct { + MSG string `json:"msg"` + Sender struct { + ID int64 `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + TradeCount int64 `json:"trafe_count"` + LastOnline string `json:"last_online"` + } `json:"sender"` + CreatedAt string `json:"created_at"` + IsAdmin bool `json:"is_admin"` + AttachmentName string `json:"attachment_name"` + AttachmentType string `json:"attachment_type"` + AttachmentURL string `json:"attachment_url"` +} + +// DashBoardInfo holds the full range of metadata for a dashboard image +type DashBoardInfo struct { + Data struct { + CreatedAt string `json:"created_at"` + Buyer struct { + Username string `json:"username"` + TradeCount string `json:"trade_count"` + FeedbackScore string `json:"feedback_score"` + Name string `json:"name"` + LastOnline string `json:"last_online"` + RealName string `json:"real_name"` + CompanyName string `json:"company_name"` + CountryCodeByIP string `json:"countrycode_by_ip"` + CountryCodeByPhoneNUmber string `json:"countrycode_by_phone_number"` + } `json:"buyer"` + Seller struct { + Username string `json:"username"` + TradeCount string `json:"trade_count"` + FeedbackScore string `json:"feedback_score"` + Name string `json:"name"` + LastOnline string `json:"last_online"` + } `json:"seller"` + ReferenceCode string `json:"reference_code"` + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` + AmountBTC float64 `json:"amount_btc,string"` + FeeBTC float64 `json:"fee_btc,string"` + ExchangeRateUpdatedAt string `json:"exchange_rate_updated_at"` + Advertisement struct { + ID int `json:"id"` + TradeType string `json:"trade_type"` + Advertiser struct { + Username string `json:"username"` + TradeCount string `json:"trade_count"` + FeedbackScore string `json:"feedback_score"` + Name string `json:"name"` + LastOnline string `json:"last_online"` + } `json:"advertiser"` + } `json:"advertisement"` + ContactID int `json:"contact_id"` + CanceledAt string `json:"canceled_at"` + EscrowedAt string `json:"escrowed_at"` + FundedAt string `json:"funded_at"` + PaymentCompletedAt string `json:"payment_completed_at"` + DisputedAt string `json:"disputed_at"` + ClosedAt string `json:"closed_at"` + ReleasedAt string `json:"released_at"` + IsBuying bool `json:"is_buying"` + IsSelling bool `json:"is_selling"` + AccountDetails struct { + ReceiverName string `json:"receiver_name"` + IBAN string `json:"iban"` + SwiftBIC string `json:"swift_bic"` + Reference string `json:"reference"` + } `json:"account_details"` + AccountInfo string `json:"account_info"` + Floating bool `json:"floating"` + } `json:"data"` + Actions struct { + MarkAsPaidURL string `json:"mark_as_paid_url"` + AdvertisementPublicView string `json:"advertisement_public_view"` + MessageURL string `json:"message_url"` + MessagePostURL string `json:"message_post_url"` + } `json:"actions"` +} + +// Invoice contains invoice data +type Invoice struct { + Invoice struct { + Description string `json:"description"` + Created string `json:"created"` + URL string `json:"url"` + Amount float64 `json:"amount,string"` + Internal bool `json:"internal"` + Currency string `json:"currency"` + State string `json:"state"` + ID string `json:"id"` + BTCAmount string `json:"btc_amount"` + BTCAddress string `json:"btc_address"` + DeletingAllowed bool `json:"deleting_allowed"` + } `json:"invoice"` +} + +// NotificationInfo holds Notification data +type NotificationInfo struct { + URL string `json:"url"` + CreatedAt string `json:"created_at"` + ContactID int64 `json:"contact_id"` + Read bool `json:"read"` + MSG string `json:"msg"` + ID string `json:"id"` +} + +// WalletInfo holds full wallet information data +type WalletInfo struct { + Message string `json:"message"` + Total Balance `json:"total"` + SentTransactions30d []WalletTransaction `json:"sent_transactions_30d"` + ReceivedTransactions30d []WalletTransaction `json:"received_transactions_30d"` + ReceivingAddressCount int `json:"receiving_address_count"` + ReceivingAddressList []WalletAddressList `json:"receiving_address_list"` +} + +// Balance is a sub-type for WalletInfo & WalletBalanceInfo +type Balance struct { + Balance float64 `json:"balance,string"` + Sendable float64 `json:"Sendable,string"` +} + +// WalletTransaction is a sub-type for WalletInfo +type WalletTransaction struct { + TXID string `json:"txid"` + Amount float64 `json:"amount,string"` + Description string `json:"description"` + TXType int `json:"tx_type"` + CreatedAt time.Time `json:"created_at"` +} + +// WalletAddressList is a sub-type for WalletInfo & WalletBalanceInfo +type WalletAddressList struct { + Address string `json:"address"` + Received float64 `json:"received,string"` +} + +// WalletBalanceInfo standard wallet balance information +type WalletBalanceInfo struct { + Message string `json:"message"` + Total Balance `json:"total"` + ReceivingAddressCount int `json:"receiving_address_count"` // always 1 + ReceivingAddressList []WalletAddressList `json:"receiving_address_list"` +} + +// Ticker contains ticker information +type Ticker struct { Avg12h float64 `json:"avg_12h,string"` Avg1h float64 `json:"avg_1h,string"` Avg24h float64 `json:"avg_24h,string"` @@ -14,71 +325,22 @@ type LocalBitcoinsTicker struct { VolumeBTC float64 `json:"volume_btc,string"` } -type LocalBitcoinsTrade struct { +// Trade holds closed trade information +type Trade struct { TID int64 `json:"tid"` Date int64 `json:"date"` Amount float64 `json:"amount,string"` Price float64 `json:"price,string"` } -type LocalBitcoinsOrderbookStructure struct { +// Orderbook is a full range of bid and asks for localbitcoins +type Orderbook struct { + Bids []Price `json:"bids"` + Asks []Price `json:"asks"` +} + +// Price is a sub-type for orderbook +type Price struct { Price float64 Amount float64 } - -type LocalBitcoinsOrderbook struct { - Bids []LocalBitcoinsOrderbookStructure `json:"bids"` - Asks []LocalBitcoinsOrderbookStructure `json:"asks"` -} - -type LocalBitcoinsAccountInfo struct { - Username string `json:"username"` - CreatedAt time.Time `json:"created_at"` - AgeText string `json:"age_text"` - TradingPartners int `json:"trading_partners_count"` - FeedbacksUnconfirmed int `json:"feedbacks_unconfirmed_count"` - TradeVolumeText string `json:"trade_volume_text"` - HasCommonTrades bool `json:"has_common_trades"` - HasFeedback bool `json:"has_feedback"` - ConfirmedTradesText string `json:"confirmed_trade_count_text"` - BlockedCount int `json:"blocked_count"` - FeedbackScore int `json:"feedback_score"` - FeedbackCount int `json:"feedback_count"` - URL string `json:"url"` - TrustedCount int `json:"trusted_count"` - IdentityVerifiedAt time.Time `json:"identify_verified_at"` -} - -type LocalBitcoinsBalance struct { - Balance float64 `json:"balance,string"` - Sendable float64 `json:"Sendable,string"` -} - -type LocalBitcoinsWalletTransaction struct { - TXID string `json:"txid"` - Amount float64 `json:"amount,string"` - Description string `json:"description"` - TXType int `json:"tx_type"` - CreatedAt time.Time `json:"created_at"` -} - -type LocalBitcoinsWalletAddressList struct { - Address string `json:"address"` - Received float64 `json:"received,string"` -} - -type LocalBitcoinsWalletInfo struct { - Message string `json:"message"` - Total LocalBitcoinsBalance `json:"total"` - SentTransactions30d []LocalBitcoinsWalletTransaction `json:"sent_transactions_30d"` - ReceivedTransactions30d []LocalBitcoinsWalletTransaction `json:"received_transactions_30d"` - ReceivingAddressCount int `json:"receiving_address_count"` - ReceivingAddressList []LocalBitcoinsWalletAddressList `json:"receiving_address_list"` -} - -type LocalBitcoinsWalletBalanceInfo struct { - Message string `json:"message"` - Total LocalBitcoinsBalance `json:"total"` - ReceivingAddressCount int `json:"receiving_address_count"` // always 1 - ReceivingAddressList []LocalBitcoinsWalletAddressList `json:"receiving_address_list"` -} diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go new file mode 100644 index 00000000..5ef3dced --- /dev/null +++ b/exchanges/okcoin/okcoin_test.go @@ -0,0 +1,35 @@ +package okcoin + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var o OKCoin + +// Please supply your own APIKEYS here for due diligence testing + +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + o.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + okcoinConfig, err := cfg.GetExchangeConfig("OKCOIN International") + if err != nil { + t.Error("Test Failed - OKCoin Setup() init error") + } + + okcoinConfig.AuthenticatedAPISupport = true + okcoinConfig.APIKey = apiKey + okcoinConfig.APISecret = apiSecret + + o.Setup(okcoinConfig) +} diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index d03e4c26..e0ce2f22 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -92,21 +92,21 @@ func TestGetOrderbook(t *testing.T) { t.Fatal("Test failed. TestGetOrderbook failed. Mismatched pairs") } - _, err = GetOrderbook("nonexistant", currency, Spot) + _, err = GetOrderbook("nonexistent", currency, Spot) if err == nil { - t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook") + t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook") } currency.FirstCurrency = "blah" _, err = GetOrderbook("Exchange", currency, Spot) if err == nil { - t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook using invalid first currency") + t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid first currency") } newCurrency := pair.NewCurrencyPair("BTC", "AUD") _, err = GetOrderbook("Exchange", newCurrency, Spot) if err == nil { - t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook using invalid second currency") + t.Fatal("Test failed. TestGetOrderbook retrieved non-existent orderbook using invalid second currency") } } diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index d0b8d753..4a23e2a9 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -16,42 +16,44 @@ import ( ) const ( - POLONIEX_API_URL = "https://poloniex.com" - POLONIEX_API_TRADING_ENDPOINT = "tradingApi" - POLONIEX_API_VERSION = "1" - POLONIEX_BALANCES = "returnBalances" - POLONIEX_BALANCES_COMPLETE = "returnCompleteBalances" - POLONIEX_DEPOSIT_ADDRESSES = "returnDepositAddresses" - POLONIEX_GENERATE_NEW_ADDRESS = "generateNewAddress" - POLONIEX_DEPOSITS_WITHDRAWALS = "returnDepositsWithdrawals" - POLONIEX_ORDERS = "returnOpenOrders" - POLONIEX_TRADE_HISTORY = "returnTradeHistory" - POLONIEX_ORDER_BUY = "buy" - POLONIEX_ORDER_SELL = "sell" - POLONIEX_ORDER_CANCEL = "cancelOrder" - POLONIEX_ORDER_MOVE = "moveOrder" - POLONIEX_WITHDRAW = "withdraw" - POLONIEX_FEE_INFO = "returnFeeInfo" - POLONIEX_AVAILABLE_BALANCES = "returnAvailableAccountBalances" - POLONIEX_TRADABLE_BALANCES = "returnTradableBalances" - POLONIEX_TRANSFER_BALANCE = "transferBalance" - POLONIEX_MARGIN_ACCOUNT_SUMMARY = "returnMarginAccountSummary" - POLONIEX_MARGIN_BUY = "marginBuy" - POLONIEX_MARGIN_SELL = "marginSell" - POLONIEX_MARGIN_POSITION = "getMarginPosition" - POLONIEX_MARGIN_POSITION_CLOSE = "closeMarginPosition" - POLONIEX_CREATE_LOAN_OFFER = "createLoanOffer" - POLONIEX_CANCEL_LOAN_OFFER = "cancelLoanOffer" - POLONIEX_OPEN_LOAN_OFFERS = "returnOpenLoanOffers" - POLONIEX_ACTIVE_LOANS = "returnActiveLoans" - POLONIEX_LENDING_HISTORY = "returnLendingHistory" - POLONIEX_AUTO_RENEW = "toggleAutoRenew" + poloniexAPIURL = "https://poloniex.com" + poloniexAPITradingEndpoint = "tradingApi" + poloniexAPIVersion = "1" + poloniexBalances = "returnBalances" + poloniexBalancesComplete = "returnCompleteBalances" + poloniexDepositAddresses = "returnDepositAddresses" + poloniexGenerateNewAddress = "generateNewAddress" + poloniexDepositsWithdrawals = "returnDepositsWithdrawals" + poloniexOrders = "returnOpenOrders" + poloniexTradeHistory = "returnTradeHistory" + poloniexOrderBuy = "buy" + poloniexOrderSell = "sell" + poloniexOrderCancel = "cancelOrder" + poloniexOrderMove = "moveOrder" + poloniexWithdraw = "withdraw" + poloniexFeeInfo = "returnFeeInfo" + poloniexAvailableBalances = "returnAvailableAccountBalances" + poloniexTradableBalances = "returnTradableBalances" + poloniexTransferBalance = "transferBalance" + poloniexMarginAccountSummary = "returnMarginAccountSummary" + poloniexMarginBuy = "marginBuy" + poloniexMarginSell = "marginSell" + poloniexMarginPosition = "getMarginPosition" + poloniexMarginPositionClose = "closeMarginPosition" + poloniexCreateLoanOffer = "createLoanOffer" + poloniexCancelLoanOffer = "cancelLoanOffer" + poloniexOpenLoanOffers = "returnOpenLoanOffers" + poloniexActiveLoans = "returnActiveLoans" + poloniexLendingHistory = "returnLendingHistory" + poloniexAutoRenew = "toggleAutoRenew" ) +// Poloniex is the overarching type across the poloniex package type Poloniex struct { exchange.Base } +// SetDefaults sets default settings for poloniex func (p *Poloniex) SetDefaults() { p.Name = "Poloniex" p.Enabled = false @@ -66,6 +68,7 @@ func (p *Poloniex) SetDefaults() { p.AssetTypes = []string{ticker.Spot} } +// Setup sets user exchange configuration settings func (p *Poloniex) Setup(exch config.ExchangeConfig) { if !exch.Enabled { p.SetEnabled(false) @@ -90,36 +93,32 @@ func (p *Poloniex) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the fee for poloniex func (p *Poloniex) GetFee() float64 { return p.Fee } +// GetTicker returns current ticker information func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { type response struct { Data map[string]PoloniexTicker } resp := response{} - path := fmt.Sprintf("%s/public?command=returnTicker", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) + path := fmt.Sprintf("%s/public?command=returnTicker", poloniexAPIURL) - if err != nil { - return resp.Data, err - } - return resp.Data, nil + return resp.Data, common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) } +// GetVolume returns a list of currencies with associated volume func (p *Poloniex) GetVolume() (interface{}, error) { var resp interface{} - path := fmt.Sprintf("%s/public?command=return24hVolume", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) + path := fmt.Sprintf("%s/public?command=return24hVolume", poloniexAPIURL) - if err != nil { - return resp, err - } - return resp, nil + return resp, common.SendHTTPGetRequest(path, true, p.Verbose, &resp) } +// GetOrderbook returns the full orderbook from poloniex func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbook, error) { vals := url.Values{} vals.Set("currencyPair", currencyPair) @@ -129,13 +128,18 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbo } resp := PoloniexOrderbookResponse{} - path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) + path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", poloniexAPIURL, vals.Encode()) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return PoloniexOrderbook{}, err } + if len(resp.Error) != 0 { + log.Println(resp.Error) + return PoloniexOrderbook{}, fmt.Errorf("Poloniex GetOrderbook() error: %s", resp.Error) + } + ob := PoloniexOrderbook{} for x := range resp.Asks { data := resp.Asks[x] @@ -159,6 +163,7 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbo return ob, nil } +// GetTradeHistory returns trades history from poloniex func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]PoloniexTradeHistory, error) { vals := url.Values{} vals.Set("currencyPair", currencyPair) @@ -172,15 +177,12 @@ func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]PoloniexT } resp := []PoloniexTradeHistory{} - path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) + path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", poloniexAPIURL, vals.Encode()) - if err != nil { - return nil, err - } - return resp, nil + return resp, common.SendHTTPGetRequest(path, true, p.Verbose, &resp) } +// GetChartData returns chart data for a specific currency pair func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]PoloniexChartData, error) { vals := url.Values{} vals.Set("currencyPair", currencyPair) @@ -198,43 +200,39 @@ func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]Polo } resp := []PoloniexChartData{} - path := fmt.Sprintf("%s/public?command=returnChartData&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) + path := fmt.Sprintf("%s/public?command=returnChartData&%s", poloniexAPIURL, vals.Encode()) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return nil, err } + return resp, nil } +// GetCurrencies returns information about currencies func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { type Response struct { Data map[string]PoloniexCurrencies } resp := Response{} - path := fmt.Sprintf("%s/public?command=returnCurrencies", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) + path := fmt.Sprintf("%s/public?command=returnCurrencies", poloniexAPIURL) - if err != nil { - return resp.Data, err - } - return resp.Data, nil + return resp.Data, common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) } +// GetLoanOrders returns the list of loan offers and demands for a given +// currency, specified by the "currency" GET parameter. func (p *Poloniex) GetLoanOrders(currency string) (PoloniexLoanOrders, error) { resp := PoloniexLoanOrders{} - path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", POLONIEX_API_URL, currency) - err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) + path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", poloniexAPIURL, currency) - if err != nil { - return resp, err - } - return resp, nil + return resp, common.SendHTTPGetRequest(path, true, p.Verbose, &resp) } func (p *Poloniex) GetBalances() (PoloniexBalance, error) { var result interface{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_BALANCES, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexBalances, url.Values{}, &result) if err != nil { return PoloniexBalance{}, err @@ -257,7 +255,7 @@ type PoloniexCompleteBalances struct { func (p *Poloniex) GetCompleteBalances() (PoloniexCompleteBalances, error) { var result interface{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_BALANCES_COMPLETE, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexBalancesComplete, url.Values{}, &result) if err != nil { return PoloniexCompleteBalances{}, err @@ -282,7 +280,7 @@ func (p *Poloniex) GetCompleteBalances() (PoloniexCompleteBalances, error) { func (p *Poloniex) GetDepositAddresses() (PoloniexDepositAddresses, error) { var result interface{} addresses := PoloniexDepositAddresses{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_DEPOSIT_ADDRESSES, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexDepositAddresses, url.Values{}, &result) if err != nil { return addresses, err @@ -307,7 +305,7 @@ func (p *Poloniex) GenerateNewAddress(currency string) (string, error) { values := url.Values{} values.Set("currency", currency) - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_GENERATE_NEW_ADDRESS, values, &resp) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexGenerateNewAddress, values, &resp) if err != nil { return "", err @@ -336,7 +334,7 @@ func (p *Poloniex) GetDepositsWithdrawals(start, end string) (PoloniexDepositsWi values.Set("end", strconv.FormatInt(time.Now().Unix(), 10)) } - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_DEPOSITS_WITHDRAWALS, values, &resp) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexDepositsWithdrawals, values, &resp) if err != nil { return resp, err @@ -351,7 +349,7 @@ func (p *Poloniex) GetOpenOrders(currency string) (interface{}, error) { if currency != "" { values.Set("currencyPair", currency) result := PoloniexOpenOrdersResponse{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_ORDERS, values, &result.Data) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexOrders, values, &result.Data) if err != nil { return result, err @@ -361,7 +359,7 @@ func (p *Poloniex) GetOpenOrders(currency string) (interface{}, error) { } else { values.Set("currencyPair", "all") result := PoloniexOpenOrdersResponseAll{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_ORDERS, values, &result.Data) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexOrders, values, &result.Data) if err != nil { return result, err @@ -385,7 +383,7 @@ func (p *Poloniex) GetAuthenticatedTradeHistory(currency, start, end string) (in if currency != "" && currency != "all" { values.Set("currencyPair", currency) result := PoloniexAuthenticatedTradeHistoryResponse{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_TRADE_HISTORY, values, &result.Data) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexTradeHistory, values, &result.Data) if err != nil { return result, err @@ -395,7 +393,7 @@ func (p *Poloniex) GetAuthenticatedTradeHistory(currency, start, end string) (in } else { values.Set("currencyPair", "all") result := PoloniexAuthenticatedTradeHistoryAll{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_TRADE_HISTORY, values, &result.Data) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexTradeHistory, values, &result.Data) if err != nil { return result, err @@ -411,9 +409,9 @@ func (p *Poloniex) PlaceOrder(currency string, rate, amount float64, immediate, var orderType string if buy { - orderType = POLONIEX_ORDER_BUY + orderType = poloniexOrderBuy } else { - orderType = POLONIEX_ORDER_SELL + orderType = poloniexOrderSell } values.Set("currencyPair", currency) @@ -442,7 +440,7 @@ func (p *Poloniex) CancelOrder(orderID int64) (bool, error) { values := url.Values{} values.Set("orderNumber", strconv.FormatInt(orderID, 10)) - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_ORDER_CANCEL, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexOrderCancel, values, &result) if err != nil { return false, err @@ -465,7 +463,7 @@ func (p *Poloniex) MoveOrder(orderID int64, rate, amount float64) (PoloniexMoveO values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) } - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_ORDER_MOVE, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexOrderMove, values, &result) if err != nil { return result, err @@ -486,7 +484,7 @@ func (p *Poloniex) Withdraw(currency, address string, amount float64) (bool, err values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) values.Set("address", address) - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_WITHDRAW, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexWithdraw, values, &result) if err != nil { return false, err @@ -501,7 +499,7 @@ func (p *Poloniex) Withdraw(currency, address string, amount float64) (bool, err func (p *Poloniex) GetFeeInfo() (PoloniexFee, error) { result := PoloniexFee{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_FEE_INFO, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexFeeInfo, url.Values{}, &result) if err != nil { return result, err @@ -516,7 +514,7 @@ func (p *Poloniex) GetTradableBalances() (map[string]map[string]float64, error) } result := Response{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_TRADABLE_BALANCES, url.Values{}, &result.Data) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexTradableBalances, url.Values{}, &result.Data) if err != nil { return nil, err @@ -543,7 +541,7 @@ func (p *Poloniex) TransferBalance(currency, from, to string, amount float64) (b values.Set("fromAccount", from) values.Set("toAccount", to) - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_TRANSFER_BALANCE, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexTransferBalance, values, &result) if err != nil { return false, err @@ -558,7 +556,7 @@ func (p *Poloniex) TransferBalance(currency, from, to string, amount float64) (b func (p *Poloniex) GetMarginAccountSummary() (PoloniexMargin, error) { result := PoloniexMargin{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_MARGIN_ACCOUNT_SUMMARY, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexMarginAccountSummary, url.Values{}, &result) if err != nil { return result, err @@ -573,9 +571,9 @@ func (p *Poloniex) PlaceMarginOrder(currency string, rate, amount, lendingRate f var orderType string if buy { - orderType = POLONIEX_MARGIN_BUY + orderType = poloniexMarginBuy } else { - orderType = POLONIEX_MARGIN_SELL + orderType = poloniexMarginSell } values.Set("currencyPair", currency) @@ -601,7 +599,7 @@ func (p *Poloniex) GetMarginPosition(currency string) (interface{}, error) { if currency != "" && currency != "all" { values.Set("currencyPair", currency) result := PoloniexMarginPosition{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_MARGIN_POSITION, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexMarginPosition, values, &result) if err != nil { return result, err @@ -616,7 +614,7 @@ func (p *Poloniex) GetMarginPosition(currency string) (interface{}, error) { } result := Response{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_MARGIN_POSITION, values, &result.Data) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexMarginPosition, values, &result.Data) if err != nil { return result, err @@ -631,7 +629,7 @@ func (p *Poloniex) CloseMarginPosition(currency string) (bool, error) { values.Set("currencyPair", currency) result := PoloniexGenericResponse{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_MARGIN_POSITION_CLOSE, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexMarginPositionClose, values, &result) if err != nil { return false, err @@ -666,7 +664,7 @@ func (p *Poloniex) CreateLoanOffer(currency string, amount, rate float64, durati result := Response{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_CREATE_LOAN_OFFER, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexCreateLoanOffer, values, &result) if err != nil { return 0, err @@ -684,7 +682,7 @@ func (p *Poloniex) CancelLoanOffer(orderNumber int64) (bool, error) { values := url.Values{} values.Set("orderID", strconv.FormatInt(orderNumber, 10)) - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_CANCEL_LOAN_OFFER, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexCancelLoanOffer, values, &result) if err != nil { return false, err @@ -703,7 +701,7 @@ func (p *Poloniex) GetOpenLoanOffers() (map[string][]PoloniexLoanOffer, error) { } result := Response{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_OPEN_LOAN_OFFERS, url.Values{}, &result.Data) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexOpenLoanOffers, url.Values{}, &result.Data) if err != nil { return nil, err @@ -718,7 +716,7 @@ func (p *Poloniex) GetOpenLoanOffers() (map[string][]PoloniexLoanOffer, error) { func (p *Poloniex) GetActiveLoans() (PoloniexActiveLoans, error) { result := PoloniexActiveLoans{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_ACTIVE_LOANS, url.Values{}, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexActiveLoans, url.Values{}, &result) if err != nil { return result, err @@ -739,7 +737,7 @@ func (p *Poloniex) GetLendingHistory(start, end string) ([]PoloniexLendingHistor } resp := []PoloniexLendingHistory{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_LENDING_HISTORY, vals, &resp) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexLendingHistory, vals, &resp) if err != nil { return nil, err @@ -752,7 +750,7 @@ func (p *Poloniex) ToggleAutoRenew(orderNumber int64) (bool, error) { values.Set("orderNumber", strconv.FormatInt(orderNumber, 10)) result := PoloniexGenericResponse{} - err := p.SendAuthenticatedHTTPRequest("POST", POLONIEX_AUTO_RENEW, values, &result) + err := p.SendAuthenticatedHTTPRequest("POST", poloniexAutoRenew, values, &result) if err != nil { return false, err @@ -784,7 +782,7 @@ func (p *Poloniex) SendAuthenticatedHTTPRequest(method, endpoint string, values hmac := common.GetHMAC(common.HashSHA512, []byte(values.Encode()), []byte(p.APISecret)) headers["Sign"] = common.HexEncodeToString(hmac) - path := fmt.Sprintf("%s/%s", POLONIEX_API_URL, POLONIEX_API_TRADING_ENDPOINT) + path := fmt.Sprintf("%s/%s", poloniexAPIURL, poloniexAPITradingEndpoint) resp, err := common.SendHTTPRequest(method, path, headers, bytes.NewBufferString(values.Encode())) if err != nil { diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go new file mode 100644 index 00000000..7ac8f683 --- /dev/null +++ b/exchanges/poloniex/poloniex_test.go @@ -0,0 +1,90 @@ +package poloniex + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var p Poloniex + +// Please supply your own APIKEYS here for due diligence testing + +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + p.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.json") + poloniexConfig, err := cfg.GetExchangeConfig("Poloniex") + if err != nil { + t.Error("Test Failed - Poloniex Setup() init error") + } + + poloniexConfig.AuthenticatedAPISupport = true + poloniexConfig.APIKey = apiKey + poloniexConfig.APISecret = apiSecret + + p.Setup(poloniexConfig) +} + +func TestGetFee(t *testing.T) { + if p.GetFee() != 0 { + t.Error("Test faild - Poloniex GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + _, err := p.GetTicker() + if err != nil { + t.Error("Test faild - Poloniex GetTicker() error") + } +} + +func TestGetVolume(t *testing.T) { + _, err := p.GetVolume() + if err != nil { + t.Error("Test faild - Poloniex GetVolume() error") + } +} + +func TestGetOrderbook(t *testing.T) { + _, err := p.GetOrderbook("BTC_XMR", 50) + if err != nil { + t.Error("Test faild - Poloniex GetOrderbook() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + _, err := p.GetTradeHistory("BTC_XMR", "", "") + if err != nil { + t.Error("Test faild - Poloniex GetTradeHistory() error", err) + } +} + +func TestGetChartData(t *testing.T) { + _, err := p.GetChartData("BTC_XMR", "1405699200", "1405699400", "300") + if err != nil { + t.Error("Test faild - Poloniex GetChartData() error", err) + } +} + +func TestGetCurrencies(t *testing.T) { + _, err := p.GetCurrencies() + if err != nil { + t.Error("Test faild - Poloniex GetCurrencies() error", err) + } +} + +func TestGetLoanOrders(t *testing.T) { + _, err := p.GetLoanOrders("BTC") + if err != nil { + t.Error("Test faild - Poloniex GetLoanOrders() error", err) + } +} diff --git a/exchanges/poloniex/poloniex_types.go b/exchanges/poloniex/poloniex_types.go index 013ba5d1..cb6b597c 100644 --- a/exchanges/poloniex/poloniex_types.go +++ b/exchanges/poloniex/poloniex_types.go @@ -16,6 +16,7 @@ type PoloniexOrderbookResponse struct { Asks [][]interface{} `json:"asks"` Bids [][]interface{} `json:"bids"` IsFrozen string `json:"isFrozen"` + Error string `json:"error"` } type PoloniexOrderbookItem struct { @@ -47,6 +48,7 @@ type PoloniexChartData struct { Volume float64 `json:"volume"` QuoteVolume float64 `json:"quoteVolume"` WeightedAverage float64 `json:"weightedAverage"` + Error string `json:"error"` } type PoloniexCurrencies struct { diff --git a/exchanges/wex/wex_test.go b/exchanges/wex/wex_test.go index 200e5270..6e81be91 100644 --- a/exchanges/wex/wex_test.go +++ b/exchanges/wex/wex_test.go @@ -20,7 +20,7 @@ func TestSetDefaults(t *testing.T) { func TestSetup(t *testing.T) { wexConfig := config.GetConfig() - wexConfig.LoadConfig("../../testdata/configtest.dat") + wexConfig.LoadConfig("../../testdata/configtest.json") conf, err := wexConfig.GetExchangeConfig("WEX") if err != nil { t.Error("Test Failed - WEX init error") diff --git a/smsglobal/smsglobal.go b/smsglobal/smsglobal.go index d5cc5aab..86c98761 100644 --- a/smsglobal/smsglobal.go +++ b/smsglobal/smsglobal.go @@ -36,7 +36,7 @@ type Base struct { SendFrom string `json:"send_from"` } -// New initalises the SMSGlobal var +// New initialises the SMSGlobal var func New(username, password, sendFrom string, contacts []Contact) *Base { if username == "" || password == "" || sendFrom == "" || len(contacts) == 0 { return nil diff --git a/smsglobal/smsglobal_test.go b/smsglobal/smsglobal_test.go index 8fcd287f..b4d9eab1 100644 --- a/smsglobal/smsglobal_test.go +++ b/smsglobal/smsglobal_test.go @@ -52,7 +52,7 @@ func TestGetContactByNumber(t *testing.T) { _, err = result.GetContactByNumber("ASDASDASD") if err == nil { - t.Fatal("Test failed. TestGetContactByNumber: Returned nil err on non-existant number") + t.Fatal("Test failed. TestGetContactByNumber: Returned nil err on non-existent number") } } @@ -73,7 +73,7 @@ func TestGetContactByName(t *testing.T) { _, err = result.GetContactByName("ASDASDASD") if err == nil { - t.Fatal("Test failed. TestGetContactByName: Returned nil err on non-existant number") + t.Fatal("Test failed. TestGetContactByName: Returned nil err on non-existent number") } } diff --git a/testdata/configtest.dat b/testdata/configtest.json similarity index 97% rename from testdata/configtest.dat rename to testdata/configtest.json index ca7b90f1..79c8ac01 100644 --- a/testdata/configtest.dat +++ b/testdata/configtest.json @@ -62,6 +62,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -84,6 +85,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -104,6 +106,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -125,6 +128,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -147,6 +151,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -167,6 +172,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -189,6 +195,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -209,6 +216,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -231,6 +239,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -251,6 +260,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -271,6 +281,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -292,6 +303,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -313,6 +325,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -333,6 +346,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -356,6 +370,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -376,6 +391,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -397,6 +413,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", @@ -418,6 +435,7 @@ "Enabled": true, "Verbose": false, "Websocket": false, + "UseSandbox": false, "RESTPollingDelay": 10, "AuthenticatedAPISupport": false, "APIKey": "Key", diff --git a/tools/config/config.go b/tools/config/config.go index a9eeea50..49201a67 100644 --- a/tools/config/config.go +++ b/tools/config/config.go @@ -20,8 +20,9 @@ func main() { var inFile, outFile, key string var encrypt bool var err error - flag.StringVar(&inFile, "infile", "config.dat", "The config input file to process.") - flag.StringVar(&outFile, "outfile", "config.dat.out", "The config output file.") + configFile := config.GetFilePath("") + flag.StringVar(&inFile, "infile", configFile, "The config input file to process.") + flag.StringVar(&outFile, "outfile", configFile+".out", "The config output file.") flag.BoolVar(&encrypt, "encrypt", true, "Wether to encrypt or decrypt.") flag.StringVar(&key, "key", "", "The key to use for AES encryption.") flag.Parse() diff --git a/tools/portfolio/portfolio.go b/tools/portfolio/portfolio.go index bc47dbd1..509057e8 100644 --- a/tools/portfolio/portfolio.go +++ b/tools/portfolio/portfolio.go @@ -57,7 +57,7 @@ func getOnlineOfflinePortfolio(coins []portfolio.Coin, online bool) { func main() { var inFile, key string - flag.StringVar(&inFile, "infile", "config.dat", "The config input file to process.") + flag.StringVar(&inFile, "infile", config.GetFilePath(""), "The config input file to process.") flag.StringVar(&key, "key", "", "The key to use for AES encryption.") flag.Parse()