diff --git a/.gitignore b/.gitignore index bbd9d210..c93b5407 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ config.json config.dat node_modules lib -.vscode \ No newline at end of file +.vscode + +testdata/dump +testdata/writefiletest \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 52f76868..f85e8420 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,13 @@ language: go go: - 1.8.x - - master + #- master before_install: - go get -t -v ./... script: - - ./test.sh + - ./testdata/test.sh install: - go get github.com/gorilla/websocket diff --git a/README.md b/README.md index 07a958b5..9841947f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -## Cryptocurrency trading bot written in Golang +# Cryptocurrency trading bot written in Golang + [![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader) [![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE) [![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader) @@ -21,6 +22,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | ANXPRO | Yes | No | NA | | Bitfinex | Yes | Yes | NA | | Bitstamp | Yes | Yes | NA | +| Bittrex | Yes | No | NA | | BTCC | Yes | Yes | No | | BTCE | Yes | NA | NA | | BTCMarkets | Yes | NA | NA | @@ -41,6 +43,7 @@ We are aiming to support the top 20 highest volume exchanges based off the [Coin ** NA means not applicable as the Exchange does not support the feature. ## Current Features + + Support for all Exchange fiat and digital currencies, with the ability to individually toggle them on/off. + AES encrypted config file. + REST API support for all exchanges. @@ -54,6 +57,7 @@ We are aiming to support the top 20 highest volume exchanges based off the [Coin + WebGUI. ## Planned Features + Planned features can be found on our [community Trello page](https://trello.com/b/ZAhMhpOy/gocryptotrader). ## Contribution @@ -62,24 +66,29 @@ Please feel free to submit any pull requests or suggest any desired features to When submitting a PR, please abide by our coding guidelines: -* Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). -* Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. -* Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md). -* Pull requests need to be based on and opened against the `master` branch. ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. ## Compiling instructions -Download and install Go from https://golang.org/dl/ + +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 ``` -Make any neccessary changes to the config file. -Run the application! + +Make any neccessary changes to the config file. +Run the application! ## Donations + If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: 1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB ## Binaries + Binaries will be published once the codebase reaches a stable condition. diff --git a/common/common.go b/common/common.go index 2921454f..0bf1791e 100644 --- a/common/common.go +++ b/common/common.go @@ -159,6 +159,11 @@ func TrimString(input, cutset string) string { return strings.Trim(input, cutset) } +// ReplaceString replaces a string with another +func ReplaceString(input, old, new string, n int) string { + return strings.Replace(input, old, new, n) +} + // StringToUpper changes strings to uppercase func StringToUpper(input string) string { return strings.ToUpper(input) @@ -378,7 +383,8 @@ func OutputCSV(path string, data [][]string) error { return err } - defer writer.Flush() + writer.Flush() + file.Close() return nil } @@ -415,6 +421,11 @@ func WriteFile(file string, data []byte) error { return nil } +// RemoveFile removes a file +func RemoveFile(file string) error { + return os.Remove(file) +} + // GetURIPath returns the path of a URL given a URI func GetURIPath(uri string) string { urip, err := url.Parse(uri) diff --git a/common/common_test.go b/common/common_test.go index 6c5eca04..806c9d85 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -310,6 +310,28 @@ func TestTrimString(t *testing.T) { } } +// ReplaceString replaces a string with another +func TestReplaceString(t *testing.T) { + t.Parallel() + currency := "BTC-USD" + expectedOutput := "BTCUSD" + + actualResult := ReplaceString(currency, "-", "", -1) + if expectedOutput != actualResult { + t.Errorf( + "Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult, + ) + } + + currency = "BTC-USD--" + actualResult = ReplaceString(currency, "-", "", 3) + if expectedOutput != actualResult { + t.Errorf( + "Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult, + ) + } +} + func TestRoundFloat(t *testing.T) { t.Parallel() originalInput := float64(1.4545445445) @@ -630,6 +652,22 @@ func TestWriteFile(t *testing.T) { } } +func TestRemoveFile(t *testing.T) { + TestWriteFile(t) + path := "../testdata/writefiletest" + err := RemoveFile(path) + if err != nil { + t.Errorf("Test failed. Common RemoveFile error: %s", err) + } + + TestOutputCSV(t) + path = "../testdata/dump" + err = RemoveFile(path) + if err != nil { + t.Errorf("Test failed. Common RemoveFile error: %s", err) + } +} + func TestGetURIPath(t *testing.T) { t.Parallel() // mapping of input vs expected result diff --git a/config/config.go b/config/config.go index 77372155..1409efbe 100644 --- a/config/config.go +++ b/config/config.go @@ -75,16 +75,23 @@ type Post struct { Data Config `json:"Data"` } +// CurrencyPairFormatConfig stores the users preferred currency pair display +type CurrencyPairFormatConfig struct { + Uppercase bool + Delimiter string +} + // Config is the overarching object that holds all the information for // prestart management of portfolio, SMSGlobal, webserver and enabled exchange type Config struct { - Name string - EncryptConfig int - Cryptocurrencies string - Portfolio portfolio.Base `json:"PortfolioAddresses"` - SMS SMSGlobalConfig `json:"SMSGlobal"` - Webserver WebserverConfig `json:"Webserver"` - Exchanges []ExchangeConfig `json:"Exchanges"` + Name string + EncryptConfig int + Cryptocurrencies string + CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat"` + Portfolio portfolio.Base `json:"PortfolioAddresses"` + SMS SMSGlobalConfig `json:"SMSGlobal"` + Webserver WebserverConfig `json:"Webserver"` + Exchanges []ExchangeConfig `json:"Exchanges"` } // ExchangeConfig holds all the information needed for each enabled Exchange. @@ -114,6 +121,11 @@ func (c *Config) GetConfigEnabledExchanges() int { return counter } +// GetCurrencyPairDisplayConfig retrieves the currency pair display preference +func (c *Config) GetCurrencyPairDisplayConfig() *CurrencyPairFormatConfig { + return c.CurrencyPairFormat +} + // GetExchangeConfig returns your exchange configurations by its indivdual name func (c *Config) GetExchangeConfig(name string) (ExchangeConfig, error) { for i := range c.Exchanges { @@ -396,6 +408,13 @@ func (c *Config) LoadConfig(configPath string) error { return fmt.Errorf(ErrCheckingConfigValues, err) } + if c.CurrencyPairFormat == nil { + c.CurrencyPairFormat = &CurrencyPairFormatConfig{ + Delimiter: "-", + Uppercase: true, + } + } + return nil } diff --git a/config/config_test.go b/config/config_test.go index a47c10e1..f31c5abd 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -19,6 +19,22 @@ func TestGetConfigEnabledExchanges(t *testing.T) { } } +func TestGetCurrencyPairDisplayConfig(t *testing.T) { + cfg := GetConfig() + err := cfg.LoadConfig(ConfigTestFile) + if err != nil { + t.Errorf( + "Test failed. GetCurrencyPairDisplayConfig. LoadConfig Error: %s", err.Error(), + ) + } + settings := cfg.GetCurrencyPairDisplayConfig() + if settings.Delimiter != "-" || !settings.Uppercase { + t.Errorf( + "Test failed. GetCurrencyPairDisplayConfi. Invalid values", + ) + } +} + func TestGetExchangeConfig(t *testing.T) { GetExchangeConfig := GetConfig() err := GetExchangeConfig.LoadConfig(ConfigTestFile) diff --git a/config_example.dat b/config_example.dat index 2120fa62..65d01df0 100644 --- a/config_example.dat +++ b/config_example.dat @@ -2,6 +2,10 @@ "Name": "Skynet", "EncryptConfig": 0, "Cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH", + "CurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + }, "PortfolioAddresses": { "Addresses": [ { @@ -87,6 +91,20 @@ "EnabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", "BaseCurrencies": "USD,EUR" }, + { + "Name": "Bittrex", + "Enabled": true, + "Verbose": false, + "Websocket": false, + "RESTPollingDelay": 10, + "AuthenticatedAPISupport": false, + "APIKey": "Key", + "APISecret": "Secret", + "ClientID": "", + "AvailablePairs": "BTCLTC,BTCDOGE,BTCVTC,BTCPPC,BTCFTC,BTCRDD,BTCNXT,BTCDASH,BTCPOT,BTCBLK,BTCEMC2,BTCXMY,BTCAUR,BTCEFL,BTCGLD,BTCSLR,BTCPTC,BTCGRS,BTCNLG,BTCRBY,BTCXWC,BTCMONA,BTCTHC,BTCENRG,BTCERC,BTCNAUT,BTCVRC,BTCCURE,BTCXBB,BTCXMR,BTCCLOAK,BTCSTART,BTCKORE,BTCXDN,BTCTRUST,BTCNAV,BTCXST,BTCBTCD,BTCVIA,BTCUNO,BTCPINK,BTCIOC,BTCCANN,BTCSYS,BTCNEOS,BTCDGB,BTCBURST,BTCEXCL,BTCSWIFT,BTCDOPE,BTCBLOCK,BTCABY,BTCBYC,BTCXMG,BTCBLITZ,BTCBAY,BTCBTS,BTCFAIR,BTCSPR,BTCVTR,BTCXRP,BTCGAME,BTCCOVAL,BTCNXS,BTCXCP,BTCBITB,BTCGEO,BTCFLDC,BTCGRC,BTCFLO,BTCNBT,BTCMUE,BTCXEM,BTCCLAM,BTCDMD,BTCGAM,BTCSPHR,BTCOK,BTCSNRG,BTCPKB,BTCCPC,BTCAEON,BTCETH,BTCGCR,BTCTX,BTCBCY,BTCEXP,BTCINFX,BTCOMNI,BTCAMP,BTCAGRS,BTCXLM,BTCBTA,USDTBTC,BITCNYBTC,BTCCLUB,BTCVOX,BTCEMC,BTCFCT,BTCMAID,BTCEGC,BTCSLS,BTCRADS,BTCDCR,BTCSAFEX,BTCBSD,BTCXVG,BTCPIVX,BTCXVC,BTCMEME,BTCSTEEM,BTC2GIVE,BTCLSK,BTCPDC,BTCBRK,BTCDGD,ETHDGD,BTCWAVES,BTCRISE,BTCLBC,BTCSBD,BTCBRX,BTCDRACO,BTCETC,ETHETC,BTCSTRAT,BTCUNB,BTCSYNX,BTCTRIG,BTCEBST,BTCVRM,BTCSEQ,BTCXAUR,BTCSNGLS,BTCREP,BTCSHIFT,BTCARDR,BTCXZC,BTCNEO,BTCZEC,BTCZCL,BTCIOP,BTCDAR,BTCGOLOS,BTCHKG,BTCUBQ,BTCKMD,BTCGBG,BTCSIB,BTCION,BTCLMC,BTCQWARK,BTCCRW,BTCSWT,BTCTIME,BTCMLN,BTCARK,BTCDYN,BTCTKS,BTCMUSIC,BTCDTB,BTCINCNT,BTCGBYTE,BTCGNT,BTCNXC,BTCEDG,BTCLGD,BTCTRST,ETHGNT,ETHREP,USDTETH,ETHWINGS,BTCWINGS,BTCRLC,BTCGNO,BTCGUP,BTCLUN,ETHGUP,ETHRLC,ETHLUN,ETHSNGLS,ETHGNO,BTCAPX,BTCTKN,ETHTKN,BTCHMQ,ETHHMQ,BTCANT,ETHTRST,ETHANT,BTCSC,ETHBAT,BTCBAT,BTCZEN,BTC1ST,BTCQRL,ETH1ST,ETHQRL,BTCCRB,ETHCRB,ETHLGD,BTCPTOY,ETHPTOY,BTCMYST,ETHMYST,BTCCFI,ETHCFI,BTCBNT,ETHBNT,BTCNMR,ETHNMR,ETHTIME,ETHLTC,ETHXRP,BTCSNT,ETHSNT,BTCDCT,BTCXEL,BTCMCO,ETHMCO,BTCADT,ETHADT,BTCFUN,ETHFUN,BTCPAY,ETHPAY,BTCMTL,ETHMTL,BTCSTORJ,ETHSTORJ,BTCADX,ETHADX,ETHDASH,ETHSC,ETHZEC,USDTZEC,USDTLTC,USDTETC,USDTXRP,BTCOMG,ETHOMG,BTCCVC,ETHCVC,BTCPART,BTCQTUM,ETHQTUM,ETHXMR,ETHXEM,ETHXLM,ETHNEO,USDTXMR,USDTDASH,ETHBCC,USDTBCC,BTCBCC,USDTNEO,ETHWAVES,ETHSTRAT,ETHDGB,ETHFCT,ETHBTS", + "EnabledPairs": "BTCLTC,BTCDOGE,BTCDASH", + "BaseCurrencies": "USD" + }, { "Name": "BTCC", "Enabled": true, diff --git a/currency/pair/pair.go b/currency/pair/pair.go index 4ab23c97..3e0c8b3e 100644 --- a/currency/pair/pair.go +++ b/currency/pair/pair.go @@ -43,6 +43,23 @@ func (c CurrencyPair) Pair() CurrencyItem { return c.FirstCurrency + CurrencyItem(c.Delimiter) + c.SecondCurrency } +// Display formats and returns the currency based on user preferences, +// overriding the default Pair() display +func (c CurrencyPair) Display(delimiter string, uppercase bool) CurrencyItem { + var pair CurrencyItem + + if delimiter != "" { + pair = c.FirstCurrency + CurrencyItem(delimiter) + c.SecondCurrency + } else { + pair = c.FirstCurrency + c.SecondCurrency + } + + if uppercase { + return pair.Upper() + } + return pair.Lower() +} + // NewCurrencyPairDelimiter splits the desired currency string at delimeter, // the returns a CurrencyPair struct func NewCurrencyPairDelimiter(currency, delimiter string) CurrencyPair { diff --git a/currency/pair/pair_test.go b/currency/pair/pair_test.go index cb6213ac..91abf22a 100644 --- a/currency/pair/pair_test.go +++ b/currency/pair/pair_test.go @@ -74,6 +74,37 @@ func TestPair(t *testing.T) { } } +func TestDisplay(t *testing.T) { + t.Parallel() + pair := NewCurrencyPairDelimiter("BTC-USD", "-") + actual := pair.Pair() + expected := CurrencyItem("BTC-USD") + if actual != expected { + t.Errorf( + "Test failed. Pair(): %s was not equal to expected value: %s", + actual, expected, + ) + } + + actual = pair.Display("", false) + expected = CurrencyItem("btcusd") + if actual != expected { + t.Errorf( + "Test failed. Pair(): %s was not equal to expected value: %s", + actual, expected, + ) + } + + actual = pair.Display("~", true) + expected = CurrencyItem("BTC~USD") + if actual != expected { + t.Errorf( + "Test failed. Pair(): %s was not equal to expected value: %s", + actual, expected, + ) + } +} + func TestNewCurrencyPair(t *testing.T) { t.Parallel() pair := NewCurrencyPair("BTC", "USD") diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 4462ee3a..8da73698 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -531,17 +531,21 @@ func (a *Alphapoint) SendRequest(method, path string, data map[string]interface{ // SendAuthenticatedHTTPRequest sends an authenticated request func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[string]interface{}, result interface{}) error { + if !a.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, a.Name) + } + + if a.Nonce.Get() == 0 { + a.Nonce.Set(time.Now().UnixNano()) + } else { + a.Nonce.Inc() + } + headers := make(map[string]string) headers["Content-Type"] = "application/json" data["apiKey"] = a.APIKey - nonce := time.Now().UnixNano() - nonceStr := strconv.FormatInt(nonce, 10) - data["apiNonce"] = nonce - hmac := common.GetHMAC( - common.HashSHA256, - []byte(nonceStr+a.ClientID+a.APIKey), - []byte(a.APISecret), - ) + data["apiNonce"] = a.Nonce.Get() + hmac := common.GetHMAC(common.HashSHA256, []byte(a.Nonce.String()+a.ClientID+a.APIKey), []byte(a.APISecret)) data["apiSig"] = common.StringToUpper(common.HexEncodeToString(hmac)) path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, alphapointAPIVersion, path) diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index b136dcba..912f421c 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -1,8 +1,16 @@ package alphapoint import ( - "reflect" "testing" + + "github.com/thrasher-/gocryptotrader/common" +) + +const ( + onlineTest = false + + testAPIKey = "" + testAPISecret = "" ) func TestSetDefaults(t *testing.T) { @@ -18,237 +26,121 @@ func TestSetDefaults(t *testing.T) { } } +func testSetAPIKey(a *Alphapoint) { + a.APIKey = testAPIKey + a.APISecret = testAPISecret + a.AuthenticatedAPISupport = true +} + +func testIsAPIKeysSet(a *Alphapoint) bool { + if testAPIKey != "" && testAPISecret != "" && a.AuthenticatedAPISupport { + return true + } + return false +} func TestGetTicker(t *testing.T) { - GetTicker := Alphapoint{} - GetTicker.SetDefaults() + alpha := Alphapoint{} + alpha.SetDefaults() - response, err := GetTicker.GetTicker("BTCUSD") - if err != nil { - t.Error("Test Failed - Alphapoint GetTicker init error: ", err) - } - if reflect.ValueOf(response).NumField() != 13 { - t.Error("Test Failed - Alphapoint GetTicker struct change/or updated") - } - if reflect.TypeOf(response.Ask).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.Ask value is not a float64") - } - if reflect.TypeOf(response.Bid).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.Bid value is not a float64") - } - if reflect.TypeOf(response.BuyOrderCount).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.BuyOrderCount value is not a float64") - } - if reflect.TypeOf(response.High).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.High value is not a float64") - } - if reflect.TypeOf(response.IsAccepted).String() != "bool" { - t.Error("Test Failed - Alphapoint GetTicker.IsAccepted value is not a bool") - } - if reflect.TypeOf(response.Last).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.Last value is not a float64") - } - if reflect.TypeOf(response.Low).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.Low value is not a float64") - } - if reflect.TypeOf(response.NumOfCreateOrders).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.NumOfCreateOrders value is not a float64") - } - if reflect.TypeOf(response.RejectReason).String() != "string" { - t.Error("Test Failed - Alphapoint GetTicker.RejectReason value is not a string") - } - if reflect.TypeOf(response.SellOrderCount).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.SellOrderCount value is not a float64") - } - if reflect.TypeOf(response.Total24HrNumTrades).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.Total24HrNumTrades value is not a float64") - } - if reflect.TypeOf(response.Total24HrQtyTraded).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.Total24HrQtyTraded value is not a float64") - } - if reflect.TypeOf(response.Volume).String() != "float64" { - t.Error("Test Failed - Alphapoint GetTicker.Volume value is not a float64") + var ticker Ticker + var err error + + if onlineTest { + ticker, err = alpha.GetTicker("BTCUSD") + if err != nil { + t.Fatal("Test Failed - Alphapoint GetTicker init error: ", err) + } + + _, err = alpha.GetTicker("wigwham") + if err == nil { + t.Error("Test Failed - Alphapoint GetTicker error") + } + } else { + mockResp := []byte( + string(`{"high":253.101,"last":249.76,"bid":248.8901,"volume":5.813354,"low":231.21,"ask":248.9012,"Total24HrQtyTraded":52.654968,"Total24HrProduct2Traded":569.05762,"Total24HrNumTrades":4,"sellOrderCount":7,"buyOrderCount":11,"numOfCreateOrders":0,"isAccepted":true}`), + ) + + err = common.JSONDecode(mockResp, &ticker) + if err != nil { + t.Fatal("Test Failed - Alphapoint GetTicker unmarshalling error: ", err) + } + + if ticker.Last != 249.76 { + t.Error("Test failed - Alphapoint GetTicker expected last = 249.76") + } } - if response.Ask < 0 { - t.Error("Test Failed - Alphapoint GetTicker.Ask value is negative") - } - if response.Bid < 0 { - t.Error("Test Failed - Alphapoint GetTicker.Bid value is negative") - } - if response.BuyOrderCount < 0 { - t.Error("Test Failed - Alphapoint GetTicker.High value is negative") - } - if response.High < 0 { - t.Error("Test Failed - Alphapoint GetTicker.Last value is negative") - } - if response.Last < 0 { - t.Error("Test Failed - Alphapoint GetTicker.Low value is negative") - } - if response.Low < 0 { - t.Error("Test Failed - Alphapoint GetTicker.Mid value is negative") - } - if response.NumOfCreateOrders < 0 { - t.Error("Test Failed - Alphapoint GetTicker.ask value is negative") - } - if response.SellOrderCount < 0 { - t.Error("Test Failed - Alphapoint GetTicker.ask value is negative") - } - if response.Total24HrNumTrades < 0 { - t.Error("Test Failed - Alphapoint GetTicker.ask value is negative") - } - if response.Total24HrQtyTraded < 0 { - t.Error("Test Failed - Alphapoint GetTicker.ask value is negative") - } - if response.Volume < 0 { - t.Error("Test Failed - Alphapoint GetTicker.ask value is negative") - } - - _, err = GetTicker.GetTicker("wigwham") - if err == nil { - t.Error("Test Failed - Alphapoint GetTicker error") + if ticker.Last < 0 { + t.Error("Test failed - Alphapoint GetTicker last < 0") } } func TestGetTrades(t *testing.T) { - GetTrades := Alphapoint{} - GetTrades.SetDefaults() + alpha := Alphapoint{} + alpha.SetDefaults() - trades, err := GetTrades.GetTrades("BTCUSD", 0, 10) - if err != nil { - t.Errorf("Test Failed - Init error: %s", err) - } - if reflect.ValueOf(trades).NumField() != 9 { - t.Error("Test Failed - Alphapoint AlphapointTrades struct updated/changed") - } - if len(trades.Trades) == 0 { - t.Error("Test Failed - Alphapoint trades.Trades: Incorrect length") - } - if reflect.ValueOf(trades.Trades[0]).NumField() != 8 { - t.Error("Test Failed - Alphapoint AlphapointTrades.Trades struct updated/changed") - } - if reflect.TypeOf(trades.Trades[0].BookServerOrderID).String() != "int" { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is not a int") - } - if reflect.TypeOf(trades.Trades[0].IncomingOrderSide).String() != "int" { - t.Error("Test Failed - Alphapoint trades.Trades.IncomingOrderSide value is not a int") - } - if reflect.TypeOf(trades.Trades[0].IncomingServerOrderID).String() != "int" { - t.Error("Test Failed - Alphapoint trades.Trades.IncomingServerOrderID value is not a int") - } - if reflect.TypeOf(trades.Trades[0].Price).String() != "float64" { - t.Error("Test Failed - Alphapoint trades.Trades.Price value is not a float64") - } - if reflect.TypeOf(trades.Trades[0].Quantity).String() != "float64" { - t.Error("Test Failed - Alphapoint trades.Trades.Quantity value is not a float64") - } - if reflect.TypeOf(trades.Trades[0].TID).String() != "int64" { - t.Error("Test Failed - Alphapoint trades.Trades.TID value is not a int64") - } - if reflect.TypeOf(trades.Trades[0].UTCTicks).String() != "int64" { - t.Error("Test Failed - Alphapoint trades.Trades.UTCTicks value is not a int64") - } - if reflect.TypeOf(trades.Trades[0].Unixtime).String() != "int" { - t.Error("Test Failed - Alphapoint trades.Trades.Unixtime value is not a int") - } - if reflect.TypeOf(trades.Count).String() != "int" { - t.Error("Test Failed - Alphapoint trades.Count value is not a int") - } - if reflect.TypeOf(trades.DateTimeUTC).String() != "int64" { - t.Error("Test Failed - Alphapoint trades.DateTimeUTC value is not a int64") - } - if reflect.TypeOf(trades.Instrument).String() != "string" { - t.Error("Test Failed - Alphapoint trades.Instrument value is not a string") - } - if reflect.TypeOf(trades.IsAccepted).String() != "bool" { - t.Error("Test Failed - Alphapoint trades.IsAccepted value is not a bool") - } - if reflect.TypeOf(trades.RejectReason).String() != "string" { - t.Error("Test Failed - Alphapoint trades.string value is not a string") - } - if reflect.TypeOf(trades.StartIndex).String() != "int" { - t.Error("Test Failed - Alphapoint trades.Count value is not a int") + var trades Trades + var err error + + if onlineTest { + trades, err = alpha.GetTrades("BTCUSD", 0, 10) + if err != nil { + t.Fatalf("Test Failed - Init error: %s", err) + } + + _, err = alpha.GetTrades("wigwham", 0, 10) + if err == nil { + t.Fatal("Test Failed - GetTrades error") + } + } else { + mockResp := []byte( + string(`{"isAccepted":true,"dateTimeUtc":635507981548085938,"ins":"BTCUSD","startIndex":0,"count":10,"trades":[{"tid":0,"px":231.8379,"qty":4.913,"unixtime":1399951989,"utcticks":635355487898355234,"incomingOrderSide":0,"incomingServerOrderId":2598,"bookServerOrderId":2588},{"tid":1,"px":7895.1487,"qty":0.25,"unixtime":1403143708,"utcticks":635387405087297421,"incomingOrderSide":0,"incomingServerOrderId":284241,"bookServerOrderId":284235},{"tid":2,"px":7935.058,"qty":0.25,"unixtime":1403195348,"utcticks":635387921488684140,"incomingOrderSide":0,"incomingServerOrderId":575845,"bookServerOrderId":574078},{"tid":3,"px":7935.0448,"qty":0.25,"unixtime":1403195378,"utcticks":635387921780090390,"incomingOrderSide":0,"incomingServerOrderId":576028,"bookServerOrderId":575946},{"tid":4,"px":7933.9566,"qty":0.1168,"unixtime":1403195510,"utcticks":635387923108371640,"incomingOrderSide":0,"incomingServerOrderId":576974,"bookServerOrderId":576947},{"tid":5,"px":7961.0856,"qty":0.25,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600338},{"tid":6,"px":7961.1388,"qty":0.011,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600418},{"tid":7,"px":7961.2451,"qty":0.02,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600428},{"tid":8,"px":7947.1437,"qty":0.09,"unixtime":1403202749,"utcticks":635387995498225156,"incomingOrderSide":0,"incomingServerOrderId":602183,"bookServerOrderId":601745},{"tid":9,"px":7818.5073,"qty":0.25,"unixtime":1403219720,"utcticks":635388165206506406,"incomingOrderSide":0,"incomingServerOrderId":661909,"bookServerOrderId":661620}]}`), + ) + + err = common.JSONDecode(mockResp, &trades) + if err != nil { + t.Fatal("Test Failed - GetTrades unmarshalling error: ", err) + } } - if trades.Count < 0 { - t.Error("Test Failed - Alphapoint trades.Count value is negative") + if !trades.IsAccepted { + t.Error("Test Failed - GetTrades IsAccepted failed") } - if trades.DateTimeUTC <= 0 { - t.Error("Test Failed - Alphapoint trades.DateTimeUTC value is negative or 0") + + if trades.Count <= 0 { + t.Error("Test failed - GetTrades trades count is <= 0") } + if trades.Instrument != "BTCUSD" { - t.Error("Test Failed - Alphapoint trades.Instrument value is incorrect") - } - if trades.IsAccepted != true { - t.Error("Test Failed - Alphapoint trades.IsAccepted value is true") - } - if len(trades.RejectReason) > 0 { - t.Error("Test Failed - Alphapoint trades.IsAccepted value has been returned") - } - if trades.StartIndex != 0 { - t.Error("Test Failed - Alphapoint trades.StartIndex value is incorrect") - } - if trades.Trades[0].BookServerOrderID < 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - if trades.Trades[0].IncomingOrderSide < 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - if trades.Trades[0].IncomingServerOrderID < 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - if trades.Trades[0].Price < 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - if trades.Trades[0].Quantity < 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - if trades.Trades[0].TID != 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - if trades.Trades[0].UTCTicks < 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - if trades.Trades[0].Unixtime < 0 { - t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative") - } - - _, err = GetTrades.GetTrades("wigwham", 0, 10) - if err == nil { - t.Error("Test Failed - GetTrades error") + t.Error("Test failed - GetTrades instrument is != BTCUSD") } } func TestGetTradesByDate(t *testing.T) { - GetTradesByDate := Alphapoint{} - GetTradesByDate.SetDefaults() + alpha := Alphapoint{} + alpha.SetDefaults() - trades, err := GetTradesByDate.GetTradesByDate("BTCUSD", 1414799400, 1414800000) - if err != nil { - t.Errorf("Test Failed - Init error: %s", err) - } - if reflect.ValueOf(trades).NumField() != 9 { - t.Error("Test Failed - Alphapoint AlphapointTrades struct updated/changed") - } - if len(trades.Trades) != 0 { - t.Error("Test Failed - Alphapoint trades.Trades: Incorrect length") - } - if reflect.TypeOf(trades.DateTimeUTC).String() != "int64" { - t.Error("Test Failed - Alphapoint trades.Count value is not a int64") - } - if reflect.TypeOf(trades.EndDate).String() != "int64" { - t.Error("Test Failed - Alphapoint trades.DateTimeUTC value is not a int64") - } - if reflect.TypeOf(trades.Instrument).String() != "string" { - t.Error("Test Failed - Alphapoint trades.Instrument value is not a string") - } - if reflect.TypeOf(trades.IsAccepted).String() != "bool" { - t.Error("Test Failed - Alphapoint trades.IsAccepted value is not a bool") - } - if reflect.TypeOf(trades.RejectReason).String() != "string" { - t.Error("Test Failed - Alphapoint trades.string value is not a string") - } - if reflect.TypeOf(trades.StartDate).String() != "int64" { - t.Error("Test Failed - Alphapoint trades.StartDate value is not a int64") + var trades Trades + var err error + + if onlineTest { + trades, err = alpha.GetTradesByDate("BTCUSD", 1414799400, 1414800000) + if err != nil { + t.Errorf("Test Failed - Init error: %s", err) + } + _, err = alpha.GetTradesByDate("wigwham", 1414799400, 1414800000) + if err == nil { + t.Error("Test Failed - GetTradesByDate error") + } + } else { + mockResp := []byte( + string(`{"isAccepted":true,"dateTimeUtc":635504540880633671,"ins":"BTCUSD","startDate":1414799400,"endDate":1414800000,"trades":[{"tid":11505,"px":334.669,"qty":0.1211,"unixtime":1414799403,"utcticks":635503962032459843,"incomingOrderSide":1,"incomingServerOrderId":5185651,"bookServerOrderId":5162440},{"tid":11506,"px":334.669,"qty":0.1211,"unixtime":1414799405,"utcticks":635503962058446171,"incomingOrderSide":1,"incomingServerOrderId":5186245,"bookServerOrderId":5162440},{"tid":11507,"px":336.498,"qty":0.011,"unixtime":1414799407,"utcticks":635503962072967656,"incomingOrderSide":0,"incomingServerOrderId":5186530,"bookServerOrderId":5178944},{"tid":11508,"px":335.948,"qty":0.011,"unixtime":1414799410,"utcticks":635503962108055546,"incomingOrderSide":0,"incomingServerOrderId":5187260,"bookServerOrderId":5186531}]}`), + ) + + err = common.JSONDecode(mockResp, &trades) + if err != nil { + t.Fatal("Test Failed - GetTradesByDate unmarshalling error: ", err) + } } if trades.DateTimeUTC < 0 { @@ -269,173 +161,179 @@ func TestGetTradesByDate(t *testing.T) { if trades.StartDate < 0 { t.Error("Test Failed - Alphapoint trades.StartIndex value is negative") } - - _, err = GetTradesByDate.GetTradesByDate("wigwham", 1414799400, 1414800000) - if err == nil { - t.Error("Test Failed - GetTradesByDate() error") - } } func TestGetOrderbook(t *testing.T) { - GetOrderbook := Alphapoint{} - GetOrderbook.SetDefaults() + alpha := Alphapoint{} + alpha.SetDefaults() - orderBook, err := GetOrderbook.GetOrderbook("BTCUSD") - if err != nil { - t.Errorf("Test Failed - Init error: %s", err) - } - if reflect.ValueOf(orderBook).NumField() != 4 { - t.Error("Test Failed - Alphapoint AlphapointOrderbook struct updated/changed") - } - if reflect.TypeOf(orderBook.IsAccepted).String() != "bool" { - t.Error("Test Failed - Alphapoint orderBook.IsAccepted value is not a bool") - } - if reflect.TypeOf(orderBook.RejectReason).String() != "string" { - t.Error("Test Failed - Alphapoint orderBook.RejectReason value is not a string") - } - _, err = GetOrderbook.GetOrderbook("wigwham") - if err == nil { - t.Error("Test Failed - GetOrderbook() error") + var orderBook Orderbook + var err error + + if onlineTest { + orderBook, err = alpha.GetOrderbook("BTCUSD") + if err != nil { + t.Errorf("Test Failed - Init error: %s", err) + } + + _, err = alpha.GetOrderbook("wigwham") + if err == nil { + t.Error("Test Failed - GetOrderbook() error") + } + } else { + mockResp := []byte( + string(`{"bids":[{"qty":725,"px":66},{"qty":1289,"px":65},{"qty":1266,"px":64}],"asks":[{"qty":1,"px":67},{"qty":1,"px":69},{"qty":2,"px":70}],"isAccepted":true}`), + ) + + err = common.JSONDecode(mockResp, &orderBook) + if err != nil { + t.Fatal("Test Failed - TestGetOrderbook unmarshalling error: ", err) + } + + if orderBook.Bids[0].Quantity != 725 { + t.Error("Test Failed - TestGetOrderbook Bids[0].Quantity != 725") + } } + if !orderBook.IsAccepted { + t.Error("Test Failed - Alphapoint orderBook.IsAccepted value is negative") + } + + if len(orderBook.Asks) == 0 { + t.Error("Test Failed - Alphapoint orderBook.Asks has len 0") + } + + if len(orderBook.Bids) == 0 { + t.Error("Test Failed - Alphapoint orderBook.Bids has len 0") + } } func TestGetProductPairs(t *testing.T) { - GetProductPairs := Alphapoint{} - GetProductPairs.SetDefaults() + alpha := Alphapoint{} + alpha.SetDefaults() - productPairs, err := GetProductPairs.GetProductPairs() - if err != nil { - t.Errorf("Test Failed - Init error: %s", err) - } - if reflect.ValueOf(productPairs).NumField() != 3 { - t.Error("Test Failed - Alphapoint GetProductPairs struct updated/changed") - } - if reflect.TypeOf(productPairs.IsAccepted).String() != "bool" { - t.Error("Test Failed - Alphapoint productPairs.IsAccepted value is not a bool") - } - if reflect.TypeOf(productPairs.RejectReason).String() != "string" { - t.Error("Test Failed - Alphapoint productPairs.RejectReason value is not a string") - } + var products ProductPairs + var err error - if len(productPairs.ProductPairs) >= 1 { - if reflect.ValueOf(productPairs.ProductPairs[0]).NumField() != 6 { - t.Error("Test Failed - Alphapoint GetProductPairs.ProductPairs[] struct updated/changed") - } - if reflect.TypeOf(productPairs.ProductPairs[0].Name).String() != "string" { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Name value is not a string") - } - if reflect.TypeOf(productPairs.ProductPairs[0].Product1Decimalplaces).String() != "int" { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product1Decimalplaces value is not a int") - } - if reflect.TypeOf(productPairs.ProductPairs[0].Product1Label).String() != "string" { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product1Label value is not a string") - } - if reflect.TypeOf(productPairs.ProductPairs[0].Product2Decimalplaces).String() != "int" { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product2Decimalplaces value is not a int") - } - if reflect.TypeOf(productPairs.ProductPairs[0].Product2Label).String() != "string" { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product2Label value is not a string") - } - if reflect.TypeOf(productPairs.ProductPairs[0].Productpaircode).String() != "int" { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Productpaircode value is not a int") - } - - if productPairs.ProductPairs[0].Product1Decimalplaces < 0 { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product1Decimalplaces value is negative") - } - if productPairs.ProductPairs[0].Product2Decimalplaces < 0 { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product2Decimalplaces value is negative") - } - if productPairs.ProductPairs[0].Productpaircode < 0 { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Productpaircode value is negative") + if onlineTest { + products, err = alpha.GetProductPairs() + if err != nil { + t.Errorf("Test Failed - Init error: %s", err) } } else { - t.Error("Test Failed - Alphapoint productPairs.ProductPairs no product pairs.") + mockResp := []byte( + string(`{"productPairs":[{"name":"LTCUSD","productPairCode":100,"product1Label":"LTC","product1DecimalPlaces":8,"product2Label":"USD","product2DecimalPlaces":6}, {"name":"BTCUSD","productPairCode":99,"product1Label":"BTC","product1DecimalPlaces":8,"product2Label":"USD","product2DecimalPlaces":6}],"isAccepted":true}`), + ) + + err = common.JSONDecode(mockResp, &products) + if err != nil { + t.Fatal("Test Failed - TestGetProductPairs unmarshalling error: ", err) + } + + if products.ProductPairs[0].Name != "LTCUSD" { + t.Error("Test Failed - Alphapoint ProductPairs 0 != LTCUSD") + } + + if products.ProductPairs[1].Product1Label != "BTC" { + t.Error("Test Failed - Alphapoint ProductPairs 1 != BTC") + } + } + + if !products.IsAccepted { + t.Error("Test Failed - Alphapoint ProductPairs.IsAccepted value is negative") + } + + if len(products.ProductPairs) == 0 { + t.Error("Test Failed - Alphapoint ProductPairs len is 0") } } func TestGetProducts(t *testing.T) { - GetProducts := Alphapoint{} - GetProducts.SetDefaults() + alpha := Alphapoint{} + alpha.SetDefaults() - products, err := GetProducts.GetProducts() - if err != nil { - t.Errorf("Test Failed - Init error: %s", err) - } - if reflect.ValueOf(products).NumField() != 3 { - t.Error("Test Failed - Alphapoint GetProductPairs struct updated/changed") - } - if reflect.TypeOf(products.IsAccepted).String() != "bool" { - t.Error("Test Failed - Alphapoint products.IsAccepted value is not a bool") - } - if reflect.TypeOf(products.RejectReason).String() != "string" { - t.Error("Test Failed - Alphapoint products.RejectReason value is not a string") - } + var products Products + var err error - if len(products.Products) >= 1 { - if reflect.ValueOf(products.Products[0]).NumField() != 5 { - t.Error("Test Failed - Alphapoint Getproducts.Products[] struct updated/changed") - } - if reflect.TypeOf(products.Products[0].DecimalPlaces).String() != "int" { - t.Error("Test Failed - Alphapoint products.Products.DecimalPlaces value is not a int") - } - if reflect.TypeOf(products.Products[0].FullName).String() != "string" { - t.Error("Test Failed - Alphapoint products.Products.FullName value is not a string") - } - if reflect.TypeOf(products.Products[0].IsDigital).String() != "bool" { - t.Error("Test Failed - Alphapoint products.Products.IsDigital value is not a bool") - } - if reflect.TypeOf(products.Products[0].Name).String() != "string" { - t.Error("Test Failed - Alphapoint products.Products.Name value is not a string") - } - if reflect.TypeOf(products.Products[0].ProductCode).String() != "int" { - t.Error("Test Failed - Alphapoint products.Products.ProductCode value is not a int") - } - - if products.Products[0].DecimalPlaces < 0 { - t.Error("Test Failed - Alphapoint products.Products.DecimalPlaces value is negative") - } - if products.Products[0].ProductCode < 0 { - t.Log(products.Products[0].ProductCode) - t.Error("Test Failed - Alphapoint products.Products.ProductCode value is negative") + if onlineTest { + products, err = alpha.GetProducts() + if err != nil { + t.Errorf("Test Failed - Init error: %s", err) } } else { - t.Error("Test Failed - Alphapoint products.Products no product pairs.") + mockResp := []byte( + string(`{"products": [{"name": "USD","isDigital": false,"productCode": 0,"decimalPlaces": 4,"fullName": "US Dollar"},{"name": "BTC","isDigital": true,"productCode": 1,"decimalPlaces": 6,"fullName": "Bitcoin"}],"isAccepted": true}`), + ) + + err = common.JSONDecode(mockResp, &products) + if err != nil { + t.Fatal("Test Failed - TestGetProducts unmarshalling error: ", err) + } + + if products.Products[0].Name != "USD" { + t.Error("Test Failed - Alphapoint Products 0 != USD") + } + + if products.Products[1].ProductCode != 1 { + t.Error("Test Failed - Alphapoint Products 1 product code != 1") + } + } + + if !products.IsAccepted { + t.Error("Test Failed - Alphapoint Products.IsAccepted value is negative") + } + + if len(products.Products) == 0 { + t.Error("Test Failed - Alphapoint Products len is 0") } } func TestCreateAccount(t *testing.T) { - CreateAccount := Alphapoint{} - CreateAccount.SetDefaults() + a := &Alphapoint{} + a.SetDefaults() + testSetAPIKey(a) - err := CreateAccount.CreateAccount("test", "account", "something@something.com", "0292383745", "lolcat123") + if !testIsAPIKeysSet(a) { + return + } + + err := a.CreateAccount("test", "account", "something@something.com", "0292383745", "lolcat123") if err != nil { t.Errorf("Test Failed - Init error: %s", err) } - err = CreateAccount.CreateAccount("test", "account", "something@something.com", "0292383745", "bla") + err = a.CreateAccount("test", "account", "something@something.com", "0292383745", "bla") if err == nil { t.Errorf("Test Failed - CreateAccount() error") } - err = CreateAccount.CreateAccount("", "", "", "", "lolcat123") + err = a.CreateAccount("", "", "", "", "lolcat123") if err == nil { t.Errorf("Test Failed - CreateAccount() error") } } func TestGetUserInfo(t *testing.T) { - GetUserInfo := Alphapoint{} - GetUserInfo.SetDefaults() + a := &Alphapoint{} + a.SetDefaults() + testSetAPIKey(a) - _, err := GetUserInfo.GetUserInfo() + if !testIsAPIKeysSet(a) { + return + } + + _, err := a.GetUserInfo() if err == nil { t.Error("Test Failed - GetUserInfo() error") } } func TestSetUserInfo(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.SetUserInfo("bla", "bla", "1", "meh", true, true) if err == nil { @@ -444,8 +342,13 @@ func TestSetUserInfo(t *testing.T) { } func TestGetAccountInfo(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.GetAccountInfo() if err == nil { @@ -454,8 +357,13 @@ func TestGetAccountInfo(t *testing.T) { } func TestGetAccountTrades(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.GetAccountTrades("", 1, 2) if err == nil { @@ -464,8 +372,13 @@ func TestGetAccountTrades(t *testing.T) { } func TestGetDepositAddresses(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.GetDepositAddresses() if err == nil { @@ -474,8 +387,13 @@ func TestGetDepositAddresses(t *testing.T) { } func TestWithdrawCoins(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } err := a.WithdrawCoins("", "", "", 0.01) if err == nil { @@ -484,8 +402,13 @@ func TestWithdrawCoins(t *testing.T) { } func TestCreateOrder(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.CreateOrder("", "", 1, 0.01, 0) if err == nil { @@ -494,8 +417,13 @@ func TestCreateOrder(t *testing.T) { } func TestModifyOrder(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.ModifyOrder("", 1, 1) if err == nil { @@ -504,8 +432,13 @@ func TestModifyOrder(t *testing.T) { } func TestCancelOrder(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.CancelOrder("", 1) if err == nil { @@ -514,8 +447,13 @@ func TestCancelOrder(t *testing.T) { } func TestCancelAllOrders(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } err := a.CancelAllOrders("") if err == nil { @@ -524,8 +462,13 @@ func TestCancelAllOrders(t *testing.T) { } func TestGetOrders(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.GetOrders() if err == nil { @@ -534,8 +477,13 @@ func TestGetOrders(t *testing.T) { } func TestGetOrderFee(t *testing.T) { - a := Alphapoint{} + a := &Alphapoint{} a.SetDefaults() + testSetAPIKey(a) + + if !testIsAPIKeysSet(a) { + return + } _, err := a.GetOrderFee("", "", 1, 1) if err == nil { diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index d7135b98..92f25b89 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -286,8 +286,18 @@ func (a *ANX) GetDepositAddress(currency, name string, new bool) (string, error) } func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interface{}, result interface{}) error { + if !a.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, a.Name) + } + + if a.Nonce.Get() == 0 { + a.Nonce.Set(time.Now().UnixNano()) + } else { + a.Nonce.Inc() + } + request := make(map[string]interface{}) - request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] + request["nonce"] = a.Nonce.String()[0:13] path = fmt.Sprintf("api/%s/%s", ANX_API_VERSION, path) if params != nil { @@ -296,23 +306,23 @@ func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interf } } - PayloadJson, err := common.JSONEncode(request) + PayloadJSON, err := common.JSONEncode(request) if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } if a.Verbose { - log.Printf("Request JSON: %s\n", PayloadJson) + log.Printf("Request JSON: %s\n", PayloadJSON) } - hmac := common.GetHMAC(common.HashSHA512, []byte(path+string("\x00")+string(PayloadJson)), []byte(a.APISecret)) + hmac := common.GetHMAC(common.HashSHA512, []byte(path+string("\x00")+string(PayloadJSON)), []byte(a.APISecret)) headers := make(map[string]string) headers["Rest-Key"] = a.APIKey headers["Rest-Sign"] = common.Base64Encode([]byte(hmac)) headers["Content-Type"] = "application/json" - resp, err := common.SendHTTPRequest("POST", ANX_API_URL+path, headers, bytes.NewBuffer(PayloadJson)) + resp, err := common.SendHTTPRequest("POST", ANX_API_URL+path, headers, bytes.NewBuffer(PayloadJSON)) if a.Verbose { log.Printf("Received raw: \n%s\n", resp) diff --git a/exchanges/anx/anx_wrapper.go b/exchanges/anx/anx_wrapper.go index 12d5d5d7..6584a5ac 100644 --- a/exchanges/anx/anx_wrapper.go +++ b/exchanges/anx/anx_wrapper.go @@ -30,7 +30,7 @@ func (a *ANX) Run() { log.Println(err) return } - log.Printf("ANX %s: Last %f High %f Low %f Volume %f\n", currency.Pair(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("ANX %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(a.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/exchanges/anx/anx_wrapper_test.go b/exchanges/anx/anx_wrapper_test.go deleted file mode 100644 index cbc17f4c..00000000 --- a/exchanges/anx/anx_wrapper_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package anx - -import ( - "testing" -) - -func TestStart(t *testing.T) { - -} - -func TestRun(t *testing.T) { - -} - -func TestGetTickerPrice(t *testing.T) { - -} - -func TestGetOrderbookEx(t *testing.T) { - -} - -func TestGetExchangeAccountInfo(t *testing.T) { - -} diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index cf0f77d0..f18e8bb5 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -564,14 +564,20 @@ func (b *Bitfinex) CloseMarginFunding(SwapID int64) (Offer, error) { // SendAuthenticatedHTTPRequest sends an autheticated http request and json // unmarshals result to a supplied variable func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) error { - if len(b.APIKey) == 0 { - return errors.New("SendAuthenticatedHTTPRequest: Invalid API key") + if !b.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + } + + if b.Nonce.Get() == 0 { + b.Nonce.Set(time.Now().UnixNano()) + } else { + b.Nonce.Inc() } respErr := ErrorCapture{} request := make(map[string]interface{}) request["request"] = fmt.Sprintf("/v%s/%s", bitfinexAPIVersion, path) - request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10) + request["nonce"] = b.Nonce.String() if params != nil { for key, value := range params { @@ -589,9 +595,7 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[ } PayloadBase64 := common.Base64Encode(PayloadJSON) - hmac := common.GetHMAC( - common.HashSHA512_384, []byte(PayloadBase64), []byte(b.APISecret), - ) + hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(b.APISecret)) headers := make(map[string]string) headers["X-BFX-APIKEY"] = b.APIKey headers["X-BFX-PAYLOAD"] = PayloadBase64 diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 998bdd93..5bffc8ec 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -17,74 +17,70 @@ const ( testAPISecret = "" ) +var b Bitfinex + func TestSetDefaults(t *testing.T) { - t.Parallel() + b.SetDefaults() - setDefaults := Bitfinex{} - setDefaults.SetDefaults() - - if setDefaults.Name != "Bitfinex" || setDefaults.Enabled != false || - setDefaults.Verbose != false || setDefaults.Websocket != false || - setDefaults.RESTPollingDelay != 10 { + if b.Name != "Bitfinex" || b.Enabled != false || + b.Verbose != false || b.Websocket != false || + b.RESTPollingDelay != 10 { t.Error("Test Failed - Bitfinex SetDefaults values not set correctly") } } func TestSetup(t *testing.T) { - t.Parallel() testConfig := config.ExchangeConfig{ Enabled: true, AuthenticatedAPISupport: true, - APIKey: "lamb", - APISecret: "cutlets", + APIKey: testAPIKey, + APISecret: testAPISecret, RESTPollingDelay: time.Duration(10), - Verbose: true, + Verbose: false, Websocket: true, BaseCurrencies: currency.DefaultCurrencies, AvailablePairs: currency.MakecurrencyPairs(currency.DefaultCurrencies), EnabledPairs: currency.MakecurrencyPairs(currency.DefaultCurrencies), } - setup := Bitfinex{} - setup.Setup(testConfig) - if !setup.Enabled || !setup.AuthenticatedAPISupport || setup.APIKey != "lamb" || - setup.APISecret != "cutlets" || setup.RESTPollingDelay != time.Duration(10) || - !setup.Verbose || !setup.Websocket || len(setup.BaseCurrencies) < 1 || - len(setup.AvailablePairs) < 1 || len(setup.EnabledPairs) < 1 { + b.Setup(testConfig) + + if !b.Enabled || !b.AuthenticatedAPISupport || b.APIKey != testAPIKey || + b.APISecret != testAPISecret || b.RESTPollingDelay != time.Duration(10) || + b.Verbose || !b.Websocket || len(b.BaseCurrencies) < 1 || + len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 { t.Error("Test Failed - Bitfinex Setup values not set correctly") } - testConfig.Enabled = false - setup.Setup(testConfig) } func TestGetTicker(t *testing.T) { - bitfinex := Bitfinex{} - _, err := bitfinex.GetTicker("BTCUSD", url.Values{}) + t.Parallel() + _, err := b.GetTicker("BTCUSD", url.Values{}) if err != nil { t.Error("BitfinexGetTicker init error: ", err) } - _, err = bitfinex.GetTicker("wigwham", url.Values{}) + _, err = b.GetTicker("wigwham", url.Values{}) if err == nil { t.Error("Test Failed - GetTicker() error") } } func TestGetStats(t *testing.T) { - BitfinexGetStatsTest := Bitfinex{} - _, err := BitfinexGetStatsTest.GetStats("BTCUSD") + t.Parallel() + _, err := b.GetStats("BTCUSD") if err != nil { t.Error("BitfinexGetStatsTest init error: ", err) } - _, err = BitfinexGetStatsTest.GetStats("wigwham") + _, err = b.GetStats("wigwham") if err == nil { t.Error("Test Failed - GetStats() error") } } func TestGetFundingBook(t *testing.T) { - b := Bitfinex{} + t.Parallel() _, err := b.GetFundingBook("USD") if err != nil { t.Error("Testing Failed - GetFundingBook() error") @@ -96,42 +92,45 @@ func TestGetFundingBook(t *testing.T) { } func TestGetLendbook(t *testing.T) { - BitfinexGetLendbook := Bitfinex{} - _, err := BitfinexGetLendbook.GetLendbook("BTCUSD", url.Values{}) + t.Parallel() + + _, err := b.GetLendbook("BTCUSD", url.Values{}) if err != nil { - t.Error("BitfinexGetLendbook init error: ", err) + t.Error("Testing Failed - GetLendbook() error: ", err) } } func TestGetOrderbook(t *testing.T) { - BitfinexGetOrderbook := Bitfinex{} - _, err := BitfinexGetOrderbook.GetOrderbook("BTCUSD", url.Values{}) + t.Parallel() + + _, err := b.GetOrderbook("BTCUSD", url.Values{}) if err != nil { t.Error("BitfinexGetOrderbook init error: ", err) } } func TestGetTrades(t *testing.T) { - BitfinexGetTrades := Bitfinex{} - _, err := BitfinexGetTrades.GetTrades("BTCUSD", url.Values{}) + t.Parallel() + + _, err := b.GetTrades("BTCUSD", url.Values{}) if err != nil { t.Error("BitfinexGetTrades init error: ", err) } } func TestGetLends(t *testing.T) { - BitfinexGetLends := Bitfinex{} + t.Parallel() - _, err := BitfinexGetLends.GetLends("BTC", url.Values{}) + _, err := b.GetLends("BTC", url.Values{}) if err != nil { t.Error("BitfinexGetLends init error: ", err) } } func TestGetSymbols(t *testing.T) { - BitfinexGetSymbols := Bitfinex{} + t.Parallel() - symbols, err := BitfinexGetSymbols.GetSymbols() + symbols, err := b.GetSymbols() if err != nil { t.Error("BitfinexGetSymbols init error: ", err) } @@ -177,17 +176,16 @@ func TestGetSymbols(t *testing.T) { } func TestGetSymbolsDetails(t *testing.T) { - BitfinexGetSymbolsDetails := Bitfinex{} - _, err := BitfinexGetSymbolsDetails.GetSymbolsDetails() + t.Parallel() + + _, err := b.GetSymbolsDetails() if err != nil { t.Error("BitfinexGetSymbolsDetails init error: ", err) } } func TestGetAccountInfo(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetAccountInfo() if err == nil { @@ -196,9 +194,7 @@ func TestGetAccountInfo(t *testing.T) { } func TestGetAccountFees(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetAccountFees() if err == nil { @@ -207,9 +203,7 @@ func TestGetAccountFees(t *testing.T) { } func TestGetAccountSummary(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetAccountSummary() if err == nil { @@ -218,9 +212,7 @@ func TestGetAccountSummary(t *testing.T) { } func TestNewDeposit(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.NewDeposit("blabla", "testwallet", 1) if err == nil { @@ -229,9 +221,7 @@ func TestNewDeposit(t *testing.T) { } func TestGetKeyPermissions(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetKeyPermissions() if err == nil { @@ -240,9 +230,7 @@ func TestGetKeyPermissions(t *testing.T) { } func TestGetMarginInfo(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetMarginInfo() if err == nil { @@ -251,9 +239,7 @@ func TestGetMarginInfo(t *testing.T) { } func TestGetAccountBalance(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetAccountBalance() if err == nil { @@ -262,9 +248,7 @@ func TestGetAccountBalance(t *testing.T) { } func TestWalletTransfer(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.WalletTransfer(0.01, "bla", "bla", "bla") if err == nil { @@ -273,9 +257,7 @@ func TestWalletTransfer(t *testing.T) { } func TestWithdrawal(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.Withdrawal("LITECOIN", "deposit", "1000", 0.01) if err == nil { @@ -284,9 +266,7 @@ func TestWithdrawal(t *testing.T) { } func TestNewOrder(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.NewOrder("BTCUSD", 1, 2, true, "market", false) if err == nil { @@ -295,9 +275,8 @@ func TestNewOrder(t *testing.T) { } func TestNewOrderMulti(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() + newOrder := []PlaceOrder{ { Symbol: "BTCUSD", @@ -316,9 +295,7 @@ func TestNewOrderMulti(t *testing.T) { } func TestCancelOrder(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.CancelOrder(1337) if err == nil { @@ -327,9 +304,7 @@ func TestCancelOrder(t *testing.T) { } func TestCancelMultipleOrders(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.CancelMultipleOrders([]int64{1337, 1336}) if err == nil { @@ -338,9 +313,7 @@ func TestCancelMultipleOrders(t *testing.T) { } func TestCancelAllOrders(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.CancelAllOrders() if err == nil { @@ -349,9 +322,7 @@ func TestCancelAllOrders(t *testing.T) { } func TestReplaceOrder(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.ReplaceOrder(1337, "BTCUSD", 1, 1, true, "market", false) if err == nil { @@ -360,9 +331,7 @@ func TestReplaceOrder(t *testing.T) { } func TestGetOrderStatus(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetOrderStatus(1337) if err == nil { @@ -371,9 +340,7 @@ func TestGetOrderStatus(t *testing.T) { } func TestGetActiveOrders(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetActiveOrders() if err == nil { @@ -382,9 +349,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetActivePositions(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetActivePositions() if err == nil { @@ -393,9 +358,7 @@ func TestGetActivePositions(t *testing.T) { } func TestClaimPosition(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.ClaimPosition(1337) if err == nil { @@ -404,9 +367,7 @@ func TestClaimPosition(t *testing.T) { } func TestGetBalanceHistory(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetBalanceHistory("USD", time.Time{}, time.Time{}, 1, "deposit") if err == nil { @@ -415,9 +376,7 @@ func TestGetBalanceHistory(t *testing.T) { } func TestGetMovementHistory(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetMovementHistory("USD", "bitcoin", time.Time{}, time.Time{}, 1) if err == nil { @@ -426,9 +385,7 @@ func TestGetMovementHistory(t *testing.T) { } func TestGetTradeHistory(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetTradeHistory("BTCUSD", time.Time{}, time.Time{}, 1, 0) if err == nil { @@ -437,9 +394,7 @@ func TestGetTradeHistory(t *testing.T) { } func TestNewOffer(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.NewOffer("BTC", 1, 1, 1, "loan") if err == nil { @@ -448,9 +403,7 @@ func TestNewOffer(t *testing.T) { } func TestCancelOffer(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.CancelOffer(1337) if err == nil { @@ -459,9 +412,7 @@ func TestCancelOffer(t *testing.T) { } func TestGetOfferStatus(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetOfferStatus(1337) if err == nil { @@ -470,9 +421,7 @@ func TestGetOfferStatus(t *testing.T) { } func TestGetActiveCredits(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetActiveCredits() if err == nil { @@ -481,9 +430,7 @@ func TestGetActiveCredits(t *testing.T) { } func TestGetActiveOffers(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetActiveOffers() if err == nil { @@ -492,9 +439,7 @@ func TestGetActiveOffers(t *testing.T) { } func TestGetActiveMarginFunding(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetActiveMarginFunding() if err == nil { @@ -503,9 +448,7 @@ func TestGetActiveMarginFunding(t *testing.T) { } func TestGetUnusedMarginFunds(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetUnusedMarginFunds() if err == nil { @@ -514,9 +457,7 @@ func TestGetUnusedMarginFunds(t *testing.T) { } func TestGetMarginTotalTakenFunds(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.GetMarginTotalTakenFunds() if err == nil { @@ -525,9 +466,7 @@ func TestGetMarginTotalTakenFunds(t *testing.T) { } func TestCloseMarginFunding(t *testing.T) { - b := Bitfinex{} - b.APIKey = testAPIKey - b.APISecret = testAPISecret + t.Parallel() _, err := b.CloseMarginFunding(1337) if err == nil { diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 03c7d785..65730c16 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -47,7 +47,7 @@ func (b *Bitfinex) Run() { if err != nil { return } - log.Printf("Bitfinex %s Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("Bitfinex %s Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index ac44f745..b5ec92a1 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -16,39 +16,44 @@ import ( ) const ( - BITSTAMP_API_URL = "https://www.bitstamp.net/api" - BITSTAMP_API_VERSION = "2" - BITSTAMP_API_TICKER = "ticker" - BITSTAMP_API_TICKER_HOURLY = "ticker_hour" - BITSTAMP_API_ORDERBOOK = "order_book" - BITSTAMP_API_TRANSACTIONS = "transactions" - BITSTAMP_API_EURUSD = "eur_usd" - BITSTAMP_API_BALANCE = "balance" - BITSTAMP_API_USER_TRANSACTIONS = "user_transactions" - BITSTAMP_API_OPEN_ORDERS = "open_orders" - BITSTAMP_API_ORDER_STATUS = "order_status" - BITSTAMP_API_CANCEL_ORDER = "cancel_order" - BITSTAMP_API_CANCEL_ALL_ORDERS = "cancel_all_orders" - BITSTAMP_API_BUY = "buy" - BITSTAMP_API_SELL = "sell" - BITSTAMP_API_MARKET = "market" - BITSTAMP_API_WITHDRAWAL_REQUESTS = "withdrawal_requests" - BITSTAMP_API_BITCOIN_WITHDRAWAL = "bitcoin_withdrawal" - BITSTAMP_API_BITCOIN_DEPOSIT = "bitcoin_deposit_address" - BITSTAMP_API_UNCONFIRMED_BITCOIN = "unconfirmed_btc" - BITSTAMP_API_RIPPLE_WITHDRAWAL = "ripple_withdrawal" - BITSTAMP_API_RIPPLE_DESPOIT = "ripple_address" - BITSTAMP_API_TRANSFER_TO_MAIN = "transfer-to-main" - BITSTAMP_API_TRANSFER_FROM_MAIN = "transfer-from-main" - BITSTAMP_API_XRP_WITHDRAWAL = "xrp_withdrawal" - BITSTAMP_API_XRP_DESPOIT = "xrp_address" + bitstampAPIURL = "https://www.bitstamp.net/api" + bitstampAPIVersion = "2" + bitstampAPITicker = "ticker" + bitstampAPITickerHourly = "ticker_hour" + bitstampAPIOrderbook = "order_book" + bitstampAPITransactions = "transactions" + bitstampAPIEURUSD = "eur_usd" + bitstampAPIBalance = "balance" + bitstampAPIUserTransactions = "user_transactions" + bitstampAPIOpenOrders = "open_orders" + bitstampAPIOrderStatus = "order_status" + bitstampAPICancelOrder = "cancel_order" + bitstampAPICancelAllOrders = "cancel_all_orders" + bitstampAPIBuy = "buy" + bitstampAPISell = "sell" + bitstampAPIMarket = "market" + bitstampAPIWithdrawalRequests = "withdrawal_requests" + bitstampAPIBitcoinWithdrawal = "bitcoin_withdrawal" + bitstampAPILTCWithdrawal = "ltc_withdrawal" + bitstampAPIETHWithdrawal = "eth_withdrawal" + bitstampAPIBitcoinDeposit = "bitcoin_deposit_address" + bitstampAPILitecoinDeposit = "ltc_address" + bitstampAPIEthereumDeposit = "eth_address" + bitstampAPIUnconfirmedBitcoin = "unconfirmed_btc" + bitstampAPITransferToMain = "transfer-to-main" + bitstampAPITransferFromMain = "transfer-from-main" + bitstampAPIXrpWithdrawal = "xrp_withdrawal" + bitstampAPIXrpDeposit = "xrp_address" + bitstampAPIReturnType = "string" ) +// Bitstamp is the overarching type across the bitstamp package type Bitstamp struct { exchange.Base - Balance BitstampBalances + Balance Balances } +// SetDefaults sets default for Bitstamp func (b *Bitstamp) SetDefaults() { b.Name = "Bitstamp" b.Enabled = false @@ -57,6 +62,7 @@ func (b *Bitstamp) SetDefaults() { b.RESTPollingDelay = 10 } +// Setup sets configuration values to bitstamp func (b *Bitstamp) Setup(exch config.ExchangeConfig) { if !exch.Enabled { b.SetEnabled(false) @@ -73,8 +79,9 @@ func (b *Bitstamp) Setup(exch config.ExchangeConfig) { } } -func (b *Bitstamp) GetFee(currency string) float64 { - switch currency { +// GetFee returns fee on a currency pair +func (b *Bitstamp) GetFee(currencyPair string) float64 { + switch currencyPair { case "BTCUSD": return b.Balance.BTCUSDFee case "BTCEUR": @@ -90,39 +97,50 @@ func (b *Bitstamp) GetFee(currency string) float64 { } } -func (b *Bitstamp) GetTicker(currency string, hourly bool) (BitstampTicker, error) { - tickerEndpoint := BITSTAMP_API_TICKER +// GetTicker returns ticker information +func (b *Bitstamp) GetTicker(currency string, hourly bool) (Ticker, error) { + response := Ticker{} + tickerEndpoint := bitstampAPITicker + if hourly { - tickerEndpoint = BITSTAMP_API_TICKER_HOURLY + tickerEndpoint = bitstampAPITickerHourly } - path := fmt.Sprintf("%s/v%s/%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, tickerEndpoint, common.StringToLower(currency)) - ticker := BitstampTicker{} - - err := common.SendHTTPGetRequest(path, true, &ticker) - - if err != nil { - return ticker, err - } - - return ticker, nil + path := fmt.Sprintf( + "%s/v%s/%s/%s/", + bitstampAPIURL, + bitstampAPIVersion, + tickerEndpoint, + common.StringToLower(currency), + ) + return response, common.SendHTTPGetRequest(path, true, &response) } -func (b *Bitstamp) GetOrderbook(currency string) (BitstampOrderbook, error) { +// GetOrderbook Returns a JSON dictionary with "bids" and "asks". Each is a list +// of open orders and each order is represented as a list holding the price and +//the amount. +func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { type response struct { - Timestamp int64 `json:"timestamp,string"` - Bids [][]string - Asks [][]string + Timestamp int64 `json:"timestamp,string"` + Bids [][]string `json:"bids"` + Asks [][]string `json:"asks"` } - resp := response{} - path := fmt.Sprintf("%s/v%s/%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, BITSTAMP_API_ORDERBOOK, common.StringToLower(currency)) + + path := fmt.Sprintf( + "%s/v%s/%s/%s/", + bitstampAPIURL, + bitstampAPIVersion, + bitstampAPIOrderbook, + common.StringToLower(currency), + ) + err := common.SendHTTPGetRequest(path, true, &resp) if err != nil { - return BitstampOrderbook{}, err + return Orderbook{}, err } - orderbook := BitstampOrderbook{} + orderbook := Orderbook{} orderbook.Timestamp = resp.Timestamp for _, x := range resp.Bids { @@ -136,7 +154,7 @@ func (b *Bitstamp) GetOrderbook(currency string) (BitstampOrderbook, error) { log.Println(err) continue } - orderbook.Bids = append(orderbook.Bids, BitstampOrderbookBase{price, amount}) + orderbook.Bids = append(orderbook.Bids, OrderbookBase{price, amount}) } for _, x := range resp.Asks { @@ -150,44 +168,49 @@ func (b *Bitstamp) GetOrderbook(currency string) (BitstampOrderbook, error) { log.Println(err) continue } - orderbook.Asks = append(orderbook.Asks, BitstampOrderbookBase{price, amount}) + orderbook.Asks = append(orderbook.Asks, OrderbookBase{price, amount}) } return orderbook, nil } -func (b *Bitstamp) GetTransactions(currency string, values url.Values) ([]BitstampTransactions, error) { - path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, BITSTAMP_API_TRANSACTIONS, common.StringToLower(currency)), values) - transactions := []BitstampTransactions{} - err := common.SendHTTPGetRequest(path, true, &transactions) - if err != nil { - return nil, err - } - return transactions, nil +// GetTransactions returns transaction information +// value paramater ["time"] = "minute", "hour", "day" will collate your +// response into time intervals. Implementation of value in test code. +func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Transactions, error) { + transactions := []Transactions{} + path := common.EncodeURLValues( + fmt.Sprintf( + "%s/v%s/%s/%s/", + bitstampAPIURL, + bitstampAPIVersion, + bitstampAPITransactions, + common.StringToLower(currencyPair), + ), + values, + ) + + return transactions, common.SendHTTPGetRequest(path, true, &transactions) } -func (b *Bitstamp) GetEURUSDConversionRate() (BitstampEURUSDConversionRate, error) { - rate := BitstampEURUSDConversionRate{} - path := fmt.Sprintf("%s/%s", BITSTAMP_API_URL, BITSTAMP_API_EURUSD) - err := common.SendHTTPGetRequest(path, true, &rate) +// GetEURUSDConversionRate returns the conversion rate between Euro and USD +func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { + rate := EURUSDConversionRate{} + path := fmt.Sprintf("%s/%s", bitstampAPIURL, bitstampAPIEURUSD) - if err != nil { - return rate, err - } - return rate, nil + return rate, common.SendHTTPGetRequest(path, true, &rate) } -func (b *Bitstamp) GetBalance() (BitstampBalances, error) { - balance := BitstampBalances{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_BALANCE, true, url.Values{}, &balance) +// GetBalance returns full balance of currency held on the exchange +func (b *Bitstamp) GetBalance() (Balances, error) { + balance := Balances{} - if err != nil { - return balance, err - } - return balance, nil + return balance, + b.SendAuthenticatedHTTPRequest(bitstampAPIBalance, true, url.Values{}, &balance) } -func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransactions, error) { +// GetUserTransactions returns an array of transactions +func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, error) { type Response struct { Date string `json:"datetime"` TransID int64 `json:"id"` @@ -200,25 +223,29 @@ func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransac Fee float64 `json:"fee,string"` OrderID int64 `json:"order_id"` } - response := []Response{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_USER_TRANSACTIONS, true, values, &response) - if err != nil { - return nil, err + if currencyPair != "" { + if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions, true, url.Values{}, &response); err != nil { + return nil, err + } + } else { + if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions+"/"+currencyPair, true, url.Values{}, &response); err != nil { + return nil, err + } } - transactions := []BitstampUserTransactions{} + transactions := []UserTransactions{} for _, y := range response { - tx := BitstampUserTransactions{} + tx := UserTransactions{} tx.Date = y.Date tx.TransID = y.TransID tx.Type = y.Type /* Hack due to inconsistent JSON values... */ varType := reflect.TypeOf(y.USD).String() - if varType == "string" { + if varType == bitstampAPIReturnType { tx.USD, _ = strconv.ParseFloat(y.USD.(string), 64) } else { tx.USD = y.USD.(float64) @@ -228,14 +255,14 @@ func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransac tx.XRP = y.XRP varType = reflect.TypeOf(y.BTC).String() - if varType == "string" { + if varType == bitstampAPIReturnType { tx.BTC, _ = strconv.ParseFloat(y.BTC.(string), 64) } else { tx.BTC = y.BTC.(float64) } varType = reflect.TypeOf(y.BTCUSD).String() - if varType == "string" { + if varType == bitstampAPIReturnType { tx.BTCUSD, _ = strconv.ParseFloat(y.BTCUSD.(string), 64) } else { tx.BTCUSD = y.BTCUSD.(float64) @@ -249,184 +276,179 @@ func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransac return transactions, nil } -func (b *Bitstamp) GetOpenOrders(currency string) ([]BitstampOrder, error) { - resp := []BitstampOrder{} - path := fmt.Sprintf("%s/%s", BITSTAMP_API_OPEN_ORDERS, common.StringToLower(currency)) - err := b.SendAuthenticatedHTTPRequest(path, true, nil, &resp) +// GetOpenOrders returns all open orders on the exchange +func (b *Bitstamp) GetOpenOrders(currencyPair string) ([]Order, error) { + resp := []Order{} + path := fmt.Sprintf( + "%s/%s", bitstampAPIOpenOrders, common.StringToLower(currencyPair), + ) - if err != nil { - return nil, err - } - - return resp, nil + return resp, b.SendAuthenticatedHTTPRequest(path, true, nil, &resp) } -func (b *Bitstamp) GetOrderStatus(OrderID int64) (BitstampOrderStatus, error) { - var req = url.Values{} +// GetOrderStatus returns an the status of an order by its ID +func (b *Bitstamp) GetOrderStatus(OrderID int64) (OrderStatus, error) { + resp := OrderStatus{} + req := url.Values{} req.Add("id", strconv.FormatInt(OrderID, 10)) - resp := BitstampOrderStatus{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_CANCEL_ORDER, false, req, &resp) - - if err != nil { - return resp, err - } - - return resp, nil + return resp, + b.SendAuthenticatedHTTPRequest(bitstampAPIOrderStatus, false, req, &resp) } +// CancelOrder cancels order by ID func (b *Bitstamp) CancelOrder(OrderID int64) (bool, error) { - var req = url.Values{} result := false + var req = url.Values{} req.Add("id", strconv.FormatInt(OrderID, 10)) - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_CANCEL_ORDER, true, req, &result) - - if err != nil { - return result, err - } - - return result, nil + return result, + b.SendAuthenticatedHTTPRequest(bitstampAPICancelOrder, true, req, &result) } +// CancelAllOrders cancels all open orders on the exchange func (b *Bitstamp) CancelAllOrders() (bool, error) { result := false - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_CANCEL_ALL_ORDERS, false, nil, &result) - if err != nil { - return result, err - } - - return result, nil + return result, + b.SendAuthenticatedHTTPRequest(bitstampAPICancelAllOrders, false, nil, &result) } -func (b *Bitstamp) PlaceOrder(currency string, price float64, amount float64, buy, market bool) (BitstampOrder, error) { +// PlaceOrder places an order on the exchange. +func (b *Bitstamp) PlaceOrder(currencyPair string, price float64, amount float64, buy, market bool) (Order, error) { var req = url.Values{} req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("price", strconv.FormatFloat(price, 'f', -1, 64)) - response := BitstampOrder{} - orderType := BITSTAMP_API_BUY - path := "" + response := Order{} + orderType := bitstampAPIBuy if !buy { - orderType = BITSTAMP_API_SELL + orderType = bitstampAPISell } - path = fmt.Sprintf("%s/%s", orderType, common.StringToLower(currency)) + path := fmt.Sprintf("%s/%s", orderType, common.StringToLower(currencyPair)) if market { - path = fmt.Sprintf("%s/%s/%s", orderType, BITSTAMP_API_MARKET, common.StringToLower(currency)) + path = fmt.Sprintf("%s/%s/%s", orderType, bitstampAPIMarket, common.StringToLower(currencyPair)) } - err := b.SendAuthenticatedHTTPRequest(path, true, req, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest(path, true, req, &response) } -func (b *Bitstamp) GetWithdrawalRequests(values url.Values) ([]BitstampWithdrawalRequests, error) { - resp := []BitstampWithdrawalRequests{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_WITHDRAWAL_REQUESTS, false, values, &resp) - - if err != nil { - return nil, err +// GetWithdrawalRequests returns withdrawl 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) { + resp := []WithdrawalRequests{} + if timedelta > 50000000 || timedelta < 0 { + return resp, errors.New("time delta exceeded, max: 50000000 min: 0") } - return resp, nil + value := url.Values{} + value.Set("timedelta", strconv.FormatInt(timedelta, 10)) + + if timedelta == 0 { + value = url.Values{} + } + + return resp, + b.SendAuthenticatedHTTPRequest(bitstampAPIWithdrawalRequests, false, value, &resp) } -func (b *Bitstamp) BitcoinWithdrawal(amount float64, address string, instant bool) (string, error) { +// CryptoWithdrawal withdraws a cryptocurrency into a supplied wallet, returns ID +// amount - The amount you want withdrawn +// address - The wallet address of the cryptocurrency +// symbol - the type of crypto ie "ltc", "btc", "eth" +// destTag - only for XRP default to "" +// instant - only for bitcoins +func (b *Bitstamp) CryptoWithdrawal(amount float64, address, symbol, destTag string, instant bool) (string, error) { var req = url.Values{} req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("address", address) - if instant { - req.Add("instant", "1") - } else { - req.Add("instant", "0") - } - type response struct { ID string `json:"id"` } - resp := response{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_BITCOIN_WITHDRAWAL, false, req, &resp) - if err != nil { - return "", err + switch common.StringToLower(symbol) { + case "btc": + if instant { + req.Add("instant", "1") + } else { + req.Add("instant", "0") + } + return resp.ID, + b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinWithdrawal, false, req, &resp) + case "ltc": + return resp.ID, + b.SendAuthenticatedHTTPRequest(bitstampAPILTCWithdrawal, true, req, &resp) + case "eth": + return resp.ID, + b.SendAuthenticatedHTTPRequest(bitstampAPIETHWithdrawal, true, req, &resp) + case "xrp": + if destTag != "" { + req.Add("destination_tag", destTag) + } + return resp.ID, + b.SendAuthenticatedHTTPRequest(bitstampAPIXrpWithdrawal, true, req, &resp) } - - return resp.ID, nil + return resp.ID, + errors.New("incorrect symbol") } -func (b *Bitstamp) GetBitcoinDepositAddress() (string, error) { - address := "" - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_BITCOIN_DEPOSIT, false, url.Values{}, &address) - - if err != nil { - return address, err - } - return address, nil -} - -func (b *Bitstamp) GetUnconfirmedBitcoinDeposits() ([]BitstampUnconfirmedBTCTransactions, error) { - response := []BitstampUnconfirmedBTCTransactions{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_UNCONFIRMED_BITCOIN, false, nil, &response) - - if err != nil { - return nil, err - } - - return response, nil -} - -func (b *Bitstamp) RippleWithdrawal(amount float64, address, currency string) (bool, error) { - var req = url.Values{} - req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - req.Add("address", address) - req.Add("currency", currency) - - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_RIPPLE_WITHDRAWAL, false, req, nil) - - if err != nil { - return false, err - } - - return true, nil -} - -func (b *Bitstamp) GetRippleDepositAddress() (string, error) { +// GetCryptoDepositAddress returns a depositing address by crypto +// crypto - example "btc", "ltc", "eth", or "xrp" +func (b *Bitstamp) GetCryptoDepositAddress(crypto string) (string, error) { type response struct { - Address string + Address string `json:"address"` } - resp := response{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_RIPPLE_DESPOIT, false, nil, &resp) - if err != nil { - return "", err + switch common.StringToLower(crypto) { + case "btc": + return resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinDeposit, false, nil, &resp.Address) + case "ltc": + return resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPILitecoinDeposit, true, nil, &resp) + case "eth": + return resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPIEthereumDeposit, true, nil, &resp) + case "xrp": + return resp.Address, + b.SendAuthenticatedHTTPRequest(bitstampAPIXrpDeposit, true, nil, &resp) } - return resp.Address, nil + return resp.Address, errors.New("incorrect cryptocurrency string") } +// GetUnconfirmedBitcoinDeposits returns unconfirmed transactions +func (b *Bitstamp) GetUnconfirmedBitcoinDeposits() ([]UnconfirmedBTCTransactions, error) { + response := []UnconfirmedBTCTransactions{} + + return response, + b.SendAuthenticatedHTTPRequest(bitstampAPIUnconfirmedBitcoin, false, nil, &response) +} + +// TransferAccountBalance transfers funds from either a main or sub account +// amount - to transfers +// currency - which currency to transfer +// subaccount - name of account +// toMain - bool either to or from account func (b *Bitstamp) TransferAccountBalance(amount float64, currency, subAccount string, toMain bool) (bool, error) { var req = url.Values{} req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) req.Add("currency", currency) req.Add("subAccount", subAccount) - path := BITSTAMP_API_TRANSFER_TO_MAIN + path := bitstampAPITransferToMain if !toMain { - path = BITSTAMP_API_TRANSFER_FROM_MAIN + path = bitstampAPITransferFromMain } err := b.SendAuthenticatedHTTPRequest(path, true, req, nil) - if err != nil { return false, err } @@ -434,55 +456,31 @@ func (b *Bitstamp) TransferAccountBalance(amount float64, currency, subAccount s return true, nil } -func (b *Bitstamp) XRPWithdrawal(amount float64, address, destTag string) (string, error) { - var req = url.Values{} - req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64)) - req.Add("address", address) - if destTag != "" { - req.Add("destination_tag", destTag) - } - - type response struct { - ID string `json:"id"` - } - - resp := response{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_XRP_WITHDRAWAL, true, req, &resp) - - if err != nil { - return "", err - } - - return resp.ID, nil -} - -func (b *Bitstamp) GetXRPDepositAddress() (BitstampXRPDepositResponse, error) { - resp := BitstampXRPDepositResponse{} - err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_XRP_DESPOIT, true, nil, &resp) - - if err != nil { - return BitstampXRPDepositResponse{}, err - } - - return resp, nil -} - +// SendAuthenticatedHTTPRequest sends an authenticated request func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url.Values, result interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().UnixNano(), 10) + if !b.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + } + + if b.Nonce.Get() == 0 { + b.Nonce.Set(time.Now().UnixNano()) + } else { + b.Nonce.Inc() + } if values == nil { values = url.Values{} } values.Set("key", b.APIKey) - values.Set("nonce", nonce) - hmac := common.GetHMAC(common.HashSHA256, []byte(nonce+b.ClientID+b.APIKey), []byte(b.APISecret)) + values.Set("nonce", b.Nonce.String()) + hmac := common.GetHMAC(common.HashSHA256, []byte(b.Nonce.String()+b.ClientID+b.APIKey), []byte(b.APISecret)) values.Set("signature", common.StringToUpper(common.HexEncodeToString(hmac))) if v2 { - path = fmt.Sprintf("%s/v%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, path) + path = fmt.Sprintf("%s/v%s/%s/", bitstampAPIURL, bitstampAPIVersion, path) } else { - path = fmt.Sprintf("%s/%s/", BITSTAMP_API_URL, path) + path = fmt.Sprintf("%s/%s/", bitstampAPIURL, path) } if b.Verbose { @@ -501,11 +499,17 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url log.Printf("Received raw: %s\n", resp) } - err = common.JSONDecode([]byte(resp), &result) - - if err != nil { - return errors.New("unable to JSON Unmarshal response") + /* inconsistent errors, needs to be improved when in production*/ + if common.StringContains(resp, "500 error") { + return errors.New("internal server: code 500") } - return nil + capture := CaptureError{} + if err = common.JSONDecode([]byte(resp), &capture); err == nil { + if capture.Code != nil || capture.Error != nil || capture.Reason != nil || capture.Status != nil { + errstring := fmt.Sprint("Status: ", capture.Status, ", Issue: ", capture.Error, ", Reason: ", capture.Reason, ", Code: ", capture.Code) + return errors.New(errstring) + } + } + return common.JSONDecode([]byte(resp), &result) } diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go new file mode 100644 index 00000000..e7133de2 --- /dev/null +++ b/exchanges/bitstamp/bitstamp_test.go @@ -0,0 +1,346 @@ +package bitstamp + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +// Please add your private keys and customerID for better tests +const ( + apiKey = "" + apiSecret = "" + customerID = "" +) + +func TestSetDefaults(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.SetDefaults() + + if b.Name != "Bitstamp" { + t.Error("Test Failed - SetDefaults() error") + } + if b.Enabled != false { + t.Error("Test Failed - SetDefaults() error") + } + if b.Verbose != false { + t.Error("Test Failed - SetDefaults() error") + } + if b.Websocket != false { + t.Error("Test Failed - SetDefaults() error") + } + if b.RESTPollingDelay != 10 { + t.Error("Test Failed - SetDefaults() error") + } +} + +func TestSetup(t *testing.T) { + t.Parallel() + b := Bitstamp{} + conf := config.ExchangeConfig{ + Name: "bla", + Enabled: true, + AuthenticatedAPISupport: true, + } + b.Setup(conf) + + if b.Name != "bla" && b.Enabled != true && b.AuthenticatedAPISupport != true { + t.Error("Test Failed - Setup() error") + } + conf.Enabled = false + b.Setup(conf) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + b := Bitstamp{} + if resp := b.GetFee("BTCUSD"); resp != 0 { + t.Error("Test Failed - GetFee() error") + } + if resp := b.GetFee("BTCEUR"); resp != 0 { + t.Error("Test Failed - GetFee() error") + } + if resp := b.GetFee("XRPEUR"); resp != 0 { + t.Error("Test Failed - GetFee() error") + } + if resp := b.GetFee("XRPUSD"); resp != 0 { + t.Error("Test Failed - GetFee() error") + } + if resp := b.GetFee("EURUSD"); resp != 0 { + t.Error("Test Failed - GetFee() error") + } + if resp := b.GetFee("bla"); resp != 0 { + t.Error("Test Failed - GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + b := Bitstamp{} + _, err := b.GetTicker("BTCUSD", false) + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } + _, err = b.GetTicker("BTCUSD", true) + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + b := Bitstamp{} + _, err := b.GetOrderbook("BTCUSD") + if err != nil { + t.Error("Test Failed - GetOrderbook() error", err) + } +} + +func TestGetTransactions(t *testing.T) { + t.Parallel() + b := Bitstamp{} + + value := url.Values{} + value.Set("time", "hour") + + _, err := b.GetTransactions("BTCUSD", value) + if err != nil { + t.Error("Test Failed - GetTransactions() error", err) + } + _, err = b.GetTransactions("wigwham", value) + if err == nil { + t.Error("Test Failed - GetTransactions() error") + } +} + +func TestGetEURUSDConversionRate(t *testing.T) { + t.Parallel() + b := Bitstamp{} + _, err := b.GetEURUSDConversionRate() + if err != nil { + t.Error("Test Failed - GetEURUSDConversionRate() error", err) + } +} + +func TestGetBalance(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.GetBalance() + if err == nil { + t.Error("Test Failed - GetBalance() error", err) + } +} + +func TestGetUserTransactions(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.GetUserTransactions("") + if err == nil { + t.Error("Test Failed - GetUserTransactions() error", err) + } + + _, err = b.GetUserTransactions("btcusd") + if err == nil { + t.Error("Test Failed - GetUserTransactions() error", err) + } +} + +func TestGetOpenOrders(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.GetOpenOrders("btcusd") + if err == nil { + t.Error("Test Failed - GetOpenOrders() error", err) + } + _, err = b.GetOpenOrders("wigwham") + if err == nil { + t.Error("Test Failed - GetOpenOrders() error") + } +} + +func TestGetOrderStatus(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.GetOrderStatus(1337) + if err == nil { + t.Error("Test Failed - GetOpenOrders() error") + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + resp, err := b.CancelOrder(1337) + if err == nil || resp != false { + t.Error("Test Failed - CancelOrder() error") + } +} + +func TestCancelAllOrders(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.CancelAllOrders() + if err == nil { + t.Error("Test Failed - CancelAllOrders() error", err) + } +} + +func TestPlaceOrder(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.PlaceOrder("btcusd", 0.01, 1, true, true) + if err == nil { + t.Error("Test Failed - PlaceOrder() error") + } + _, err = b.PlaceOrder("btcusd", 0.01, 1, true, false) + if err == nil { + t.Error("Test Failed - PlaceOrder() error") + } + _, err = b.PlaceOrder("btcusd", 0.01, 1, false, false) + if err == nil { + t.Error("Test Failed - PlaceOrder() error") + } + _, err = b.PlaceOrder("wigwham", 0.01, 1, false, false) + if err == nil { + t.Error("Test Failed - PlaceOrder() error") + } +} + +func TestGetWithdrawalRequests(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.GetWithdrawalRequests(0) + if err == nil { + t.Error("Test Failed - GetWithdrawalRequests() error", err) + } + _, err = b.GetWithdrawalRequests(-1) + if err == nil { + t.Error("Test Failed - GetWithdrawalRequests() error") + } +} + +func TestCryptoWithdrawal(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.CryptoWithdrawal(0, "bla", "btc", "", true) + if err == nil { + t.Error("Test Failed - CryptoWithdrawal() error", err) + } + _, err = b.CryptoWithdrawal(0, "bla", "btc", "", false) + if err == nil { + t.Error("Test Failed - CryptoWithdrawal() error", err) + } + _, err = b.CryptoWithdrawal(0, "bla", "ltc", "", false) + if err == nil { + t.Error("Test Failed - CryptoWithdrawal() error", err) + } + _, err = b.CryptoWithdrawal(0, "bla", "eth", "", false) + if err == nil { + t.Error("Test Failed - CryptoWithdrawal() error", err) + } + _, err = b.CryptoWithdrawal(0, "bla", "xrp", "someplace", false) + if err == nil { + t.Error("Test Failed - CryptoWithdrawal() error", err) + } + _, err = b.CryptoWithdrawal(0, "bla", "ding!", "", false) + if err == nil { + t.Error("Test Failed - CryptoWithdrawal() error", err) + } +} + +func TestGetBitcoinDepositAddress(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.GetCryptoDepositAddress("btc") + if err == nil { + t.Error("Test Failed - GetCryptoDepositAddress() error", err) + } + _, err = b.GetCryptoDepositAddress("LTc") + if err == nil { + t.Error("Test Failed - GetCryptoDepositAddress() error", err) + } + _, err = b.GetCryptoDepositAddress("eth") + if err == nil { + t.Error("Test Failed - GetCryptoDepositAddress() error", err) + } + _, err = b.GetCryptoDepositAddress("xrp") + if err == nil { + t.Error("Test Failed - GetCryptoDepositAddress() error", err) + } + _, err = b.GetCryptoDepositAddress("wigwham") + if err == nil { + t.Error("Test Failed - GetCryptoDepositAddress() error") + } +} + +func TestGetUnconfirmedBitcoinDeposits(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.GetUnconfirmedBitcoinDeposits() + if err == nil { + t.Error("Test Failed - GetUnconfirmedBitcoinDeposits() error", err) + } +} + +func TestTransferAccountBalance(t *testing.T) { + t.Parallel() + b := Bitstamp{} + b.APIKey = apiKey + b.APISecret = apiSecret + b.ClientID = customerID + + _, err := b.TransferAccountBalance(1, "", "", true) + if err == nil { + t.Error("Test Failed - TransferAccountBalance() error", err) + } + _, err = b.TransferAccountBalance(1, "btc", "", false) + if err == nil { + t.Error("Test Failed - TransferAccountBalance() error", err) + } +} diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index bc90479d..d029b62f 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -1,6 +1,7 @@ package bitstamp -type BitstampTicker struct { +// Ticker holds ticker information +type Ticker struct { Last float64 `json:"last,string"` High float64 `json:"high,string"` Low float64 `json:"low,string"` @@ -12,38 +13,21 @@ type BitstampTicker struct { Open float64 `json:"open,string"` } -type BitstampBalances struct { - BTCReserved float64 `json:"btc_reserved,string"` - BTCEURFee float64 `json:"btceur_fee,string"` - BTCAvailable float64 `json:"btc_available,string"` - XRPAvailable float64 `json:"xrp_available,string"` - EURAvailable float64 `json:"eur_available,string"` - USDReserved float64 `json:"usd_reserved,string"` - EURReserved float64 `json:"eur_reserved,string"` - XRPEURFee float64 `json:"xrpeur_fee,string"` - XRPReserved float64 `json:"xrp_reserved,string"` - XRPBalance float64 `json:"xrp_balance,string"` - XRPUSDFee float64 `json:"xrpusd_fee,string"` - EURBalance float64 `json:"eur_balance,string"` - BTCBalance float64 `json:"btc_balance,string"` - BTCUSDFee float64 `json:"btcusd_fee,string"` - USDBalance float64 `json:"usd_balance,string"` - USDAvailable float64 `json:"usd_available,string"` - EURUSDFee float64 `json:"eurusd_fee,string"` -} - -type BitstampOrderbookBase struct { +// OrderbookBase holds singular price information +type OrderbookBase struct { Price float64 Amount float64 } -type BitstampOrderbook struct { +// Orderbook holds orderbook information +type Orderbook struct { Timestamp int64 `json:"timestamp,string"` - Bids []BitstampOrderbookBase - Asks []BitstampOrderbookBase + Bids []OrderbookBase + Asks []OrderbookBase } -type BitstampTransactions struct { +// Transactions holds transaction data +type Transactions struct { Date int64 `json:"date,string"` TradeID int64 `json:"tid,string"` Price float64 `json:"price,string"` @@ -51,12 +35,37 @@ type BitstampTransactions struct { Amount float64 `json:"amount,string"` } -type BitstampEURUSDConversionRate struct { +// EURUSDConversionRate holds buy sell conversion rate information +type EURUSDConversionRate struct { Buy float64 `json:"buy,string"` Sell float64 `json:"sell,string"` } -type BitstampUserTransactions struct { +// Balances holds full balance information with the supplied APIKEYS +type Balances struct { + USDBalance float64 `json:"usd_balance,string"` + BTCBalance float64 `json:"btc_balance,string"` + EURBalance float64 `json:"eur_balance,string"` + XRPBalance float64 `json:"xrp_balance,string"` + USDReserved float64 `json:"usd_reserved,string"` + BTCReserved float64 `json:"btc_reserved,string"` + EURReserved float64 `json:"eur_reserved,string"` + XRPReserved float64 `json:"xrp_reserved,string"` + USDAvailable float64 `json:"usd_available,string"` + BTCAvailable float64 `json:"btc_available,string"` + EURAvailable float64 `json:"eur_available,string"` + XRPAvailable float64 `json:"xrp_available,string"` + BTCUSDFee float64 `json:"btcusd_fee,string"` + BTCEURFee float64 `json:"btceur_fee,string"` + EURUSDFee float64 `json:"eurusd_fee,string"` + XRPUSDFee float64 `json:"xrpusd_fee,string"` + XRPEURFee float64 `json:"xrpeur_fee,string"` + XRPBTCFee float64 `json:"xrpbtc_fee,string"` + Fee float64 `json:"fee,string"` +} + +// UserTransactions holds user transaction information +type UserTransactions struct { Date string `json:"datetime"` TransID int64 `json:"id"` Type int `json:"type,string"` @@ -69,7 +78,8 @@ type BitstampUserTransactions struct { OrderID int64 `json:"order_id"` } -type BitstampOrder struct { +// Order holds current open order data +type Order struct { ID int64 `json:"id"` Date string `json:"datetime"` Type int `json:"type"` @@ -77,7 +87,8 @@ type BitstampOrder struct { Amount float64 `json:"amount"` } -type BitstampOrderStatus struct { +// OrderStatus holds order status information +type OrderStatus struct { Status string Transactions []struct { TradeID int64 `json:"tid"` @@ -88,22 +99,30 @@ type BitstampOrderStatus struct { } } -type BitstampWithdrawalRequests struct { - OrderID int64 `json:"id"` - Date string `json:"datetime"` - Type int `json:"type"` - Amount float64 `json:"amount,string"` - Status int `json:"status"` - Data interface{} +// WithdrawalRequests holds request information on withdrawals +type WithdrawalRequests struct { + OrderID int64 `json:"id"` + Date string `json:"datetime"` + Type int `json:"type"` + Amount float64 `json:"amount,string"` + Status int `json:"status"` + Data interface{} + Address string `json:"address"` // Bitcoin withdrawals only + TransactionID string `json:"transaction_id"` // Bitcoin withdrawals only } -type BitstampUnconfirmedBTCTransactions struct { +// UnconfirmedBTCTransactions holds address information about unconfirmed +// transactions +type UnconfirmedBTCTransactions struct { Amount float64 `json:"amount,string"` Address string `json:"address"` Confirmations int `json:"confirmations"` } -type BitstampXRPDepositResponse struct { - Address string `json:"address"` - DestinationTag int64 `json:"destination_tag"` +// CaptureError is used to capture unmarshalled errors +type CaptureError struct { + Status interface{} `json:"status"` + Reason interface{} `json:"reason"` + Code interface{} `json:"code"` + Error interface{} `json:"error"` } diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 6958d1a0..35748654 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -7,23 +7,28 @@ import ( "github.com/toorop/go-pusher" ) -type BitstampPusherOrderbook struct { +// PusherOrderbook holds order book information to be pushed +type PusherOrderbook struct { Asks [][]string `json:"asks"` Bids [][]string `json:"bids"` } -type BitstampPusherTrade struct { + +// PusherTrade holds trade information to be pushed +type PusherTrade struct { Price float64 `json:"price"` Amount float64 `json:"amount"` ID int64 `json:"id"` } const ( - BITSTAMP_PUSHER_KEY = "de504dc5763aeef9ff52" + // BitstampPusherKey holds the current pusher key + BitstampPusherKey = "de504dc5763aeef9ff52" ) +// PusherClient starts the push mechanism func (b *Bitstamp) PusherClient() { for b.Enabled && b.Websocket { - pusherClient, err := pusher.NewClient(BITSTAMP_PUSHER_KEY) + pusherClient, err := pusher.NewClient(BitstampPusherKey) if err != nil { log.Printf("%s Unable to connect to Websocket. Error: %s\n", b.GetName(), err) continue @@ -55,13 +60,13 @@ func (b *Bitstamp) PusherClient() { for b.Websocket { select { case data := <-dataChannelTrade: - result := BitstampPusherOrderbook{} + result := PusherOrderbook{} err := common.JSONDecode([]byte(data.Data), &result) if err != nil { log.Println(err) } case trade := <-tradeChannelTrade: - result := BitstampPusherTrade{} + result := PusherTrade{} err := common.JSONDecode([]byte(trade.Data), &result) if err != nil { log.Println(err) diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index a6692071..c4988c78 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -6,16 +6,18 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" - "github.com/thrasher-/gocryptotrader/exchanges" + exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts a new go routine run func (b *Bitstamp) Start() { go b.Run() } +// Run starts a new websocket connection runs a new go routine pusher func (b *Bitstamp) Run() { if b.Verbose { log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) @@ -36,7 +38,7 @@ func (b *Bitstamp) Run() { log.Println(err) return } - log.Printf("Bitstamp %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("Bitstamp %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } @@ -44,6 +46,7 @@ func (b *Bitstamp) Run() { } } +// GetTickerPrice returns ticker price information func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p) if err == nil { @@ -67,6 +70,7 @@ func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro return tickerPrice, nil } +// GetOrderbookEx returns base orderbook information func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { @@ -94,11 +98,12 @@ func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, return orderBook, nil } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Bitstamp exchange -func (e *Bitstamp) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// Bitstamp exchange +func (b *Bitstamp) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetBalance() + response.ExchangeName = b.GetName() + accountBalance, err := b.GetBalance() if err != nil { return response, err } diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index 011fa499..cdcb857a 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -170,7 +170,7 @@ func (b *Bittrex) PlaceBuyLimit(currencyPair string, quantity, rate float64) ([] values.Set("market", currencyPair) values.Set("quantity", strconv.FormatFloat(quantity, 'E', -1, 64)) values.Set("rate", strconv.FormatFloat(rate, 'E', -1, 64)) - path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetBalances) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIBuyLimit) return id, b.HTTPRequest(path, true, values, &id) } @@ -187,7 +187,7 @@ func (b *Bittrex) PlaceSellLimit(currencyPair string, quantity, rate float64) ([ values.Set("market", currencyPair) values.Set("quantity", strconv.FormatFloat(quantity, 'E', -1, 64)) values.Set("rate", strconv.FormatFloat(rate, 'E', -1, 64)) - path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetBalances) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPISellLimit) return id, b.HTTPRequest(path, true, values, &id) } @@ -200,7 +200,7 @@ func (b *Bittrex) GetOpenOrders(currencyPair string) ([]Order, error) { if !(currencyPair == "" || currencyPair == " ") { values.Set("market", currencyPair) } - path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetBalances) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetOpenOrders) return orders, b.HTTPRequest(path, true, values, &orders) } @@ -210,7 +210,7 @@ func (b *Bittrex) CancelOrder(uuid string) ([]Balance, error) { var balances []Balance values := url.Values{} values.Set("uuid", uuid) - path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetBalances) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPICancel) return balances, b.HTTPRequest(path, true, values, &balances) } @@ -283,16 +283,16 @@ func (b *Bittrex) GetOrderHistory(currencyPair string) ([]Order, error) { return orders, b.HTTPRequest(path, true, values, &orders) } -// GetWithdrawelHistory is used to retrieve your withdrawal history. If currency +// GetWithdrawalHistory is used to retrieve your withdrawal history. If currency // omitted it will return the entire history -func (b *Bittrex) GetWithdrawelHistory(currency string) ([]WithdrawalHistory, error) { +func (b *Bittrex) GetWithdrawalHistory(currency string) ([]WithdrawalHistory, error) { var history []WithdrawalHistory values := url.Values{} if !(currency == "" || currency == " ") { values.Set("currency", currency) } - path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetOrderHistory) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetWithdrawalHistory) return history, b.HTTPRequest(path, true, values, &history) } @@ -306,7 +306,7 @@ func (b *Bittrex) GetDepositHistory(currency string) ([]WithdrawalHistory, error if !(currency == "" || currency == " ") { values.Set("currency", currency) } - path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetOrderHistory) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetDepositHistory) return history, b.HTTPRequest(path, true, values, &history) } @@ -314,10 +314,18 @@ func (b *Bittrex) GetDepositHistory(currency string) ([]WithdrawalHistory, error // SendAuthenticatedHTTPRequest sends an authenticated http request to a desired // path func (b *Bittrex) SendAuthenticatedHTTPRequest(path string, values url.Values, result interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().UnixNano(), 10) + if !b.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + } + + if b.Nonce.Get() == 0 { + b.Nonce.Set(time.Now().UnixNano()) + } else { + b.Nonce.Inc() + } values.Set("apikey", b.APIKey) values.Set("apisecret", b.APISecret) - values.Set("nonce", nonce) + values.Set("nonce", b.Nonce.String()) rawQuery := path + "?" + values.Encode() hmac := common.GetHMAC( common.HashSHA512, []byte(rawQuery), []byte(b.APISecret), diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go index 4adedb33..32f7786f 100644 --- a/exchanges/bittrex/bittrex_test.go +++ b/exchanges/bittrex/bittrex_test.go @@ -13,6 +13,7 @@ const ( ) func TestSetDefaults(t *testing.T) { + t.Parallel() b := Bittrex{} b.SetDefaults() if b.GetName() != "Bittrex" { @@ -21,6 +22,7 @@ func TestSetDefaults(t *testing.T) { } func TestSetup(t *testing.T) { + t.Parallel() exch := config.ExchangeConfig{ Name: "Bittrex", APIKey: apiKey, @@ -39,6 +41,7 @@ func TestSetup(t *testing.T) { } func TestGetMarkets(t *testing.T) { + t.Parallel() obj := Bittrex{} _, err := obj.GetMarkets() if err != nil { @@ -47,6 +50,7 @@ func TestGetMarkets(t *testing.T) { } func TestGetCurrencies(t *testing.T) { + t.Parallel() obj := Bittrex{} _, err := obj.GetCurrencies() if err != nil { @@ -55,6 +59,7 @@ func TestGetCurrencies(t *testing.T) { } func TestGetTicker(t *testing.T) { + t.Parallel() invalid := "" btc := "btc-ltc" doge := "btc-DOGE" @@ -75,6 +80,7 @@ func TestGetTicker(t *testing.T) { } func TestGetMarketSummaries(t *testing.T) { + t.Parallel() obj := Bittrex{} _, err := obj.GetMarketSummaries() if err != nil { @@ -83,6 +89,7 @@ func TestGetMarketSummaries(t *testing.T) { } func TestGetMarketSummary(t *testing.T) { + t.Parallel() pairOne := "BTC-LTC" invalid := "WigWham" @@ -98,6 +105,7 @@ func TestGetMarketSummary(t *testing.T) { } func TestGetOrderbook(t *testing.T) { + t.Parallel() obj := Bittrex{} _, err := obj.GetOrderbook("btc-ltc") if err != nil { @@ -110,6 +118,7 @@ func TestGetOrderbook(t *testing.T) { } func TestGetMarketHistory(t *testing.T) { + t.Parallel() obj := Bittrex{} _, err := obj.GetMarketHistory("btc-ltc") if err != nil { @@ -122,6 +131,7 @@ func TestGetMarketHistory(t *testing.T) { } func TestPlaceBuyLimit(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -132,6 +142,7 @@ func TestPlaceBuyLimit(t *testing.T) { } func TestPlaceSellLimit(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -142,6 +153,7 @@ func TestPlaceSellLimit(t *testing.T) { } func TestGetOpenOrders(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -156,6 +168,7 @@ func TestGetOpenOrders(t *testing.T) { } func TestCancelOrder(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -166,6 +179,7 @@ func TestCancelOrder(t *testing.T) { } func TestGetAccountBalances(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -176,6 +190,7 @@ func TestGetAccountBalances(t *testing.T) { } func TestGetAccountBalanceByCurrency(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -186,6 +201,7 @@ func TestGetAccountBalanceByCurrency(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -196,6 +212,7 @@ func TestGetDepositAddress(t *testing.T) { } func TestWithdraw(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -206,6 +223,7 @@ func TestWithdraw(t *testing.T) { } func TestGetOrder(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -220,6 +238,7 @@ func TestGetOrder(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret @@ -234,20 +253,22 @@ func TestGetOrderHistory(t *testing.T) { } func TestGetWithdrawelHistory(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret - _, err := obj.GetWithdrawelHistory("") + _, err := obj.GetWithdrawalHistory("") if err == nil { - t.Error("Test Failed - Bittrex - GetWithdrawelHistory() error") + t.Error("Test Failed - Bittrex - GetWithdrawalHistory() error") } - _, err = obj.GetWithdrawelHistory("btc-ltc") + _, err = obj.GetWithdrawalHistory("btc-ltc") if err == nil { - t.Error("Test Failed - Bittrex - GetWithdrawelHistory() error") + t.Error("Test Failed - Bittrex - GetWithdrawalHistory() error") } } func TestGetDepositHistory(t *testing.T) { + t.Parallel() obj := Bittrex{} obj.APIKey = apiKey obj.APISecret = apiSecret diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go new file mode 100644 index 00000000..7689a478 --- /dev/null +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -0,0 +1,137 @@ +package bittrex + +import ( + "log" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/exchanges" + "github.com/thrasher-/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-/gocryptotrader/exchanges/stats" + "github.com/thrasher-/gocryptotrader/exchanges/ticker" +) + +// Start stats the Bittrex go routine +func (b *Bittrex) Start() { + go b.Run() +} + +// Run implements the Bittrex wrapper +func (b *Bittrex) Run() { + if b.Verbose { + log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) + log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) + } + + exchangeProducts, err := b.GetMarkets() + if err != nil { + log.Printf("%s Failed to get available symbols.\n", b.GetName()) + } else { + var currencies []string + for x := range exchangeProducts { + if !exchangeProducts[x].IsActive { + continue + } + currencies = append(currencies, + common.ReplaceString(exchangeProducts[x].MarketName, "-", "", -1)) + } + err = b.UpdateAvailableCurrencies(currencies) + if err != nil { + log.Printf("%s Failed to get config.\n", b.GetName()) + } + } + + for b.Enabled { + for _, x := range b.EnabledPairs { + currency := pair.NewCurrencyPair(x[0:3], x[3:]) + currency.Delimiter = "-" + go func() { + ticker, err := b.GetTickerPrice(currency) + if err != nil { + log.Println(err) + return + } + log.Printf("Bittrex %s Last %f Bid %f Ask %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.Bid, ticker.Ask, ticker.Volume) + stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) + }() + } + time.Sleep(time.Second * b.RESTPollingDelay) + } +} + +//GetExchangeAccountInfo Retrieves balances for all enabled currencies for the Bittrexexchange +func (b *Bittrex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { + var response exchange.AccountInfo + response.ExchangeName = b.GetName() + accountBalance, err := b.GetAccountBalances() + if err != nil { + return response, err + } + + for i := 0; i < len(accountBalance); i++ { + var exchangeCurrency exchange.AccountCurrencyInfo + exchangeCurrency.CurrencyName = accountBalance[i].Currency + exchangeCurrency.TotalValue = accountBalance[i].Balance + exchangeCurrency.Hold = accountBalance[i].Balance - accountBalance[i].Available + response.Currencies = append(response.Currencies, exchangeCurrency) + } + return response, nil +} + +// GetTickerPrice returns the ticker for a currencyp pair +func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { + tickerNew, err := ticker.GetTicker(b.GetName(), p) + if err == nil { + return tickerNew, nil + } + + var tickerPrice ticker.TickerPrice + tick, err := b.GetMarketSummary(p.Pair().Lower().String()) + if err != nil { + return tickerPrice, err + } + tickerPrice.Pair = p + tickerPrice.Ask = tick[0].Ask + tickerPrice.Bid = tick[0].Bid + tickerPrice.Last = tick[0].Last + tickerPrice.Volume = tick[0].Volume + ticker.ProcessTicker(b.GetName(), p, tickerPrice) + return tickerPrice, nil +} + +// GetOrderbookEx returns the orderbook for a currencyp pair +func (b *Bittrex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { + ob, err := orderbook.GetOrderbook(b.GetName(), p) + if err == nil { + return ob, nil + } + + var orderBook orderbook.OrderbookBase + orderbookNew, err := b.GetOrderbook(p.Pair().Lower().String()) + if err != nil { + return orderBook, err + } + + for x := range orderbookNew.Buy { + orderBook.Bids = append(orderBook.Bids, + orderbook.OrderbookItem{ + Amount: orderbookNew.Buy[x].Quantity, + Price: orderbookNew.Buy[x].Rate, + }, + ) + } + + for x := range orderbookNew.Sell { + orderBook.Asks = append(orderBook.Asks, + orderbook.OrderbookItem{ + Amount: orderbookNew.Sell[x].Quantity, + Price: orderbookNew.Sell[x].Rate, + }, + ) + } + + orderBook.Pair = p + orderbook.ProcessOrderbook(b.GetName(), p, orderBook) + return orderBook, nil +} diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index dfae06c7..eb4beffa 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -15,37 +15,39 @@ import ( ) const ( - BTCC_API_URL = "https://api.btcchina.com/" - BTCC_API_AUTHENTICATED_METHOD = "api_trade_v1.php" - BTCC_API_VER = "2.0.1.3" - BTCC_ORDER_BUY = "buyOrder2" - BTCC_ORDER_SELL = "sellOrder2" - BTCC_ORDER_CANCEL = "cancelOrder" - BTCC_ICEBERG_BUY = "buyIcebergOrder" - BTCC_ICEBERG_SELL = "sellIcebergOrder" - BTCC_ICEBERG_ORDER = "getIcebergOrder" - BTCC_ICEBERG_ORDERS = "getIcebergOrders" - BTCC_ICEBERG_CANCEL = "cancelIcebergOrder" - BTCC_ACCOUNT_INFO = "getAccountInfo" - BTCC_DEPOSITS = "getDeposits" - BTCC_MARKETDEPTH = "getMarketDepth2" - BTCC_ORDER = "getOrder" - BTCC_ORDERS = "getOrders" - BTCC_TRANSACTIONS = "getTransactions" - BTCC_WITHDRAWAL = "getWithdrawal" - BTCC_WITHDRAWALS = "getWithdrawals" - BTCC_WITHDRAWAL_REQUEST = "requestWithdrawal" - BTCC_STOPORDER_BUY = "buyStopOrder" - BTCC_STOPORDER_SELL = "sellStopOrder" - BTCC_STOPORDER_CANCEL = "cancelStopOrder" - BTCC_STOPORDER = "getStopOrder" - BTCC_STOPORDERS = "getStopOrders" + btccAPIUrl = "https://api.btcchina.com/" + btccAPIAuthenticatedMethod = "api_trade_v1.php" + btccAPIVersion = "2.0.1.3" + btccOrderBuy = "buyOrder2" + btccOrderSell = "sellOrder2" + btccOrderCancel = "cancelOrder" + btccIcebergBuy = "buyIcebergOrder" + btccIcebergSell = "sellIcebergOrder" + btccIcebergOrder = "getIcebergOrder" + btccIcebergOrders = "getIcebergOrders" + btccIcebergCancel = "cancelIcebergOrder" + btccAccountInfo = "getAccountInfo" + btccDeposits = "getDeposits" + btccMarketdepth = "getMarketDepth2" + btccOrder = "getOrder" + btccOrders = "getOrders" + btccTransactions = "getTransactions" + btccWithdrawal = "getWithdrawal" + btccWithdrawals = "getWithdrawals" + btccWithdrawalRequest = "requestWithdrawal" + btccStoporderBuy = "buyStopOrder" + btccStoporderSell = "sellStopOrder" + btccStoporderCancel = "cancelStopOrder" + btccStoporder = "getStopOrder" + btccStoporders = "getStopOrders" ) +// BTCC is the main overaching type across the BTCC package type BTCC struct { exchange.Base } +// SetDefaults sets default values for the exchange func (b *BTCC) SetDefaults() { b.Name = "BTCC" b.Enabled = false @@ -55,7 +57,7 @@ func (b *BTCC) SetDefaults() { b.RESTPollingDelay = 10 } -//Setup is run on startup to setup exchange with config values +// Setup is run on startup to setup exchange with config values func (b *BTCC) Setup(exch config.ExchangeConfig) { if !exch.Enabled { b.SetEnabled(false) @@ -72,36 +74,40 @@ func (b *BTCC) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the fees associated with transactions func (b *BTCC) GetFee() float64 { return b.Fee } -func (b *BTCC) GetTicker(symbol string) (BTCCTicker, error) { - type Response struct { - Ticker BTCCTicker - } - +// GetTicker returns ticker information +// currencyPair - Example "btccny", "ltccny" or "ltcbtc" +func (b *BTCC) GetTicker(currencyPair string) (Ticker, error) { resp := Response{} - req := fmt.Sprintf("%sdata/ticker?market=%s", BTCC_API_URL, symbol) - err := common.SendHTTPGetRequest(req, true, &resp) - if err != nil { - return BTCCTicker{}, err - } - return resp.Ticker, nil + req := fmt.Sprintf("%sdata/ticker?market=%s", btccAPIUrl, currencyPair) + + return resp.Ticker, common.SendHTTPGetRequest(req, true, &resp) } -func (b *BTCC) GetTradesLast24h(symbol string) bool { - req := fmt.Sprintf("%sdata/trades?market=%s", BTCC_API_URL, symbol) - err := common.SendHTTPGetRequest(req, true, nil) - if err != nil { - log.Println(err) - return false - } - return true +// GetTradesLast24h returns the trades executed on the exchange over the past +// 24 hours by currency pair +// currencyPair - Example "btccny", "ltccny" or "ltcbtc" +func (b *BTCC) GetTradesLast24h(currencyPair string) ([]Trade, error) { + trades := []Trade{} + req := fmt.Sprintf("%sdata/trades?market=%s", btccAPIUrl, currencyPair) + + return trades, common.SendHTTPGetRequest(req, true, &trades) } -func (b *BTCC) GetTradeHistory(symbol string, limit, sinceTid int64, time time.Time) bool { - req := fmt.Sprintf("%sdata/historydata?market=%s", BTCC_API_URL, symbol) +// GetTradeHistory returns trade history data +// currencyPair - Example "btccny", "ltccny" or "ltcbtc" +// limit - limits the returned trades example "10" +// sinceTid - returns trade records starting from id supplied example "5000" +// time - returns trade records starting from unix time 1406794449 +func (b *BTCC) GetTradeHistory(currencyPair string, limit, sinceTid int64, time time.Time) ([]Trade, error) { + trades := []Trade{} + + req := fmt.Sprintf("%sdata/historydata?market=%s", btccAPIUrl, currencyPair) + v := url.Values{} if limit > 0 { @@ -115,37 +121,33 @@ func (b *BTCC) GetTradeHistory(symbol string, limit, sinceTid int64, time time.T } req = common.EncodeURLValues(req, v) - err := common.SendHTTPGetRequest(req, true, nil) - if err != nil { - log.Println(err) - return false - } - return true + + return trades, common.SendHTTPGetRequest(req, true, &trades) } -func (b *BTCC) GetOrderBook(symbol string, limit int) (BTCCOrderbook, error) { - result := BTCCOrderbook{} - req := fmt.Sprintf("%sdata/orderbook?market=%s&limit=%d", BTCC_API_URL, symbol, limit) - err := common.SendHTTPGetRequest(req, true, &result) - if err != nil { - return BTCCOrderbook{}, err +// GetOrderBook returns current market order book +// currencyPair - Example "btccny", "ltccny" or "ltcbtc" +// limit - limits the returned trades example "10" if 0 will return full +// orderbook +func (b *BTCC) GetOrderBook(currencyPair string, limit int) (Orderbook, error) { + result := Orderbook{} + + req := fmt.Sprintf("%sdata/orderbook?market=%s&limit=%d", btccAPIUrl, currencyPair, limit) + if limit == 0 { + req = fmt.Sprintf("%sdata/orderbook?market=%s", btccAPIUrl, currencyPair) } - return result, nil + return result, common.SendHTTPGetRequest(req, true, &result) } -func (b *BTCC) GetAccountInfo(infoType string) { +func (b *BTCC) GetAccountInfo(infoType string) error { params := make([]interface{}, 0) if len(infoType) > 0 { params = append(params, infoType) } - err := b.SendAuthenticatedHTTPRequest(BTCC_ACCOUNT_INFO, params) - - if err != nil { - log.Println(err) - } + return b.SendAuthenticatedHTTPRequest(btccAccountInfo, params) } func (b *BTCC) PlaceOrder(buyOrder bool, price, amount float64, market string) { @@ -157,9 +159,9 @@ func (b *BTCC) PlaceOrder(buyOrder bool, price, amount float64, market string) { params = append(params, market) } - req := BTCC_ORDER_BUY + req := btccOrderBuy if !buyOrder { - req = BTCC_ORDER_SELL + req = btccOrderSell } err := b.SendAuthenticatedHTTPRequest(req, params) @@ -177,7 +179,7 @@ func (b *BTCC) CancelOrder(orderID int64, market string) { params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_ORDER_CANCEL, params) + err := b.SendAuthenticatedHTTPRequest(btccOrderCancel, params) if err != nil { log.Println(err) @@ -192,7 +194,7 @@ func (b *BTCC) GetDeposits(currency string, pending bool) { params = append(params, pending) } - err := b.SendAuthenticatedHTTPRequest(BTCC_DEPOSITS, params) + err := b.SendAuthenticatedHTTPRequest(btccDeposits, params) if err != nil { log.Println(err) @@ -210,7 +212,7 @@ func (b *BTCC) GetMarketDepth(market string, limit int64) { params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_MARKETDEPTH, params) + err := b.SendAuthenticatedHTTPRequest(btccMarketdepth, params) if err != nil { log.Println(err) @@ -229,7 +231,7 @@ func (b *BTCC) GetOrder(orderID int64, market string, detailed bool) { params = append(params, detailed) } - err := b.SendAuthenticatedHTTPRequest(BTCC_ORDER, params) + err := b.SendAuthenticatedHTTPRequest(btccOrder, params) if err != nil { log.Println(err) @@ -263,7 +265,7 @@ func (b *BTCC) GetOrders(openonly bool, market string, limit, offset, since int6 params = append(params, detailed) } - err := b.SendAuthenticatedHTTPRequest(BTCC_ORDERS, params) + err := b.SendAuthenticatedHTTPRequest(btccOrders, params) if err != nil { log.Println(err) @@ -293,7 +295,7 @@ func (b *BTCC) GetTransactions(transType string, limit, offset, since int64, sin params = append(params, sinceType) } - err := b.SendAuthenticatedHTTPRequest(BTCC_TRANSACTIONS, params) + err := b.SendAuthenticatedHTTPRequest(btccTransactions, params) if err != nil { log.Println(err) @@ -308,7 +310,7 @@ func (b *BTCC) GetWithdrawal(withdrawalID int64, currency string) { params = append(params, currency) } - err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWAL, params) + err := b.SendAuthenticatedHTTPRequest(btccWithdrawal, params) if err != nil { log.Println(err) @@ -323,7 +325,7 @@ func (b *BTCC) GetWithdrawals(currency string, pending bool) { params = append(params, pending) } - err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWALS, params) + err := b.SendAuthenticatedHTTPRequest(btccWithdrawals, params) if err != nil { log.Println(err) @@ -335,7 +337,7 @@ func (b *BTCC) RequestWithdrawal(currency string, amount float64) { params = append(params, currency) params = append(params, amount) - err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWAL_REQUEST, params) + err := b.SendAuthenticatedHTTPRequest(btccWithdrawalRequest, params) if err != nil { log.Println(err) @@ -353,9 +355,9 @@ func (b *BTCC) IcebergOrder(buyOrder bool, price, amount, discAmount, variance f params = append(params, market) } - req := BTCC_ICEBERG_BUY + req := btccIcebergBuy if !buyOrder { - req = BTCC_ICEBERG_SELL + req = btccIcebergSell } err := b.SendAuthenticatedHTTPRequest(req, params) @@ -373,7 +375,7 @@ func (b *BTCC) GetIcebergOrder(orderID int64, market string) { params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_ORDER, params) + err := b.SendAuthenticatedHTTPRequest(btccIcebergOrder, params) if err != nil { log.Println(err) @@ -395,7 +397,7 @@ func (b *BTCC) GetIcebergOrders(limit, offset int64, market string) { params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_ORDERS, params) + err := b.SendAuthenticatedHTTPRequest(btccIcebergOrders, params) if err != nil { log.Println(err) @@ -410,7 +412,7 @@ func (b *BTCC) CancelIcebergOrder(orderID int64, market string) { params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_CANCEL, params) + err := b.SendAuthenticatedHTTPRequest(btccIcebergCancel, params) if err != nil { log.Println(err) @@ -439,9 +441,9 @@ func (b *BTCC) PlaceStopOrder(buyOder bool, stopPrice, price, amount, trailingAm params = append(params, market) } - req := BTCC_STOPORDER_BUY + req := btccStoporderBuy if !buyOder { - req = BTCC_STOPORDER_SELL + req = btccStoporderSell } err := b.SendAuthenticatedHTTPRequest(req, params) @@ -459,7 +461,7 @@ func (b *BTCC) GetStopOrder(orderID int64, market string) { params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDER, params) + err := b.SendAuthenticatedHTTPRequest(btccStoporder, params) if err != nil { log.Println(err) @@ -493,7 +495,7 @@ func (b *BTCC) GetStopOrders(status, orderType string, stopPrice float64, limit, params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDERS, params) + err := b.SendAuthenticatedHTTPRequest(btccStoporders, params) if err != nil { log.Println(err) @@ -508,7 +510,7 @@ func (b *BTCC) CancelStopOrder(orderID int64, market string) { params = append(params, market) } - err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDER_CANCEL, params) + err := b.SendAuthenticatedHTTPRequest(btccStoporderCancel, params) if err != nil { log.Println(err) @@ -516,8 +518,16 @@ func (b *BTCC) CancelStopOrder(orderID int64, market string) { } func (b *BTCC) SendAuthenticatedHTTPRequest(method string, params []interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:16] - encoded := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=%d&method=%s¶ms=", nonce, b.APIKey, 1, method) + if !b.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + } + + if b.Nonce.Get() == 0 { + b.Nonce.Set(time.Now().UnixNano()) + } else { + b.Nonce.Inc() + } + encoded := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=%d&method=%s¶ms=", b.Nonce.String()[0:16], b.APIKey, 1, method) if len(params) == 0 { params = make([]interface{}, 0) @@ -563,7 +573,7 @@ func (b *BTCC) SendAuthenticatedHTTPRequest(method string, params []interface{}) postData["method"] = method postData["params"] = params postData["id"] = 1 - apiURL := BTCC_API_URL + BTCC_API_AUTHENTICATED_METHOD + apiURL := btccAPIUrl + btccAPIAuthenticatedMethod data, err := common.JSONEncode(postData) if err != nil { @@ -577,7 +587,7 @@ func (b *BTCC) SendAuthenticatedHTTPRequest(method string, params []interface{}) headers := make(map[string]string) headers["Content-type"] = "application/json-rpc" headers["Authorization"] = "Basic " + common.Base64Encode([]byte(b.APIKey+":"+common.HexEncodeToString(hmac))) - headers["Json-Rpc-Tonce"] = nonce + headers["Json-Rpc-Tonce"] = b.Nonce.String() resp, err := common.SendHTTPRequest("POST", apiURL, headers, strings.NewReader(string(data))) diff --git a/exchanges/btcc/btcc_test.go b/exchanges/btcc/btcc_test.go new file mode 100644 index 00000000..aca0c326 --- /dev/null +++ b/exchanges/btcc/btcc_test.go @@ -0,0 +1,79 @@ +package btcc + +import ( + "testing" + "time" + + "github.com/thrasher-/gocryptotrader/config" +) + +// Please supply your own APIkeys here to do better tests +const ( + apiKey = "" + apiSecret = "" +) + +var b BTCC + +func TestSetDefaults(t *testing.T) { + b.SetDefaults() +} + +func TestSetup(t *testing.T) { + conf := config.ExchangeConfig{ + Enabled: true, + } + b.Setup(conf) + + conf = config.ExchangeConfig{ + Enabled: false, + APIKey: apiKey, + APISecret: apiSecret, + } + b.Setup(conf) +} + +func TestGetFee(t *testing.T) { + if b.GetFee() != 0 { + t.Error("Test failed - GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + _, err := b.GetTicker("ltccny") + if err != nil { + t.Error("Test failed - GetTicker() error", err) + } +} + +func TestGetTradesLast24h(t *testing.T) { + _, err := b.GetTradesLast24h("ltccny") + if err != nil { + t.Error("Test failed - GetTradesLast24h() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + _, err := b.GetTradeHistory("ltccny", 0, 0, time.Time{}) + if err != nil { + t.Error("Test failed - GetTradeHistory() error", err) + } +} + +func TestGetOrderBook(t *testing.T) { + _, err := b.GetOrderBook("ltccny", 100) + if err != nil { + t.Error("Test failed - GetOrderBook() error", err) + } + _, err = b.GetOrderBook("ltccny", 0) + if err != nil { + t.Error("Test failed - GetOrderBook() error", err) + } +} + +func TestGetAccountInfo(t *testing.T) { + err := b.GetAccountInfo("") + if err == nil { + t.Error("Test failed - GetAccountInfo() error", err) + } +} diff --git a/exchanges/btcc/btcc_types.go b/exchanges/btcc/btcc_types.go index 7cd5bc48..26457652 100644 --- a/exchanges/btcc/btcc_types.go +++ b/exchanges/btcc/btcc_types.go @@ -1,19 +1,45 @@ package btcc -type BTCCTicker struct { - High float64 `json:",string"` - Low float64 `json:",string"` - Buy float64 `json:",string"` - Sell float64 `json:",string"` - Last float64 `json:",string"` - Vol float64 `json:",string"` - Date int64 - Vwap float64 `json:",string"` - Prev_close float64 `json:",string"` - Open float64 `json:",string"` +// Response is the generalized response type +type Response struct { + Ticker Ticker `json:"ticker"` + BtcCny Ticker `json:"ticker_btccny"` + LtcCny Ticker `json:"ticker_ltccny"` + LtcBtc Ticker `json:"ticker_ltcbtc"` } -type BTCCProfile struct { +// Ticker holds basic ticker information +type Ticker struct { + High float64 `json:"high,string"` + Low float64 `json:"low,string"` + Buy float64 `json:"buy,string"` + Sell float64 `json:"sell,string"` + Last float64 `json:"last,string"` + Vol float64 `json:"vol,string"` + Date int64 `json:"date"` + Vwap float64 `json:"vwap,string"` + PrevClose float64 `json:"prev_close,string"` + Open float64 `json:"open,string"` +} + +// Trade holds executed trade data +type Trade struct { + Date int64 `json:"date,string"` + Price float64 `json:"price"` + Amount float64 `json:"amount"` + TID int64 `json:"tid,string"` + Type string `json:"type"` +} + +// Orderbook holds orderbook data +type Orderbook struct { + Bids [][]float64 `json:"bids"` + Asks [][]float64 `json:"asks"` + Date int64 `json:"date"` +} + +// Profile holds profile information +type Profile struct { Username string TradePasswordEnabled bool `json:"trade_password_enabled,bool"` OTPEnabled bool `json:"otp_enabled,bool"` @@ -29,13 +55,8 @@ type BTCCProfile struct { APIKeyPermission int64 `json:"api_key_permission"` } -type BTCCOrderbook struct { - Bids [][]float64 `json:"bids"` - Asks [][]float64 `json:"asks"` - Date int64 `json:"date"` -} - -type BTCCCurrencyGeneric struct { +// CurrencyGeneric holds currency information +type CurrencyGeneric struct { Currency string Symbol string Amount string @@ -43,7 +64,8 @@ type BTCCCurrencyGeneric struct { AmountDecimal float64 `json:"amount_decimal"` } -type BTCCOrder struct { +// Order holds order information +type Order struct { ID int64 Type string Price float64 @@ -52,16 +74,18 @@ type BTCCOrder struct { AmountOrig float64 `json:"amount_original"` Date int64 Status string - Detail BTCCOrderDetail + Detail OrderDetail } -type BTCCOrderDetail struct { +// OrderDetail holds order detail information +type OrderDetail struct { Dateline int64 Price float64 Amount float64 } -type BTCCWithdrawal struct { +// Withdrawal holds withdrawal transaction information +type Withdrawal struct { ID int64 Address string Currency string @@ -71,7 +95,8 @@ type BTCCWithdrawal struct { Status string } -type BTCCDeposit struct { +// Deposit holds deposit address information +type Deposit struct { ID int64 Address string Currency string @@ -80,17 +105,20 @@ type BTCCDeposit struct { Status string } -type BTCCBidAsk struct { +// BidAsk holds bid and ask information +type BidAsk struct { Price float64 Amount float64 } -type BTCCDepth struct { - Bid []BTCCBidAsk - Ask []BTCCBidAsk +// Depth holds order book depth +type Depth struct { + Bid []BidAsk + Ask []BidAsk } -type BTCCTransaction struct { +// Transaction holds transaction information +type Transaction struct { ID int64 Type string BTCAmount float64 `json:"btc_amount"` @@ -99,7 +127,8 @@ type BTCCTransaction struct { Date int64 } -type BTCCIcebergOrder struct { +// IcebergOrder holds iceberg lettuce +type IcebergOrder struct { ID int64 Type string Price float64 @@ -112,7 +141,8 @@ type BTCCIcebergOrder struct { Status string } -type BTCCStopOrder struct { +// StopOrder holds stop order information +type StopOrder struct { ID int64 Type string StopPrice float64 `json:"stop_price"` @@ -126,19 +156,22 @@ type BTCCStopOrder struct { OrderID int64 `json:"order_id"` } -type BTCCWebsocketOrder struct { +// WebsocketOrder holds websocket order information +type WebsocketOrder struct { Price float64 `json:"price"` TotalAmount float64 `json:"totalamount"` Type string `json:"type"` } -type BTCCWebsocketGroupOrder struct { - Asks []BTCCWebsocketOrder `json:"ask"` - Bids []BTCCWebsocketOrder `json:"bid"` - Market string `json:"market"` +// WebsocketGroupOrder holds websocket group order book information +type WebsocketGroupOrder struct { + Asks []WebsocketOrder `json:"ask"` + Bids []WebsocketOrder `json:"bid"` + Market string `json:"market"` } -type BTCCWebsocketTrade struct { +// WebsocketTrade holds websocket trade information +type WebsocketTrade struct { Amount float64 `json:"amount"` Date float64 `json:"date"` Market string `json:"market"` @@ -147,7 +180,8 @@ type BTCCWebsocketTrade struct { Type string `json:"type"` } -type BTCCWebsocketTicker struct { +// WebsocketTicker holds websocket ticker information +type WebsocketTicker struct { Buy float64 `json:"buy"` Date float64 `json:"date"` High float64 `json:"high"` diff --git a/exchanges/btcc/btcc_websocket.go b/exchanges/btcc/btcc_websocket.go index 029b8df5..c7414d53 100644 --- a/exchanges/btcc/btcc_websocket.go +++ b/exchanges/btcc/btcc_websocket.go @@ -56,7 +56,7 @@ func (b *BTCC) OnMessage(message []byte, output chan socketio.Message) { func (b *BTCC) OnTicker(message []byte, output chan socketio.Message) { type Response struct { - Ticker BTCCWebsocketTicker `json:"ticker"` + Ticker WebsocketTicker `json:"ticker"` } var resp Response err := common.JSONDecode(message, &resp) @@ -69,7 +69,7 @@ func (b *BTCC) OnTicker(message []byte, output chan socketio.Message) { func (b *BTCC) OnGroupOrder(message []byte, output chan socketio.Message) { type Response struct { - GroupOrder BTCCWebsocketGroupOrder `json:"grouporder"` + GroupOrder WebsocketGroupOrder `json:"grouporder"` } var resp Response err := common.JSONDecode(message, &resp) @@ -81,7 +81,7 @@ func (b *BTCC) OnGroupOrder(message []byte, output chan socketio.Message) { } func (b *BTCC) OnTrade(message []byte, output chan socketio.Message) { - trade := BTCCWebsocketTrade{} + trade := WebsocketTrade{} err := common.JSONDecode(message, &trade) if err != nil { diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index c7c868ca..88fca71c 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -6,7 +6,7 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/currency/pair" - "github.com/thrasher-/gocryptotrader/exchanges" + exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/stats" "github.com/thrasher-/gocryptotrader/exchanges/ticker" @@ -36,7 +36,7 @@ func (b *BTCC) Run() { log.Println(err) return } - log.Printf("BTCC %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("BTCC %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/exchanges/btce/btce.go b/exchanges/btce/btce.go index d3b399ae..f96819c3 100644 --- a/exchanges/btce/btce.go +++ b/exchanges/btce/btce.go @@ -293,8 +293,16 @@ func (b *BTCE) RedeemCoupon(coupon string) (BTCERedeemCoupon, error) { } func (b *BTCE) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().Unix(), 10) - values.Set("nonce", nonce) + if !b.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + } + + if b.Nonce.Get() == 0 { + b.Nonce.Set(time.Now().Unix()) + } else { + b.Nonce.Inc() + } + values.Set("nonce", b.Nonce.String()) values.Set("method", method) encoded := values.Encode() diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index f68d8eac..10446ed4 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -6,7 +6,6 @@ import ( "fmt" "log" "net/url" - "strconv" "time" "github.com/thrasher-/gocryptotrader/common" @@ -15,22 +14,36 @@ import ( ) const ( - BTCMARKETS_API_URL = "https://api.btcmarkets.net" - BTCMARKETS_API_VERSION = "0" - BTCMARKETS_ACCOUNT_BALANCE = "/account/balance" - BTCMARKETS_ORDER_CREATE = "/order/create" - BTCMARKETS_ORDER_CANCEL = "/order/cancel" - BTCMARKETS_ORDER_HISTORY = "/order/history" - BTCMARKETS_ORDER_OPEN = "/order/open" - BTCMARKETS_ORDER_TRADE_HISTORY = "/order/trade/history" - BTCMARKETS_ORDER_DETAIL = "/order/detail" + btcMarketsAPIURL = "https://api.btcmarkets.net" + btcMarketsAPIVersion = "0" + btcMarketsAccountBalance = "/account/balance" + btcMarketsOrderCreate = "/order/create" + btcMarketsOrderCancel = "/order/cancel" + btcMarketsOrderHistory = "/order/history" + btcMarketsOrderOpen = "/order/open" + btcMarketsOrderTradeHistory = "/order/trade/history" + btcMarketsOrderDetail = "/order/detail" + btcMarketsWithdrawCrypto = "/fundtransfer/withdrawCrypto" + btcMarketsWithdrawAud = "/fundtransfer/withdrawEFT" + + //Status Values + orderStatusNew = "New" + orderStatusPlaced = "Placed" + orderStatusFailed = "Failed" + orderStatusError = "Error" + orderStatusCancelled = "Cancelled" + orderStatusPartiallyCancelled = "Partially Cancelled" + orderStatusFullyMatched = "Fully Matched" + orderStatusPartiallyMatched = "Partially Matched" ) +// BTCMarkets is the overarching type across the BTCMarkets package type BTCMarkets struct { exchange.Base - Ticker map[string]BTCMarketsTicker + Ticker map[string]Ticker } +// SetDefaults sets basic defaults func (b *BTCMarkets) SetDefaults() { b.Name = "BTC Markets" b.Enabled = false @@ -38,9 +51,10 @@ func (b *BTCMarkets) SetDefaults() { b.Verbose = false b.Websocket = false b.RESTPollingDelay = 10 - b.Ticker = make(map[string]BTCMarketsTicker) + b.Ticker = make(map[string]Ticker) } +// Setup takes in an exchange configuration and sets all paramaters func (b *BTCMarkets) Setup(exch config.ExchangeConfig) { if !exch.Enabled { b.SetEnabled(false) @@ -58,109 +72,90 @@ func (b *BTCMarkets) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the BTCMarkets fee on transactions func (b *BTCMarkets) GetFee() float64 { return b.Fee } -func (b *BTCMarkets) GetTicker(symbol string) (BTCMarketsTicker, error) { - ticker := BTCMarketsTicker{} - path := fmt.Sprintf("/market/%s/AUD/tick", symbol) - err := common.SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &ticker) - if err != nil { - return BTCMarketsTicker{}, err - } - return ticker, nil +// GetTicker returns a ticker +// symbol - example "btc" or "ltc" +func (b *BTCMarkets) GetTicker(symbol string) (Ticker, error) { + ticker := Ticker{} + path := fmt.Sprintf("/market/%s/AUD/tick", common.StringToUpper(symbol)) + + return ticker, + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &ticker) } -func (b *BTCMarkets) GetOrderbook(symbol string) (BTCMarketsOrderbook, error) { - orderbook := BTCMarketsOrderbook{} - path := fmt.Sprintf("/market/%s/AUD/orderbook", symbol) - err := common.SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &orderbook) - if err != nil { - return BTCMarketsOrderbook{}, err - } - return orderbook, nil +// GetOrderbook returns current orderbook +// symbol - example "btc" or "ltc" +func (b *BTCMarkets) GetOrderbook(symbol string) (Orderbook, error) { + orderbook := Orderbook{} + path := fmt.Sprintf("/market/%s/AUD/orderbook", common.StringToUpper(symbol)) + + return orderbook, + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &orderbook) } -func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]BTCMarketsTrade, error) { - trades := []BTCMarketsTrade{} - path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", BTCMARKETS_API_URL, symbol), values) - err := common.SendHTTPGetRequest(path, true, &trades) - if err != nil { - return nil, err - } - return trades, nil +// GetTrades returns executed trades on the exchange +// symbol - example "btc" or "ltc" +// values - optional paramater "since" example values.Set(since, "59868345231") +func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]Trade, error) { + trades := []Trade{} + path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", btcMarketsAPIURL, symbol), values) + + return trades, common.SendHTTPGetRequest(path, true, &trades) } -func (b *BTCMarkets) Order(currency, instrument string, price, amount int64, orderSide, orderType, clientReq string) (int, error) { - type Order struct { - Currency string `json:"currency"` - Instrument string `json:"instrument"` - Price int64 `json:"price"` - Volume int64 `json:"volume"` - OrderSide string `json:"orderSide"` - OrderType string `json:"ordertype"` - ClientRequestId string `json:"clientRequestId"` +// NewOrder requests a new order and returns an ID +// currency - example "AUD" +// instrument - example "BTC" +// price - example 13000000000 (i.e x 100000000) +// amount - example 100000000 (i.e x 100000000) +// orderside - example "Bid" or "Ask" +// orderType - example "limit" +// clientReq - example "abc-cdf-1000" +func (b *BTCMarkets) NewOrder(currency, instrument string, price, amount int64, orderSide, orderType, clientReq string) (int, error) { + order := OrderToGo{ + Currency: common.StringToUpper(currency), + Instrument: common.StringToUpper(instrument), + Price: price * common.SatoshisPerBTC, + Volume: amount * common.SatoshisPerBTC, + OrderSide: orderSide, + OrderType: orderType, + ClientRequestID: clientReq, } - order := Order{} - order.Currency = currency - order.Instrument = instrument - order.Price = price * common.SatoshisPerBTC - order.Volume = amount * common.SatoshisPerBTC - order.OrderSide = orderSide - order.OrderType = orderType - order.ClientRequestId = clientReq - type Response struct { - Success bool `json:"success"` - ErrorCode int `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - ID int `json:"id"` - ClientRequestID string `json:"clientRequestId"` - } - var resp Response - - err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CREATE, order, &resp) + resp := Response{} + err := b.SendAuthenticatedRequest("POST", btcMarketsOrderCreate, order, &resp) if err != nil { return 0, err } if !resp.Success { - return 0, fmt.Errorf("%s Unable to place order. Error message: %s\n", b.GetName(), resp.ErrorMessage) + return 0, fmt.Errorf("%s Unable to place order. Error message: %s", b.GetName(), resp.ErrorMessage) } return resp.ID, nil } +// CancelOrder cancels an order by its ID +// orderID - id for order example "1337" func (b *BTCMarkets) CancelOrder(orderID []int64) (bool, error) { + resp := Response{} type CancelOrder struct { OrderIDs []int64 `json:"orderIds"` } orders := CancelOrder{} orders.OrderIDs = append(orders.OrderIDs, orderID...) - type Response struct { - Success bool `json:"success"` - ErrorCode int `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - Responses []struct { - Success bool `json:"success"` - ErrorCode int `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - ID int64 `json:"id"` - } - ClientRequestID string `json:"clientRequestId"` - } - var resp Response - - err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CANCEL, orders, &resp) - + err := b.SendAuthenticatedRequest("POST", btcMarketsOrderCancel, orders, &resp) if err != nil { return false, err } if !resp.Success { - return false, fmt.Errorf("%s Unable to cancel order. Error message: %s\n", b.GetName(), resp.ErrorMessage) + return false, fmt.Errorf("%s Unable to cancel order. Error message: %s", b.GetName(), resp.ErrorMessage) } ordersToBeCancelled := len(orderID) @@ -170,39 +165,37 @@ func (b *BTCMarkets) CancelOrder(orderID []int64) (bool, error) { ordersCancelled++ log.Printf("%s Cancelled order %d.\n", b.GetName(), y.ID) } else { - log.Printf("%s Unable to cancel order %d. Error message: %s\n", b.GetName(), y.ID, y.ErrorMessage) + log.Printf("%s Unable to cancel order %d. Error message: %s", b.GetName(), y.ID, y.ErrorMessage) } } if ordersCancelled == ordersToBeCancelled { return true, nil - } else { - return false, fmt.Errorf("%s Unable to cancel order(s).", b.GetName()) } + return false, fmt.Errorf("%s Unable to cancel order(s)", b.GetName()) } -func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) ([]BTCMarketsOrder, error) { +// GetOrders returns current order information on the exchange +// currency - example "AUD" +// instrument - example "BTC" +// limit - example "10" +// since - since a time example "33434568724" +// historic - if false just normal Orders open +func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) ([]Order, error) { request := make(map[string]interface{}) - request["currency"] = currency - request["instrument"] = instrument + request["currency"] = common.StringToUpper(currency) + request["instrument"] = common.StringToUpper(instrument) request["limit"] = limit request["since"] = since - path := BTCMARKETS_ORDER_OPEN + path := btcMarketsOrderOpen if historic { - path = BTCMARKETS_ORDER_HISTORY + path = btcMarketsOrderHistory } - type response struct { - Success bool `json:"success"` - ErrorCode int `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - Orders []BTCMarketsOrder `json:"orders"` - } + resp := Response{} - resp := response{} err := b.SendAuthenticatedRequest("POST", path, request, &resp) - if err != nil { return nil, err } @@ -225,23 +218,18 @@ func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, return resp.Orders, nil } -func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]BTCMarketsOrder, error) { +// GetOrderDetail returns order information an a specific order +// orderID - example "1337" +func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]Order, error) { type OrderDetail struct { OrderIDs []int64 `json:"orderIds"` } orders := OrderDetail{} orders.OrderIDs = append(orders.OrderIDs, orderID...) - type response struct { - Success bool `json:"success"` - ErrorCode int `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - Orders []BTCMarketsOrder `json:"orders"` - } - - resp := response{} - err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_DETAIL, orders, &resp) + resp := Response{} + err := b.SendAuthenticatedRequest("POST", btcMarketsOrderDetail, orders, &resp) if err != nil { return nil, err } @@ -264,10 +252,11 @@ func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]BTCMarketsOrder, error) return resp.Orders, nil } -func (b *BTCMarkets) GetAccountBalance() ([]BTCMarketsAccountBalance, error) { - balance := []BTCMarketsAccountBalance{} - err := b.SendAuthenticatedRequest("GET", BTCMARKETS_ACCOUNT_BALANCE, nil, &balance) +// GetAccountBalance returns the full account balance +func (b *BTCMarkets) GetAccountBalance() ([]AccountBalance, error) { + balance := []AccountBalance{} + err := b.SendAuthenticatedRequest("GET", btcMarketsAccountBalance, nil, &balance) if err != nil { return nil, err } @@ -280,9 +269,64 @@ func (b *BTCMarkets) GetAccountBalance() ([]BTCMarketsAccountBalance, error) { return balance, nil } +// WithdrawCrypto withdraws cryptocurrency into a designated address +func (b *BTCMarkets) WithdrawCrypto(amount int64, currency, address string) (string, error) { + req := WithdrawRequestCrypto{ + Amount: amount, + Currency: common.StringToUpper(currency), + Address: address, + } + + resp := Response{} + err := b.SendAuthenticatedRequest("POST", btcMarketsWithdrawCrypto, req, &resp) + if err != nil { + return "", err + } + + if !resp.Success { + return "", errors.New(resp.ErrorMessage) + } + + return resp.Status, nil +} + +// WithdrawAUD withdraws AUD into a designated bank address +// Does not return a TxID! +func (b *BTCMarkets) WithdrawAUD(accountName, accountNumber, bankName, bsbNumber, currency string, amount int64) (string, error) { + req := WithdrawRequestAUD{ + AccountName: accountName, + AccountNumber: accountNumber, + BankName: bankName, + BSBNumber: bsbNumber, + Amount: amount, + Currency: common.StringToUpper(currency), + } + + resp := Response{} + err := b.SendAuthenticatedRequest("POST", btcMarketsWithdrawAud, req, &resp) + if err != nil { + return "", err + } + + if !resp.Success { + return "", errors.New(resp.ErrorMessage) + } + + return resp.Status, nil +} + +// SendAuthenticatedRequest sends an authenticated HTTP request func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interface{}, result interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] - request := "" + if !b.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) + } + + if b.Nonce.Get() == 0 { + b.Nonce.Set(time.Now().UnixNano()) + } else { + b.Nonce.Inc() + } + var request string payload := []byte("") if data != nil { @@ -290,15 +334,15 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interfa if err != nil { return err } - request = path + "\n" + nonce + "\n" + string(payload) + request = path + "\n" + b.Nonce.String()[0:13] + "\n" + string(payload) } else { - request = path + "\n" + nonce + "\n" + request = path + "\n" + b.Nonce.String()[0:13] + "\n" } hmac := common.GetHMAC(common.HashSHA512, []byte(request), []byte(b.APISecret)) if b.Verbose { - log.Printf("Sending %s request to URL %s with params %s\n", reqType, BTCMARKETS_API_URL+path, request) + log.Printf("Sending %s request to URL %s with params %s\n", reqType, btcMarketsAPIURL+path, request) } headers := make(map[string]string) @@ -306,10 +350,10 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interfa headers["Accept-Charset"] = "UTF-8" headers["Content-Type"] = "application/json" headers["apikey"] = b.APIKey - headers["timestamp"] = nonce + headers["timestamp"] = b.Nonce.String()[0:13] headers["signature"] = common.Base64Encode(hmac) - resp, err := common.SendHTTPRequest(reqType, BTCMARKETS_API_URL+path, headers, bytes.NewBuffer(payload)) + resp, err := common.SendHTTPRequest(reqType, btcMarketsAPIURL+path, headers, bytes.NewBuffer(payload)) if err != nil { return err diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go new file mode 100644 index 00000000..ca7ded8b --- /dev/null +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -0,0 +1,131 @@ +package btcmarkets + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var bm BTCMarkets + +// Please supply your own keys here to do better tests +const ( + apiKey = "" + apiSecret = "" +) + +func TestSetDefaults(t *testing.T) { + bm.SetDefaults() +} + +func TestSetup(t *testing.T) { + conf := config.ExchangeConfig{} + bm.Setup(conf) + + conf = config.ExchangeConfig{ + APIKey: apiKey, + APISecret: apiSecret, + Enabled: true, + AuthenticatedAPISupport: true, + } + bm.Setup(conf) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if fee := bm.GetFee(); fee == 0 { + t.Error("Test failed - GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := bm.GetTicker("BTC") + if err != nil { + t.Error("Test failed - GetTicker() error", err) + } +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := bm.GetOrderbook("BTC") + if err != nil { + t.Error("Test failed - GetOrderbook() error", err) + } +} + +func TestGetTrades(t *testing.T) { + t.Parallel() + _, err := bm.GetTrades("BTC", nil) + if err != nil { + t.Error("Test failed - GetTrades() error", err) + } + + val := url.Values{} + val.Set("since", "0") + _, err = bm.GetTrades("BTC", val) + if err != nil { + t.Error("Test failed - GetTrades() error", err) + } +} + +func TestNewOrder(t *testing.T) { + t.Parallel() + _, err := bm.NewOrder("AUD", "BTC", 0, 0, "Bid", "limit", "testTest") + if err == nil { + t.Error("Test failed - NewOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + t.Parallel() + _, err := bm.CancelOrder([]int64{1337}) + if err == nil { + t.Error("Test failed - CancelOrder() error", err) + } +} + +func TestGetOrders(t *testing.T) { + t.Parallel() + _, err := bm.GetOrders("AUD", "BTC", 10, 0, false) + if err == nil { + t.Error("Test failed - GetOrders() error", err) + } + _, err = bm.GetOrders("AUD", "BTC", 10, 0, true) + if err == nil { + t.Error("Test failed - GetOrders() error", err) + } +} + +func TestGetOrderDetail(t *testing.T) { + t.Parallel() + _, err := bm.GetOrderDetail([]int64{1337}) + if err == nil { + t.Error("Test failed - GetOrderDetail() error", err) + } +} + +func TestGetAccountBalance(t *testing.T) { + t.Parallel() + _, err := bm.GetAccountBalance() + if err == nil { + t.Error("Test failed - GetAccountBalance() error", err) + } +} + +func TestWithdrawCrypto(t *testing.T) { + t.Parallel() + _, err := bm.WithdrawCrypto(0, "BTC", "LOLOLOL") + if err == nil { + t.Error("Test failed - WithdrawCrypto() error", err) + } +} + +func TestWithdrawAUD(t *testing.T) { + t.Parallel() + _, err := bm.WithdrawAUD("BLA", "1337", "blawest", "1336", "BTC", 10000000) + if err == nil { + t.Error("Test failed - WithdrawAUD() error", err) + } +} diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index 45a2f8a9..e357137e 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -1,22 +1,35 @@ package btcmarkets -type BTCMarketsTicker struct { - BestBID float64 - BestAsk float64 - LastPrice float64 - Currency string - Instrument string - Timestamp int64 +// Response is the genralized response type +type Response struct { + Success bool `json:"success"` + ErrorCode int `json:"errorCode"` + ErrorMessage string `json:"errorMessage"` + ID int `json:"id"` + Responses []struct { + Success bool `json:"success"` + ErrorCode int `json:"errorCode"` + ErrorMessage string `json:"errorMessage"` + ID int64 `json:"id"` + } + ClientRequestID string `json:"clientRequestId"` + Orders []Order `json:"orders"` + Status string `json:"status"` } -type BTCMarketsTrade struct { - TradeID int64 `json:"tid"` - Amount float64 `json:"amount"` - Price float64 `json:"price"` - Date int64 `json:"date"` +// Ticker holds ticker information +type Ticker struct { + BestBID float64 `json:"bestBid"` + BestAsk float64 `json:"bestAsk"` + LastPrice float64 `json:"lastPrice"` + Currency string `json:"currency"` + Instrument string `json:"instrument"` + Timestamp int64 `json:"timestamp"` + Volume float64 `json:"volume24h"` } -type BTCMarketsOrderbook struct { +// Orderbook holds current orderbook information returned from the exchange +type Orderbook struct { Currency string `json:"currency"` Instrument string `json:"instrument"` Timestamp int64 `json:"timestamp"` @@ -24,7 +37,44 @@ type BTCMarketsOrderbook struct { Bids [][]float64 `json:"bids"` } -type BTCMarketsTradeResponse struct { +// Trade holds trade information +type Trade struct { + TradeID int64 `json:"tid"` + Amount float64 `json:"amount"` + Price float64 `json:"price"` + Date int64 `json:"date"` +} + +// OrderToGo holds order information to be sent to the exchange +type OrderToGo struct { + Currency string `json:"currency"` + Instrument string `json:"instrument"` + Price int64 `json:"price"` + Volume int64 `json:"volume"` + OrderSide string `json:"orderSide"` + OrderType string `json:"ordertype"` + ClientRequestID string `json:"clientRequestId"` +} + +// Order holds order information +type Order struct { + ID int64 `json:"id"` + Currency string `json:"currency"` + Instrument string `json:"instrument"` + OrderSide string `json:"orderSide"` + OrderType string `json:"ordertype"` + CreationTime float64 `json:"creationTime"` + Status string `json:"status"` + ErrorMessage string `json:"errorMessage"` + Price float64 `json:"price"` + Volume float64 `json:"volume"` + OpenVolume float64 `json:"openVolume"` + ClientRequestID string `json:"clientRequestId"` + Trades []TradeResponse `json:"trades"` +} + +// TradeResponse holds trade information +type TradeResponse struct { ID int64 `json:"id"` CreationTime float64 `json:"creationTime"` Description string `json:"description"` @@ -33,24 +83,26 @@ type BTCMarketsTradeResponse struct { Fee float64 `json:"fee"` } -type BTCMarketsOrder struct { - ID int64 `json:"id"` - Currency string `json:"currency"` - Instrument string `json:"instrument"` - OrderSide string `json:"orderSide"` - OrderType string `json:"ordertype"` - CreationTime float64 `json:"creationTime"` - Status string `json:"status"` - ErrorMessage string `json:"errorMessage"` - Price float64 `json:"price"` - Volume float64 `json:"volume"` - OpenVolume float64 `json:"openVolume"` - ClientRequestId string `json:"clientRequestId"` - Trades []BTCMarketsTradeResponse `json:"trades"` -} - -type BTCMarketsAccountBalance struct { +// AccountBalance holds account balance details +type AccountBalance struct { Balance float64 `json:"balance"` PendingFunds float64 `json:"pendingFunds"` Currency string `json:"currency"` } + +// WithdrawRequestCrypto is a generalized withdraw request type +type WithdrawRequestCrypto struct { + Amount int64 `json:"amount"` + Currency string `json:"currency"` + Address string `json:"address"` +} + +// WithdrawRequestAUD is a generalized withdraw request type +type WithdrawRequestAUD struct { + Amount int64 `json:"amount"` + Currency string `json:"currency"` + AccountName string `json:"accountName"` + AccountNumber string `json:"accountNumber"` + BankName string `json:"bankName"` + BSBNumber string `json:"bsbNumber"` +} diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index ee19d7be..cfd09dc9 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -12,10 +12,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start runs ticker monitor in a new routine func (b *BTCMarkets) Start() { go b.Run() } +// Run starts a go routine to monitor ticker price func (b *BTCMarkets) Run() { if b.Verbose { log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) @@ -33,7 +35,7 @@ func (b *BTCMarkets) Run() { BTCMarketsLastUSD, _ := currency.ConvertCurrency(ticker.Last, "AUD", "USD") BTCMarketsBestBidUSD, _ := currency.ConvertCurrency(ticker.Bid, "AUD", "USD") BTCMarketsBestAskUSD, _ := currency.ConvertCurrency(ticker.Ask, "AUD", "USD") - log.Printf("BTC Markets %s: Last %f (%f) Bid %f (%f) Ask %f (%f)\n", curr.Pair().String(), BTCMarketsLastUSD, ticker.Last, BTCMarketsBestBidUSD, ticker.Bid, BTCMarketsBestAskUSD, ticker.Ask) + log.Printf("BTC Markets %s: Last %f (%f) Bid %f (%f) Ask %f (%f)\n", exchange.FormatCurrency(curr).String(), BTCMarketsLastUSD, ticker.Last, BTCMarketsBestBidUSD, ticker.Bid, BTCMarketsBestAskUSD, ticker.Ask) stats.AddExchangeInfo(b.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, 0) stats.AddExchangeInfo(b.GetName(), curr.GetFirstCurrency().String(), "USD", BTCMarketsLastUSD, 0) }() @@ -42,6 +44,7 @@ func (b *BTCMarkets) Run() { } } +// GetTickerPrice returns ticker information func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tickerNew, err := ticker.GetTicker(b.GetName(), p) if err == nil { @@ -61,6 +64,7 @@ func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, er return tickerPrice, nil } +// GetOrderbookEx returns orderbook base on the currency pair func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { @@ -88,11 +92,12 @@ func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBas return orderBook, nil } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the BTCMarkets exchange -func (e *BTCMarkets) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies for the +// BTCMarkets exchange +func (b *BTCMarkets) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetAccountBalance() + response.ExchangeName = b.GetName() + accountBalance, err := b.GetAccountBalance() if err != nil { return response, err } diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 1b6fc0f8..5d262881 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -3,6 +3,7 @@ package coinut import ( "bytes" "errors" + "fmt" "log" "time" @@ -271,13 +272,21 @@ func (c *COINUT) GetOpenPosition(instrumentID int) ([]CoinutOpenPosition, error) //to-do: user position update via websocket func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[string]interface{}, result interface{}) (err error) { - timestamp := time.Now().Unix() + if !c.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, c.Name) + } + + if c.Nonce.Get() == 0 { + c.Nonce.Set(time.Now().Unix()) + } else { + c.Nonce.Inc() + } payload := []byte("") if params == nil { params = map[string]interface{}{} } - params["nonce"] = timestamp + params["nonce"] = c.Nonce.Get() params["request"] = apiRequest payload, err = common.JSONEncode(params) diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 82ca99b1..66dcd242 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -54,7 +54,7 @@ func (c *COINUT) Run() { log.Println(err) return } - log.Printf("COINUT %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("COINUT %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(c.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 997d95b6..3eb08590 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -7,12 +7,16 @@ import ( "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/exchanges/nonce" "github.com/thrasher-/gocryptotrader/exchanges/orderbook" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) 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 = "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." ) @@ -40,6 +44,7 @@ type Base struct { RESTPollingDelay time.Duration AuthenticatedAPISupport bool APISecret, APIKey, ClientID string + Nonce nonce.Nonce TakerFee, MakerFee, Fee float64 BaseCurrencies []string AvailablePairs []string @@ -60,6 +65,13 @@ type IBotExchange interface { GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) GetEnabledCurrencies() []string GetExchangeAccountInfo() (AccountInfo, error) + GetAuthenticatedAPISupport() bool +} + +// GetAuthenticatedAPISupport returns whether the exchange supports +// authenticated API requests +func (e *Base) GetAuthenticatedAPISupport() bool { + return e.AuthenticatedAPISupport } // GetName is a method that returns the name of the exchange base @@ -73,6 +85,20 @@ func (e *Base) GetEnabledCurrencies() []string { return e.EnabledPairs } +// GetAvailableCurrencies is a method that returns the available currency pairs +// of the exchange base +func (e *Base) GetAvailableCurrencies() []string { + return e.AvailablePairs +} + +// FormatCurrency is a method that formats and returns a currency pair +// based on the user currency display preferences +func FormatCurrency(p pair.CurrencyPair) pair.CurrencyItem { + cfg := config.GetConfig() + return p.Display(cfg.CurrencyPairFormat.Delimiter, + cfg.CurrencyPairFormat.Uppercase) +} + // SetEnabled is a method that sets if the exchange is enabled func (e *Base) SetEnabled(enabled bool) { e.Enabled = enabled diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index d3043a7c..bc17f371 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -3,6 +3,8 @@ package exchange import ( "testing" + "github.com/thrasher-/gocryptotrader/currency/pair" + "github.com/thrasher-/gocryptotrader/config" ) @@ -30,6 +32,35 @@ func TestGetEnabledCurrencies(t *testing.T) { } } +func TestGetAvailableCurrencies(t *testing.T) { + availablePairs := []string{"BTCUSD", "BTCAUD", "LTCUSD", "LTCAUD"} + GetEnabledCurrencies := Base{ + Name: "TESTNAME", + AvailablePairs: availablePairs, + } + + enCurr := GetEnabledCurrencies.GetAvailableCurrencies() + if enCurr[0] != "BTCUSD" { + t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string") + } +} + +func TestFormatCurrency(t *testing.T) { + cfg := config.GetConfig() + err := cfg.LoadConfig(config.ConfigTestFile) + if err != nil { + t.Fatalf("Failed to load config file. Error: %s", err) + } + + currency := pair.NewCurrencyPair("btc", "usd") + expected := "BTC-USD" + actual := FormatCurrency(currency).String() + if actual != expected { + t.Errorf("Test failed - Exchange TestFormatCurrency %s != %s", + actual, expected) + } +} + func TestSetEnabled(t *testing.T) { SetEnabled := Base{ Name: "TESTNAME", diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index ba294a82..7c73f68d 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -370,7 +370,15 @@ func (g *GDAX) GetReportStatus(reportID string) (GDAXReportResponse, error) { } func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { - timestamp := strconv.FormatInt(time.Now().Unix(), 10) + if !g.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) + } + + if g.Nonce.Get() == 0 { + g.Nonce.Set(time.Now().Unix()) + } else { + g.Nonce.Inc() + } payload := []byte("") @@ -386,11 +394,11 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri } } - message := timestamp + method + "/" + path + string(payload) + message := g.Nonce.String() + method + "/" + path + string(payload) hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(g.APISecret)) headers := make(map[string]string) headers["CB-ACCESS-SIGN"] = common.Base64Encode([]byte(hmac)) - headers["CB-ACCESS-TIMESTAMP"] = timestamp + headers["CB-ACCESS-TIMESTAMP"] = g.Nonce.String() headers["CB-ACCESS-KEY"] = g.APIKey headers["CB-ACCESS-PASSPHRASE"] = g.ClientID headers["Content-Type"] = "application/json" diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index c68dab0c..88d41e43 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -54,7 +54,7 @@ func (g *GDAX) Run() { log.Println(err) return } - log.Printf("GDAX %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("GDAX %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(g.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index ef2f40a4..654347da 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -245,9 +245,19 @@ func (g *Gemini) PostHeartbeat() (bool, error) { } func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) { + if !g.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name) + } + + if g.Nonce.Get() == 0 { + g.Nonce.Set(time.Now().UnixNano()) + } else { + g.Nonce.Inc() + } + request := make(map[string]interface{}) request["request"] = fmt.Sprintf("/v%s/%s", GEMINI_API_VERSION, path) - request["nonce"] = time.Now().UnixNano() + request["nonce"] = g.Nonce.Get() if params != nil { for key, value := range params { @@ -255,17 +265,17 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st } } - PayloadJson, err := common.JSONEncode(request) + PayloadJSON, err := common.JSONEncode(request) if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } if g.Verbose { - log.Printf("Request JSON: %s\n", PayloadJson) + log.Printf("Request JSON: %s\n", PayloadJSON) } - PayloadBase64 := common.Base64Encode(PayloadJson) + PayloadBase64 := common.Base64Encode(PayloadJSON) hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(g.APISecret)) headers := make(map[string]string) headers["X-GEMINI-APIKEY"] = g.APIKey diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index a392cebf..272f5eb9 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -41,7 +41,7 @@ func (g *Gemini) Run() { log.Println(err) return } - log.Printf("Gemini %s Last %f Bid %f Ask %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.Bid, ticker.Ask, ticker.Volume) + log.Printf("Gemini %s Last %f Bid %f Ask %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.Bid, ticker.Ask, ticker.Volume) stats.AddExchangeInfo(g.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 672bb8d3..f3edab71 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -177,6 +177,10 @@ func (h *HUOBI) GetOrderIDByTradeID(coinType, orderID int) { } func (h *HUOBI) SendAuthenticatedRequest(method string, v url.Values) error { + if !h.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name) + } + v.Set("access_key", h.APIKey) v.Set("created", strconv.FormatInt(time.Now().Unix(), 10)) v.Set("method", method) diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 038d10fa..a17c28d2 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -40,7 +40,7 @@ func (h *HUOBI) Run() { HuobiLastUSD, _ := currency.ConvertCurrency(ticker.Last, "CNY", "USD") HuobiHighUSD, _ := currency.ConvertCurrency(ticker.High, "CNY", "USD") HuobiLowUSD, _ := currency.ConvertCurrency(ticker.Low, "CNY", "USD") - log.Printf("Huobi %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", curr.Pair().String(), HuobiLastUSD, ticker.Last, HuobiHighUSD, ticker.High, HuobiLowUSD, ticker.Low, ticker.Volume) + log.Printf("Huobi %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", exchange.FormatCurrency(curr).String(), HuobiLastUSD, ticker.Last, HuobiHighUSD, ticker.High, HuobiLowUSD, ticker.Low, ticker.Volume) stats.AddExchangeInfo(h.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume) stats.AddExchangeInfo(h.GetName(), curr.GetFirstCurrency().String(), "USD", HuobiLastUSD, ticker.Volume) }() diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index e051e099..47a97532 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -3,6 +3,7 @@ package itbit import ( "bytes" "errors" + "fmt" "log" "net/url" "strconv" @@ -226,14 +227,16 @@ func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount } func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}) (err error) { - timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13] - nonce, err := strconv.Atoi(timestamp) - - if err != nil { - return err + if !i.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, i.Name) + } + + if i.Nonce.Get() == 0 { + i.Nonce.Set(time.Now().UnixNano()) + } else { + i.Nonce.Inc() } - nonce-- request := make(map[string]interface{}) url := ITBIT_API_URL + path @@ -257,21 +260,20 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } } - nonceStr := strconv.Itoa(nonce) - message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), nonceStr, timestamp}) + message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), i.Nonce.String(), i.Nonce.String()[0:13]}) if err != nil { log.Println(err) return } - hash := common.GetSHA256([]byte(nonceStr + string(message))) + hash := common.GetSHA256([]byte(i.Nonce.String() + string(message))) hmac := common.GetHMAC(common.HashSHA512, []byte(url+string(hash)), []byte(i.APISecret)) signature := common.Base64Encode(hmac) headers := make(map[string]string) headers["Authorization"] = i.ClientID + ":" + signature - headers["X-Auth-Timestamp"] = timestamp - headers["X-Auth-Nonce"] = nonceStr + headers["X-Auth-Timestamp"] = i.Nonce.String()[0:13] + headers["X-Auth-Nonce"] = i.Nonce.String() headers["Content-Type"] = "application/json" resp, err := common.SendHTTPRequest(method, url, headers, bytes.NewBuffer([]byte(PayloadJSON))) diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index d6276475..deb80fa2 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -30,7 +30,7 @@ func (i *ItBit) Run() { log.Println(err) return } - log.Printf("ItBit %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("ItBit %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(i.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index bee3ea15..0be7fe3c 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -509,8 +509,18 @@ func (k *Kraken) CancelOrder(orderID int64) { } 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) - values.Set("nonce", strconv.FormatInt(time.Now().UnixNano(), 10)) + if k.Nonce.Get() == 0 { + k.Nonce.Set(time.Now().UnixNano()) + } else { + k.Nonce.Inc() + } + + values.Set("nonce", k.Nonce.String()) secret, err := common.Base64Decode(k.APISecret) if err != nil { diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index ecb50f29..97121ecb 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -272,8 +272,17 @@ func (l *LakeBTC) CreateWithdraw(amount float64, accountID int64) (LakeBTCWithdr } func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().UnixNano(), 10) - req := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s¶ms=%s", nonce, l.APIKey, method, params) + if !l.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) + } + + if l.Nonce.Get() == 0 { + l.Nonce.Set(time.Now().UnixNano()) + } else { + l.Nonce.Inc() + } + + req := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s¶ms=%s", l.Nonce.String(), l.APIKey, method, params) hmac := common.GetHMAC(common.HashSHA1, []byte(req), []byte(l.APISecret)) if l.Verbose { @@ -291,7 +300,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int } headers := make(map[string]string) - headers["Json-Rpc-Tonce"] = nonce + headers["Json-Rpc-Tonce"] = l.Nonce.String() headers["Authorization"] = "Basic " + common.Base64Encode([]byte(l.APIKey+":"+common.HexEncodeToString(hmac))) headers["Content-Type"] = "application/json-rpc" diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 0d7ec64a..a2a1f920 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -30,7 +30,7 @@ func (l *LakeBTC) Run() { log.Println(err) continue } - log.Printf("LakeBTC BTC %s: Last %f High %f Low %f Volume %f\n", x[3:], ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("LakeBTC %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) } time.Sleep(time.Second * l.RESTPollingDelay) diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index 0a58328a..15ecdcd2 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -249,8 +249,16 @@ func (l *Liqui) WithdrawCoins(coin string, amount float64, address string) (Liqu } func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().Unix(), 10) - values.Set("nonce", nonce) + if !l.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) + } + + if l.Nonce.Get() == 0 { + l.Nonce.Set(time.Now().UnixNano()) + } else { + l.Nonce.Inc() + } + values.Set("nonce", l.Nonce.String()) values.Set("method", method) encoded := values.Encode() diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 67a293e8..634fc1f4 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -52,7 +52,7 @@ func (l *Liqui) Run() { } for x, y := range ticker { currency := pair.NewCurrencyPairDelimiter(common.StringToUpper(x), "_") - log.Printf("Liqui %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), y.Last, y.High, y.Low, y.Vol_cur) + log.Printf("Liqui %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), y.Last, y.High, y.Low, y.Vol_cur) l.Ticker[x] = y stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), y.Last, y.Vol_cur) } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 43ad14a7..d657869e 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -267,7 +267,16 @@ func (l *LocalBitcoins) GetWalletAddress() (string, error) { } func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values url.Values, result interface{}) (err error) { - nonce := strconv.FormatInt(time.Now().UnixNano(), 10) + if !l.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name) + } + + if l.Nonce.Get() == 0 { + l.Nonce.Set(time.Now().UnixNano()) + } else { + l.Nonce.Inc() + } + payload := "" path = "/api/" + path @@ -275,11 +284,11 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values payload = values.Encode() } - message := string(nonce) + l.APIKey + path + payload + message := l.Nonce.String() + l.APIKey + path + payload hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(l.APISecret)) headers := make(map[string]string) headers["Apiauth-Key"] = l.APIKey - headers["Apiauth-Nonce"] = string(nonce) + headers["Apiauth-Nonce"] = l.Nonce.String() headers["Apiauth-Signature"] = common.StringToUpper(common.HexEncodeToString(hmac)) headers["Content-Type"] = "application/x-www-form-urlencoded" diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 7f088a39..a4642ba2 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -31,7 +31,7 @@ func (l *LocalBitcoins) Run() { return } - log.Printf("LocalBitcoins BTC %s: Last %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.Volume) + log.Printf("LocalBitcoins BTC %s: Last %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.Volume) stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) } time.Sleep(time.Second * l.RESTPollingDelay) diff --git a/exchanges/nonce/nonce.go b/exchanges/nonce/nonce.go new file mode 100644 index 00000000..79ff8751 --- /dev/null +++ b/exchanges/nonce/nonce.go @@ -0,0 +1,49 @@ +package nonce + +import ( + "strconv" + "sync" +) + +// Nonce struct holds the nonce value +type Nonce struct { + n int64 + mtx sync.Mutex +} + +// Inc increments the nonce value +func (n *Nonce) Inc() { + n.mtx.Lock() + n.n++ + n.mtx.Unlock() +} + +// Get retrives the nonce value +func (n *Nonce) Get() int64 { + n.mtx.Lock() + defer n.mtx.Unlock() + return n.n +} + +// GetInc increments and returns the value of the nonce +func (n *Nonce) GetInc() int64 { + n.mtx.Lock() + defer n.mtx.Unlock() + n.n++ + return n.n +} + +// Set sets the nonce value +func (n *Nonce) Set(val int64) { + n.mtx.Lock() + n.n = val + n.mtx.Unlock() +} + +// Returns a string version of the nonce +func (n *Nonce) String() string { + n.mtx.Lock() + result := strconv.FormatInt(n.n, 10) + n.mtx.Unlock() + return result +} diff --git a/exchanges/nonce/nonce_test.go b/exchanges/nonce/nonce_test.go new file mode 100644 index 00000000..8bf6d6da --- /dev/null +++ b/exchanges/nonce/nonce_test.go @@ -0,0 +1,75 @@ +package nonce + +import ( + "testing" + "time" +) + +func TestInc(t *testing.T) { + var nonce Nonce + nonce.Set(1) + nonce.Inc() + expected := int64(2) + result := nonce.Get() + if result != expected { + t.Errorf("Test failed. Expected %d got %d", expected, result) + } +} + +func TestGet(t *testing.T) { + var nonce Nonce + nonce.Set(112321313) + expected := int64(112321313) + result := nonce.Get() + if expected != result { + t.Errorf("Test failed. Expected %d got %d", expected, result) + } +} + +func TestGetInc(t *testing.T) { + var nonce Nonce + nonce.Set(1) + expected := int64(2) + result := nonce.GetInc() + if expected != result { + t.Errorf("Test failed. Expected %d got %d", expected, result) + } +} + +func TestSet(t *testing.T) { + var nonce Nonce + nonce.Set(1) + expected := int64(1) + result := nonce.Get() + if expected != result { + t.Errorf("Test failed. Expected %d got %d", expected, result) + } +} + +func TestString(t *testing.T) { + var nonce Nonce + nonce.Set(12312313131) + expected := "12312313131" + result := nonce.String() + if expected != result { + t.Errorf("Test failed. Expected %s got %s", expected, result) + } +} + +func TestNonceConcurrency(t *testing.T) { + var nonce Nonce + nonce.Set(12312) + + for i := 0; i < 1000; i++ { + go nonce.Inc() + } + + // Allow sufficient time for all routines to finish + time.Sleep(time.Second) + + result := nonce.Get() + expected := int64(12312 + 1000) + if expected != result { + t.Errorf("Test failed. Expected %d got %d", expected, result) + } +} diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index 6a7d531f..0f2994aa 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -2,6 +2,7 @@ package okcoin import ( "errors" + "fmt" "log" "net/url" "strconv" @@ -877,6 +878,10 @@ func (o *OKCoin) GetFuturesUserPosition4Fix(symbol, contractType string) { } func (o *OKCoin) SendAuthenticatedHTTPRequest(method string, v url.Values, result interface{}) (err error) { + if !o.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name) + } + v.Set("api_key", o.APIKey) hasher := common.GetMD5([]byte(v.Encode() + "&secret_key=" + o.APISecret)) v.Set("sign", strings.ToUpper(common.HexEncodeToString(hasher))) diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index a538f9da..7e869fa6 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -41,7 +41,7 @@ func (o *OKCoin) Run() { log.Println(err) return } - log.Printf("OKCoin Intl Futures %s (%s): Last %f High %f Low %f Volume %f\n", curr.Pair().String(), futuresValue, ticker.Last, ticker.High, ticker.Low, ticker.Vol) + log.Printf("OKCoin Intl Futures %s (%s): Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(curr).String(), futuresValue, ticker.Last, ticker.High, ticker.Low, ticker.Vol) stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Vol) }() } @@ -51,7 +51,7 @@ func (o *OKCoin) Run() { log.Println(err) return } - log.Printf("OKCoin Intl Spot %s: Last %f High %f Low %f Volume %f\n", curr.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("OKCoin Intl Spot %s: Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(curr).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } else { @@ -64,7 +64,7 @@ func (o *OKCoin) Run() { tickerLastUSD, _ := currency.ConvertCurrency(ticker.Last, "CNY", "USD") tickerHighUSD, _ := currency.ConvertCurrency(ticker.High, "CNY", "USD") tickerLowUSD, _ := currency.ConvertCurrency(ticker.Low, "CNY", "USD") - log.Printf("OKCoin China %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", curr.Pair().String(), tickerLastUSD, ticker.Last, tickerHighUSD, ticker.High, tickerLowUSD, ticker.Low, ticker.Volume) + log.Printf("OKCoin China %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", exchange.FormatCurrency(curr).String(), tickerLastUSD, ticker.Last, tickerHighUSD, ticker.High, tickerLowUSD, ticker.Low, ticker.Volume) stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume) stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), "USD", tickerLastUSD, ticker.Volume) }() diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index a895d787..0ce8e3b0 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -745,14 +745,19 @@ func (p *Poloniex) ToggleAutoRenew(orderNumber int64) (bool, error) { } func (p *Poloniex) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error { + if !p.AuthenticatedAPISupport { + return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, p.Name) + } headers := make(map[string]string) headers["Content-Type"] = "application/x-www-form-urlencoded" headers["Key"] = p.APIKey - nonce := time.Now().UnixNano() - nonceStr := strconv.FormatInt(nonce, 10) - - values.Set("nonce", nonceStr) + if p.Nonce.Get() == 0 { + p.Nonce.Set(time.Now().UnixNano()) + } else { + p.Nonce.Inc() + } + values.Set("nonce", p.Nonce.String()) values.Set("command", endpoint) hmac := common.GetHMAC(common.HashSHA512, []byte(values.Encode()), []byte(p.APISecret)) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index ce04d372..1c4220f3 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -36,7 +36,7 @@ func (p *Poloniex) Run() { log.Println(err) return } - log.Printf("Poloniex %s Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) + log.Printf("Poloniex %s Last %f High %f Low %f Volume %f\n", exchange.FormatCurrency(currency).String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume) stats.AddExchangeInfo(p.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume) }() } diff --git a/main.go b/main.go index 935b8121..d7a1e47c 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/anx" "github.com/thrasher-/gocryptotrader/exchanges/bitfinex" "github.com/thrasher-/gocryptotrader/exchanges/bitstamp" + "github.com/thrasher-/gocryptotrader/exchanges/bittrex" "github.com/thrasher-/gocryptotrader/exchanges/btcc" "github.com/thrasher-/gocryptotrader/exchanges/btce" "github.com/thrasher-/gocryptotrader/exchanges/btcmarkets" @@ -42,6 +43,7 @@ type ExchangeMain struct { btcc btcc.BTCC bitstamp bitstamp.Bitstamp bitfinex bitfinex.Bitfinex + bittrex bittrex.Bittrex btce btce.BTCE btcmarkets btcmarkets.BTCMarkets coinut coinut.COINUT @@ -143,6 +145,7 @@ func main() { new(btcc.BTCC), new(bitstamp.Bitstamp), new(bitfinex.Bitfinex), + new(bittrex.Bittrex), new(btce.BTCE), new(btcmarkets.BTCMarkets), new(coinut.COINUT), diff --git a/testdata/configtest.dat b/testdata/configtest.dat index a1ae8bb6..9757e8c9 100644 --- a/testdata/configtest.dat +++ b/testdata/configtest.dat @@ -2,6 +2,10 @@ "Name": "Skynet", "EncryptConfig": 0, "Cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH", + "CurrencyPairFormat": { + "Uppercase": true, + "Delimiter": "-" + }, "PortfolioAddresses": { "Addresses": [ { diff --git a/coverage.txt b/testdata/coverage.txt similarity index 100% rename from coverage.txt rename to testdata/coverage.txt diff --git a/test.sh b/testdata/test.sh similarity index 51% rename from test.sh rename to testdata/test.sh index fc77c1ce..ec56d6e2 100755 --- a/test.sh +++ b/testdata/test.sh @@ -1,12 +1,19 @@ #!/usr/bin/env bash set -e -echo "" > coverage.txt + +if [ -n "$TRAVIS_BUILD_DIR" ]; then + cd $TRAVIS_BUILD_DIR +else + cd $GOPATH/src/github.com/thrasher-/gocryptotrader +fi + +echo "" > testdata/coverage.txt for d in $(go list ./... | grep -v vendor); do go test -race -coverprofile=profile.out -covermode=atomic -cover $d if [ -f profile.out ]; then - cat profile.out >> coverage.txt + cat profile.out >> testdata/coverage.txt rm profile.out fi done diff --git a/wallet_routes.go b/wallet_routes.go index 61dec117..3ed2e1f3 100644 --- a/wallet_routes.go +++ b/wallet_routes.go @@ -54,11 +54,15 @@ func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts { var response AllEnabledExchangeAccounts for _, individualBot := range bot.exchanges { if individualBot != nil && individualBot.IsEnabled() { + if !individualBot.GetAuthenticatedAPISupport() { + log.Printf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName()) + continue + } individualExchange, err := individualBot.GetExchangeAccountInfo() if err != nil { - log.Println( - "Error encountered retrieving exchange account for '" + individualExchange.ExchangeName + "'", - ) + log.Printf("Error encountered retrieving exchange account info for %s. Error %s", + individualBot.GetName(), err) + continue } response.Data = append(response.Data, individualExchange) }