diff --git a/README.md b/README.md index 54e690f7..07a958b5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![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) [![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader) A cryptocurrency trading bot supporting multiple exchanges written in Golang. @@ -10,7 +11,7 @@ A cryptocurrency trading bot supporting multiple exchanges written in Golang. ## Community -Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader](https://gocryptotrader.herokuapp.com/) +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://gocryptotrader.herokuapp.com/) ## Exchange Support Table @@ -35,24 +36,25 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader] | OKCoin (both) | Yes | Yes | No | | Poloniex | Yes | Yes | NA | +We are aiming to support the top 20 highest volume exchanges based off the [CoinMarketCap exchange data](https://coinmarketcap.com/exchanges/volume/24-hour/). + ** 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. + Websocket support for applicable exchanges. + Ability to turn off/on certain exchanges. + Ability to adjust manual polling timer for exchanges. + SMS notification support via SMS Gateway. ++ Packages for handling currency pairs, ticker/orderbook fetching and currency conversion. ++ Portfolio management tool; fetches balances from supported exchanges and allows for custom address tracking. + Basic event trigger system. ++ WebGUI. ## Planned Features -+ WebGUI. -+ FIX support. -+ Expanding event trigger system. -+ TALib. -+ Trade history summary generation for tax purposes. -+ ZMQ Hub for manging different gocryptotrader instances. +Planned features can be found on our [community Trello page](https://trello.com/b/ZAhMhpOy/gocryptotrader). ## Contribution @@ -66,12 +68,18 @@ When submitting a PR, please abide by our coding guidelines: * Pull requests need to be based on and opened against the `master` branch. ## Compiling instructions -Download Go from https://golang.org/dl/ -Using a terminal, type go get github.com/thrasher-/gocryptotrader -Change directory to the package directory, then type go install. -Copy config_example.dat to config.dat. +Download and install Go from 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! +## 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 5c6ca60d..2921454f 100644 --- a/common/common.go +++ b/common/common.go @@ -87,7 +87,7 @@ func GetHMAC(hashType int, input, key []byte) []byte { return hmac.Sum(nil) } -// HexEncodeToString takes in a hexidecimal byte array and returns a string +// HexEncodeToString takes in a hexadecimal byte array and returns a string func HexEncodeToString(input []byte) string { return hex.EncodeToString(input) } @@ -142,16 +142,16 @@ func DataContains(haystack []string, needle string) bool { return strings.Contains(data, needle) } -// JoinStrings joins an array together with the required seperator and returns +// JoinStrings joins an array together with the required separator and returns // it as a string -func JoinStrings(input []string, seperator string) string { - return strings.Join(input, seperator) +func JoinStrings(input []string, separator string) string { + return strings.Join(input, separator) } // SplitStrings splits blocks of strings from string into a string array using -// a seperator ie "," or "_" -func SplitStrings(input, seperator string) []string { - return strings.Split(input, seperator) +// a separator ie "," or "_" +func SplitStrings(input, separator string) []string { + return strings.Split(input, separator) } // TrimString trims unwanted prefixes or postfixes @@ -200,7 +200,7 @@ func IsEnabled(isEnabled bool) string { } // IsValidCryptoAddress validates your cryptocurrency address string using the -// regexp package // Validation issues occuring because "3" is contained in +// regexp package // Validation issues occurring because "3" is contained in // litecoin and Bitcoin addresses - non-fatal func IsValidCryptoAddress(address, crypto string) (bool, error) { switch StringToLower(crypto) { @@ -228,7 +228,7 @@ func CalculateAmountWithFee(amount, fee float64) float64 { return amount + CalculateFee(amount, fee) } -// CalculateFee retuns a simple fee on amount +// CalculateFee returns a simple fee on amount func CalculateFee(amount, fee float64) float64 { return amount * (fee / 100) } @@ -356,7 +356,7 @@ func ExtractPort(host string) int { return port } -// OutputCSV dumps data into a file as comma-seperated values +// OutputCSV dumps data into a file as comma-separated values func OutputCSV(path string, data [][]string) error { _, err := ReadFile(path) if err != nil { diff --git a/common/common_test.go b/common/common_test.go index a18a696c..6c5eca04 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -274,9 +274,9 @@ func TestDataContains(t *testing.T) { func TestJoinStrings(t *testing.T) { t.Parallel() originalInputOne := []string{"hello", "moto"} - seperator := "," + separator := "," expectedOutput := "hello,moto" - actualResult := JoinStrings(originalInputOne, seperator) + actualResult := JoinStrings(originalInputOne, separator) if expectedOutput != actualResult { t.Error(fmt.Sprintf( "Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult), @@ -287,9 +287,9 @@ func TestJoinStrings(t *testing.T) { func TestSplitStrings(t *testing.T) { t.Parallel() originalInputOne := "hello,moto" - seperator := "," + separator := "," expectedOutput := []string{"hello", "moto"} - actualResult := SplitStrings(originalInputOne, seperator) + actualResult := SplitStrings(originalInputOne, separator) if !reflect.DeepEqual(expectedOutput, actualResult) { t.Error(fmt.Sprintf( "Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult), diff --git a/config/config.go b/config/config.go index 211ebe5d..77372155 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,7 @@ package config import ( "encoding/json" "errors" + "flag" "fmt" "log" "os" @@ -80,10 +81,10 @@ 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"` + 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. @@ -284,6 +285,18 @@ func (c *Config) RetrieveConfigCurrencyPairs() error { return nil } +// GetFilePath returns the desired config file or the default config file name +// based on if the application is being run under test or normal mode. +func GetFilePath(file string) string { + if file != "" { + return file + } + if flag.Lookup("test.v") == nil { + return ConfigFile + } + return ConfigTestFile +} + // CheckConfig checks to see if there is an old configuration filename and path // if found it will change it to correct filename. func CheckConfig() error { @@ -301,14 +314,7 @@ func CheckConfig() error { // ReadConfig verifies and checks for encryption and verifies the unencrypted // file contains JSON. func (c *Config) ReadConfig(configPath string) error { - var defaultPath string - - if configPath == "" { - defaultPath = ConfigTestFile - } else { - defaultPath = configPath - } - + defaultPath := GetFilePath(configPath) err := CheckConfig() if err != nil { return err @@ -356,14 +362,7 @@ func (c *Config) ReadConfig(configPath string) error { // SaveConfig saves your configuration to your desired path func (c *Config) SaveConfig(configPath string) error { - var defaultPath string - - if configPath == "" { - defaultPath = ConfigFile - } else { - defaultPath = configPath - } - + defaultPath := GetFilePath(configPath) payload, err := json.MarshalIndent(c, "", " ") if c.EncryptConfig == configFileEncryptionEnabled { @@ -389,7 +388,7 @@ func (c *Config) SaveConfig(configPath string) error { func (c *Config) LoadConfig(configPath string) error { err := c.ReadConfig(configPath) if err != nil { - return fmt.Errorf(ErrFailureOpeningConfig, ConfigFile, err) + return fmt.Errorf(ErrFailureOpeningConfig, configPath, err) } err = c.CheckExchangeConfigValues() diff --git a/config/config_test.go b/config/config_test.go index 1da0b2b0..a47c10e1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -305,3 +305,17 @@ func TestSaveConfig(t *testing.T) { t.Errorf("Test failed. TestSaveConfig.SaveConfig, %s", err2.Error()) } } + +func TestGetFilePath(t *testing.T) { + expected := "blah.json" + result := GetFilePath("blah.json") + if result != "blah.json" { + t.Errorf("Test failed. TestGetFilePath: expected %s got %s", expected, result) + } + + expected = ConfigTestFile + result = GetFilePath("") + if result != expected { + t.Errorf("Test failed. TestGetFilePath: expected %s got %s", expected, result) + } +} diff --git a/config_routes.go b/config_routes.go index 28413d7a..8de3474e 100644 --- a/config_routes.go +++ b/config_routes.go @@ -39,11 +39,11 @@ func SaveAllSettings(w http.ResponseWriter, r *http.Request) { } } //Reload the configuration - err := bot.config.SaveConfig("") + err := bot.config.SaveConfig(bot.configFile) if err != nil { panic(err) } - err = bot.config.LoadConfig("") + err = bot.config.LoadConfig(bot.configFile) if err != nil { panic(err) } diff --git a/currency/currency.go b/currency/currency.go index dbd057f1..794cea42 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -197,19 +197,20 @@ func MakecurrencyPairs(supportedCurrencies string) string { // or vice versa. func ConvertCurrency(amount float64, from, to string) (float64, error) { currency := common.StringToUpper(from + to) - if CurrencyStore[currency].Name != currency { + + _, ok := CurrencyStore[currency] + if !ok { err := SeedCurrencyData(currency[:len(from)] + "," + currency[len(to):]) if err != nil { return 0, err } } - for x, y := range CurrencyStore { - if x == currency { - return amount * y.Rate, nil - } + result, ok := CurrencyStore[currency] + if !ok { + return 0, ErrCurrencyNotFound } - return 0, ErrCurrencyNotFound + return amount * result.Rate, nil } // FetchYahooCurrencyData seeds the variable CurrencyStore; this is a @@ -268,7 +269,7 @@ func QueryYahooCurrencyValues(currencies string) error { pairs = currencyPairs[index : index+maxCurrencyPairsPerRequest] index += maxCurrencyPairsPerRequest } else { - pairs = currencyPairs[index:len(currencyPairs)] + pairs = currencyPairs[index:] index += (len(currencyPairs) - index) } err = FetchYahooCurrencyData(pairs) @@ -277,7 +278,7 @@ func QueryYahooCurrencyValues(currencies string) error { } } } else { - pairs = currencyPairs[index:len(currencyPairs)] + pairs = currencyPairs[index:] err = FetchYahooCurrencyData(pairs) if err != nil { return err diff --git a/exchanges/alphapoint/alphapoint.go b/exchanges/alphapoint/alphapoint.go index 6aea4d3a..4462ee3a 100644 --- a/exchanges/alphapoint/alphapoint.go +++ b/exchanges/alphapoint/alphapoint.go @@ -14,44 +14,51 @@ import ( ) const ( - ALPHAPOINT_DEFAULT_API_URL = "https://sim3.alphapoint.com:8400" - ALPHAPOINT_API_VERSION = "1" - ALPHAPOINT_TICKER = "GetTicker" - ALPHAPOINT_TRADES = "GetTrades" - ALPHAPOINT_TRADESBYDATE = "GetTradesByDate" - ALPHAPOINT_ORDERBOOK = "GetOrderBook" - ALPHAPOINT_PRODUCT_PAIRS = "GetProductPairs" - ALPHAPOINT_PRODUCTS = "GetProducts" - ALPHAPOINT_CREATE_ACCOUNT = "CreateAccount" - ALPHAPOINT_USERINFO = "GetUserInfo" - ALPHAPOINT_ACCOUNT_INFO = "GetAccountInfo" - ALPHAPOINT_ACCOUNT_TRADES = "GetAccountTrades" - ALPHAPOINT_DEPOSIT_ADDRESSES = "GetDepositAddresses" - ALPHAPOINT_WITHDRAW = "Withdraw" - ALPHAPOINT_CREATE_ORDER = "CreateOrder" - ALPHAPOINT_MODIFY_ORDER = "ModifyOrder" - ALPHAPOINT_CANCEL_ORDER = "CancelOrder" - ALPHAPOINT_CANCEALLORDERS = "CancelAllOrders" - ALPHAPOINT_OPEN_ORDERS = "GetAccountOpenOrders" - ALPHAPOINT_ORDER_FEE = "GetOrderFee" + alphapointDefaultAPIURL = "https://sim3.alphapoint.com:8400" + alphapointAPIVersion = "1" + alphapointTicker = "GetTicker" + alphapointTrades = "GetTrades" + alphapointTradesByDate = "GetTradesByDate" + alphapointOrderbook = "GetOrderBook" + alphapointProductPairs = "GetProductPairs" + alphapointProducts = "GetProducts" + alphapointCreateAccount = "CreateAccount" + alphapointUserInfo = "GetUserInfo" + alphapointAccountInfo = "GetAccountInfo" + alphapointAccountTrades = "GetAccountTrades" + alphapointDepositAddresses = "GetDepositAddresses" + alphapointWithdraw = "Withdraw" + alphapointCreateOrder = "CreateOrder" + alphapointModifyOrder = "ModifyOrder" + alphapointCancelOrder = "CancelOrder" + alphapointCancelAllOrders = "CancelAllOrders" + alphapointOpenOrders = "GetAccountOpenOrders" + alphapointOrderFee = "GetOrderFee" + + // Anymore and you get IP banned + alphapointMaxRequestsPer10minutes = 500 ) +// Alphapoint is the overarching type across the alphapoint package type Alphapoint struct { exchange.Base WebsocketConn *websocket.Conn } +// SetDefaults sets current default settings func (a *Alphapoint) SetDefaults() { - a.APIUrl = ALPHAPOINT_DEFAULT_API_URL - a.WebsocketURL = ALPHAPOINT_DEFAULT_WEBSOCKET_URL + a.APIUrl = alphapointDefaultAPIURL + a.WebsocketURL = alphapointDefaultWebsocketURL } -func (a *Alphapoint) GetTicker(symbol string) (AlphapointTicker, error) { +// GetTicker returns current ticker information from Alphapoint for a selected +// currency pair ie "BTCUSD" +func (a *Alphapoint) GetTicker(currencyPair string) (Ticker, error) { request := make(map[string]interface{}) - request["productPair"] = symbol - response := AlphapointTicker{} - err := a.SendRequest("POST", ALPHAPOINT_TICKER, request, &response) + request["productPair"] = currencyPair + response := Ticker{} + err := a.SendRequest("POST", alphapointTicker, request, &response) if err != nil { return response, err } @@ -61,14 +68,20 @@ func (a *Alphapoint) GetTicker(symbol string) (AlphapointTicker, error) { return response, nil } -func (a *Alphapoint) GetTrades(symbol string, startIndex, count int) (AlphapointTrades, error) { +// GetTrades fetches past trades for the given currency pair +// currencyPair: ie "BTCUSD" +// StartIndex: specifies the index to begin from, -1 being the first trade on +// AlphaPoint Exchange. To begin from the most recent trade, set startIndex to +// 0 (default: 0) +// Count: specifies the number of trades to return (default: 10) +func (a *Alphapoint) GetTrades(currencyPair string, startIndex, count int) (Trades, error) { request := make(map[string]interface{}) - request["ins"] = symbol + request["ins"] = currencyPair request["startIndex"] = startIndex request["Count"] = count - response := AlphapointTrades{} - err := a.SendRequest("POST", ALPHAPOINT_TRADES, request, &response) + response := Trades{} + err := a.SendRequest("POST", alphapointTrades, request, &response) if err != nil { return response, err } @@ -78,14 +91,18 @@ func (a *Alphapoint) GetTrades(symbol string, startIndex, count int) (Alphapoint return response, nil } -func (a *Alphapoint) GetTradesByDate(symbol string, startDate, endDate int64) (AlphapointTradesByDate, error) { +// GetTradesByDate gets trades by date +// CurrencyPair - instrument code (ex: “BTCUSD”) +// StartDate - specifies the starting time in epoch time, type is long +// EndDate - specifies the end time in epoch time, type is long +func (a *Alphapoint) GetTradesByDate(currencyPair string, startDate, endDate int64) (Trades, error) { request := make(map[string]interface{}) - request["ins"] = symbol + request["ins"] = currencyPair request["startDate"] = startDate request["endDate"] = endDate - response := AlphapointTradesByDate{} - err := a.SendRequest("POST", ALPHAPOINT_TRADESBYDATE, request, &response) + response := Trades{} + err := a.SendRequest("POST", alphapointTradesByDate, request, &response) if err != nil { return response, err } @@ -95,12 +112,14 @@ func (a *Alphapoint) GetTradesByDate(symbol string, startDate, endDate int64) (A return response, nil } -func (a *Alphapoint) GetOrderbook(symbol string) (AlphapointOrderbook, error) { +// GetOrderbook fetches the current orderbook for a given currency pair +// CurrencyPair - trade pair (ex: “BTCUSD”) +func (a *Alphapoint) GetOrderbook(currencyPair string) (Orderbook, error) { request := make(map[string]interface{}) - request["productPair"] = symbol - response := AlphapointOrderbook{} - err := a.SendRequest("POST", ALPHAPOINT_ORDERBOOK, request, &response) + request["productPair"] = currencyPair + response := Orderbook{} + err := a.SendRequest("POST", alphapointOrderbook, request, &response) if err != nil { return response, err } @@ -110,10 +129,11 @@ func (a *Alphapoint) GetOrderbook(symbol string) (AlphapointOrderbook, error) { return response, nil } -func (a *Alphapoint) GetProductPairs() (AlphapointProductPairs, error) { - response := AlphapointProductPairs{} - err := a.SendRequest("POST", ALPHAPOINT_PRODUCT_PAIRS, nil, &response) +// GetProductPairs gets the currency pairs currently traded on alphapoint +func (a *Alphapoint) GetProductPairs() (ProductPairs, error) { + response := ProductPairs{} + err := a.SendRequest("POST", alphapointProductPairs, nil, &response) if err != nil { return response, err } @@ -123,10 +143,11 @@ func (a *Alphapoint) GetProductPairs() (AlphapointProductPairs, error) { return response, nil } -func (a *Alphapoint) GetProducts() (AlphapointProducts, error) { - response := AlphapointProducts{} - err := a.SendRequest("POST", ALPHAPOINT_PRODUCTS, nil, &response) +// GetProducts gets the currency products currently supported on alphapoint +func (a *Alphapoint) GetProducts() (Products, error) { + response := Products{} + err := a.SendRequest("POST", alphapointProducts, nil, &response) if err != nil { return response, err } @@ -136,9 +157,17 @@ func (a *Alphapoint) GetProducts() (AlphapointProducts, error) { return response, nil } +// CreateAccount creates a new account on alphapoint +// FirstName - First name +// LastName - Last name +// Email - Email address +// Phone - Phone number (ex: “+12223334444”) +// Password - Minimum 8 characters func (a *Alphapoint) CreateAccount(firstName, lastName, email, phone, password string) error { if len(password) < 8 { - return errors.New("Alphapoint Error - Create account - Password must be 8 characters or more.") + return errors.New( + "alphapoint Error - Create account - Password must be 8 characters or more", + ) } request := make(map[string]interface{}) @@ -147,38 +176,99 @@ func (a *Alphapoint) CreateAccount(firstName, lastName, email, phone, password s request["email"] = email request["phone"] = phone request["password"] = password - - type Response struct { - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - } - response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_CREATE_ACCOUNT, request, &response) + err := a.SendAuthenticatedHTTPRequest("POST", alphapointCreateAccount, request, &response) if err != nil { log.Println(err) } - if !response.IsAccepted { return errors.New(response.RejectReason) } - return nil } -func (a *Alphapoint) GetUserInfo() (AlphapointUserInfo, error) { - response := AlphapointUserInfo{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_USERINFO, map[string]interface{}{}, &response) +// GetUserInfo returns current account user information +func (a *Alphapoint) GetUserInfo() (UserInfo, error) { + response := UserInfo{} + + err := a.SendAuthenticatedHTTPRequest("POST", alphapointUserInfo, map[string]interface{}{}, &response) if err != nil { - return AlphapointUserInfo{}, err + return UserInfo{}, err + } + if !response.IsAccepted { + return response, errors.New(response.RejectReason) } return response, nil } -func (a *Alphapoint) GetAccountInfo() (AlphapointAccountInfo, error) { - response := AlphapointAccountInfo{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_ACCOUNT_INFO, map[string]interface{}{}, &response) +// SetUserInfo changes user name and/or 2FA settings +// userInfoKVP - An array of key value pairs +// FirstName - First name +// LastName - Last name +// UseAuthy2FA - “true” or “false” toggle Authy app +// Cell2FACountryCode - Cell country code (ex: 1), required for Authentication +// Cell2FAValue - Cell phone number, required for Authentication +// Use2FAForWithdraw - “true” or “false” set to true for using 2FA for +// withdrawals +func (a *Alphapoint) SetUserInfo(firstName, lastName, cell2FACountryCode, cell2FAValue string, useAuthy2FA, use2FAForWithdraw bool) (UserInfoSet, error) { + response := UserInfoSet{} + + var userInfoKVPs = []UserInfoKVP{ + UserInfoKVP{ + Key: "FirstName", + Value: firstName, + }, + UserInfoKVP{ + Key: "LastName", + Value: lastName, + }, + UserInfoKVP{ + Key: "Cell2FACountryCode", + Value: cell2FACountryCode, + }, + UserInfoKVP{ + Key: "Cell2FAValue", + Value: cell2FAValue, + }, + UserInfoKVP{ + Key: "UseAuthy2FA", + Value: strconv.FormatBool(useAuthy2FA), + }, + UserInfoKVP{ + Key: "Use2FAForWithdraw", + Value: strconv.FormatBool(use2FAForWithdraw), + }, + } + + request := make(map[string]interface{}) + request["userInfoKVP"] = userInfoKVPs + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointUserInfo, + request, + &response, + ) + if err != nil { + return response, err + } + if response.IsAccepted != "true" { + return response, errors.New(response.RejectReason) + } + return response, nil +} + +// GetAccountInfo returns account info +func (a *Alphapoint) GetAccountInfo() (AccountInfo, error) { + response := AccountInfo{} + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointAccountInfo, + map[string]interface{}{}, + &response, + ) if err != nil { return response, err } @@ -188,14 +278,23 @@ func (a *Alphapoint) GetAccountInfo() (AlphapointAccountInfo, error) { return response, nil } -func (a *Alphapoint) GetAccountTrades(symbol string, startIndex, count int) (AlphapointTrades, error) { +// GetAccountTrades returns the trades executed on the account. +// CurrencyPair - Instrument code (ex: “BTCUSD”) +// StartIndex - Starting index, if less than 0 then start from the beginning +// Count - Returns last trade, (Default: 30) +func (a *Alphapoint) GetAccountTrades(currencyPair string, startIndex, count int) (Trades, error) { request := make(map[string]interface{}) - request["ins"] = symbol + request["ins"] = currencyPair request["startIndex"] = startIndex request["count"] = count + response := Trades{} - response := AlphapointTrades{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_ACCOUNT_TRADES, request, &response) + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointAccountTrades, + request, + &response, + ) if err != nil { return response, err } @@ -205,15 +304,13 @@ func (a *Alphapoint) GetAccountTrades(symbol string, startIndex, count int) (Alp return response, nil } -func (a *Alphapoint) GetDepositAddresses() ([]AlphapointDepositAddresses, error) { - type Response struct { - Addresses []AlphapointDepositAddresses - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - } - +// GetDepositAddresses generates a deposit address +func (a *Alphapoint) GetDepositAddresses() ([]DepositAddresses, error) { response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_DEPOSIT_ADDRESSES, map[string]interface{}{}, &response) + + err := a.SendAuthenticatedHTTPRequest("POST", alphapointDepositAddresses, + map[string]interface{}{}, &response, + ) if err != nil { return nil, err } @@ -223,30 +320,40 @@ func (a *Alphapoint) GetDepositAddresses() ([]AlphapointDepositAddresses, error) return response.Addresses, nil } -func (a *Alphapoint) WithdrawCoins(symbol, product string, amount float64, address string) error { +// WithdrawCoins withdraws a coin to a specific address +// symbol - Instrument name (ex: “BTCUSD”) +// product - Currency name (ex: “BTC”) +// amount - Amount (ex: “.011”) +// address - Withdraw address +func (a *Alphapoint) WithdrawCoins(symbol, product, address string, amount float64) error { request := make(map[string]interface{}) request["ins"] = symbol request["product"] = product request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) request["sendToAddress"] = address - type Response struct { - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - } - response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_WITHDRAW, request, &response) + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointWithdraw, + request, + &response, + ) if err != nil { return err } - if !response.IsAccepted { return errors.New(response.RejectReason) } return nil } +// CreateOrder creates a market or limit order +// symbol - Instrument code (ex: “BTCUSD”) +// side - “buy” or “sell” +// orderType - “1” for market orders, “0” for limit orders +// quantity - Quantity +// price - Price in USD func (a *Alphapoint) CreateOrder(symbol, side string, orderType int, quantity, price float64) (int64, error) { request := make(map[string]interface{}) request["ins"] = symbol @@ -254,160 +361,175 @@ func (a *Alphapoint) CreateOrder(symbol, side string, orderType int, quantity, p request["orderType"] = orderType request["qty"] = strconv.FormatFloat(quantity, 'f', -1, 64) request["px"] = strconv.FormatFloat(price, 'f', -1, 64) - - type Response struct { - ServerOrderID int64 `json:"serverOrderId"` - DateTimeUTC float64 `json:"dateTimeUtc"` - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - } - response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_CREATE_ORDER, request, &response) + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointCreateOrder, + request, + &response, + ) if err != nil { return 0, err } - if !response.IsAccepted { return 0, errors.New(response.RejectReason) } return response.ServerOrderID, nil } +// ModifyOrder modifies and existing Order +// OrderId - tracked order id number +// symbol - Instrument code (ex: “BTCUSD”) +// modifyAction - “0” or “1” +// “0” means "Move to top", which will modify the order price to the top of the +// book. A buy order will be modified to the highest bid and a sell order will +// be modified to the lowest ask price. “1” means "Execute now", which will +// convert a limit order into a market order. func (a *Alphapoint) ModifyOrder(symbol string, OrderID, action int64) (int64, error) { request := make(map[string]interface{}) request["ins"] = symbol request["serverOrderId"] = OrderID request["modifyAction"] = action - - type Response struct { - ModifyOrderID int64 `json:"modifyOrderId"` - ServerOrderID int64 `json:"serverOrderId"` - DateTimeUTC float64 `json:"dateTimeUtc"` - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - } - response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_MODIFY_ORDER, request, &response) + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointModifyOrder, + request, + &response, + ) if err != nil { return 0, err } - if !response.IsAccepted { return 0, errors.New(response.RejectReason) } return response.ModifyOrderID, nil } +// CancelOrder cancels an order that has not been executed. +// symbol - Instrument code (ex: “BTCUSD”) +// OrderId - Order id (ex: 1000) func (a *Alphapoint) CancelOrder(symbol string, OrderID int64) (int64, error) { request := make(map[string]interface{}) request["ins"] = symbol request["serverOrderId"] = OrderID - - type Response struct { - CancelOrderID int64 `json:"cancelOrderId"` - ServerOrderID int64 `json:"serverOrderId"` - DateTimeUTC float64 `json:"dateTimeUtc"` - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - } - response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_CANCEL_ORDER, request, &response) + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointCancelOrder, + request, + &response, + ) if err != nil { return 0, err } - if !response.IsAccepted { return 0, errors.New(response.RejectReason) } return response.CancelOrderID, nil } +// CancelAllOrders cancels all open orders by symbol +// symbol - Instrument code (ex: “BTCUSD”) func (a *Alphapoint) CancelAllOrders(symbol string) error { request := make(map[string]interface{}) request["ins"] = symbol - - type Response struct { - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - } - response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_CANCEALLORDERS, request, &response) + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointCancelAllOrders, + request, + &response, + ) if err != nil { return err } - if !response.IsAccepted { return errors.New(response.RejectReason) } return nil } -func (a *Alphapoint) GetOrders() ([]AlphapointOpenOrders, error) { - response := AlphapointOrderInfo{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_OPEN_ORDERS, map[string]interface{}{}, &response) +// GetOrders returns all current open orders +func (a *Alphapoint) GetOrders() ([]OpenOrders, error) { + response := OrderInfo{} + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointOpenOrders, + map[string]interface{}{}, + &response, + ) if err != nil { return nil, err } - if !response.IsAccepted { return nil, errors.New(response.RejectReason) } return response.OpenOrders, nil } +// GetOrderFee returns a fee associated with an order +// symbol - Instrument code (ex: “BTCUSD”) +// side - “buy” or “sell” +// quantity - Quantity +// price - Price in USD func (a *Alphapoint) GetOrderFee(symbol, side string, quantity, price float64) (float64, error) { - type Response struct { - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - Fee float64 `json:"fee"` - FeeProduct string `json:"feeProduct"` - } - request := make(map[string]interface{}) request["ins"] = symbol request["side"] = side request["qty"] = strconv.FormatFloat(quantity, 'f', -1, 64) request["px"] = strconv.FormatFloat(price, 'f', -1, 64) - response := Response{} - err := a.SendAuthenticatedHTTPRequest("POST", ALPHAPOINT_ORDER_FEE, request, &response) + + err := a.SendAuthenticatedHTTPRequest( + "POST", + alphapointOrderFee, + request, + &response, + ) if err != nil { return 0, err } - if !response.IsAccepted { return 0, errors.New(response.RejectReason) } return response.Fee, nil } +// SendRequest sends an unauthenticated request func (a *Alphapoint) SendRequest(method, path string, data map[string]interface{}, result interface{}) error { headers := make(map[string]string) headers["Content-Type"] = "application/json" - path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, ALPHAPOINT_API_VERSION, path) - PayloadJson, err := common.JSONEncode(data) + path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, alphapointAPIVersion, path) + PayloadJSON, err := common.JSONEncode(data) if err != nil { - return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") + return errors.New("SendHTTPRequest: Unable to JSON request") } - resp, err := common.SendHTTPRequest(method, path, headers, bytes.NewBuffer(PayloadJson)) + resp, err := common.SendHTTPRequest( + method, + path, + headers, + bytes.NewBuffer(PayloadJSON), + ) if err != nil { return err } err = common.JSONDecode([]byte(resp), &result) - if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil } +// SendAuthenticatedHTTPRequest sends an authenticated request func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[string]interface{}, result interface{}) error { headers := make(map[string]string) headers["Content-Type"] = "application/json" @@ -415,25 +537,29 @@ func (a *Alphapoint) SendAuthenticatedHTTPRequest(method, path string, data map[ 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)) + hmac := common.GetHMAC( + common.HashSHA256, + []byte(nonceStr+a.ClientID+a.APIKey), + []byte(a.APISecret), + ) data["apiSig"] = common.StringToUpper(common.HexEncodeToString(hmac)) - path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, ALPHAPOINT_API_VERSION, path) - PayloadJson, err := common.JSONEncode(data) + path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, alphapointAPIVersion, path) + PayloadJSON, err := common.JSONEncode(data) if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } - resp, err := common.SendHTTPRequest(method, path, headers, bytes.NewBuffer(PayloadJson)) - + resp, err := common.SendHTTPRequest( + method, path, headers, bytes.NewBuffer(PayloadJSON), + ) if err != nil { return err } err = common.JSONDecode([]byte(resp), &result) - if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil } diff --git a/exchanges/alphapoint/alphapoint_test.go b/exchanges/alphapoint/alphapoint_test.go index e4b2edc2..b136dcba 100644 --- a/exchanges/alphapoint/alphapoint_test.go +++ b/exchanges/alphapoint/alphapoint_test.go @@ -102,6 +102,11 @@ func TestGetTicker(t *testing.T) { 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") + } } func TestGetTrades(t *testing.T) { @@ -112,7 +117,7 @@ func TestGetTrades(t *testing.T) { if err != nil { t.Errorf("Test Failed - Init error: %s", err) } - if reflect.ValueOf(trades).NumField() != 7 { + if reflect.ValueOf(trades).NumField() != 9 { t.Error("Test Failed - Alphapoint AlphapointTrades struct updated/changed") } if len(trades.Trades) == 0 { @@ -206,6 +211,11 @@ func TestGetTrades(t *testing.T) { 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") + } } func TestGetTradesByDate(t *testing.T) { @@ -216,7 +226,7 @@ func TestGetTradesByDate(t *testing.T) { if err != nil { t.Errorf("Test Failed - Init error: %s", err) } - if reflect.ValueOf(trades).NumField() != 7 { + if reflect.ValueOf(trades).NumField() != 9 { t.Error("Test Failed - Alphapoint AlphapointTrades struct updated/changed") } if len(trades.Trades) != 0 { @@ -259,6 +269,11 @@ 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) { @@ -278,12 +293,11 @@ func TestGetOrderbook(t *testing.T) { if reflect.TypeOf(orderBook.RejectReason).String() != "string" { t.Error("Test Failed - Alphapoint orderBook.RejectReason value is not a string") } - // if len(orderBook.Asks) < 1 { - // t.Error("Test Failed - Alphapoint orderBook.Asks does not contain anything.") - // } - // if len(orderBook.Bids) < 1 { - // t.Error("Test Failed - Alphapoint orderBook.Asks does not contain anything.") - // } + _, err = GetOrderbook.GetOrderbook("wigwham") + if err == nil { + t.Error("Test Failed - GetOrderbook() error") + } + } func TestGetProductPairs(t *testing.T) { @@ -399,15 +413,132 @@ func TestCreateAccount(t *testing.T) { if err != nil { t.Errorf("Test Failed - Init error: %s", err) } + err = CreateAccount.CreateAccount("test", "account", "something@something.com", "0292383745", "bla") + if err == nil { + t.Errorf("Test Failed - CreateAccount() error") + } + err = CreateAccount.CreateAccount("", "", "", "", "lolcat123") + if err == nil { + t.Errorf("Test Failed - CreateAccount() error") + } } func TestGetUserInfo(t *testing.T) { GetUserInfo := Alphapoint{} GetUserInfo.SetDefaults() - userInfo, err := GetUserInfo.GetUserInfo() - if err != nil { - t.Errorf("Test Failed - Init error: %s", err) + _, err := GetUserInfo.GetUserInfo() + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestSetUserInfo(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.SetUserInfo("bla", "bla", "1", "meh", true, true) + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestGetAccountInfo(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.GetAccountInfo() + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestGetAccountTrades(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.GetAccountTrades("", 1, 2) + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestGetDepositAddresses(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.GetDepositAddresses() + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestWithdrawCoins(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + err := a.WithdrawCoins("", "", "", 0.01) + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestCreateOrder(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.CreateOrder("", "", 1, 0.01, 0) + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestModifyOrder(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.ModifyOrder("", 1, 1) + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestCancelOrder(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.CancelOrder("", 1) + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestCancelAllOrders(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + err := a.CancelAllOrders("") + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestGetOrders(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.GetOrders() + if err == nil { + t.Error("Test Failed - GetUserInfo() error") + } +} + +func TestGetOrderFee(t *testing.T) { + a := Alphapoint{} + a.SetDefaults() + + _, err := a.GetOrderFee("", "", 1, 1) + if err == nil { + t.Error("Test Failed - GetUserInfo() error") } - t.Log(userInfo) } diff --git a/exchanges/alphapoint/alphapoint_types.go b/exchanges/alphapoint/alphapoint_types.go index 353b8ea4..549b48c4 100644 --- a/exchanges/alphapoint/alphapoint_types.go +++ b/exchanges/alphapoint/alphapoint_types.go @@ -1,37 +1,20 @@ package alphapoint -type AlphapointTrade struct { - TID int64 `json:"tid"` - Price float64 `json:"px"` - Quantity float64 `json:"qty"` - Unixtime int `json:"unixtime"` - UTCTicks int64 `json:"utcticks"` - IncomingOrderSide int `json:"incomingOrderSide"` - IncomingServerOrderID int `json:"incomingServerOrderId"` - BookServerOrderID int `json:"bookServerOrderId"` +// Response contains general responses from the exchange +type Response struct { + IsAccepted bool `json:"isAccepted"` + RejectReason string `json:"rejectReason"` + Fee float64 `json:"fee"` + FeeProduct string `json:"feeProduct"` + CancelOrderID int64 `json:"cancelOrderId"` + ServerOrderID int64 `json:"serverOrderId"` + DateTimeUTC float64 `json:"dateTimeUtc"` + ModifyOrderID int64 `json:"modifyOrderId"` + Addresses []DepositAddresses } -type AlphapointTrades struct { - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - DateTimeUTC int64 `json:"dateTimeUtc"` - Instrument string `json:"ins"` - StartIndex int `json:"startIndex"` - Count int `json:"count"` - Trades []AlphapointTrade `json:"trades"` -} - -type AlphapointTradesByDate struct { - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` - DateTimeUTC int64 `json:"dateTimeUtc"` - Instrument string `json:"ins"` - StartDate int64 `json:"startDate"` - EndDate int64 `json:"endDate"` - Trades []AlphapointTrade `json:"trades"` -} - -type AlphapointTicker struct { +// Ticker holds ticker information +type Ticker struct { High float64 `json:"high"` Last float64 `json:"last"` Bid float64 `json:"bid"` @@ -47,19 +30,55 @@ type AlphapointTicker struct { RejectReason string `json:"rejectReason"` } -type AlphapointOrderbookEntry struct { +// Trades holds trade information +type Trades struct { + IsAccepted bool `json:"isAccepted"` + RejectReason string `json:"rejectReason"` + DateTimeUTC int64 `json:"dateTimeUtc"` + Instrument string `json:"ins"` + StartIndex int `json:"startIndex"` + Count int `json:"count"` + StartDate int64 `json:"startDate"` + EndDate int64 `json:"endDate"` + Trades []Trade `json:"trades"` +} + +// Trade is a sub-type which holds the singular trade that occured in the past +type Trade struct { + TID int64 `json:"tid"` + Price float64 `json:"px"` + Quantity float64 `json:"qty"` + Unixtime int `json:"unixtime"` + UTCTicks int64 `json:"utcticks"` + IncomingOrderSide int `json:"incomingOrderSide"` + IncomingServerOrderID int `json:"incomingServerOrderId"` + BookServerOrderID int `json:"bookServerOrderId"` +} + +// Orderbook holds the total Bids and Asks on the exchange +type Orderbook struct { + Bids []OrderbookEntry `json:"bids"` + Asks []OrderbookEntry `json:"asks"` + IsAccepted bool `json:"isAccepted"` + RejectReason string `json:"rejectReason"` +} + +// OrderbookEntry is a sub-type that takes has the individual quantity and price +type OrderbookEntry struct { Quantity float64 `json:"qty"` Price float64 `json:"px"` } -type AlphapointOrderbook struct { - Bids []AlphapointOrderbookEntry `json:"bids"` - Asks []AlphapointOrderbookEntry `json:"asks"` - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` +// ProductPairs holds the full range of product pairs that the exchange can +// trade between +type ProductPairs struct { + ProductPairs []ProductPair `json:"productPairs"` + IsAccepted bool `json:"isAccepted"` + RejectReason string `json:"rejectReason"` } -type AlphapointProductPair struct { +// ProductPair holds the individual product pairs that are currently traded +type ProductPair struct { Name string `json:"name"` Productpaircode int `json:"productPairCode"` Product1Label string `json:"product1Label"` @@ -68,13 +87,15 @@ type AlphapointProductPair struct { Product2Decimalplaces int `json:"product2DecimalPlaces"` } -type AlphapointProductPairs struct { - ProductPairs []AlphapointProductPair `json:"productPairs"` - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` +// Products holds the full range of supported currency products +type Products struct { + Products []Product `json:"products"` + IsAccepted bool `json:"isAccepted"` + RejectReason string `json:"rejectReason"` } -type AlphapointProduct struct { +// Product holds the a single currency product that is supported +type Product struct { Name string `json:"name"` IsDigital bool `json:"isDigital"` ProductCode int `json:"productCode"` @@ -82,22 +103,30 @@ type AlphapointProduct struct { FullName string `json:"fullName"` } -type AlphapointProducts struct { - Products []AlphapointProduct `json:"products"` - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` +// UserInfo holds current user information associated with the apiKey details +type UserInfo struct { + UserInforKVPs []UserInfoKVP `json:"userInfoKVP"` + IsAccepted bool `json:"isAccepted"` + RejectReason string `json:"rejectReason"` } -type AlphapointUserInfo struct { - UserInfoKVP []struct { - Key string `json:"key"` - Value string `json:"value"` - } `json:"userInfoKVP"` - IsAccepted bool `json:"isAccepted"` - RejectReason string `json:"rejectReason"` +// UserInfoKVP is a sub-type that holds key value pairs +type UserInfoKVP struct { + Key string `json:"key"` + Value string `json:"value"` } -type AlphapointAccountInfo struct { +// UserInfoSet is the returned response from set user information request +type UserInfoSet struct { + IsAccepted string `json:"isAccepted"` + RejectReason string `json:"rejectReason"` + RequireAuthy2FA bool `json:"requireAuthy2FA"` + Val2FaRequestCode string `json:"val2FaRequestCode"` +} + +// AccountInfo holds your current account information like balances, trade count +// and volume +type AccountInfo struct { Currencies []struct { Name string `json:"name"` Balance int `json:"balance"` @@ -113,7 +142,8 @@ type AlphapointAccountInfo struct { RejectReason string `json:"rejectReason"` } -type AlphapointOrder struct { +// Order is a generalised order type +type Order struct { Serverorderid int `json:"ServerOrderId"` AccountID int `json:"AccountId"` Price int `json:"Price"` @@ -123,24 +153,29 @@ type AlphapointOrder struct { Side int `json:"Side"` } -type AlphapointOpenOrders struct { - Instrument string `json:"ins"` - Openorders []AlphapointOrder `json:"openOrders"` +// OpenOrders holds the full range of orders by instrument +type OpenOrders struct { + Instrument string `json:"ins"` + Openorders []Order `json:"openOrders"` } -type AlphapointOrderInfo struct { - OpenOrders []AlphapointOpenOrders `json:"openOrdersInfo"` - IsAccepted bool `json:"isAccepted"` - DateTimeUTC int64 `json:"dateTimeUtc"` - RejectReason string `json:"rejectReason"` +// OrderInfo holds all open orders across the entire range of all instruments +type OrderInfo struct { + OpenOrders []OpenOrders `json:"openOrdersInfo"` + IsAccepted bool `json:"isAccepted"` + DateTimeUTC int64 `json:"dateTimeUtc"` + RejectReason string `json:"rejectReason"` } -type AlphapointDepositAddresses struct { +// DepositAddresses holds information about the generated deposit address for +// a specific currency +type DepositAddresses struct { Name string `json:"name"` DepositAddress string `json:"depositAddress"` } -type AlphapointWebsocketTicker struct { +// WebsocketTicker holds current up to date ticker information +type WebsocketTicker struct { MessageType string `json:"messageType"` ProductPair string `json:"prodPair"` High float64 `json:"high"` diff --git a/exchanges/alphapoint/alphapoint_websocket.go b/exchanges/alphapoint/alphapoint_websocket.go index ac114fdc..3dc8b458 100644 --- a/exchanges/alphapoint/alphapoint_websocket.go +++ b/exchanges/alphapoint/alphapoint_websocket.go @@ -9,9 +9,10 @@ import ( ) const ( - ALPHAPOINT_DEFAULT_WEBSOCKET_URL = "wss://sim3.alphapoint.com:8401/v1/GetTicker/" + alphapointDefaultWebsocketURL = "wss://sim3.alphapoint.com:8401/v1/GetTicker/" ) +// WebsocketClient starts a new webstocket connection func (a *Alphapoint) WebsocketClient() { for a.Enabled && a.Websocket { var Dialer websocket.Dialer @@ -56,7 +57,7 @@ func (a *Alphapoint) WebsocketClient() { switch msgType.MessageType { case "Ticker": - ticker := AlphapointWebsocketTicker{} + ticker := WebsocketTicker{} err = common.JSONDecode(resp, &ticker) if err != nil { log.Println(err) diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 973ad751..e9e1f34d 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -9,11 +9,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Alphapoint exchange -func (e *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies on the +// Alphapoint exchange +func (a *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - account, err := e.GetAccountInfo() + response.ExchangeName = a.GetName() + account, err := a.GetAccountInfo() if err != nil { return response, err } @@ -29,6 +30,7 @@ func (e *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) { return response, nil } +// GetTickerPrice returns the current ticker price by currency pair func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) ticker.TickerPrice { var tickerPrice ticker.TickerPrice tick, err := a.GetTicker(p.Pair().String()) @@ -42,6 +44,7 @@ func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) ticker.TickerPrice { return tickerPrice } +// GetOrderbookEx returns an orderbookbase by currency pair func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(a.GetName(), p) if err == nil { @@ -54,12 +57,12 @@ func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBas return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Quantity, Price: data.Price}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Quantity, Price: data.Price}) } diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 7be5faa0..d7135b98 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -315,13 +315,13 @@ func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interf resp, err := common.SendHTTPRequest("POST", ANX_API_URL+path, headers, bytes.NewBuffer(PayloadJson)) if a.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 6a272d42..cf0f77d0 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -16,59 +16,74 @@ import ( ) const ( - BITFINEX_API_URL = "https://api.bitfinex.com/v1/" - BITFINEX_API_VERSION = "1" - BITFINEX_TICKER = "pubticker/" - BITFINEX_STATS = "stats/" - BITFINEX_LENDBOOK = "lendbook/" - BITFINEX_ORDERBOOK = "book/" - BITFINEX_TRADES = "trades/" - BITFINEX_LENDS = "lends/" - BITFINEX_SYMBOLS = "symbols/" - BITFINEX_SYMBOLS_DETAILS = "symbols_details/" - BITFINEX_ACCOUNT_INFO = "account_infos" - BITFINEX_DEPOSIT = "deposit/new" - BITFINEX_ORDER_NEW = "order/new" - BITFINEX_ORDER_NEW_MULTI = "order/new/multi" - BITFINEX_ORDER_CANCEL = "order/cancel" - BITFINEX_ORDER_CANCEL_MULTI = "order/cancel/multi" - BITFINEX_ORDER_CANCEL_ALL = "order/cancel/all" - BITFINEX_ORDER_CANCEL_REPLACE = "order/cancel/replace" - BITFINEX_ORDER_STATUS = "order/status" - BITFINEX_ORDERS = "orders" - BITFINEX_POSITIONS = "positions" - BITFINEX_CLAIM_POSITION = "position/claim" - BITFINEX_HISTORY = "history" - BITFINEX_HISTORY_MOVEMENTS = "history/movements" - BITFINEX_TRADE_HISTORY = "mytrades" - BITFINEX_OFFER_NEW = "offer/new" - BITFINEX_OFFER_CANCEL = "offer/cancel" - BITFINEX_OFFER_STATUS = "offer/status" - BITFINEX_OFFERS = "offers" - BITFINEX_MARGIN_ACTIVE_FUNDS = "taken_funds" - BITFINEX_MARGIN_TOTAL_FUNDS = "total_taken_funds" - BITFINEX_MARGIN_CLOSE = "funding/close" - BITFINEX_BALANCES = "balances" - BITFINEX_MARGIN_INFO = "margin_infos" - BITFINEX_TRANSFER = "transfer" - BITFINEX_WITHDRAWAL = "withdrawal" + bitfinexAPIURL = "https://api.bitfinex.com/v1/" + bitfinexAPIVersion = "1" + bitfinexTicker = "pubticker/" + bitfinexStats = "stats/" + bitfinexLendbook = "lendbook/" + bitfinexOrderbook = "book/" + bitfinexTrades = "trades/" + bitfinexKeyPermissions = "key_info" + bitfinexLends = "lends/" + bitfinexSymbols = "symbols/" + bitfinexSymbolsDetails = "symbols_details/" + bitfinexAccountInfo = "account_infos" + bitfinexAccountFees = "account_fees" + bitfinexAccountSummary = "summary" + bitfinexDeposit = "deposit/new" + bitfinexOrderNew = "order/new" + bitfinexOrderNewMulti = "order/new/multi" + bitfinexOrderCancel = "order/cancel" + bitfinexOrderCancelMulti = "order/cancel/multi" + bitfinexOrderCancelAll = "order/cancel/all" + bitfinexOrderCancelReplace = "order/cancel/replace" + bitfinexOrderStatus = "order/status" + bitfinexOrders = "orders" + bitfinexPositions = "positions" + bitfinexClaimPosition = "position/claim" + bitfinexHistory = "history" + bitfinexHistoryMovements = "history/movements" + bitfinexTradeHistory = "mytrades" + bitfinexOfferNew = "offer/new" + bitfinexOfferCancel = "offer/cancel" + bitfinexOfferStatus = "offer/status" + bitfinexOffers = "offers" + bitfinexMarginActiveFunds = "taken_funds" + bitfinexMarginTotalFunds = "total_taken_funds" + bitfinexMarginUnusedFunds = "unused_taken_funds" + bitfinexMarginClose = "funding/close" + bitfinexBalances = "balances" + bitfinexMarginInfo = "margin_infos" + bitfinexTransfer = "transfer" + bitfinexWithdrawal = "withdraw" + bitfinexActiveCredits = "credits" + + // bitfinexMaxRequests if exceeded IP address blocked 10-60 sec, JSON response + // {"error": "ERR_RATE_LIMIT"} + bitfinexMaxRequests = 90 ) +// Bitfinex is the overarching type across the bitfinex package +// Notes: Bitfinex has added a rate limit to the number of REST requests. +// Rate limit policy can vary in a range of 10 to 90 requests per minute +// depending on some factors (e.g. servers load, endpoint, etc.). type Bitfinex struct { exchange.Base WebsocketConn *websocket.Conn - WebsocketSubdChannels map[int]BitfinexWebsocketChanInfo + WebsocketSubdChannels map[int]WebsocketChanInfo } +// SetDefaults sets the basic defaults for bitfinex func (b *Bitfinex) SetDefaults() { b.Name = "Bitfinex" b.Enabled = false b.Verbose = false b.Websocket = false b.RESTPollingDelay = 10 - b.WebsocketSubdChannels = make(map[int]BitfinexWebsocketChanInfo) + b.WebsocketSubdChannels = make(map[int]WebsocketChanInfo) } +// Setup takes in the supplied exchange configuration details and sets params func (b *Bitfinex) Setup(exch config.ExchangeConfig) { if !exch.Enabled { b.SetEnabled(false) @@ -85,199 +100,266 @@ func (b *Bitfinex) Setup(exch config.ExchangeConfig) { } } -func (b *Bitfinex) GetTicker(symbol string, values url.Values) (BitfinexTicker, error) { - path := common.EncodeURLValues(BITFINEX_API_URL+BITFINEX_TICKER+symbol, values) - response := BitfinexTicker{} - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return response, err - } - return response, nil +// GetTicker returns ticker information +func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) { + response := Ticker{} + path := common.EncodeURLValues(bitfinexAPIURL+bitfinexTicker+symbol, values) + + return response, common.SendHTTPGetRequest(path, true, &response) } -func (b *Bitfinex) GetStats(symbol string) ([]BitfinexStats, error) { - response := []BitfinexStats{} - err := common.SendHTTPGetRequest(BITFINEX_API_URL+BITFINEX_STATS+symbol, true, &response) - if err != nil { - return response, err - } - return response, nil +// GetStats returns various statistics about the requested pair +func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) { + response := []Stat{} + path := fmt.Sprint(bitfinexAPIURL + bitfinexStats + symbol) + + return response, common.SendHTTPGetRequest(path, true, &response) } -func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (BitfinexLendbook, error) { +// GetFundingBook the entire margin funding book for both bids and asks sides +// per currency string +// symbol - example "USD" +func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) { + response := FundingBook{} + path := fmt.Sprint(bitfinexAPIURL + bitfinexLendbook + symbol) + + return response, common.SendHTTPGetRequest(path, true, &response) +} + +// GetOrderbook retieves the entire orderbook bid and ask price on a currency +// pair +// CurrencyPair - Example "BTCUSD" +func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbook, error) { + response := Orderbook{} + path := common.EncodeURLValues( + bitfinexAPIURL+bitfinexOrderbook+currencyPair, + values, + ) + return response, common.SendHTTPGetRequest(path, true, &response) +} + +// GetTrades returns a list of the most recent trades for the given curencyPair +// CurrencyPair - Example "BTCUSD" +func (b *Bitfinex) GetTrades(currencyPair string, values url.Values) ([]TradeStructure, error) { + response := []TradeStructure{} + path := common.EncodeURLValues( + bitfinexAPIURL+bitfinexTrades+currencyPair, + values, + ) + return response, common.SendHTTPGetRequest(path, true, &response) +} + +// GetLendbook returns a list of the most recent funding data for the given +// currency: total amount provided and Flash Return Rate (in % by 365 days) over +// time +// Symbol - example "USD" +func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (Lendbook, error) { + response := Lendbook{} if len(symbol) == 6 { symbol = symbol[:3] } - path := common.EncodeURLValues(BITFINEX_API_URL+BITFINEX_LENDBOOK+symbol, values) - response := BitfinexLendbook{} - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return response, err - } - return response, nil + path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLendbook+symbol, values) + + return response, common.SendHTTPGetRequest(path, true, &response) } -func (b *Bitfinex) GetOrderbook(symbol string, values url.Values) (BitfinexOrderbook, error) { - path := common.EncodeURLValues(BITFINEX_API_URL+BITFINEX_ORDERBOOK+symbol, values) - response := BitfinexOrderbook{} - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return response, err - } - return response, nil -} - -func (b *Bitfinex) GetTrades(symbol string, values url.Values) ([]BitfinexTradeStructure, error) { - path := common.EncodeURLValues(BITFINEX_API_URL+BITFINEX_TRADES+symbol, values) - response := []BitfinexTradeStructure{} - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return nil, err - } - return response, nil -} - -func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]BitfinexLends, error) { - path := common.EncodeURLValues(BITFINEX_API_URL+BITFINEX_LENDS+symbol, values) - response := []BitfinexLends{} - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return nil, err - } - return response, nil +// GetLends returns a list of the most recent funding data for the given +// currency: total amount provided and Flash Return Rate (in % by 365 days) +// over time +// Symbol - example "USD" +func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) { + response := []Lends{} + path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLends+symbol, values) + + return response, common.SendHTTPGetRequest(path, true, &response) } +// GetSymbols returns the avaliable currency pairs on the exchange func (b *Bitfinex) GetSymbols() ([]string, error) { products := []string{} - err := common.SendHTTPGetRequest(BITFINEX_API_URL+BITFINEX_SYMBOLS, true, &products) - if err != nil { - return nil, err - } - return products, nil + path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbols) + + return products, common.SendHTTPGetRequest(path, true, &products) } -func (b *Bitfinex) GetSymbolsDetails() ([]BitfinexSymbolDetails, error) { - response := []BitfinexSymbolDetails{} - err := common.SendHTTPGetRequest(BITFINEX_API_URL+BITFINEX_SYMBOLS_DETAILS, true, &response) - if err != nil { - return nil, err - } - return response, nil +// GetSymbolsDetails a list of valid symbol IDs and the pair details +func (b *Bitfinex) GetSymbolsDetails() ([]SymbolDetails, error) { + response := []SymbolDetails{} + path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbolsDetails) + + return response, common.SendHTTPGetRequest(path, true, &response) } -func (b *Bitfinex) GetAccountInfo() ([]BitfinexAccountInfo, error) { - response := []BitfinexAccountInfo{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ACCOUNT_INFO, nil, &response) +// GetAccountInfo returns information about your account incl. trading fees +func (b *Bitfinex) GetAccountInfo() ([]AccountInfo, error) { + response := []AccountInfo{} - if err != nil { - return response, err - } - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexAccountInfo, nil, &response) } -func (b *Bitfinex) NewDeposit(method, walletName string, renew int) (BitfinexDepositResponse, error) { +// GetAccountFees - NOT YET IMPLEMENTED +func (b *Bitfinex) GetAccountFees() (AccountFees, error) { + response := AccountFees{} + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexAccountFees, nil, &response) +} + +// GetAccountSummary returns a 30-day summary of your trading volume and return +// on margin funding +func (b *Bitfinex) GetAccountSummary() (AccountSummary, error) { + response := AccountSummary{} + + return response, + b.SendAuthenticatedHTTPRequest( + "POST", bitfinexAccountSummary, nil, &response, + ) +} + +// NewDeposit returns a new deposit address +// Method - Example methods accepted: “bitcoin”, “litecoin”, “ethereum”, +//“tethers", "ethereumc", "zcash", "monero", "iota", "bcash" +// WalletName - accepted: “trading”, “exchange”, “deposit” +// renew - Default is 0. If set to 1, will return a new unused deposit address +func (b *Bitfinex) NewDeposit(method, walletName string, renew int) (DepositResponse, error) { + response := DepositResponse{} request := make(map[string]interface{}) request["method"] = method request["wallet_name"] = walletName request["renew"] = renew - response := BitfinexDepositResponse{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_DEPOSIT, request, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexDeposit, request, &response) } -func (b *Bitfinex) NewOrder(Symbol string, Amount float64, Price float64, Buy bool, Type string, Hidden bool) (BitfinexOrder, error) { - request := make(map[string]interface{}) - request["symbol"] = Symbol - request["amount"] = strconv.FormatFloat(Amount, 'f', -1, 64) - request["price"] = strconv.FormatFloat(Price, 'f', -1, 64) - request["exchange"] = "bitfinex" +// GetKeyPermissions checks the permissions of the key being used to generate +// this request. +func (b *Bitfinex) GetKeyPermissions() (KeyPermissions, error) { + response := KeyPermissions{} - if Buy { + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexKeyPermissions, nil, &response) +} + +// GetMarginInfo shows your trading wallet information for margin trading +func (b *Bitfinex) GetMarginInfo() ([]MarginInfo, error) { + response := []MarginInfo{} + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexMarginInfo, nil, &response) +} + +// GetAccountBalance returns full wallet balance information +func (b *Bitfinex) GetAccountBalance() ([]Balance, error) { + response := []Balance{} + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexBalances, nil, &response) +} + +// WalletTransfer move available balances between your wallets +// Amount - Amount to move +// Currency - example "BTC" +// WalletFrom - example "exchange" +// WalletTo - example "deposit" +func (b *Bitfinex) WalletTransfer(amount float64, currency, walletFrom, walletTo string) ([]WalletTransfer, error) { + response := []WalletTransfer{} + request := make(map[string]interface{}) + request["amount"] = amount + request["currency"] = currency + request["walletfrom"] = walletFrom + request["walletTo"] = walletTo + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexTransfer, request, &response) +} + +// Withdrawal requests a withdrawal from one of your wallets. +// Major Upgrade needed on this function to include all query params +func (b *Bitfinex) Withdrawal(withdrawType, wallet, address string, amount float64) ([]Withdrawal, error) { + response := []Withdrawal{} + request := make(map[string]interface{}) + request["withdrawal_type"] = withdrawType + request["walletselected"] = wallet + request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) + request["address"] = address + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexWithdrawal, request, &response) +} + +// NewOrder submits a new order and returns a order information +// Major Upgrade needed on this function to include all query params +func (b *Bitfinex) NewOrder(currencyPair string, amount float64, price float64, buy bool, Type string, hidden bool) (Order, error) { + response := Order{} + request := make(map[string]interface{}) + request["symbol"] = currencyPair + request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) + request["price"] = strconv.FormatFloat(price, 'f', -1, 64) + request["exchange"] = "bitfinex" + request["type"] = Type + request["is_hidden"] = hidden + + if buy { request["side"] = "buy" } else { request["side"] = "sell" } - request["type"] = Type - //request["is_hidden"] = Hidden - - response := BitfinexOrder{} - - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDER_NEW, request, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderNew, request, &response) } -func (b *Bitfinex) NewOrderMulti(orders []BitfinexPlaceOrder) (BitfinexOrderMultiResponse, error) { +// NewOrderMulti allows several new orders at once +func (b *Bitfinex) NewOrderMulti(orders []PlaceOrder) (OrderMultiResponse, error) { + response := OrderMultiResponse{} request := make(map[string]interface{}) request["orders"] = orders - response := BitfinexOrderMultiResponse{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDER_NEW_MULTI, request, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderNewMulti, request, &response) } -func (b *Bitfinex) CancelOrder(OrderID int64) (BitfinexOrder, error) { +// CancelOrder cancels a single order +func (b *Bitfinex) CancelOrder(OrderID int64) (Order, error) { + response := Order{} request := make(map[string]interface{}) request["order_id"] = OrderID - response := BitfinexOrder{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDER_CANCEL, request, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderCancel, request, &response) } +// CancelMultipleOrders cancels multiple orders func (b *Bitfinex) CancelMultipleOrders(OrderIDs []int64) (string, error) { + response := GenericResponse{} request := make(map[string]interface{}) request["order_ids"] = OrderIDs - response := BitfinexGenericResponse{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDER_CANCEL_MULTI, request, nil) - - if err != nil { - return "", err - } - - return response.Result, nil + return response.Result, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderCancelMulti, request, nil) } +// CancelAllOrders cancels all active and open orders func (b *Bitfinex) CancelAllOrders() (string, error) { - response := BitfinexGenericResponse{} - err := b.SendAuthenticatedHTTPRequest("GET", BITFINEX_ORDER_CANCEL_ALL, nil, nil) + response := GenericResponse{} - if err != nil { - return "", err - } - - return response.Result, nil + return response.Result, + b.SendAuthenticatedHTTPRequest("GET", bitfinexOrderCancelAll, nil, nil) } -func (b *Bitfinex) ReplaceOrder(OrderID int64, Symbol string, Amount float64, Price float64, Buy bool, Type string, Hidden bool) (BitfinexOrder, error) { +// ReplaceOrder replaces an older order with a new order +func (b *Bitfinex) ReplaceOrder(OrderID int64, Symbol string, Amount float64, Price float64, Buy bool, Type string, Hidden bool) (Order, error) { + response := Order{} request := make(map[string]interface{}) request["order_id"] = OrderID request["symbol"] = Symbol request["amount"] = strconv.FormatFloat(Amount, 'f', -1, 64) request["price"] = strconv.FormatFloat(Price, 'f', -1, 64) request["exchange"] = "bitfinex" + request["type"] = Type + request["is_hidden"] = Hidden if Buy { request["side"] = "buy" @@ -285,158 +367,116 @@ func (b *Bitfinex) ReplaceOrder(OrderID int64, Symbol string, Amount float64, Pr request["side"] = "sell" } - request["type"] = Type - //request["is_hidden"] = Hidden - - response := BitfinexOrder{} - - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDER_CANCEL_REPLACE, request, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderCancelReplace, request, &response) } -func (b *Bitfinex) GetOrderStatus(OrderID int64) (BitfinexOrder, error) { +// GetOrderStatus returns order status information +func (b *Bitfinex) GetOrderStatus(OrderID int64) (Order, error) { + orderStatus := Order{} request := make(map[string]interface{}) request["order_id"] = OrderID - orderStatus := BitfinexOrder{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDER_STATUS, request, &orderStatus) - - if err != nil { - return orderStatus, err - } - - return orderStatus, err + return orderStatus, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderStatus, request, &orderStatus) } -func (b *Bitfinex) GetActiveOrders() ([]BitfinexOrder, error) { - response := []BitfinexOrder{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDERS, nil, &response) +// GetActiveOrders returns all active orders and statuses +func (b *Bitfinex) GetActiveOrders() ([]Order, error) { + response := []Order{} - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrders, nil, &response) } -func (b *Bitfinex) GetActivePositions() ([]BitfinexPosition, error) { - response := []BitfinexPosition{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_POSITIONS, nil, &response) +// GetActivePositions returns an array of active positions +func (b *Bitfinex) GetActivePositions() ([]Position, error) { + response := []Position{} - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexPositions, nil, &response) } -func (b *Bitfinex) ClaimPosition(PositionID int) (BitfinexPosition, error) { +// ClaimPosition allows positions to be claimed +func (b *Bitfinex) ClaimPosition(PositionID int) (Position, error) { + response := Position{} request := make(map[string]interface{}) request["position_id"] = PositionID - response := BitfinexPosition{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_CLAIM_POSITION, nil, nil) - - if err != nil { - return BitfinexPosition{}, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexClaimPosition, nil, nil) } -func (b *Bitfinex) GetBalanceHistory(symbol string, timeSince time.Time, timeUntil time.Time, limit int, wallet string) ([]BitfinexBalanceHistory, error) { +// GetBalanceHistory returns balance history for the account +func (b *Bitfinex) GetBalanceHistory(symbol string, timeSince, timeUntil time.Time, limit int, wallet string) ([]BalanceHistory, error) { + response := []BalanceHistory{} request := make(map[string]interface{}) request["currency"] = symbol if !timeSince.IsZero() { request["since"] = timeSince } - if !timeUntil.IsZero() { request["until"] = timeUntil } - if limit > 0 { request["limit"] = limit } - if len(wallet) > 0 { request["wallet"] = wallet } - response := []BitfinexBalanceHistory{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_HISTORY, request, &response) - - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexHistory, request, &response) } -func (b *Bitfinex) GetMovementHistory(symbol, method string, timeSince, timeUntil time.Time, limit int) ([]BitfinexMovementHistory, error) { +// GetMovementHistory returns an array of past deposits and withdrawels +func (b *Bitfinex) GetMovementHistory(symbol, method string, timeSince, timeUntil time.Time, limit int) ([]MovementHistory, error) { + response := []MovementHistory{} request := make(map[string]interface{}) request["currency"] = symbol if len(method) > 0 { request["method"] = method } - if !timeSince.IsZero() { request["since"] = timeSince } - if !timeUntil.IsZero() { request["until"] = timeUntil } - if limit > 0 { request["limit"] = limit } - response := []BitfinexMovementHistory{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_HISTORY_MOVEMENTS, request, &response) - - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexHistoryMovements, request, &response) } -func (b *Bitfinex) GetTradeHistory(symbol string, timestamp, until time.Time, limit, reverse int) ([]BitfinexTradeHistory, error) { +// GetTradeHistory returns past executed trades +func (b *Bitfinex) GetTradeHistory(currencyPair string, timestamp, until time.Time, limit, reverse int) ([]TradeHistory, error) { + response := []TradeHistory{} request := make(map[string]interface{}) - request["currency"] = symbol + request["currency"] = currencyPair request["timestamp"] = timestamp if !until.IsZero() { request["until"] = until } - if limit > 0 { request["limit"] = limit } - if reverse > 0 { request["reverse"] = reverse } - response := []BitfinexTradeHistory{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_TRADE_HISTORY, request, &response) - - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexTradeHistory, request, &response) } -func (b *Bitfinex) NewOffer(symbol string, amount, rate float64, period int64, direction string) int64 { +// NewOffer submits a new offer +func (b *Bitfinex) NewOffer(symbol string, amount, rate float64, period int64, direction string) (Offer, error) { + response := Offer{} request := make(map[string]interface{}) request["currency"] = symbol request["amount"] = amount @@ -444,157 +484,93 @@ func (b *Bitfinex) NewOffer(symbol string, amount, rate float64, period int64, d request["period"] = period request["direction"] = direction - type OfferResponse struct { - Offer_Id int64 - } - - response := OfferResponse{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_OFFER_NEW, request, &response) - - if err != nil { - log.Println(err) - return 0 - } - - return response.Offer_Id + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOfferNew, request, &response) } -func (b *Bitfinex) CancelOffer(OfferID int64) (BitfinexOffer, error) { +// CancelOffer cancels offer by offerID +func (b *Bitfinex) CancelOffer(OfferID int64) (Offer, error) { + response := Offer{} request := make(map[string]interface{}) request["offer_id"] = OfferID - response := BitfinexOffer{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_OFFER_CANCEL, request, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOfferCancel, request, &response) } -func (b *Bitfinex) GetOfferStatus(OfferID int64) (BitfinexOffer, error) { +// GetOfferStatus checks offer status whether it has been cancelled, execute or +// is still active +func (b *Bitfinex) GetOfferStatus(OfferID int64) (Offer, error) { + response := Offer{} request := make(map[string]interface{}) request["offer_id"] = OfferID - response := BitfinexOffer{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_ORDER_STATUS, request, &response) - - if err != nil { - return response, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOrderStatus, request, &response) } -func (b *Bitfinex) GetActiveOffers() ([]BitfinexOffer, error) { - response := []BitfinexOffer{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_OFFERS, nil, &response) +// GetActiveCredits returns all available credits +func (b *Bitfinex) GetActiveCredits() ([]Offer, error) { + response := []Offer{} - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexActiveCredits, nil, &response) } -func (b *Bitfinex) GetActiveMarginFunding() ([]BitfinexMarginFunds, error) { - response := []BitfinexMarginFunds{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_MARGIN_ACTIVE_FUNDS, nil, &response) +// GetActiveOffers returns all current active offers +func (b *Bitfinex) GetActiveOffers() ([]Offer, error) { + response := []Offer{} - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexOffers, nil, &response) } -func (b *Bitfinex) GetMarginTotalTakenFunds() ([]BitfinexMarginTotalTakenFunds, error) { - response := []BitfinexMarginTotalTakenFunds{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_MARGIN_TOTAL_FUNDS, nil, &response) +// GetActiveMarginFunding returns an array of active margin funds +func (b *Bitfinex) GetActiveMarginFunding() ([]MarginFunds, error) { + response := []MarginFunds{} - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexMarginActiveFunds, nil, &response) } -func (b *Bitfinex) CloseMarginFunding(SwapID int64) (BitfinexOffer, error) { +// GetUnusedMarginFunds returns an array of funding borrowed but not currently +// used +func (b *Bitfinex) GetUnusedMarginFunds() ([]MarginFunds, error) { + response := []MarginFunds{} + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexMarginUnusedFunds, nil, &response) +} + +// GetMarginTotalTakenFunds returns an array of active funding used in a +// position +func (b *Bitfinex) GetMarginTotalTakenFunds() ([]MarginTotalTakenFunds, error) { + response := []MarginTotalTakenFunds{} + + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexMarginTotalFunds, nil, &response) +} + +// CloseMarginFunding closes an unused or used taken fund +func (b *Bitfinex) CloseMarginFunding(SwapID int64) (Offer, error) { + response := Offer{} request := make(map[string]interface{}) request["swap_id"] = SwapID - response := BitfinexOffer{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_MARGIN_CLOSE, request, &response) - - if err != nil { - return response, err - } - - return response, nil -} - -func (b *Bitfinex) GetAccountBalance() ([]BitfinexBalance, error) { - response := []BitfinexBalance{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_BALANCES, nil, &response) - if err != nil { - return nil, err - } - return response, nil -} - -func (b *Bitfinex) GetMarginInfo() ([]BitfinexMarginInfo, error) { - response := []BitfinexMarginInfo{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_MARGIN_INFO, nil, &response) - - if err != nil { - return nil, err - } - - return response, nil -} - -func (b *Bitfinex) WalletTransfer(amount float64, currency, walletFrom, walletTo string) ([]BitfinexWalletTransfer, error) { - request := make(map[string]interface{}) - request["amount"] = amount - request["currency"] = currency - request["walletfrom"] = walletFrom - request["walletTo"] = walletTo - - response := []BitfinexWalletTransfer{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_TRANSFER, request, &response) - - if err != nil { - return nil, err - } - - return response, nil -} - -func (b *Bitfinex) Withdrawal(withdrawType, wallet, address string, amount float64) ([]BitfinexWithdrawal, error) { - request := make(map[string]interface{}) - request["withdrawal_type"] = withdrawType - request["walletselected"] = wallet - request["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) - request["address"] = address - - response := []BitfinexWithdrawal{} - err := b.SendAuthenticatedHTTPRequest("POST", BITFINEX_WITHDRAWAL, request, &response) - - if err != nil { - return nil, err - } - - return response, nil + return response, + b.SendAuthenticatedHTTPRequest("POST", bitfinexMarginClose, request, &response) } +// 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") } + respErr := ErrorCapture{} request := make(map[string]interface{}) - request["request"] = fmt.Sprintf("/v%s/%s", BITFINEX_API_VERSION, path) + request["request"] = fmt.Sprintf("/v%s/%s", bitfinexAPIVersion, path) request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10) if params != nil { @@ -603,39 +579,43 @@ func (b *Bitfinex) SendAuthenticatedHTTPRequest(method, path string, params map[ } } - PayloadJson, err := common.JSONEncode(request) + PayloadJSON, err := common.JSONEncode(request) if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request") } if b.Verbose { - log.Printf("Request JSON: %s\n", PayloadJson) + log.Printf("Request JSON: %s\n", PayloadJSON) } - PayloadBase64 := common.Base64Encode(PayloadJson) - hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(b.APISecret)) + PayloadBase64 := common.Base64Encode(PayloadJSON) + 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 headers["X-BFX-SIGNATURE"] = common.HexEncodeToString(hmac) - resp, err := common.SendHTTPRequest(method, BITFINEX_API_URL+path, headers, strings.NewReader("")) + resp, err := common.SendHTTPRequest( + method, bitfinexAPIURL+path, headers, strings.NewReader(""), + ) if err != nil { return err } - if strings.Contains(resp, "message") { - return errors.New("SendAuthenticatedHTTPRequest: " + resp[11:]) - } - if b.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } - err = common.JSONDecode([]byte(resp), &result) - if err != nil { - return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON Unmarshal response.") + if err = common.JSONDecode([]byte(resp), &respErr); err == nil { + if len(respErr.Message) != 0 { + return errors.New("Responded Error Issue: " + respErr.Message) + } } + if err = common.JSONDecode([]byte(resp), &result); err != nil { + return errors.New("sendAuthenticatedHTTPRequest: Unable to JSON Unmarshal response") + } return nil } diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 8912c9ce..998bdd93 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -1,10 +1,8 @@ package bitfinex import ( - "fmt" "net/url" "reflect" - "strconv" "testing" "time" @@ -13,7 +11,11 @@ import ( "github.com/thrasher-/gocryptotrader/currency" ) -var ACCOUNT_LIVE_TEST bool = false //Supply correct API keys in testdata/configtest.dat before changing this. +// Please supply your own keys here to do better tests +const ( + testAPIKey = "" + testAPISecret = "" +) func TestSetDefaults(t *testing.T) { t.Parallel() @@ -21,18 +23,15 @@ func TestSetDefaults(t *testing.T) { setDefaults := Bitfinex{} setDefaults.SetDefaults() - if setDefaults.Name != "Bitfinex" || setDefaults.Enabled != false || setDefaults.Verbose != false || - setDefaults.Websocket != false || setDefaults.RESTPollingDelay != 10 { + if setDefaults.Name != "Bitfinex" || setDefaults.Enabled != false || + setDefaults.Verbose != false || setDefaults.Websocket != false || + setDefaults.RESTPollingDelay != 10 { t.Error("Test Failed - Bitfinex SetDefaults values not set correctly") } - if reflect.TypeOf(setDefaults.WebsocketSubdChannels).String() != "map[int]bitfinex.BitfinexWebsocketChanInfo" { - t.Error("Test Failed - Bitfinex SetDefaults value: MAP not set correctly") - } } func TestSetup(t *testing.T) { t.Parallel() - testConfig := config.ExchangeConfig{ Enabled: true, AuthenticatedAPISupport: true, @@ -52,386 +51,84 @@ func TestSetup(t *testing.T) { setup.APISecret != "cutlets" || setup.RESTPollingDelay != time.Duration(10) || !setup.Verbose || !setup.Websocket || len(setup.BaseCurrencies) < 1 || len(setup.AvailablePairs) < 1 || len(setup.EnabledPairs) < 1 { - t.Error("Test Failed - Bitfinex Setup values not set correctly") } + testConfig.Enabled = false + setup.Setup(testConfig) } -//Live Testing func TestGetTicker(t *testing.T) { - t.Parallel() bitfinex := Bitfinex{} - - response, err := bitfinex.GetTicker("BTCUSD", url.Values{}) + _, err := bitfinex.GetTicker("BTCUSD", url.Values{}) if err != nil { t.Error("BitfinexGetTicker init error: ", err) } - if reflect.ValueOf(response).NumField() != 8 { - t.Error("BitfinexGetTicker struct change/or updated") - } - if reflect.TypeOf(response.Timestamp).String() != "string" { - t.Error("Bitfinex ticker.Timestamp value is not a string variable") - } - if reflect.TypeOf(response.Ask).String() != "float64" { - t.Error("Bitfinex ticker.Ask value is not a float64 variable") - } - if reflect.TypeOf(response.Bid).String() != "float64" { - t.Error("Bitfinex ticker.Bid value is not a float64 variable") - } - if reflect.TypeOf(response.High).String() != "float64" { - t.Error("Bitfinex ticker.High value is not a float64 variable") - } - if reflect.TypeOf(response.Last).String() != "float64" { - t.Error("Bitfinex ticker.Last value is not a float64 variable") - } - if reflect.TypeOf(response.Low).String() != "float64" { - t.Error("Bitfinex ticker.Low value is not a float64 variable") - } - if reflect.TypeOf(response.Mid).String() != "float64" { - t.Error("Bitfinex ticker.Mid value is not a float64 variable") - } - if reflect.TypeOf(response.Volume).String() != "float64" { - t.Error("Bitfinex ticker.Volume value is not a float64 variable") - } - responseTimestamp, err := strconv.ParseFloat(response.Timestamp, 64) - if err != nil { - t.Error("ticker.Timestamp value cannot be converted to a float64") - } - if responseTimestamp <= 0 { - t.Error("ticker.Timestamp value is negative or 0") - } - if response.Ask < 0 { - t.Error("ticker.Ask value is negative") - } - if response.Bid < 0 { - t.Error("ticker.Bid value is negative") - } - if response.High < 0 { - t.Error("ticker.High value is negative") - } - if response.Last < 0 { - t.Error("ticker.Last value is negative") - } - if response.Low < 0 { - t.Error("ticker.Low value is negative") - } - if response.Mid < 0 { - t.Error("ticker.Mid value is negative") - } - if response.Volume < 0 { - t.Error("ticker.ask value is negative") + _, err = bitfinex.GetTicker("wigwham", url.Values{}) + if err == nil { + t.Error("Test Failed - GetTicker() error") } } -//Live Testing func TestGetStats(t *testing.T) { - t.Parallel() BitfinexGetStatsTest := Bitfinex{} - - response, err := BitfinexGetStatsTest.GetStats("BTCUSD") + _, err := BitfinexGetStatsTest.GetStats("BTCUSD") if err != nil { t.Error("BitfinexGetStatsTest init error: ", err) } - if reflect.ValueOf(response[0]).NumField() != 2 { - t.Error("BitfinexGetTicker []struct change/or updated") - } - if reflect.TypeOf(response[0].Period).String() != "int64" { - t.Error("Bitfinex Getstats.Period is not an int64") - } - if reflect.TypeOf(response[0].Volume).String() != "float64" { - t.Error("Bitfiniex Getstats.Volume is not a float64") - } - for _, explicitResponse := range response { - if explicitResponse.Period <= 0 { - t.Error("response.Period value is negative or zero") - } - if explicitResponse.Volume < 0 { - t.Error("response.Volume value is negative") - } + _, err = BitfinexGetStatsTest.GetStats("wigwham") + if err == nil { + t.Error("Test Failed - GetStats() error") } } -//Live Testing -func TestGetLendbook(t *testing.T) { - t.Parallel() - BitfinexGetLendbook := Bitfinex{} +func TestGetFundingBook(t *testing.T) { + b := Bitfinex{} + _, err := b.GetFundingBook("USD") + if err != nil { + t.Error("Testing Failed - GetFundingBook() error") + } + _, err = b.GetFundingBook("wigwham") + if err == nil { + t.Error("Testing Failed - GetFundingBook() error") + } +} - response, err := BitfinexGetLendbook.GetLendbook("BTCUSD", url.Values{}) +func TestGetLendbook(t *testing.T) { + BitfinexGetLendbook := Bitfinex{} + _, err := BitfinexGetLendbook.GetLendbook("BTCUSD", url.Values{}) if err != nil { t.Error("BitfinexGetLendbook init error: ", err) } - if reflect.ValueOf(response).NumField() != 2 { - t.Error("BitfinexGetLendbook struct change/or updated") - } - if reflect.ValueOf(response.Asks[0]).NumField() != 5 { - t.Error("BitfinexGetLendbook GetLendbook.Asks []struct change/or updated") - } - if reflect.TypeOf(response.Asks[0].Amount).String() != "float64" { - t.Error("Bitfinex GetLendbook.Asks.Amount is not a float64") - } - if reflect.TypeOf(response.Asks[0].FlashReturnRate).String() != "string" { - t.Error("Bitfinex GetLendbook.Asks.FlashReturnRate is not a string") - } - if reflect.TypeOf(response.Asks[0].Period).String() != "int" { - t.Error("Bitfinex GetLendbook.Asks.Period is not an int") - } - if reflect.TypeOf(response.Asks[0].Rate).String() != "float64" { - t.Error("Bitfinex GetLendbook.Asks.Rate is not a float64") - } - if reflect.ValueOf(response.Bids[0]).NumField() != 5 { - t.Error("BitfinexGetLendbook GetLendbook.Bids []struct change/or updated") - } - if reflect.TypeOf(response.Bids[0].Amount).String() != "float64" { - t.Error("Bitfinex GetLendbook.Bids.Amount is not a float64") - } - if reflect.TypeOf(response.Bids[0].FlashReturnRate).String() != "string" { - t.Error("Bitfinex GetLendbook.Bids.FlashReturnRate is not a string") - } - if reflect.TypeOf(response.Bids[0].Period).String() != "int" { - t.Error("Bitfinex GetLendbook.Bids.Period is not an int") - } - if reflect.TypeOf(response.Bids[0].Rate).String() != "float64" { - t.Error("Bitfinex GetLendbook.Bids.Rate is not a float64") - } - - for _, asks := range response.Asks { - responseTimestamp, err := strconv.ParseFloat(asks.Timestamp, 64) - if err != nil { - t.Error("Could not convert Bitfinex GetLendbook.Asks.Timestamp into float64") - } - if asks.Amount <= 0 { - t.Error("Bitfinex GetLendbook.Asks.Amount is negative or 0") - } - if asks.FlashReturnRate != "No" && asks.FlashReturnRate != "Yes" { - t.Error("Bitfinex GetLendbook.Bids.FlashReturnRate incorrect string") - } - if asks.Period <= 0 { - t.Error("Bitfinex GetLendbook.Asks.Period is negative or 0") - } - if asks.Rate <= 0 { - t.Error("Bitfinex GetLendbook.Asks.Rate is negative or 0") - } - if responseTimestamp <= 0 { - t.Error("Bitfinex GetLendbook.Asks.Timestamp is negative or 0") - } - } - - for _, bids := range response.Bids { - responseTimetamp, err := strconv.ParseFloat(bids.Timestamp, 64) - if err != nil { - t.Error("Could not convert Bitfinex GetLendbook.Bids.Timestamp into float64") - } - if bids.Amount <= 0 { - t.Error("Bitfinex GetLendbook.Bids.Amount is negative or 0") - } - if bids.FlashReturnRate == "no" || bids.FlashReturnRate == "yes" { - t.Error("Bitfinex GetLendbook.Bids.FlashReturnRate incorrect string") - } - if bids.Period <= 0 { - t.Error("Bitfinex GetLendbook.Bids.Period is negative or 0") - } - if bids.Rate <= 0 { - t.Error("Bitfinex GetLendbook.Bids.Rate is negative or 0") - } - if responseTimetamp <= 0 { - t.Error("Bitfinex GetLendbook.Bids.Timestamp is negative or 0") - } - } } -//Live Testing func TestGetOrderbook(t *testing.T) { - t.Parallel() BitfinexGetOrderbook := Bitfinex{} - - orderBook, err := BitfinexGetOrderbook.GetOrderbook("BTCUSD", url.Values{}) + _, err := BitfinexGetOrderbook.GetOrderbook("BTCUSD", url.Values{}) if err != nil { t.Error("BitfinexGetOrderbook init error: ", err) } - if reflect.ValueOf(orderBook).NumField() != 2 { - t.Error("BitfinexGetOrderbook struct change/or updated") - } - if reflect.ValueOf(orderBook.Asks[0]).NumField() != 3 { - t.Error("BitfinexGetOrderbook []struct change/or updated") - } - if reflect.ValueOf(orderBook.Bids[0]).NumField() != 3 { - t.Error("BitfinexGetOrderbook []struct change/or updated") - } - if reflect.TypeOf(orderBook.Asks[0].Amount).String() != "string" { - t.Error("Bitfinex GetOrderbook.Bids.Amount is not a string") - } - if reflect.TypeOf(orderBook.Asks[0].Price).String() != "string" { - t.Error("Bitfinex GetOrderbook.Bids.Amount is not a string") - } - if reflect.TypeOf(orderBook.Asks[0].Timestamp).String() != "string" { - t.Error("Bitfinex GetOrderbook.Bids.Amount is not a string") - } - if reflect.TypeOf(orderBook.Bids[0].Amount).String() != "string" { - t.Error("Bitfinex GetOrderbook.Bids.Amount is not a string") - } - if reflect.TypeOf(orderBook.Bids[0].Price).String() != "string" { - t.Error("Bitfinex GetOrderbook.Bids.Amount is not a string") - } - if reflect.TypeOf(orderBook.Bids[0].Timestamp).String() != "string" { - t.Error("Bitfinex GetOrderbook.Bids.Amount is not a string") - } - - for _, asks := range orderBook.Asks { - amount, err := strconv.ParseFloat(asks.Amount, 64) - if err != nil { - t.Error("Cannot convert Bitfinex Orderbook.Asks.Amount into a float64") - } - if amount < 0 { - t.Error("Bitfinex Orderbook.Asks.Amount is negative") - } - price, err2 := strconv.ParseFloat(asks.Price, 64) - if err2 != nil { - t.Error("Cannot convert Bitfinex Orderbook.Asks.Price into a float64") - } - if price < 0 { - t.Error("Bitfinex Orderbook.Asks.Price is negative") - } - timestamp, err3 := strconv.ParseFloat(asks.Timestamp, 64) - if err3 != nil { - t.Error("Cannot convert Bitfinex Orderbook.Asks.timestamp into a float64") - } - if timestamp <= 0 { - t.Error("Bitfinex Orderbook.Asks.Amount is negative or 0") - } - } - - for _, bids := range orderBook.Bids { - amount, err := strconv.ParseFloat(bids.Amount, 64) - if err != nil { - t.Error("Cannot convert Bitfinex Orderbook.bids.Amount into a float64") - } - if amount < 0 { - t.Error("Bitfinex Orderbook.bids.Amount is negative") - } - price, err2 := strconv.ParseFloat(bids.Price, 64) - if err2 != nil { - t.Error("Cannot convert Bitfinex Orderbook.bids.Price into a float64") - } - if price < 0 { - t.Error("Bitfinex Orderbook.bids.Price is negative") - } - timestamp, err3 := strconv.ParseFloat(bids.Timestamp, 64) - if err3 != nil { - t.Error("Cannot convert Bitfinex Orderbook.bids.timestamp into a float64") - } - if timestamp <= 0 { - t.Error("Bitfinex Orderbook.bids.Amount is negative or 0") - } - } } -//Live Testing func TestGetTrades(t *testing.T) { - t.Parallel() BitfinexGetTrades := Bitfinex{} - - trades, err := BitfinexGetTrades.GetTrades("BTCUSD", url.Values{}) + _, err := BitfinexGetTrades.GetTrades("BTCUSD", url.Values{}) if err != nil { t.Error("BitfinexGetTrades init error: ", err) } - if reflect.ValueOf(trades[0]).NumField() != 6 { - t.Error("BitfinexGetTrades struct change/or updated") - } - if reflect.TypeOf(trades[0].Amount).String() != "string" { - t.Error("Bitfinex GetGetTrades.Amount is not a string") - } - if reflect.TypeOf(trades[0].Exchange).String() != "string" { - t.Error("Bitfinex GetGetTrades.Exchange is not a string") - } - if reflect.TypeOf(trades[0].Price).String() != "string" { - t.Error("Bitfinex GetGetTrades.Price is not a string") - } - if reflect.TypeOf(trades[0].Tid).String() != "int64" { - t.Error("Bitfinex GetGetTrades.Tid is not a int64") - } - if reflect.TypeOf(trades[0].Timestamp).String() != "int64" { - t.Error("Bitfinex GetGetTrades.Timestamp is not a int64") - } - if reflect.TypeOf(trades[0].Type).String() != "string" { - t.Error("Bitfinex GetGetTrades.Type is not a string") - } - - for _, explicitTrades := range trades { - amount, err := strconv.ParseFloat(explicitTrades.Amount, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetTrades.Amount into a float64") - } - if amount <= 0 { - t.Error("Bitfinex GetTrades.Amount is negative or 0") - } - if explicitTrades.Exchange != "bitfinex" { - t.Error("Bitfinex GetTrades.Exchange incorrect name") - } - price, err2 := strconv.ParseFloat(explicitTrades.Price, 64) - if err2 != nil { - t.Error("Cannot convert Bitfinex GetTrades.Price into a float64") - } - if price <= 0 { - t.Error("Bitfinex GetTrades.Price is negative or 0") - } - if explicitTrades.Tid <= 0 { - t.Error("Bitfinex GetTrades.Tid is negative or 0") - } - if explicitTrades.Timestamp <= 0 { - t.Error("Bitfinex GetTrades.Timestamp is negative or 0") - } - if explicitTrades.Type != "buy" && explicitTrades.Type != "sell" { - t.Error("Bitfinex GetTrades.Type is wrong") - } - } } -//Live Testing func TestGetLends(t *testing.T) { - t.Parallel() BitfinexGetLends := Bitfinex{} - lends, err := BitfinexGetLends.GetLends("BTC", url.Values{}) + _, err := BitfinexGetLends.GetLends("BTC", url.Values{}) if err != nil { t.Error("BitfinexGetLends init error: ", err) } - if reflect.ValueOf(lends[0]).NumField() != 4 { - t.Error("BitfinexGetLends struct change/or updated") - } - if reflect.TypeOf(lends[0].AmountLent).String() != "float64" { - t.Error("Bitfinex GetGetLends.AmountLent is not a float64") - } - if reflect.TypeOf(lends[0].AmountUsed).String() != "float64" { - t.Error("Bitfinex GetGetLends.AmountUsed is not a float64") - } - if reflect.TypeOf(lends[0].Rate).String() != "float64" { - t.Error("Bitfinex GetGetLends.Rate is not a float64") - } - if reflect.TypeOf(lends[0].Timestamp).String() != "int64" { - t.Error("Bitfinex GetGetLends.Timestamp is not a int64") - } - - for _, explicitLends := range lends { - if explicitLends.AmountLent <= 0 { - t.Error("Bitfinex GetLends.AmountLent is negative or 0") - } - if explicitLends.AmountUsed <= 0 { - t.Error("Bitfinex GetLends.AmountUsed is negative or 0") - } - if explicitLends.Rate <= 0 { - t.Error("Bitfinex GetLends.Rate is negative or 0") - } - if explicitLends.Timestamp <= 0 { - t.Error("Bitfinex GetLends.Timestamp is negative or 0") - } - } } -//Live Testing func TestGetSymbols(t *testing.T) { - t.Parallel() BitfinexGetSymbols := Bitfinex{} symbols, err := BitfinexGetSymbols.GetSymbols() @@ -479,1087 +176,361 @@ func TestGetSymbols(t *testing.T) { } } -//Live Testing func TestGetSymbolsDetails(t *testing.T) { - t.Parallel() BitfinexGetSymbolsDetails := Bitfinex{} - - symbolDetails, err := BitfinexGetSymbolsDetails.GetSymbolsDetails() + _, err := BitfinexGetSymbolsDetails.GetSymbolsDetails() if err != nil { t.Error("BitfinexGetSymbolsDetails init error: ", err) } - if reflect.ValueOf(symbolDetails[0]).NumField() != 7 { - t.Error("BitfinexGetSymbolsDetails struct change/or updated") - } - if reflect.TypeOf(symbolDetails[0].Expiration).String() != "string" { - t.Error("Bitfinex GetSymbolsDetails.Expiration is not a string") - } - if reflect.TypeOf(symbolDetails[0].InitialMargin).String() != "float64" { - t.Error("Bitfinex GetSymbolsDetails.InitialMargin is not a float64") - } - if reflect.TypeOf(symbolDetails[0].MaximumOrderSize).String() != "float64" { - t.Error("Bitfinex GetSymbolsDetails.MaximumOrderSize is not a float64") - } - if reflect.TypeOf(symbolDetails[0].MinimumMargin).String() != "float64" { - t.Error("Bitfinex GetSymbolsDetails.MinimumMargin is not a float64") - } - if reflect.TypeOf(symbolDetails[0].MinimumOrderSize).String() != "float64" { - t.Error("Bitfinex GetSymbolsDetails.MinimumOrderSize is not a float64") - } - if reflect.TypeOf(symbolDetails[0].Pair).String() != "string" { - t.Error("Bitfinex GetSymbolsDetails.Pair is not a string") - } - if reflect.TypeOf(symbolDetails[0].PricePrecision).String() != "int" { - t.Error("Bitfinex GetSymbolsDetails.PricePrecision is not a int") - } - - for _, explicitDetails := range symbolDetails { - if explicitDetails.Expiration != "NA" { - expiration, err := strconv.ParseFloat(explicitDetails.Expiration, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetSymbolsDetails.Expiration into a float64") - } - if expiration < 0 { - t.Error("Bitfinex GetSymbolsDetails.Expiration is negative") - } - } - if explicitDetails.InitialMargin <= 0 { - t.Error("Bitfinex GetSymbolsDetails.InitialMargin is negative or 0") - } - if explicitDetails.MaximumOrderSize <= 0 { - t.Error("Bitfinex GetSymbolsDetails.MaximumOrderSize is negative or 0") - } - if explicitDetails.MinimumMargin <= 0 { - t.Error("Bitfinex GetSymbolsDetails.MinimumMargin is negative or 0") - } - if explicitDetails.MinimumOrderSize <= 0 { - t.Error("Bitfinex GetSymbolsDetails.MinimumOrderSize is negative or 0") - } - if len(explicitDetails.Pair) != 6 { - t.Error("Bitfinex GetSymbolsDetails.Pair incorrect length") - } - if explicitDetails.PricePrecision <= 0 { - t.Error("Bitfinex GetSymbolsDetails.PricePrecision is negative or 0") - } - } } -//Hybrid Testing func TestGetAccountInfo(t *testing.T) { - expectedCryptoCurrencies := []string{ - "BTC", - "LTC", - "ETH", - "ETC", - "ZEC", - "XMR", - "DSH", - } + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret - if ACCOUNT_LIVE_TEST { //Live Test - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - - BitfinexGetAccountInfo := Bitfinex{} - BitfinexGetAccountInfo.Setup(exchangeConfig) - - response, err := BitfinexGetAccountInfo.GetAccountInfo() - if err != nil { - newErrString := fmt.Sprintf("TestGetAccountInfo: \nError: %s\n", err) - t.Error(newErrString) - response = append(response, BitfinexAccountInfo{}) - } - - if reflect.ValueOf(response[0]).NumField() != 3 { - t.Error("BitfinexGetAccountInfo struct change/or updated") - } - if reflect.TypeOf(response[0].MakerFees).String() != "string" { - t.Error("Bitfinex GetAccountInfo.MakerFees is not a string") - } - if reflect.TypeOf(response[0].TakerFees).String() != "string" { - t.Error("Bitfinex GetAccountInfo.TakerFees is not a string") - } - - if len(expectedCryptoCurrencies) == len(response[0].Fees) { - if !common.DataContains(expectedCryptoCurrencies, response[0].Fees[0].Pairs) { - t.Error("Bitfinex GetAccountInfo currency mismatch") - } - } else if len(expectedCryptoCurrencies) > len(response[0].Fees) { - t.Error("BitfinexGetSymbols currency mismatch, Expected Currencies > Exchange Currencies") - } else { - t.Error("BitfinexGetSymbols currency mismatch, Expected Currencies < Exchange Currencies") - } - - if len(response[0].Fees) != 7 { - t.Error("Bitfinex GetAccountInfo.Fees incorrect length") - } - - for _, explicitAI := range response { - makerFees, err := strconv.ParseFloat(explicitAI.MakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.MakerFees into float64") - } - if makerFees < 0 { - t.Error("Bitfinex GetAccountInfo.MakerFees is negative") - } - - takerFees, err := strconv.ParseFloat(explicitAI.TakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.TakerFees into float64") - } - if takerFees < 0 { - t.Error("Bitfinex GetAccountInfo.TakerFees is negative") - } - - for _, fees := range explicitAI.Fees { - MakerFees, err := strconv.ParseFloat(fees.MakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.Fees.MakerFees into float64") - } - if MakerFees < 0 { - t.Error("Bitfinex GetAccountInfo.Fees.MakerFees is negative") - } - TakerFees, err := strconv.ParseFloat(fees.TakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.Fees.TakerFees into float64") - } - if TakerFees < 0 { - t.Error("Bitfinex GetAccountInfo.Fees.TakerFees is negative") - } - } - } - - } else { //Non-Live Test - type Fees struct { - Pairs string `json:"pairs"` - MakerFees string `json:"maker_fees"` - TakerFees string `json:"taker_fees"` - } - accountInfoNonLive := [1]BitfinexAccountInfo{} - accountInfoNonLive[0].MakerFees = "0.1" - accountInfoNonLive[0].TakerFees = "0.2" - nonLiveFees := Fees{} - nonLiveFees.MakerFees = "0.1" - nonLiveFees.Pairs = "BTC" - nonLiveFees.TakerFees = "0.2" - accountInfoNonLive[0].Fees = append(accountInfoNonLive[0].Fees, nonLiveFees) - - if reflect.ValueOf(accountInfoNonLive[0]).NumField() != 3 { - t.Error("BitfinexGetAccountInfo struct change/or updated") - } - if reflect.TypeOf(accountInfoNonLive[0].MakerFees).String() != "string" { - t.Error("Bitfinex GetAccountInfo.MakerFees is not a string") - } - if reflect.TypeOf(accountInfoNonLive[0].TakerFees).String() != "string" { - t.Error("Bitfinex GetAccountInfo.TakerFees is not a string") - } - - for _, explicitAI := range accountInfoNonLive { - makerFees, err := strconv.ParseFloat(explicitAI.MakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.MakerFees into float64") - } - if makerFees < 0 { - t.Error("Bitfinex GetAccountInfo.MakerFees is negative") - } - - takerFees, err := strconv.ParseFloat(explicitAI.TakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.TakerFees into float64") - } - if takerFees < 0 { - t.Error("Bitfinex GetAccountInfo.TakerFees is negative") - } - if len(explicitAI.Fees) != 1 { - t.Error("Bitfinex GetAccountInfo.Fees.Pairs incorrect length") - } - - for _, fees := range explicitAI.Fees { - MakerFees, err := strconv.ParseFloat(fees.MakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.Fees.MakerFees into float64") - } - if MakerFees < 0 { - t.Error("Bitfinex GetAccountInfo.Fees.MakerFees is negative") - } - TakerFees, err := strconv.ParseFloat(fees.TakerFees, 64) - if err != nil { - t.Error("Cannot convert Bitfinex GetAccountInfo.Fees.TakerFees into float64") - } - if TakerFees < 0 { - t.Error("Bitfinex GetAccountInfo.Fees.TakerFees is negative") - } - } - } + _, err := b.GetAccountInfo() + if err == nil { + t.Error("Test Failed - GetAccountInfo error") + } +} + +func TestGetAccountFees(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetAccountFees() + if err == nil { + t.Error("Test Failed - GetAccountFees error") + } +} + +func TestGetAccountSummary(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetAccountSummary() + if err == nil { + t.Error("Test Failed - GetAccountSummary() error:") } } -//Hybrid Testing func TestNewDeposit(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret - applicableMethods := []string{ - "bitcoin_address", - "litecoin_address", - "ethereum_address", - "mastercoin_address", //Requires verified account - "ethereumc_address", - "zcash_address", - "monero_address", - } - expectedCryptoCurrencies := []string{ - "BTC", - "LTC", - "ETH", - "ETC", - "ZEC", - "XMR", - "DSH", - } - - if ACCOUNT_LIVE_TEST { //Live Test - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - - BitfinexNewDeposit := Bitfinex{} - BitfinexNewDeposit.Setup(exchangeConfig) - - liveResponse, err := BitfinexNewDeposit.NewDeposit("bitcoin", "deposit", 0) - if err != nil { - t.Error("BitfinexNewDeposit init error: ", err) - } - - if reflect.ValueOf(liveResponse).NumField() != 4 { - t.Error("BitfinexNewDeposit struct change/or updated") - } - if reflect.TypeOf(liveResponse.Address).String() != "string" { - t.Error("Bitfinex NewDeposit.Address is not a string") - } - if reflect.TypeOf(liveResponse.Currency).String() != "string" { - t.Error("Bitfinex NewDeposit.Currency is not a string") - } - if reflect.TypeOf(liveResponse.Method).String() != "string" { - t.Error("Bitfinex NewDeposit.Method) is not a string") - } - if reflect.TypeOf(liveResponse.Result).String() != "string" { - t.Error("Bitfinex NewDeposit.Result is not a string") - } - if len(liveResponse.Address) != 34 { - t.Error("Bitfinex NewDeposit.Address is incorrect") - } - if !common.DataContains(expectedCryptoCurrencies, liveResponse.Currency) { - t.Error("Bitfinex NewDeposit.Currency currency mismatch" + liveResponse.Currency) - } - if !common.DataContains(applicableMethods, liveResponse.Method) { - t.Error("Bitfinex NewDeposit.Method method mismatch") - } - if liveResponse.Result != "" && liveResponse.Result != "success" { - t.Error("Bitfinex NewDeposit.Result " + liveResponse.Result) - } - - } else { //Non-Live Test - nonLiveResponse := BitfinexDepositResponse{} - nonLiveResponse.Address = "1DPUgBaZoKbL38BEC1A3exPKCDZjQpnBa1" - nonLiveResponse.Currency = "BTC" - nonLiveResponse.Method = "bitcoin_address" - nonLiveResponse.Result = "" - - if reflect.ValueOf(nonLiveResponse).NumField() != 4 { - t.Error("BitfinexNewDeposit struct change/or updated") - } - if reflect.TypeOf(nonLiveResponse.Address).String() != "string" { - t.Error("Bitfinex NewDeposit.Address is not a string") - } - if reflect.TypeOf(nonLiveResponse.Currency).String() != "string" { - t.Error("Bitfinex NewDeposit.Currency is not a string") - } - if reflect.TypeOf(nonLiveResponse.Method).String() != "string" { - t.Error("Bitfinex NewDeposit.Method) is not a string") - } - if reflect.TypeOf(nonLiveResponse.Result).String() != "string" { - t.Error("Bitfinex NewDeposit.Result is not a string") - } - - if len(nonLiveResponse.Address) != 34 { - t.Error("Bitfinex NewDeposit.Address is incorrect") - } - if !common.DataContains(expectedCryptoCurrencies, nonLiveResponse.Currency) { - t.Error("Bitfinex NewDeposit.Currency currency mismatch") - } - if !common.DataContains(applicableMethods, nonLiveResponse.Method) { - t.Error("Bitfinex NewDeposit.Method method mismatch") - } - if nonLiveResponse.Result != "" && nonLiveResponse.Result != "success" { - t.Error("Bitfinex NewDeposit.Result " + nonLiveResponse.Result) - } + _, err := b.NewDeposit("blabla", "testwallet", 1) + if err == nil { + t.Error("Test Failed - NewDeposit() error:", err) } } -//Non-Live Testing -func TestNewOrder(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - - BitfinexNewOrder := Bitfinex{} - BitfinexNewOrder.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - response, errLive := BitfinexNewOrder.NewOrder("BTCUSD", 0, 0, true, "test", false) - if errLive == nil { - t.Errorf("Test Failed - BitfinexNewOrder - Error: Expected Error Status: %t", response.IsLive) - } - } - - nonLiveResponse := BitfinexOrder{} - nonLiveResponse.AverageExecutionPrice = 0.0 - nonLiveResponse.Exchange = "bitfinex" - nonLiveResponse.ExecutedAmount = 0.0 - nonLiveResponse.ID = 448364249 - nonLiveResponse.IsCancelled = false - nonLiveResponse.IsHidden = false - nonLiveResponse.IsLive = true - nonLiveResponse.OrderID = 448364249 - nonLiveResponse.OriginalAmount = 0.01 - nonLiveResponse.Price = 0.01 - nonLiveResponse.RemainingAmount = 0.01 - nonLiveResponse.Side = "buy" - nonLiveResponse.Symbol = "btcusd" - nonLiveResponse.Timestamp = "1444272165.252370982" - nonLiveResponse.Type = "exchange limit" - nonLiveResponse.WasForced = false - - if reflect.ValueOf(nonLiveResponse).NumField() != 16 { - t.Error("BitfinexNewDeposit struct change/or updated") - } - if reflect.TypeOf(nonLiveResponse.AverageExecutionPrice).String() != "float64" { - t.Error("Bitfinex NewOrder.AverageExecutionPrice is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Exchange).String() != "string" { - t.Error("Bitfinex NewOrder.Exchange is not a string") - } - if reflect.TypeOf(nonLiveResponse.ExecutedAmount).String() != "float64" { - t.Error("Bitfinex NewOrder.ExecutedAmount is not a float64") - } - if reflect.TypeOf(nonLiveResponse.OrderID).String() != "int64" { - t.Error("Bitfinex NewOrder.ID is not an int64") - } - if reflect.TypeOf(nonLiveResponse.IsCancelled).String() != "bool" { - t.Error("Bitfinex NewOrder.IsCancelled is not a bool") - } - if reflect.TypeOf(nonLiveResponse.IsHidden).String() != "bool" { - t.Error("Bitfinex NewOrder.IsHidden is not a bool") - } - if reflect.TypeOf(nonLiveResponse.IsLive).String() != "bool" { - t.Error("Bitfinex NewOrder.IsLive is not a bool") - } - if reflect.TypeOf(nonLiveResponse.OrderID).String() != "int64" { - t.Error("Bitfinex NewOrder.OrderID is not an int64") - } - if reflect.TypeOf(nonLiveResponse.OriginalAmount).String() != "float64" { - t.Error("Bitfinex NewOrder.OriginalAmount is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Price).String() != "float64" { - t.Error("Bitfinex NewOrder.Price is not a float64") - } - if reflect.TypeOf(nonLiveResponse.RemainingAmount).String() != "float64" { - t.Error("Bitfinex NewOrder.RemainingAmount is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Side).String() != "string" { - t.Error("Bitfinex NewOrder.Side is not a string") - } - if reflect.TypeOf(nonLiveResponse.Symbol).String() != "string" { - t.Error("Bitfinex NewOrder.Address is not a string") - } - if reflect.TypeOf(nonLiveResponse.Timestamp).String() != "string" { - t.Error("Bitfinex NewOrder.Timestamp is not a string") - } - if reflect.TypeOf(nonLiveResponse.Type).String() != "string" { - t.Error("Bitfinex NewOrder.Type is not a string") - } - if reflect.TypeOf(nonLiveResponse.WasForced).String() != "bool" { - t.Error("Bitfinex NewOrder.WasForced is not a bool") - } - - if nonLiveResponse.AverageExecutionPrice < 0 { - t.Error("Bitfinex NewOrder.AverageExecutionPrice is negative") - } - if nonLiveResponse.Exchange != "bitfinex" { - t.Error("Bitfinex NewOrder.AverageExecutionPrice wrong exchange name") - } - if nonLiveResponse.ExecutedAmount < 0 { - t.Error("Bitfinex NewOrder.ExecutedAmount is negative or 0") - } - if nonLiveResponse.ID <= 0 { - t.Error("Bitfinex NewOrder.ID is negative or 0") - } - if nonLiveResponse.OrderID <= 0 { - t.Error("Bitfinex NewOrder.OrderID is negative or 0") - } - if nonLiveResponse.OriginalAmount <= 0 { - t.Error("Bitfinex NewOrder.OriginalAmount is negative or 0") - } - if nonLiveResponse.Price <= 0 { - t.Error("Bitfinex NewOrder.Price is negative or 0") - } - if nonLiveResponse.RemainingAmount <= 0 { - t.Error("Bitfinex NewOrder.RemainingAmount is negative or 0") - } - nonLiveTimestamp, err := strconv.ParseFloat(nonLiveResponse.Timestamp, 64) - if err != nil { - t.Error("Bitfinex NewOrder.Timestamp cannot convert to float64") - } - if nonLiveTimestamp <= 0 { - t.Error("Bitfinex NewOrder.Timestamp is negative or 0") - } -} - -//Non-Live Testing -func TestNewOrderMulti(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - New Order init error: %s\n", err) - } - - BitfinexNewOrderMulti := Bitfinex{} - BitfinexNewOrderMulti.Setup(exchangeConfig) - - var orders []BitfinexPlaceOrder - order := BitfinexPlaceOrder{} - order.Amount = 0.0 - order.Exchange = "bitfinex" - order.Price = 0.0 - order.Side = "test" - order.Symbol = "BTCUSD" - order.Type = "test" - orders = append(orders, order) - - if ACCOUNT_LIVE_TEST { - response, err := BitfinexNewOrderMulti.NewOrderMulti(orders) - if err == nil { - newErrString := fmt.Sprintf("BitfinexNewOrderMulti - Error: Expected Error Status: %s\n", response.Status) - t.Error(newErrString) - } - } - - nonLiveResponse := BitfinexOrderMultiResponse{} - nonLiveResponse.Status = "success" - - orderTest := BitfinexOrder{} - orderTest.AverageExecutionPrice = 0.0 - orderTest.Exchange = "bitfinex" - orderTest.ExecutedAmount = 0.0 - orderTest.ID = 448364249 - orderTest.IsCancelled = false - orderTest.IsHidden = false - orderTest.IsLive = true - orderTest.OrderID = 448364249 - orderTest.OriginalAmount = 0.01 - orderTest.Price = 0.01 - orderTest.RemainingAmount = 0.01 - orderTest.Side = "buy" - orderTest.Symbol = "btcusd" - orderTest.Timestamp = "1444272165.252370982" - orderTest.Type = "exchange limit" - orderTest.WasForced = false - - nonLiveResponse.Orders = append(nonLiveResponse.Orders, orderTest) - - if reflect.ValueOf(nonLiveResponse).NumField() != 2 { - t.Error("Bitfinex NewOrderMulti struct change/or updated") - } - if reflect.TypeOf(nonLiveResponse.Status).String() != "string" { - t.Error("Bitfinex NewOrderMulti.Status is not a string") - } - if reflect.ValueOf(nonLiveResponse.Orders[0]).NumField() != 16 { - t.Error("Bitfinex NewOrderMulti struct change/or updated") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].AverageExecutionPrice).String() != "float64" { - t.Error("Bitfinex NewOrderMulti.AverageExecutionPrice is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].Exchange).String() != "string" { - t.Error("Bitfinex NewOrderMulti.Exchange is not a string") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].ExecutedAmount).String() != "float64" { - t.Error("Bitfinex NewOrderMulti.ExecutedAmount is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].OrderID).String() != "int64" { - t.Error("Bitfinex NewOrderMulti.ID is not an int64") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].IsCancelled).String() != "bool" { - t.Error("Bitfinex NewOrderMulti.IsCancelled is not a bool") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].IsHidden).String() != "bool" { - t.Error("Bitfinex NewOrderMulti.IsHidden is not a bool") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].IsLive).String() != "bool" { - t.Error("Bitfinex NewOrderMulti.IsLive is not a bool") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].OrderID).String() != "int64" { - t.Error("Bitfinex NewOrderMulti.OrderID is not an int64") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].OriginalAmount).String() != "float64" { - t.Error("Bitfinex NewOrderMulti.OriginalAmount is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].Price).String() != "float64" { - t.Error("Bitfinex NewOrderMulti.Price is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].RemainingAmount).String() != "float64" { - t.Error("Bitfinex NewOrderMulti.RemainingAmount is not a float64") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].Side).String() != "string" { - t.Error("Bitfinex NewOrderMulti.Side is not a string") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].Symbol).String() != "string" { - t.Error("Bitfinex NewOrderMulti.Address is not a string") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].Timestamp).String() != "string" { - t.Error("Bitfinex NewOrderMulti.Timestamp is not a string") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].Type).String() != "string" { - t.Error("Bitfinex NewOrderMulti.Type is not a string") - } - if reflect.TypeOf(nonLiveResponse.Orders[0].WasForced).String() != "bool" { - t.Error("Bitfinex NewOrderMulti.WasForced is not a bool") - } -} - -func TestCancelOrder(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex CancelOrder init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex CancelOrder init error: %s\n", err) - } - - BitfinexCancelOrder := Bitfinex{} - BitfinexCancelOrder.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexCancelOrder.CancelOrder(1337) - if err == nil { - t.Errorf("Test Failed - Bitfinex CancelOrder - Error: %s", err) - } - } -} - -func TestCancelMultipleOrders(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex CancelMultipleOrders init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex CancelMultipleOrders init error: %s\n", err) - } - - BitfinexMultipleOrders := Bitfinex{} - BitfinexMultipleOrders.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - orders := []int64{1336, 1337} - response, err := BitfinexMultipleOrders.CancelMultipleOrders(orders) - if err != nil || response != "" { - t.Errorf("Test Failed - Bitfinex CancelMultipleOrders - Error: %s", err) - } - } -} - -func TestCancelAllOrders(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex CancelAllOrders init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex CancelAllOrders init error: %s\n", err) - } - - BitfinexCancelAllOrders := Bitfinex{} - BitfinexCancelAllOrders.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - response, err := BitfinexCancelAllOrders.CancelAllOrders() - if err != nil || response != "" { - t.Errorf("Test Failed - Bitfinex CancelAllOrders - Error: %s", err) - } - } -} - -func TestReplaceOrder(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex ReplaceOrder init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex ReplaceOrder init error: %s\n", err) - } - - BitfinexReplaceOrder := Bitfinex{} - BitfinexReplaceOrder.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexReplaceOrder.ReplaceOrder(1337, "BTC", 0, 0, true, "exchange limit", false) - if err == nil { - t.Error("Test Failed - Bitfinex ReplaceOrder - Expected Error") - } - } -} - -func TestGetOrderStatus(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetOrderStatus init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetOrderStatus init error: %s\n", err) - } - - BitfinexGetOrderStatus := Bitfinex{} - BitfinexGetOrderStatus.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetOrderStatus.GetOrderStatus(1337) - if err == nil { - t.Error("Test Failed - Bitfinex GetOrderStatus - Expected Error") - } - } -} - -func TestGetActiveOrders(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActiveOrders init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActiveOrders init error: %s\n", err) - } - - BitfinexGetActiveOrders := Bitfinex{} - BitfinexGetActiveOrders.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetActiveOrders.GetActiveOrders() - if err != nil { - t.Error("Test Failed - Bitfinex GetActiveOrders - Expected Error") - } - } -} - -func TestGetActivePositions(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActivePositions init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActivePositions init error: %s\n", err) - } - - BitfinexGetActivePositions := Bitfinex{} - BitfinexGetActivePositions.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetActivePositions.GetActivePositions() - if err != nil { - t.Error("Test Failed - Bitfinex GetActivePositions - Expected Error") - } - } -} - -func TestClaimPosition(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex ClaimPosition init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex ClaimPosition init error: %s\n", err) - } - - BitfinexClaimPosition := Bitfinex{} - BitfinexClaimPosition.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexClaimPosition.ClaimPosition(1337) - if err == nil { - t.Error("Test Failed - Bitfinex ClaimPosition - Expected Error") - } - } -} - -func TestGetBalanceHistory(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetBalanceHistory init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetBalanceHistory init error: %s\n", err) - } - - BitfinexGetBalanceHistory := Bitfinex{} - BitfinexGetBalanceHistory.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetBalanceHistory.GetBalanceHistory("BTC", time.Now(), time.Now(), 1, "testWallet") - if err == nil { - t.Error("Test Failed - Bitfinex GetBalanceHistory - Expected Error") - } - } -} - -func TestGetMovementHistory(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetMovementHistory init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetMovementHistory init error: %s\n", err) - } - - BitfinexGetMovementHistory := Bitfinex{} - BitfinexGetMovementHistory.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetMovementHistory.GetMovementHistory("BTC", "BITCOIN", time.Now(), time.Now(), 1) - if err == nil { - t.Error("Test Failed - Bitfinex GetMovementHistory - Expected Error") - } - } -} - -func TestGetTradeHistory(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetTradeHistory init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetTradeHistory init error: %s\n", err) - } - - BitfinexGetTradeHistory := Bitfinex{} - BitfinexGetTradeHistory.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetTradeHistory.GetTradeHistory("BTC", time.Now(), time.Now(), 1, 0) - if err == nil { - t.Error("Test Failed - Bitfinex GetTradeHistory - Expected Error") - } - } -} - -func TestNewOffer(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex NewOffer init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex NewOffer init error: %s\n", err) - } - - BitfinexNewOffer := Bitfinex{} - BitfinexNewOffer.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - response := BitfinexNewOffer.NewOffer("BTC", 1, 2, 2, "buy") - if response != 0 { - t.Error("Test Failed - Bitfinex NewOffer - Expected Error") - } - } -} - -func TestGetOfferStatus(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetOfferStatus init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetOfferStatus init error: %s\n", err) - } - - BitfinexGetOfferStatus := Bitfinex{} - BitfinexGetOfferStatus.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetOfferStatus.GetOfferStatus(1337) - if err == nil { - t.Error("Test Failed - Bitfinex GetOfferStatus - Expected Error") - } - } -} - -func TestGetActiveOffers(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActiveOffers init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActiveOffers init error: %s\n", err) - } - - BitfinexGetActiveOffers := Bitfinex{} - BitfinexGetActiveOffers.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetActiveOffers.GetActiveOffers() - if err != nil { - t.Error("Test Failed - Bitfinex GetActiveOffers - Expected Error") - } - } -} - -func TestGetActiveMarginFunding(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActiveMarginFunding init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetActiveMarginFunding init error: %s\n", err) - } - - BitfinexGetActiveMarginFunding := Bitfinex{} - BitfinexGetActiveMarginFunding.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetActiveMarginFunding.GetActiveMarginFunding() - if err != nil { - t.Error("Test Failed - Bitfinex GetActiveMarginFunding - Expected Error") - } - } -} - -func TestGetMarginTotalTakenFunds(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetMarginTotalTakenFunds init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetMarginTotalTakenFunds init error: %s\n", err) - } - - BitfinexGetMarginTotalTakenFunds := Bitfinex{} - BitfinexGetMarginTotalTakenFunds.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetMarginTotalTakenFunds.GetMarginTotalTakenFunds() - if err != nil { - t.Error("Test Failed - Bitfinex GetMarginTotalTakenFunds - Expected Error") - } - } -} - -func TestCloseMarginFunding(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex CloseMarginFunding init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex CloseMarginFunding init error: %s\n", err) - } - - BitfinexCloseMarginFunding := Bitfinex{} - BitfinexCloseMarginFunding.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexCloseMarginFunding.CloseMarginFunding(1337) - if err == nil { - t.Error("Test Failed - Bitfinex CloseMarginFunding - Expected Error") - } - } -} - -func TestGetAccountBalance(t *testing.T) { - newConfig := config.Config{} - - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetAccountBalance init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetAccountBalance init error: %s\n", err) - } - - BitfinexGetAccountBalance := Bitfinex{} - BitfinexGetAccountBalance.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetAccountBalance.GetAccountBalance() - if err != nil { - t.Error("Test Failed - Bitfinex GetAccountBalance - Expected Error") - } +func TestGetKeyPermissions(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetKeyPermissions() + if err == nil { + t.Error("Test Failed - GetKeyPermissions() error:") } } func TestGetMarginInfo(t *testing.T) { - newConfig := config.Config{} + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetMarginInfo init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex GetMarginInfo init error: %s\n", err) + _, err := b.GetMarginInfo() + if err == nil { + t.Error("Test Failed - GetMarginInfo() error") } +} - BitfinexGetMarginInfo := Bitfinex{} - BitfinexGetMarginInfo.Setup(exchangeConfig) +func TestGetAccountBalance(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret - if ACCOUNT_LIVE_TEST { - _, err := BitfinexGetMarginInfo.GetMarginInfo() - if err == nil { - t.Error("Test Failed - Bitfinex GetMarginInfo - Expected Error") - } + _, err := b.GetAccountBalance() + if err == nil { + t.Error("Test Failed - GetAccountBalance() error") } } func TestWalletTransfer(t *testing.T) { - newConfig := config.Config{} + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex WalletTransfer init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex WalletTransfer init error: %s\n", err) - } - - BitfinexWalletTransfer := Bitfinex{} - BitfinexWalletTransfer.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexWalletTransfer.WalletTransfer(100, "BTC", "somewallet", "someotherwallet") - if err == nil { - t.Error("Test Failed - Bitfinex WalletTransfer - Expected Error") - } + _, err := b.WalletTransfer(0.01, "bla", "bla", "bla") + if err == nil { + t.Error("Test Failed - WalletTransfer() error") } } func TestWithdrawal(t *testing.T) { - newConfig := config.Config{} + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - Bitfinex Withdrawal init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - Bitfinex Withdrawal init error: %s\n", err) - } - - BitfinexWithdrawal := Bitfinex{} - BitfinexWithdrawal.Setup(exchangeConfig) - - if ACCOUNT_LIVE_TEST { - _, err := BitfinexWithdrawal.Withdrawal("BITCOIN", "somewallet", "123A87612376", 100) - if err == nil { - t.Error("Test Failed - Bitfinex Withdrawal - Expected Error") - } + _, err := b.Withdrawal("LITECOIN", "deposit", "1000", 0.01) + if err == nil { + t.Error("Test Failed - Withdrawal() error") } } -func TestSendAuthenticatedHTTPRequest(t *testing.T) { - if ACCOUNT_LIVE_TEST { - newConfig := config.Config{} +func TestNewOrder(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret - err := newConfig.LoadConfig("../../testdata/configtest.dat") - if err != nil { - t.Errorf("Test Failed - SendAuthenticatedHTTPRequest init error: %s\n", err) - } - exchangeConfig, err := newConfig.GetExchangeConfig("Bitfinex") - if err != nil { - t.Errorf("Test Failed - SendAuthenticatedHTTPRequest init error: %s\n", err) - } - - sendAuthHTTPRequest := Bitfinex{} - sendAuthHTTPRequest.Setup(exchangeConfig) - result := []BitfinexAccountInfo{} - - err = sendAuthHTTPRequest.SendAuthenticatedHTTPRequest("POST", BITFINEX_ACCOUNT_INFO, nil, &result) - if err != nil { - t.Errorf("Test Failed - Bitfinex SendAuthenticatedHTTPRequest() error: %s", err) - } - - if len(result) < 1 { - t.Error("Test Failed - Bitfinex SendAuthenticatedHTTPRequest() incorrect length") - } + _, err := b.NewOrder("BTCUSD", 1, 2, true, "market", false) + if err == nil { + t.Error("Test Failed - NewOrder() error") + } +} + +func TestNewOrderMulti(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + newOrder := []PlaceOrder{ + { + Symbol: "BTCUSD", + Amount: 1, + Price: 1, + Exchange: "bitfinex", + Side: "buy", + Type: "market", + }, + } + + _, err := b.NewOrderMulti(newOrder) + if err == nil { + t.Error("Test Failed - NewOrderMulti() error") + } +} + +func TestCancelOrder(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.CancelOrder(1337) + if err == nil { + t.Error("Test Failed - CancelOrder() error") + } +} + +func TestCancelMultipleOrders(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.CancelMultipleOrders([]int64{1337, 1336}) + if err == nil { + t.Error("Test Failed - CancelMultipleOrders() error") + } +} + +func TestCancelAllOrders(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.CancelAllOrders() + if err == nil { + t.Error("Test Failed - CancelAllOrders() error") + } +} + +func TestReplaceOrder(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.ReplaceOrder(1337, "BTCUSD", 1, 1, true, "market", false) + if err == nil { + t.Error("Test Failed - ReplaceOrder() error") + } +} + +func TestGetOrderStatus(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetOrderStatus(1337) + if err == nil { + t.Error("Test Failed - GetOrderStatus() error") + } +} + +func TestGetActiveOrders(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetActiveOrders() + if err == nil { + t.Error("Test Failed - GetActiveOrders() error") + } +} + +func TestGetActivePositions(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetActivePositions() + if err == nil { + t.Error("Test Failed - GetActivePositions() error") + } +} + +func TestClaimPosition(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.ClaimPosition(1337) + if err == nil { + t.Error("Test Failed - ClaimPosition() error") + } +} + +func TestGetBalanceHistory(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetBalanceHistory("USD", time.Time{}, time.Time{}, 1, "deposit") + if err == nil { + t.Error("Test Failed - GetBalanceHistory() error") + } +} + +func TestGetMovementHistory(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetMovementHistory("USD", "bitcoin", time.Time{}, time.Time{}, 1) + if err == nil { + t.Error("Test Failed - GetMovementHistory() error") + } +} + +func TestGetTradeHistory(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetTradeHistory("BTCUSD", time.Time{}, time.Time{}, 1, 0) + if err == nil { + t.Error("Test Failed - GetTradeHistory() error") + } +} + +func TestNewOffer(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.NewOffer("BTC", 1, 1, 1, "loan") + if err == nil { + t.Error("Test Failed - NewOffer() error") + } +} + +func TestCancelOffer(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.CancelOffer(1337) + if err == nil { + t.Error("Test Failed - CancelOffer() error") + } +} + +func TestGetOfferStatus(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetOfferStatus(1337) + if err == nil { + t.Error("Test Failed - NewOffer() error") + } +} + +func TestGetActiveCredits(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetActiveCredits() + if err == nil { + t.Error("Test Failed - GetActiveCredits() error", err) + } +} + +func TestGetActiveOffers(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetActiveOffers() + if err == nil { + t.Error("Test Failed - GetActiveOffers() error", err) + } +} + +func TestGetActiveMarginFunding(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetActiveMarginFunding() + if err == nil { + t.Error("Test Failed - GetActiveMarginFunding() error", err) + } +} + +func TestGetUnusedMarginFunds(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetUnusedMarginFunds() + if err == nil { + t.Error("Test Failed - GetUnusedMarginFunds() error", err) + } +} + +func TestGetMarginTotalTakenFunds(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.GetMarginTotalTakenFunds() + if err == nil { + t.Error("Test Failed - GetMarginTotalTakenFunds() error", err) + } +} + +func TestCloseMarginFunding(t *testing.T) { + b := Bitfinex{} + b.APIKey = testAPIKey + b.APISecret = testAPISecret + + _, err := b.CloseMarginFunding(1337) + if err == nil { + t.Error("Test Failed - CloseMarginFunding() error") } } diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index db9eab38..976c6b02 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -1,50 +1,208 @@ package bitfinex -type BitfinexStats struct { - Period int64 - Volume float64 `json:",string"` +// Ticker holds basic ticker information from the exchange +type Ticker struct { + Mid float64 `json:"mid,string"` + Bid float64 `json:"bid,string"` + Ask float64 `json:"ask,string"` + Last float64 `json:"last_price,string"` + Low float64 `json:"low,string"` + High float64 `json:"high,string"` + Volume float64 `json:"volume,string"` + Timestamp string `json:"timestamp"` } -type BitfinexTicker struct { - Mid float64 `json:",string"` - Bid float64 `json:",string"` - Ask float64 `json:",string"` - Last float64 `json:"Last_price,string"` - Low float64 `json:",string"` - High float64 `json:",string"` - Volume float64 `json:",string"` - Timestamp string +// Stat holds individual statistics from exchange +type Stat struct { + Period int64 `json:"period"` + Volume float64 `json:"volume,string"` } -type BitfinexMarginLimits struct { - On_Pair string +// FundingBook holds current the full margin funding book +type FundingBook struct { + Bids []Book `json:"bids"` + Asks []Book `json:"asks"` +} + +// Orderbook holds orderbook information from bid and ask sides +type Orderbook struct { + Bids []Book + Asks []Book +} + +// TradeStructure holds executed trade information +type TradeStructure struct { + Timestamp int64 `json:"timestamp"` + Tid int64 `json:"tid"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + Exchange string `json:"exchange"` + Type string `json:"sell"` +} + +// Lendbook holds most recent funding data for a relevent currency +type Lendbook struct { + Bids []Book `json:"bids"` + Asks []Book `json:"asks"` +} + +// Book is a generalised sub-type to hold book information +type Book struct { + Price float64 `json:"price,string"` + Rate float64 `json:"rate,string"` + Amount float64 `json:"amount,string"` + Period int `json:"period"` + Timestamp string `json:"timestamp"` + FlashReturnRate string `json:"frr"` +} + +// Lends holds the lent information by currency +type Lends struct { + Rate float64 `json:"rate,string"` + AmountLent float64 `json:"amount_lent,string"` + AmountUsed float64 `json:"amount_used,string"` + Timestamp int64 `json:"timestamp"` +} + +// SymbolDetails holds currency pair information +type SymbolDetails struct { + Pair string `json:"pair"` + PricePrecision int `json:"price_precision"` + InitialMargin float64 `json:"initial_margin,string"` + MinimumMargin float64 `json:"minimum_margin,string"` + MaximumOrderSize float64 `json:"maximum_order_size,string"` + MinimumOrderSize float64 `json:"minimum_order_size,string"` + Expiration string `json:"expiration"` +} + +// AccountInfo general account information with fees +type AccountInfo struct { + MakerFees string `json:"maker_fees"` + TakerFees string `json:"taker_fees"` + Fees []struct { + Pairs string `json:"pairs"` + MakerFees string `json:"maker_fees"` + TakerFees string `json:"taker_fees"` + } `json:"fees"` +} + +// AccountFees stores withdrawel account fee data from Bitfinex +type AccountFees struct { + Withdraw struct { + BTC float64 `json:"BTC,string"` + LTC float64 `json:"LTC,string"` + ETH float64 `json:"ETH,string"` + ETC float64 `json:"ETC,string"` + ZEC float64 `json:"ZEC,string"` + XMR float64 `json:"XMR,string"` + DSH float64 `json:"DSH,string"` + XRP float64 `json:"XRP,string"` + IOT float64 `json:"IOT"` + EOS float64 `json:"EOS,string"` + SAN float64 `json:"SAN,string"` + OMG float64 `json:"OMG,string"` + BCH float64 `json:"BCH,string"` + } `json:"withdraw"` +} + +// AccountSummary holds account summary data +type AccountSummary struct { + TradeVolumePer30D []Currency `json:"trade_vol_30d"` + FundingProfit30D []Currency `json:"funding_profit_30d"` + MakerFee float64 `json:"maker_fee"` + TakerFee float64 `json:"taker_fee"` +} + +// Currency is a sub-type for AccountSummary data +type Currency struct { + Currency string `json:"curr"` + Volume float64 `json:"vol,string"` + Amount float64 `json:"amount,string"` +} + +// DepositResponse holds deposit address information +type DepositResponse struct { + Result string `json:"string"` + Method string `json:"method"` + Currency string `json:"currency"` + Address string `json:"address"` +} + +// KeyPermissions holds the key permissions for the API key set +type KeyPermissions struct { + Account Permission `json:"account"` + History Permission `json:"history"` + Orders Permission `json:"orders"` + Positions Permission `json:"positions"` + Funding Permission `json:"funding"` + Wallets Permission `json:"wallets"` + Withdraw Permission `json:"withdraw"` +} + +// Permission sub-type for KeyPermissions +type Permission struct { + Read bool `json:"read"` + Write bool `json:"write"` +} + +// MarginInfo holds metadata for margin information from bitfinex +type MarginInfo struct { + Info MarginData + Message string `json:"message"` +} + +// MarginData holds wallet information for margin trading +type MarginData struct { + MarginBalance float64 `json:"margin_balance,string"` + TradableBalance float64 `json:"tradable_balance,string"` + UnrealizedPL int64 `json:"unrealized_pl"` + UnrealizedSwap int64 `json:"unrealized_swap"` + NetValue float64 `json:"net_value,string"` + RequiredMargin int64 `json:"required_margin"` + Leverage float64 `json:"leverage,string"` + MarginRequirement float64 `json:"margin_requirement,string"` + MarginLimits []MarginLimits `json:"margin_limits"` +} + +// MarginLimits holds limit data per pair +type MarginLimits struct { + OnPair string `json:"on_pair"` InitialMargin float64 `json:"initial_margin,string"` MarginRequirement float64 `json:"margin_requirement,string"` TradableBalance float64 `json:"tradable_balance,string"` } -type BitfinexMarginInfo struct { - MarginBalance float64 `json:"margin_balance,string"` - TradableBalance float64 `json:"tradable_balance,string"` - UnrealizedPL int64 `json:"unrealized_pl"` - UnrealizedSwap int64 `json:"unrealized_swap"` - NetValue float64 `json:"net_value,string"` - RequiredMargin int64 `json:"required_margin"` - Leverage float64 `json:"leverage,string"` - MarginRequirement float64 `json:"margin_requirement,string"` - MarginLimits []BitfinexMarginLimits `json:"margin_limits"` - Message string +// Balance holds current balance data +type Balance struct { + Type string `json:"type"` + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` + Available float64 `json:"available,string"` } -type BitfinexOrder struct { - ID int64 - Symbol string - Exchange string +// WalletTransfer holds status of wallet to wallet content transfer on exchange +type WalletTransfer struct { + Status string `json:"status"` + Message string `json:"message"` +} + +// Withdrawal holds withdrawel status information +type Withdrawal struct { + Status string `json:"status"` + Message string `json:"message"` + WithdrawalID int64 `json:"withdrawal_id,string"` +} + +// Order holds order information when an order is in the market +type Order struct { + ID int64 `json:"id"` + Symbol string `json:"symbol"` + Exchange string `json:"exchange"` Price float64 `json:"price,string"` AverageExecutionPrice float64 `json:"avg_execution_price,string"` - Side string - Type string - Timestamp string + Side string `json:"side"` + Type string `json:"type"` + Timestamp string `json:"timestamp"` IsLive bool `json:"is_live"` IsCancelled bool `json:"is_cancelled"` IsHidden bool `json:"is_hidden"` @@ -55,7 +213,14 @@ type BitfinexOrder struct { OrderID int64 `json:"order_id"` } -type BitfinexPlaceOrder struct { +// OrderMultiResponse holds order information on the executed orders +type OrderMultiResponse struct { + Orders []Order `json:"order_ids"` + Status string `json:"status"` +} + +// PlaceOrder is used for order placement +type PlaceOrder struct { Symbol string `json:"symbol"` Amount float64 `json:"amount,string"` Price float64 `json:"price,string"` @@ -64,101 +229,13 @@ type BitfinexPlaceOrder struct { Type string `json:"type"` } -type BitfinexBalance struct { - Type string - Currency string - Amount float64 `json:"amount,string"` - Available float64 `json:"available,string"` +// GenericResponse holds the result for a generic response +type GenericResponse struct { + Result string `json:"result"` } -type BitfinexOffer struct { - ID int64 - Currency string - Rate float64 `json:"rate,string"` - Period int64 - Direction string - Timestamp string - Type string - IsLive bool `json:"is_live"` - IsCancelled bool `json:"is_cancelled"` - OriginalAmount float64 `json:"original_amount,string"` - RemainingAmount float64 `json:"remaining_amount,string"` - ExecutedAmount float64 `json:"executed_amount,string"` -} - -type BitfinexBookStructure struct { - Price, Amount, Timestamp string -} - -type BitfinexFee struct { - Currency string - TakerFees float64 - MakerFees float64 -} - -type BitfinexOrderbook struct { - Bids []BitfinexBookStructure - Asks []BitfinexBookStructure -} - -type BitfinexTradeStructure struct { - Timestamp, Tid int64 - Price, Amount, Exchange, Type string -} - -type BitfinexSymbolDetails struct { - Pair string `json:"pair"` - PricePrecision int `json:"price_precision"` - InitialMargin float64 `json:"initial_margin,string"` - MinimumMargin float64 `json:"minimum_margin,string"` - MaximumOrderSize float64 `json:"maximum_order_size,string"` - MinimumOrderSize float64 `json:"minimum_order_size,string"` - Expiration string `json:"expiration"` -} - -type BitfinexLends struct { - Rate float64 `json:"rate,string"` - AmountLent float64 `json:"amount_lent,string"` - AmountUsed float64 `json:"amount_used,string"` - Timestamp int64 `json:"timestamp"` -} - -type BitfinexAccountInfo struct { - MakerFees string `json:"maker_fees"` - TakerFees string `json:"taker_fees"` - Fees []struct { - Pairs string `json:"pairs"` - MakerFees string `json:"maker_fees"` - TakerFees string `json:"taker_fees"` - } `json:"fees"` -} - -type BitfinexDepositResponse struct { - Result string `json:"string"` - Method string `json:"method"` - Currency string `json:"currency"` - Address string `json:"address"` -} - -type BitfinexOrderMultiResponse struct { - Orders []BitfinexOrder `json:"order_ids"` - Status string `json:"status"` -} - -type BitfinexLendbookBidAsk struct { - Rate float64 `json:"rate,string"` - Amount float64 `json:"amount,string"` - Period int `json:"period"` - Timestamp string `json:"timestamp"` - FlashReturnRate string `json:"frr"` -} - -type BitfinexLendbook struct { - Bids []BitfinexLendbookBidAsk `json:"bids"` - Asks []BitfinexLendbookBidAsk `json:"asks"` -} - -type BitfinexPosition struct { +// Position holds position information +type Position struct { ID int64 `json:"id"` Symbol string `json:"string"` Status string `json:"active"` @@ -169,7 +246,8 @@ type BitfinexPosition struct { PL float64 `json:"pl,string"` } -type BitfinexBalanceHistory struct { +// BalanceHistory holds balance history information +type BalanceHistory struct { Currency string `json:"currency"` Amount float64 `json:"amount,string"` Balance float64 `json:"balance,string"` @@ -177,18 +255,24 @@ type BitfinexBalanceHistory struct { Timestamp string `json:"timestamp"` } -type BitfinexMovementHistory struct { - ID int64 `json:"id"` - Currency string `json:"currency"` - Method string `json:"method"` - Type string `json:"withdrawal"` - Amount float64 `json:"amount,string"` - Description string `json:"description"` - Status string `json:"status"` - Timestamp string `json:"timestamp"` +// MovementHistory holds deposit and withdrawal history data +type MovementHistory struct { + ID int64 `json:"id"` + TxID int64 `json:"txid"` + Currency string `json:"currency"` + Method string `json:"method"` + Type string `json:"withdrawal"` + Amount float64 `json:"amount,string"` + Description string `json:"description"` + Address string `json:"address"` + Status string `json:"status"` + Timestamp string `json:"timestamp"` + TimestampCreated string `json:"timestamp_created"` + Fee float64 `json:"fee"` } -type BitfinexTradeHistory struct { +// TradeHistory holds trade history data +type TradeHistory struct { Price float64 `json:"price,string"` Amount float64 `json:"amount,string"` Timestamp string `json:"timestamp"` @@ -200,7 +284,24 @@ type BitfinexTradeHistory struct { OrderID int64 `json:"order_id"` } -type BitfinexMarginFunds struct { +// Offer holds offer information +type Offer struct { + ID int64 `json:"id"` + Currency string `json:"currency"` + Rate float64 `json:"rate,string"` + Period int64 `json:"period"` + Direction string `json:"direction"` + Timestamp string `json:"timestamp"` + Type string `json:"type"` + IsLive bool `json:"is_live"` + IsCancelled bool `json:"is_cancelled"` + OriginalAmount float64 `json:"original_amount,string"` + RemainingAmount float64 `json:"remaining_amount,string"` + ExecutedAmount float64 `json:"executed_amount,string"` +} + +// MarginFunds holds active funding information used in a margin positon +type MarginFunds struct { ID int64 `json:"id"` PositionID int64 `json:"position_id"` Currency string `json:"currency"` @@ -208,47 +309,46 @@ type BitfinexMarginFunds struct { Period int `json:"period"` Amount float64 `json:"amount,string"` Timestamp string `json:"timestamp"` + AutoClose bool `json:"auto_close"` } -type BitfinexMarginTotalTakenFunds struct { +// MarginTotalTakenFunds holds position funding including sum of active backing +// as total swaps +type MarginTotalTakenFunds struct { PositionPair string `json:"position_pair"` TotalSwaps float64 `json:"total_swaps,string"` } -type BitfinexWalletTransfer struct { - Status string `json:"status"` - Message string `json:"message"` +// Fee holds fee data for a specified currency +type Fee struct { + Currency string + TakerFees float64 + MakerFees float64 } -type BitfinexWithdrawal struct { - Status string `json:"status"` - Message string `json:"message"` - WithdrawalID int64 `json:"withdrawal_id"` -} - -type BitfinexGenericResponse struct { - Result string `json:"result"` -} - -type BitfinexWebsocketChanInfo struct { +// WebsocketChanInfo holds websocket channel information +type WebsocketChanInfo struct { Channel string Pair string } -type BitfinexWebsocketBook struct { +// WebsocketBook holds booking information +type WebsocketBook struct { Price float64 Count int Amount float64 } -type BitfinexWebsocketTrade struct { +// WebsocketTrade holds trade information +type WebsocketTrade struct { ID int64 Timestamp int64 Price float64 Amount float64 } -type BitfinexWebsocketTicker struct { +// WebsocketTicker holds ticker information +type WebsocketTicker struct { Bid float64 BidSize float64 Ask float64 @@ -259,7 +359,8 @@ type BitfinexWebsocketTicker struct { Volume float64 } -type BitfinexWebsocketPosition struct { +// WebsocketPosition holds position information +type WebsocketPosition struct { Pair string Status string Amount float64 @@ -268,14 +369,16 @@ type BitfinexWebsocketPosition struct { MarginFundingType int } -type BitfinexWebsocketWallet struct { +// WebsocketWallet holds wallet information +type WebsocketWallet struct { Name string Currency string Balance float64 UnsettledInterest float64 } -type BitfinexWebsocketOrder struct { +// WebsocketOrder holds order data +type WebsocketOrder struct { OrderID int64 Pair string Amount float64 @@ -288,7 +391,8 @@ type BitfinexWebsocketOrder struct { Notify int } -type BitfinexWebsocketTradeExecuted struct { +// WebsocketTradeExecuted holds executed trade data +type WebsocketTradeExecuted struct { TradeID int64 Pair string Timestamp int64 @@ -296,3 +400,8 @@ type BitfinexWebsocketTradeExecuted struct { AmountExecuted float64 PriceExecuted float64 } + +// ErrorCapture is a simple type for returned errors from Bitfinex +type ErrorCapture struct { + Message string `json:"message"` +} diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 9d1673e2..17feb203 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -12,50 +12,49 @@ import ( ) const ( - BITFINEX_WEBSOCKET = "wss://api.bitfinex.com/ws" - BITFINEX_WEBSOCKET_VERSION = "1.1" - BITFINEX_WEBSOCKET_POSITION_SNAPSHOT = "ps" - BITFINEX_WEBSOCKET_POSITION_NEW = "pn" - BITFINEX_WEBSOCKET_POSITION_UPDATE = "pu" - BITFINEX_WEBSOCKET_POSITION_CLOSE = "pc" - BITFINEX_WEBSOCKET_WALLET_SNAPSHOT = "ws" - BITFINEX_WEBSOCKET_WALLET_UPDATE = "wu" - BITFINEX_WEBSOCKET_ORDER_SNAPSHOT = "os" - BITFINEX_WEBSOCKET_ORDER_NEW = "on" - BITFINEX_WEBSOCKET_ORDER_UPDATE = "ou" - BITFINEX_WEBSOCKET_ORDER_CANCEL = "oc" - BITFINEX_WEBSOCKET_TRADE_EXECUTED = "te" - BITFINEX_WEBSOCKET_HEARTBEAT = "hb" - BITFINEX_WEBSOCKET_ALERT_RESTARTING = "20051" - BITFINEX_WEBSOCKET_ALERT_REFRESHING = "20060" - BITFINEX_WEBSOCKET_ALERT_RESUME = "20061" - BITFINEX_WEBSOCKET_UNKNOWN_EVENT = "10000" - BITFINEX_WEBSOCKET_UNKNOWN_PAIR = "10001" - BITFINEX_WEBSOCKET_SUBSCRIPTION_FAILED = "10300" - BITFINEX_WEBSOCKET_ALREADY_SUBSCRIBED = "10301" - BITFINEX_WEBSOCKET_UNKNOWN_CHANNEL = "10302" + bitfinexWebsocket = "wss://api.bitfinex.com/ws" + bitfinexWebsocketVersion = "1.1" + bitfinexWebsocketPositionSnapshot = "ps" + bitfinexWebsocketPositionNew = "pn" + bitfinexWebsocketPositionUpdate = "pu" + bitfinexWebsocketPositionClose = "pc" + bitfinexWebsocketWalletSnapshot = "ws" + bitfinexWebsocketWalletUpdate = "wu" + bitfinexWebsocketOrderSnapshot = "os" + bitfinexWebsocketOrderNew = "on" + bitfinexWebsocketOrderUpdate = "ou" + bitfinexWebsocketOrderCancel = "oc" + bitfinexWebsocketTradeExecuted = "te" + bitfinexWebsocketHeartbeat = "hb" + bitfinexWebsocketAlertRestarting = "20051" + bitfinexWebsocketAlertRefreshing = "20060" + bitfinexWebsocketAlertResume = "20061" + bitfinexWebsocketUnknownEvent = "10000" + bitfinexWebsocketUnknownPair = "10001" + bitfinexWebsocketSubscriptionFailed = "10300" + bitfinexWebsocketAlreadySubscribed = "10301" + bitfinexWebsocketUnknownChannel = "10302" ) +// WebsocketPingHandler sends a ping request to the websocket server func (b *Bitfinex) WebsocketPingHandler() error { request := make(map[string]string) request["event"] = "ping" + return b.WebsocketSend(request) } +// WebsocketSend sends data to the websocket server func (b *Bitfinex) WebsocketSend(data interface{}) error { json, err := common.JSONEncode(data) if err != nil { return err } - err = b.WebsocketConn.WriteMessage(websocket.TextMessage, json) - - if err != nil { - return err - } - return nil + return b.WebsocketConn.WriteMessage(websocket.TextMessage, json) } +// WebsocketSubscribe subscribes to the websocket channel func (b *Bitfinex) WebsocketSubscribe(channel string, params map[string]string) error { request := make(map[string]string) request["event"] = "subscribe" @@ -69,6 +68,7 @@ func (b *Bitfinex) WebsocketSubscribe(channel string, params map[string]string) return b.WebsocketSend(request) } +// WebsocketSendAuth sends a autheticated event payload func (b *Bitfinex) WebsocketSendAuth() error { request := make(map[string]interface{}) payload := "AUTH" + strconv.FormatInt(time.Now().UnixNano(), 10)[:13] @@ -76,17 +76,22 @@ func (b *Bitfinex) WebsocketSendAuth() error { request["apiKey"] = b.APIKey request["authSig"] = common.HexEncodeToString(common.GetHMAC(common.HashSHA512_384, []byte(payload), []byte(b.APISecret))) request["authPayload"] = payload + return b.WebsocketSend(request) } +// WebsocketSendUnauth sends an unauthenticated payload func (b *Bitfinex) WebsocketSendUnauth() error { request := make(map[string]string) request["event"] = "unauth" + return b.WebsocketSend(request) } +// WebsocketAddSubscriptionChannel adds a new subscription channel to the +// WebsocketSubdChannels map in bitfinex.go (Bitfinex struct) func (b *Bitfinex) WebsocketAddSubscriptionChannel(chanID int, channel, pair string) { - chanInfo := BitfinexWebsocketChanInfo{Pair: pair, Channel: channel} + chanInfo := WebsocketChanInfo{Pair: pair, Channel: channel} b.WebsocketSubdChannels[chanID] = chanInfo if b.Verbose { @@ -94,12 +99,13 @@ func (b *Bitfinex) WebsocketAddSubscriptionChannel(chanID int, channel, pair str } } +// WebsocketClient makes a connection with the websocket server func (b *Bitfinex) WebsocketClient() { channels := []string{"book", "trades", "ticker"} for b.Enabled && b.Websocket { var Dialer websocket.Dialer var err error - b.WebsocketConn, _, err = Dialer.Dial(BITFINEX_WEBSOCKET, http.Header{}) + b.WebsocketConn, _, err = Dialer.Dial(bitfinexWebsocket, http.Header{}) if err != nil { log.Printf("%s Unable to connect to Websocket. Error: %s\n", b.GetName(), err) @@ -196,94 +202,96 @@ func (b *Bitfinex) WebsocketClient() { } else { if len(chanData) == 2 { if reflect.TypeOf(chanData[1]).String() == "string" { - if chanData[1].(string) == BITFINEX_WEBSOCKET_HEARTBEAT { + if chanData[1].(string) == bitfinexWebsocketHeartbeat { continue } } } switch chanInfo.Channel { case "book": - orderbook := []BitfinexWebsocketBook{} + orderbook := []WebsocketBook{} switch len(chanData) { case 2: data := chanData[1].([]interface{}) for _, x := range data { y := x.([]interface{}) - orderbook = append(orderbook, BitfinexWebsocketBook{Price: y[0].(float64), Count: int(y[1].(float64)), Amount: y[2].(float64)}) + orderbook = append(orderbook, WebsocketBook{Price: y[0].(float64), Count: int(y[1].(float64)), Amount: y[2].(float64)}) } case 4: - orderbook = append(orderbook, BitfinexWebsocketBook{Price: chanData[1].(float64), Count: int(chanData[2].(float64)), Amount: chanData[3].(float64)}) + orderbook = append(orderbook, WebsocketBook{Price: chanData[1].(float64), Count: int(chanData[2].(float64)), Amount: chanData[3].(float64)}) } + log.Println(orderbook) case "ticker": - ticker := BitfinexWebsocketTicker{Bid: chanData[1].(float64), BidSize: chanData[2].(float64), Ask: chanData[3].(float64), AskSize: chanData[4].(float64), + ticker := WebsocketTicker{Bid: chanData[1].(float64), BidSize: chanData[2].(float64), Ask: chanData[3].(float64), AskSize: chanData[4].(float64), DailyChange: chanData[5].(float64), DialyChangePerc: chanData[6].(float64), LastPrice: chanData[7].(float64), Volume: chanData[8].(float64)} log.Printf("Bitfinex %s Websocket Last %f Volume %f\n", chanInfo.Pair, ticker.LastPrice, ticker.Volume) case "account": switch chanData[1].(string) { - case BITFINEX_WEBSOCKET_POSITION_SNAPSHOT: - positionSnapshot := []BitfinexWebsocketPosition{} + case bitfinexWebsocketPositionSnapshot: + positionSnapshot := []WebsocketPosition{} data := chanData[2].([]interface{}) for _, x := range data { y := x.([]interface{}) - positionSnapshot = append(positionSnapshot, BitfinexWebsocketPosition{Pair: y[0].(string), Status: y[1].(string), Amount: y[2].(float64), Price: y[3].(float64), + positionSnapshot = append(positionSnapshot, WebsocketPosition{Pair: y[0].(string), Status: y[1].(string), Amount: y[2].(float64), Price: y[3].(float64), MarginFunding: y[4].(float64), MarginFundingType: int(y[5].(float64))}) } log.Println(positionSnapshot) - case BITFINEX_WEBSOCKET_POSITION_NEW, BITFINEX_WEBSOCKET_POSITION_UPDATE, BITFINEX_WEBSOCKET_POSITION_CLOSE: + case bitfinexWebsocketPositionNew, bitfinexWebsocketPositionUpdate, bitfinexWebsocketPositionClose: data := chanData[2].([]interface{}) - position := BitfinexWebsocketPosition{Pair: data[0].(string), Status: data[1].(string), Amount: data[2].(float64), Price: data[3].(float64), + position := WebsocketPosition{Pair: data[0].(string), Status: data[1].(string), Amount: data[2].(float64), Price: data[3].(float64), MarginFunding: data[4].(float64), MarginFundingType: int(data[5].(float64))} log.Println(position) - case BITFINEX_WEBSOCKET_WALLET_SNAPSHOT: + case bitfinexWebsocketWalletSnapshot: data := chanData[2].([]interface{}) - walletSnapshot := []BitfinexWebsocketWallet{} + walletSnapshot := []WebsocketWallet{} for _, x := range data { y := x.([]interface{}) - walletSnapshot = append(walletSnapshot, BitfinexWebsocketWallet{Name: y[0].(string), Currency: y[1].(string), Balance: y[2].(float64), UnsettledInterest: y[3].(float64)}) + walletSnapshot = append(walletSnapshot, WebsocketWallet{Name: y[0].(string), Currency: y[1].(string), Balance: y[2].(float64), UnsettledInterest: y[3].(float64)}) } log.Println(walletSnapshot) - case BITFINEX_WEBSOCKET_WALLET_UPDATE: + case bitfinexWebsocketWalletUpdate: data := chanData[2].([]interface{}) - wallet := BitfinexWebsocketWallet{Name: data[0].(string), Currency: data[1].(string), Balance: data[2].(float64), UnsettledInterest: data[3].(float64)} + wallet := WebsocketWallet{Name: data[0].(string), Currency: data[1].(string), Balance: data[2].(float64), UnsettledInterest: data[3].(float64)} log.Println(wallet) - case BITFINEX_WEBSOCKET_ORDER_SNAPSHOT: - orderSnapshot := []BitfinexWebsocketOrder{} + case bitfinexWebsocketOrderSnapshot: + orderSnapshot := []WebsocketOrder{} data := chanData[2].([]interface{}) for _, x := range data { y := x.([]interface{}) - orderSnapshot = append(orderSnapshot, BitfinexWebsocketOrder{OrderID: int64(y[0].(float64)), Pair: y[1].(string), Amount: y[2].(float64), OrigAmount: y[3].(float64), + orderSnapshot = append(orderSnapshot, WebsocketOrder{OrderID: int64(y[0].(float64)), Pair: y[1].(string), Amount: y[2].(float64), OrigAmount: y[3].(float64), OrderType: y[4].(string), Status: y[5].(string), Price: y[6].(float64), PriceAvg: y[7].(float64), Timestamp: y[8].(string)}) } log.Println(orderSnapshot) - case BITFINEX_WEBSOCKET_ORDER_NEW, BITFINEX_WEBSOCKET_ORDER_UPDATE, BITFINEX_WEBSOCKET_ORDER_CANCEL: + case bitfinexWebsocketOrderNew, bitfinexWebsocketOrderUpdate, bitfinexWebsocketOrderCancel: data := chanData[2].([]interface{}) - order := BitfinexWebsocketOrder{OrderID: int64(data[0].(float64)), Pair: data[1].(string), Amount: data[2].(float64), OrigAmount: data[3].(float64), + order := WebsocketOrder{OrderID: int64(data[0].(float64)), Pair: data[1].(string), Amount: data[2].(float64), OrigAmount: data[3].(float64), OrderType: data[4].(string), Status: data[5].(string), Price: data[6].(float64), PriceAvg: data[7].(float64), Timestamp: data[8].(string), Notify: int(data[9].(float64))} log.Println(order) - case BITFINEX_WEBSOCKET_TRADE_EXECUTED: + case bitfinexWebsocketTradeExecuted: data := chanData[2].([]interface{}) - trade := BitfinexWebsocketTradeExecuted{TradeID: int64(data[0].(float64)), Pair: data[1].(string), Timestamp: int64(data[2].(float64)), OrderID: int64(data[3].(float64)), + trade := WebsocketTradeExecuted{TradeID: int64(data[0].(float64)), Pair: data[1].(string), Timestamp: int64(data[2].(float64)), OrderID: int64(data[3].(float64)), AmountExecuted: data[4].(float64), PriceExecuted: data[5].(float64)} log.Println(trade) } case "trades": - trades := []BitfinexWebsocketTrade{} + trades := []WebsocketTrade{} switch len(chanData) { case 2: data := chanData[1].([]interface{}) for _, x := range data { y := x.([]interface{}) - trades = append(trades, BitfinexWebsocketTrade{ID: int64(y[0].(float64)), Timestamp: int64(y[1].(float64)), Price: y[2].(float64), Amount: y[3].(float64)}) + trades = append(trades, WebsocketTrade{ID: int64(y[0].(float64)), Timestamp: int64(y[1].(float64)), Price: y[2].(float64), Amount: y[3].(float64)}) } case 5: - trade := BitfinexWebsocketTrade{ID: int64(chanData[1].(float64)), Timestamp: int64(chanData[2].(float64)), Price: chanData[3].(float64), Amount: chanData[4].(float64)} + trade := WebsocketTrade{ID: int64(chanData[1].(float64)), Timestamp: int64(chanData[2].(float64)), Price: chanData[3].(float64), Amount: chanData[4].(float64)} trades = append(trades, trade) if b.Verbose { log.Printf("Bitfinex %s Websocket Trade ID %d Timestamp %d Price %f Amount %f\n", chanInfo.Pair, trade.ID, trade.Timestamp, trade.Price, trade.Amount) } } + log.Println(trades) } } } diff --git a/exchanges/bitfinex/bitfinex_websocket_test.go b/exchanges/bitfinex/bitfinex_websocket_test.go index ed802cd0..a3308a73 100644 --- a/exchanges/bitfinex/bitfinex_websocket_test.go +++ b/exchanges/bitfinex/bitfinex_websocket_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/gorilla/websocket" - "github.com/thrasher-/gocryptotrader/common" ) func TestWebsocketPingHandler(t *testing.T) { @@ -13,7 +12,7 @@ func TestWebsocketPingHandler(t *testing.T) { var Dialer websocket.Dialer var err error - wsPingHandler.WebsocketConn, _, err = Dialer.Dial(BITFINEX_WEBSOCKET, http.Header{}) + wsPingHandler.WebsocketConn, _, err = Dialer.Dial(bitfinexWebsocket, http.Header{}) if err != nil { t.Errorf("Test Failed - Bitfinex dialer error: %s", err) } @@ -27,131 +26,6 @@ func TestWebsocketPingHandler(t *testing.T) { } } -func TestWebsocketSend(t *testing.T) { - wsSend := Bitfinex{} - var Dialer websocket.Dialer - var err error - - type WebsocketHandshake struct { - Event string `json:"event"` - Code int64 `json:"code"` - Version float64 `json:"version"` - } - - request, dodgyrequest := make(map[string]string), make(map[string]string) - request["event"] = "ping" - dodgyrequest["dodgyEvent"] = "didgereedodge" - - hs := WebsocketHandshake{} - - for { - wsSend.WebsocketConn, _, err = Dialer.Dial(BITFINEX_WEBSOCKET, http.Header{}) - if err != nil { - if err.Error() == "websocket: close 1006 (abnormal closure): unexpected EOF" { - err = wsSend.WebsocketConn.Close() - if err != nil { - t.Errorf("Test Failed - Bitfinex websocketConn.Close() error: %s", err) - } - continue - } else { - t.Errorf("Test Failed - Bitfinex websocket connection error: %s", err) - } - } - mType, resp, err := wsSend.WebsocketConn.ReadMessage() - if err != nil { - t.Errorf("Test Failed - Bitfinex websocketconn.ReadMessage() error: %s", err) - } - if mType != websocket.TextMessage { - t.Errorf("Test Failed - Bitfinex websocketconn.ReadMessage() mType error: %d", mType) - } - err = common.JSONDecode(resp, &hs) - if err != nil { - t.Errorf("Test Failed - Bitfinex JSONDecode error: %s", err) - } - if hs.Code != 0 { - t.Errorf("Test Failed - Bitfinex hs.Code incorrect: %d", hs.Code) - } - if hs.Event != "info" { - t.Errorf("Test Failed - Bitfinex hs.Event incorrect: %s", hs.Event) - } - if hs.Version != 1.1 { - t.Errorf("Test Failed - Bitfinex hs.Version incorrect: %f", hs.Version) - } - - err = wsSend.WebsocketSend(request) - if err != nil { - t.Errorf("Test Failed - Bitfinex websocket send error: %s", err) - } - mType, resp, err = wsSend.WebsocketConn.ReadMessage() - if err != nil { - if err.Error() == "websocket: close 1006 (abnormal closure): unexpected EOF" { - err = wsSend.WebsocketConn.Close() - if err != nil { - t.Errorf("Test Failed - Bitfinex websocketConn.Close() error: %s", err) - } - continue - } else { - t.Errorf("Test Failed - Bitfinex websocketConn.ReadMessage() error: %s", err) - } - } - if mType != websocket.TextMessage { - t.Errorf("Test Failed - Bitfinex websocketconn.ReadMessage() mType error: %d", mType) - } - err = common.JSONDecode(resp, &hs) - if err != nil { - t.Errorf("Test Failed - Bitfinex JSONDecode error: %s", err) - } - if hs.Code != 0 { - t.Errorf("Test Failed - Bitfinex hs.Code incorrect: %d", hs.Code) - } - if hs.Event != "pong" { - t.Errorf("Test Failed - Bitfinex hs.Event incorrect: %s", hs.Event) - } - if hs.Version != 1.1 { - t.Errorf("Test Failed - Bitfinex hs.Version incorrect: %f", hs.Version) - } - - err = wsSend.WebsocketSend(dodgyrequest) - if err != nil { - t.Errorf("Test Failed - Bitfinex websocket send error: %s", err) - } - mType, resp, err = wsSend.WebsocketConn.ReadMessage() - if err != nil { - if err.Error() == "websocket: close 1006 (abnormal closure): unexpected EOF" { - err = wsSend.WebsocketConn.Close() - if err != nil { - t.Errorf("Test Failed - Bitfinex websocketConn.Close() error: %s", err) - } - continue - } else { - t.Errorf("Test Failed - Bitfinex websocketConn.ReadMessage() error: %s", err) - } - } - if mType != websocket.TextMessage { - t.Errorf("Test Failed - Bitfinex websocketconn.ReadMessage() mType error: %d", mType) - } - err = common.JSONDecode(resp, &hs) - if err != nil { - t.Errorf("Test Failed - Bitfinex JSONDecode error: %s", err) - } - if hs.Code != 10000 { - t.Errorf("Test Failed - Bitfinex hs.Code incorrect: %d", hs.Code) - } - if hs.Event != "error" { - t.Errorf("Test Failed - Bitfinex hs.Event incorrect: %s", hs.Event) - } - if hs.Version != 1.1 { - t.Errorf("Test Failed - Bitfinex hs.Version incorrect: %f", hs.Version) - } - - err = wsSend.WebsocketConn.Close() - if err != nil { - t.Errorf("Test Failed - Bitfinex websocketConn.Close() error: %s", err) - } - break - } -} - func TestWebsocketSubscribe(t *testing.T) { websocketSubcribe := Bitfinex{} var Dialer websocket.Dialer @@ -159,7 +33,7 @@ func TestWebsocketSubscribe(t *testing.T) { params := make(map[string]string) params["pair"] = "BTCUSD" - websocketSubcribe.WebsocketConn, _, err = Dialer.Dial(BITFINEX_WEBSOCKET, http.Header{}) + websocketSubcribe.WebsocketConn, _, err = Dialer.Dial(bitfinexWebsocket, http.Header{}) if err != nil { t.Errorf("Test Failed - Bitfinex Dialer error: %s", err) } @@ -179,7 +53,7 @@ func TestWebsocketSendAuth(t *testing.T) { var Dialer websocket.Dialer var err error - wsSendAuth.WebsocketConn, _, err = Dialer.Dial(BITFINEX_WEBSOCKET, http.Header{}) + wsSendAuth.WebsocketConn, _, err = Dialer.Dial(bitfinexWebsocket, http.Header{}) if err != nil { t.Errorf("Test Failed - Bitfinex Dialer error: %s", err) } @@ -189,31 +63,13 @@ func TestWebsocketSendAuth(t *testing.T) { } } -func TestWebsocketSendUnauth(t *testing.T) { - // --- FAIL: TestWebsocketSendUnauth (0.32s) - // bitfinex_websocket_test.go:199: Test Failed - Bitfinex Dialer error: websocket: bad handshake - - // wsSendUnauth := Bitfinex{} - // var Dialer websocket.Dialer - // var err error - // - // wsSendUnauth.WebsocketConn, _, err = Dialer.Dial(BITFINEX_WEBSOCKET, http.Header{}) - // if err != nil { - // t.Errorf("Test Failed - Bitfinex Dialer error: %s", err) - // } - // err = wsSendUnauth.WebsocketSendUnauth() - // if err != nil { - // t.Errorf("Test Failed - Bitfinex WebsocketSendAuth() error: %s", err) - // } -} - func TestWebsocketAddSubscriptionChannel(t *testing.T) { wsAddSubscriptionChannel := Bitfinex{} wsAddSubscriptionChannel.SetDefaults() var Dialer websocket.Dialer var err error - wsAddSubscriptionChannel.WebsocketConn, _, err = Dialer.Dial(BITFINEX_WEBSOCKET, http.Header{}) + wsAddSubscriptionChannel.WebsocketConn, _, err = Dialer.Dial(bitfinexWebsocket, http.Header{}) if err != nil { t.Errorf("Test Failed - Bitfinex Dialer error: %s", err) } @@ -229,7 +85,3 @@ func TestWebsocketAddSubscriptionChannel(t *testing.T) { t.Errorf("Test Failed - Bitfinex WebsocketAddSubscriptionChannel() error: %s", err) } } - -// func TestWebsocketClient(t *testing.T) { -// -// } diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index e205d57e..03c7d785 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -2,7 +2,6 @@ package bitfinex import ( "log" - "strconv" "time" "github.com/thrasher-/gocryptotrader/common" @@ -13,10 +12,12 @@ import ( "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) +// Start starts a new wrapper through a go routine func (b *Bitfinex) Start() { go b.Run() } +// Run starts a new websocketclient connection and monitors ticker information func (b *Bitfinex) Run() { if b.Verbose { log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket)) @@ -54,6 +55,7 @@ func (b *Bitfinex) Run() { } } +// GetTickerPrice returns ticker information func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { tick, err := ticker.GetTicker(b.GetName(), p) if err == nil { @@ -76,6 +78,7 @@ func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro return tickerPrice, nil } +// GetOrderbookEx returns orderbook information based on currency pair func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) { ob, err := orderbook.GetOrderbook(b.GetName(), p) if err == nil { @@ -88,16 +91,12 @@ func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, return orderBook, err } - for x, _ := range orderbookNew.Asks { - price, _ := strconv.ParseFloat(orderbookNew.Asks[x].Price, 64) - amount, _ := strconv.ParseFloat(orderbookNew.Asks[x].Amount, 64) - orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: price, Amount: amount}) + for x := range orderbookNew.Asks { + orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Price: orderbookNew.Asks[x].Price, Amount: orderbookNew.Asks[x].Amount}) } - for x, _ := range orderbookNew.Bids { - price, _ := strconv.ParseFloat(orderbookNew.Bids[x].Price, 64) - amount, _ := strconv.ParseFloat(orderbookNew.Bids[x].Amount, 64) - orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: price, Amount: amount}) + for x := range orderbookNew.Bids { + orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Price: orderbookNew.Bids[x].Price, Amount: orderbookNew.Bids[x].Amount}) } orderBook.Pair = p @@ -105,25 +104,49 @@ func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, return orderBook, nil } -//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Bitfinex exchange -func (e *Bitfinex) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +// GetExchangeAccountInfo retrieves balances for all enabled currencies on the +// Bitfinex exchange +func (b *Bitfinex) 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 } - if !e.Enabled { + if !b.Enabled { return response, nil } - for i := 0; i < len(accountBalance); i++ { - var exchangeCurrency exchange.AccountCurrencyInfo - exchangeCurrency.CurrencyName = common.StringToUpper(accountBalance[i].Currency) - exchangeCurrency.TotalValue = accountBalance[i].Amount - exchangeCurrency.Hold = accountBalance[i].Available + type bfxCoins struct { + OnHold float64 + Available float64 + } + accounts := make(map[string]bfxCoins) + + for i := range accountBalance { + onHold := accountBalance[i].Amount - accountBalance[i].Available + coins := bfxCoins{ + OnHold: onHold, + Available: accountBalance[i].Available, + } + result, ok := accounts[accountBalance[i].Currency] + if !ok { + accounts[accountBalance[i].Currency] = coins + } else { + result.Available += accountBalance[i].Available + result.OnHold += onHold + accounts[accountBalance[i].Currency] = result + } + } + + for x, y := range accounts { + var exchangeCurrency exchange.AccountCurrencyInfo + exchangeCurrency.CurrencyName = common.StringToUpper(x) + exchangeCurrency.TotalValue = y.Available + y.OnHold + exchangeCurrency.Hold = y.OnHold response.Currencies = append(response.Currencies, exchangeCurrency) } + return response, nil } diff --git a/exchanges/bitfinex/bitfinex_wrapper_test.go b/exchanges/bitfinex/bitfinex_wrapper_test.go index 7def6cad..e1f634ad 100644 --- a/exchanges/bitfinex/bitfinex_wrapper_test.go +++ b/exchanges/bitfinex/bitfinex_wrapper_test.go @@ -31,18 +31,3 @@ func TestGetOrderbookEx(t *testing.T) { t.Errorf("Test Failed - Bitfinex GetOrderbookEx() error: %s", err) } } - -func TestGetExchangeAccountInfo(t *testing.T) { - // getExchangeAccountInfo := Bitfinex{} - // newConfig := config.GetConfig() - // newConfig.LoadConfig("../../testdata/configtest.dat") - // exchConf, err := newConfig.GetExchangeConfig("Bitfinex") - // if err != nil { - // t.Errorf("Test Failed - Bitfinex getExchangeConfig(): %s", err) - // } - // getExchangeAccountInfo.Setup(exchConf) - // _, err = getExchangeAccountInfo.GetExchangeAccountInfo() - // if err != nil { - // t.Errorf("Test Failed - Bitfinex GetExchangeAccountInfo() error: %s", err) - // } -} diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 577e387d..ac44f745 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -498,13 +498,13 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url } if b.Verbose { - log.Printf("Recieved raw: %s\n", resp) + log.Printf("Received raw: %s\n", resp) } err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 65ccd114..a6692071 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -79,12 +79,12 @@ func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) } diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go new file mode 100644 index 00000000..011fa499 --- /dev/null +++ b/exchanges/bittrex/bittrex.go @@ -0,0 +1,362 @@ +package bittrex + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net/url" + "strconv" + "strings" + "time" + + "github.com/thrasher-/gocryptotrader/common" + "github.com/thrasher-/gocryptotrader/config" + "github.com/thrasher-/gocryptotrader/exchanges" +) + +const ( + bittrexAPIURL = "https://bittrex.com/api/v1.1" + bittrexAPIVersion = "v1.1" + bittrexMaxOpenOrders = 500 + bittrexMaxOrderCountPerDay = 200000 + + // Returned messages from Bittrex API + bittrexAddressGenerating = "ADDRESS_GENERATING" + bittrexErrorMarketNotProvided = "MARKET_NOT_PROVIDED" + bittrexErrorInvalidMarket = "INVALID_MARKET" + bittrexErrorAPIKeyInvalid = "APIKEY_INVALID" + bittrexErrorInvalidPermission = "INVALID_PERMISSION" + + // Public requests + bittrexAPIGetMarkets = "public/getmarkets" + bittrexAPIGetCurrencies = "public/getcurrencies" + bittrexAPIGetTicker = "public/getticker" + bittrexAPIGetMarketSummaries = "public/getmarketsummaries" + bittrexAPIGetMarketSummary = "public/getmarketsummary" + bittrexAPIGetOrderbook = "public/getorderbook" + bittrexAPIGetMarketHistory = "public/getmarkethistory" + + // Market requests + bittrexAPIBuyLimit = "market/buylimit" + bittrexAPISellLimit = "market/selllimit" + bittrexAPICancel = "market/cancel" + bittrexAPIGetOpenOrders = "market/getopenorders" + + // Account requests + bittrexAPIGetBalances = "account/getbalances" + bittrexAPIGetBalance = "account/getbalance" + bittrexAPIGetDepositAddress = "account/getdepositaddress" + bittrexAPIWithdraw = "account/withdraw" + bittrexAPIGetOrder = "account/getorder" + bittrexAPIGetOrderHistory = "account/getorderhistory" + bittrexAPIGetWithdrawalHistory = "account/getwithdrawalhistory" + bittrexAPIGetDepositHistory = "account/getdeposithistory" +) + +// Bittrex is the overaching type across the bittrex methods +type Bittrex struct { + exchange.Base +} + +// SetDefaults method assignes the default values for Bittrex +func (b *Bittrex) SetDefaults() { + b.Name = "Bittrex" + b.Enabled = false + b.Verbose = false + b.Websocket = false + b.RESTPollingDelay = 10 +} + +// Setup method sets current configuration details if enabled +func (b *Bittrex) Setup(exch config.ExchangeConfig) { + if !exch.Enabled { + b.SetEnabled(false) + } else { + b.Enabled = true + b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport + b.SetAPIKeys(exch.APIKey, exch.APISecret, exch.ClientID, false) + b.RESTPollingDelay = exch.RESTPollingDelay + b.Verbose = exch.Verbose + b.Websocket = exch.Websocket + b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") + b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") + b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") + } +} + +// GetMarkets is used to get the open and available trading markets at Bittrex +// along with other meta data. +func (b *Bittrex) GetMarkets() ([]Market, error) { + var markets []Market + path := fmt.Sprintf("%s/%s/", bittrexAPIURL, bittrexAPIGetMarkets) + + return markets, b.HTTPRequest(path, false, url.Values{}, &markets) +} + +// GetCurrencies is used to get all supported currencies at Bittrex +func (b *Bittrex) GetCurrencies() ([]Currency, error) { + var currencies []Currency + path := fmt.Sprintf("%s/%s/", bittrexAPIURL, bittrexAPIGetCurrencies) + + return currencies, b.HTTPRequest(path, false, url.Values{}, ¤cies) +} + +// GetTicker sends a public get request and returns current ticker information +// on the supplied currency. Example currency input param "btc-ltc". +func (b *Bittrex) GetTicker(currencyPair string) (Ticker, error) { + ticker := Ticker{} + path := fmt.Sprintf("%s/%s?market=%s", bittrexAPIURL, bittrexAPIGetTicker, + common.StringToUpper(currencyPair), + ) + return ticker, b.HTTPRequest(path, false, url.Values{}, &ticker) +} + +// GetMarketSummaries is used to get the last 24 hour summary of all active +// exchanges +func (b *Bittrex) GetMarketSummaries() ([]MarketSummary, error) { + var summaries []MarketSummary + path := fmt.Sprintf("%s/%s/", bittrexAPIURL, bittrexAPIGetMarketSummaries) + + return summaries, b.HTTPRequest(path, false, url.Values{}, &summaries) +} + +// GetMarketSummary is used to get the last 24 hour summary of all active +// exchanges by currency pair (btc-ltc). +func (b *Bittrex) GetMarketSummary(currencyPair string) ([]MarketSummary, error) { + var summary []MarketSummary + path := fmt.Sprintf("%s/%s?market=%s", bittrexAPIURL, + bittrexAPIGetMarketSummary, common.StringToLower(currencyPair), + ) + return summary, b.HTTPRequest(path, false, url.Values{}, &summary) +} + +// GetOrderbook method returns current order book information by currency, type +// & depth. +// "Currency Pair" ie btc-ltc +// "Category" either "buy", "sell" or "both"; for ease of use and reduced +// complexity this function is set to "both" +// "Depth" max depth is 50 but you can literally set it any integer you want and +// it returns full depth. So depth default is 50. +func (b *Bittrex) GetOrderbook(currencyPair string) (OrderBooks, error) { + var orderbooks OrderBooks + path := fmt.Sprintf("%s/%s?market=%s&type=both&depth=50", bittrexAPIURL, + bittrexAPIGetOrderbook, common.StringToUpper(currencyPair), + ) + + return orderbooks, b.HTTPRequest(path, false, url.Values{}, &orderbooks) +} + +// GetMarketHistory retrieves the latest trades that have occurred for a specific +// market +func (b *Bittrex) GetMarketHistory(currencyPair string) ([]MarketHistory, error) { + var marketHistoriae []MarketHistory + path := fmt.Sprintf("%s/%s?market=%s", bittrexAPIURL, + bittrexAPIGetMarketHistory, common.StringToUpper(currencyPair), + ) + return marketHistoriae, b.HTTPRequest(path, false, url.Values{}, + &marketHistoriae) +} + +// PlaceBuyLimit is used to place a buy order in a specific market. Use buylimit +// to place limit orders. Make sure you have the proper permissions set on your +// API keys for this call to work. +// "Currency" ie "btc-ltc" +// "Quantity" is the amount to purchase +// "Rate" is the rate at which to purchase +func (b *Bittrex) PlaceBuyLimit(currencyPair string, quantity, rate float64) ([]UUID, error) { + var id []UUID + values := url.Values{} + 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) + + return id, b.HTTPRequest(path, true, values, &id) +} + +// PlaceSellLimit is used to place a sell order in a specific market. Use +// selllimit to place limit orders. Make sure you have the proper permissions +// set on your API keys for this call to work. +// "Currency" ie "btc-ltc" +// "Quantity" is the amount to purchase +// "Rate" is the rate at which to purchase +func (b *Bittrex) PlaceSellLimit(currencyPair string, quantity, rate float64) ([]UUID, error) { + var id []UUID + values := url.Values{} + 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) + + return id, b.HTTPRequest(path, true, values, &id) +} + +// GetOpenOrders returns all orders that you currently have opened. +// A specific market can be requested for example "btc-ltc" +func (b *Bittrex) GetOpenOrders(currencyPair string) ([]Order, error) { + var orders []Order + values := url.Values{} + if !(currencyPair == "" || currencyPair == " ") { + values.Set("market", currencyPair) + } + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetBalances) + + return orders, b.HTTPRequest(path, true, values, &orders) +} + +// CancelOrder is used to cancel a buy or sell order. +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) + + return balances, b.HTTPRequest(path, true, values, &balances) +} + +// GetAccountBalances is used to retrieve all balances from your account +func (b *Bittrex) GetAccountBalances() ([]Balance, error) { + var balances []Balance + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetBalances) + + return balances, b.HTTPRequest(path, true, url.Values{}, &balances) +} + +// GetAccountBalanceByCurrency is used to retrieve the balance from your account +// for a specific currency. ie. "btc" or "ltc" +func (b *Bittrex) GetAccountBalanceByCurrency(currency string) (Balance, error) { + var balance Balance + values := url.Values{} + values.Set("currency", currency) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetBalance) + + return balance, b.HTTPRequest(path, true, values, &balance) +} + +// GetDepositAddress is used to retrieve or generate an address for a specific +// currency. If one does not exist, the call will fail and return +// ADDRESS_GENERATING until one is available. +func (b *Bittrex) GetDepositAddress(currency string) (DepositAddress, error) { + var address DepositAddress + values := url.Values{} + values.Set("currency", currency) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetDepositAddress) + + return address, b.HTTPRequest(path, true, values, &address) +} + +// Withdraw is used to withdraw funds from your account. +// note: Please account for transaction fee. +func (b *Bittrex) Withdraw(currency, paymentID, address string, quantity float64) (UUID, error) { + var id UUID + values := url.Values{} + values.Set("currency", currency) + values.Set("quantity", strconv.FormatFloat(quantity, 'E', -1, 64)) + values.Set("address", address) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIWithdraw) + + return id, b.HTTPRequest(path, true, values, &id) +} + +// GetOrder is used to retrieve a single order by UUID. +func (b *Bittrex) GetOrder(uuid string) (Order, error) { + var order Order + values := url.Values{} + values.Set("uuid", uuid) + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetOrder) + + return order, b.HTTPRequest(path, true, values, &order) +} + +// GetOrderHistory is used to retrieve your order history. If currencyPair +// omitted it will return the entire order History. +func (b *Bittrex) GetOrderHistory(currencyPair string) ([]Order, error) { + var orders []Order + values := url.Values{} + + if !(currencyPair == "" || currencyPair == " ") { + values.Set("market", currencyPair) + } + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetOrderHistory) + + return orders, b.HTTPRequest(path, true, values, &orders) +} + +// GetWithdrawelHistory is used to retrieve your withdrawal history. If currency +// omitted it will return the entire history +func (b *Bittrex) GetWithdrawelHistory(currency string) ([]WithdrawalHistory, error) { + var history []WithdrawalHistory + values := url.Values{} + + if !(currency == "" || currency == " ") { + values.Set("currency", currency) + } + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetOrderHistory) + + return history, b.HTTPRequest(path, true, values, &history) +} + +// GetDepositHistory is used to retrieve your deposit history. If currency is +// is omitted it will return the entire deposit history +func (b *Bittrex) GetDepositHistory(currency string) ([]WithdrawalHistory, error) { + var history []WithdrawalHistory + values := url.Values{} + + if !(currency == "" || currency == " ") { + values.Set("currency", currency) + } + path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetOrderHistory) + + return history, b.HTTPRequest(path, true, values, &history) +} + +// 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) + values.Set("apikey", b.APIKey) + values.Set("apisecret", b.APISecret) + values.Set("nonce", nonce) + rawQuery := path + "?" + values.Encode() + hmac := common.GetHMAC( + common.HashSHA512, []byte(rawQuery), []byte(b.APISecret), + ) + headers := make(map[string]string) + headers["apisign"] = common.HexEncodeToString(hmac) + + resp, err := common.SendHTTPRequest( + "GET", rawQuery, headers, strings.NewReader(""), + ) + if err != nil { + return err + } + + if b.Verbose { + log.Printf("Received raw: %s\n", resp) + } + + err = common.JSONDecode([]byte(resp), &result) + if err != nil { + return errors.New("Unable to JSON Unmarshal response." + err.Error()) + } + return nil +} + +// HTTPRequest is a generalised http request function. +func (b *Bittrex) HTTPRequest(path string, auth bool, values url.Values, v interface{}) error { + response := Response{} + if auth { + if err := b.SendAuthenticatedHTTPRequest(path, values, &response); err != nil { + return err + } + } else { + if err := common.SendHTTPGetRequest(path, true, &response); err != nil { + return err + } + } + if response.Success { + return json.Unmarshal(response.Result, &v) + } + return errors.New(response.Message) +} diff --git a/exchanges/bittrex/bittrex_test.go b/exchanges/bittrex/bittrex_test.go new file mode 100644 index 00000000..4adedb33 --- /dev/null +++ b/exchanges/bittrex/bittrex_test.go @@ -0,0 +1,262 @@ +package bittrex + +import ( + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +// Please supply you own test keys here to run better tests. +const ( + apiKey = "Testy" + apiSecret = "TestyTesty" +) + +func TestSetDefaults(t *testing.T) { + b := Bittrex{} + b.SetDefaults() + if b.GetName() != "Bittrex" { + t.Error("Test Failed - Bittrex - SetDefaults() error") + } +} + +func TestSetup(t *testing.T) { + exch := config.ExchangeConfig{ + Name: "Bittrex", + APIKey: apiKey, + } + exch.Enabled = true + b := Bittrex{} + b.Setup(exch) + if b.APIKey != apiKey { + t.Error("Test Failed - Bittrex - Setup() error") + } + exch.Enabled = false + b.Setup(exch) + if b.IsEnabled() { + t.Error("Test Failed - Bittrex - Setup() error") + } +} + +func TestGetMarkets(t *testing.T) { + obj := Bittrex{} + _, err := obj.GetMarkets() + if err != nil { + t.Errorf("Test Failed - Bittrex - GetMarkets() error: %s", err) + } +} + +func TestGetCurrencies(t *testing.T) { + obj := Bittrex{} + _, err := obj.GetCurrencies() + if err != nil { + t.Errorf("Test Failed - Bittrex - GetCurrencies() error: %s", err) + } +} + +func TestGetTicker(t *testing.T) { + invalid := "" + btc := "btc-ltc" + doge := "btc-DOGE" + + obj := Bittrex{} + _, err := obj.GetTicker(invalid) + if err == nil { + t.Error("Test Failed - Bittrex - GetTicker() error") + } + _, err = obj.GetTicker(btc) + if err != nil { + t.Errorf("Test Failed - Bittrex - GetTicker() error: %s", err) + } + _, err = obj.GetTicker(doge) + if err != nil { + t.Errorf("Test Failed - Bittrex - GetTicker() error: %s", err) + } +} + +func TestGetMarketSummaries(t *testing.T) { + obj := Bittrex{} + _, err := obj.GetMarketSummaries() + if err != nil { + t.Errorf("Test Failed - Bittrex - GetMarketSummaries() error: %s", err) + } +} + +func TestGetMarketSummary(t *testing.T) { + pairOne := "BTC-LTC" + invalid := "WigWham" + + obj := Bittrex{} + _, err := obj.GetMarketSummary(pairOne) + if err != nil { + t.Errorf("Test Failed - Bittrex - GetMarketSummary() error: %s", err) + } + _, err = obj.GetMarketSummary(invalid) + if err == nil { + t.Error("Test Failed - Bittrex - GetMarketSummary() error") + } +} + +func TestGetOrderbook(t *testing.T) { + obj := Bittrex{} + _, err := obj.GetOrderbook("btc-ltc") + if err != nil { + t.Errorf("Test Failed - Bittrex - GetOrderbook() error: %s", err) + } + _, err = obj.GetOrderbook("wigwham") + if err == nil { + t.Errorf("Test Failed - Bittrex - GetOrderbook() error") + } +} + +func TestGetMarketHistory(t *testing.T) { + obj := Bittrex{} + _, err := obj.GetMarketHistory("btc-ltc") + if err != nil { + t.Errorf("Test Failed - Bittrex - GetMarketHistory() error: %s", err) + } + _, err = obj.GetMarketHistory("malum") + if err == nil { + t.Errorf("Test Failed - Bittrex - GetMarketHistory() error") + } +} + +func TestPlaceBuyLimit(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.PlaceBuyLimit("btc-ltc", 1, 1) + if err == nil { + t.Error("Test Failed - Bittrex - PlaceBuyLimit() error") + } +} + +func TestPlaceSellLimit(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.PlaceSellLimit("btc-ltc", 1, 1) + if err == nil { + t.Error("Test Failed - Bittrex - PlaceSellLimit() error") + } +} + +func TestGetOpenOrders(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetOpenOrders("") + if err == nil { + t.Error("Test Failed - Bittrex - GetOrder() error") + } + _, err = obj.GetOpenOrders("btc-ltc") + if err == nil { + t.Error("Test Failed - Bittrex - GetOrder() error") + } +} + +func TestCancelOrder(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.CancelOrder("blaaaaaaa") + if err == nil { + t.Error("Test Failed - Bittrex - CancelOrder() error") + } +} + +func TestGetAccountBalances(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetAccountBalances() + if err == nil { + t.Error("Test Failed - Bittrex - GetAccountBalances() error") + } +} + +func TestGetAccountBalanceByCurrency(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetAccountBalanceByCurrency("btc") + if err == nil { + t.Error("Test Failed - Bittrex - GetAccountBalanceByCurrency() error") + } +} + +func TestGetDepositAddress(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetDepositAddress("btc") + if err == nil { + t.Error("Test Failed - Bittrex - GetDepositAddress() error") + } +} + +func TestWithdraw(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.Withdraw("btc", "something", "someplace", 1) + if err == nil { + t.Error("Test Failed - Bittrex - Withdraw() error") + } +} + +func TestGetOrder(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetOrder("0cb4c4e4-bdc7-4e13-8c13-430e587d2cc1") + if err == nil { + t.Error("Test Failed - Bittrex - GetOrder() error") + } + _, err = obj.GetOrder("") + if err == nil { + t.Error("Test Failed - Bittrex - GetOrder() error") + } +} + +func TestGetOrderHistory(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetOrderHistory("") + if err == nil { + t.Error("Test Failed - Bittrex - GetOrderHistory() error") + } + _, err = obj.GetOrderHistory("btc-ltc") + if err == nil { + t.Error("Test Failed - Bittrex - GetOrderHistory() error") + } +} + +func TestGetWithdrawelHistory(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetWithdrawelHistory("") + if err == nil { + t.Error("Test Failed - Bittrex - GetWithdrawelHistory() error") + } + _, err = obj.GetWithdrawelHistory("btc-ltc") + if err == nil { + t.Error("Test Failed - Bittrex - GetWithdrawelHistory() error") + } +} + +func TestGetDepositHistory(t *testing.T) { + obj := Bittrex{} + obj.APIKey = apiKey + obj.APISecret = apiSecret + _, err := obj.GetDepositHistory("") + if err == nil { + t.Error("Test Failed - Bittrex - GetDepositHistory() error") + } + _, err = obj.GetDepositHistory("btc-ltc") + if err == nil { + t.Error("Test Failed - Bittrex - GetDepositHistory() error") + } +} diff --git a/exchanges/bittrex/bittrex_types.go b/exchanges/bittrex/bittrex_types.go new file mode 100644 index 00000000..a94637d0 --- /dev/null +++ b/exchanges/bittrex/bittrex_types.go @@ -0,0 +1,147 @@ +package bittrex + +import "encoding/json" + +// Response is the generalised response type for Bittrex +type Response struct { + Success bool `json:"success"` + Message string `json:"message"` + Result json.RawMessage `json:"result"` +} + +// Market holds current market metadata +type Market struct { + MarketCurrency string `json:"MarketCurrency"` + BaseCurrency string `json:"BaseCurrency"` + MarketCurrencyLong string `json:"MarketCurrencyLong"` + BaseCurrencyLong string `json:"BaseCurrencyLong"` + MinTradeSize float64 `json:"MinTradeSize"` + MarketName string `json:"MarketName"` + IsActive bool `json:"IsActive"` + Created string `json:"Created"` +} + +// Currency holds supported currency metadata +type Currency struct { + Currency string `json:"Currency"` + CurrencyLong string `json:"CurrencyLong"` + MinConfirmation int `json:"MinConfirmation"` + TxFee float64 `json:"TxFee"` + IsActive bool `json:"IsActive"` + CoinType string `json:"CoinType"` + BaseAddress string `json:"BaseAddress"` +} + +// Ticker holds basic ticker information +type Ticker struct { + Bid float64 `json:"Bid"` + Ask float64 `json:"Ask"` + Last float64 `json:"Last"` +} + +// MarketSummary holds last 24 hour metadata of an active exchange +type MarketSummary struct { + MarketName string `json:"MarketName"` + High float64 `json:"High"` + Low float64 `json:"Low"` + Volume float64 `json:"Volume"` + Last float64 `json:"Last"` + BaseVolume float64 `json:"BaseVolume"` + TimeStamp string `json:"TimeStamp"` + Bid float64 `json:"Bid"` + Ask float64 `json:"Ask"` + OpenBuyOrders int `json:"OpenBuyOrders"` + OpenSellOrders int `json:"OpenSellOrders"` + PrevDay float64 `json:"PrevDay"` + Created string `json:"Created"` + DisplayMarketName string `json:"DisplayMarketName"` +} + +// OrderBooks holds an array of buy & sell orders held on the exchange +type OrderBooks struct { + Buy []OrderBook `json:"buy"` + Sell []OrderBook `json:"sell"` +} + +// OrderBook holds a singular order on an exchange +type OrderBook struct { + Quantity float64 `json:"Quantity"` + Rate float64 `json:"Rate"` +} + +// MarketHistory holds an executed trade's data for a market ie "BTC-LTC" +type MarketHistory struct { + ID int `json:"Id"` + Timestamp string `json:"TimeStamp"` + Quantity float64 `json:"Quantity"` + Price float64 `json:"Price"` + Total float64 `json:"Total"` + FillType string `json:"FillType"` + OrderType string `json:"OrderType"` +} + +// Balance holds the balance from your account for a specified currency +type Balance struct { + Currency string `json:"Currency"` + Balance float64 `json:"Balance"` + Available float64 `json:"Available"` + Pending float64 `json:"Pending"` + CryptoAddress string `json:"CryptoAddress"` + Requested bool `json:"Requested"` + UUID string `json:"Uuid"` +} + +// DepositAddress holds a generated address to send specific coins to the +// exchange +type DepositAddress struct { + Currency string `json:"Currency"` + Address string `json:"Address"` +} + +// UUID contains the universal unique identifier for one or multiple +// transactions on the exchange +type UUID struct { + ID string `json:"uuid"` +} + +// Order holds the full order information associated with the UUID supplied +type Order struct { + AccountID string `json:"AccountId"` + OrderUUID string `json:"OrderUuid"` + Exchange string `json:"Exchange"` + Type string `json:"Type"` + Quantity float64 `json:"Quantity"` + QuantityRemaining float64 `json:"QuantityRemaining"` + Limit float64 `json:"Limit"` + Reserved float64 `json:"Reserved"` + ReserveRemaining float64 `json:"ReserveRemaining"` + CommissionReserved float64 `json:"CommissionReserved"` + CommissionReserveRemaining float64 `json:"CommissionReserveRemaining"` + CommissionPaid float64 `json:"CommissionPaid"` + Price float64 `json:"Price"` + PricePerUnit float64 `json:"PricePerUnit"` + Opened string `json:"Opened"` + Closed string `json:"Closed"` + IsOpen bool `json:"IsOpen"` + Sentinel string `json:"Sentinel"` + CancelInitiated bool `json:"CancelInitiated"` + ImmediateOrCancel bool `json:"ImmediateOrCancel"` + IsConditional bool `json:"IsConditional"` + Condition string `json:"Condition"` + ConditionTarget string `json:"ConditionTarget"` +} + +// WithdrawalHistory holds the Withdrawal history data +type WithdrawalHistory struct { + PaymentUUID string `json:"PaymentUuid"` + Currency string `json:"Currency"` + Amount float64 `json:"Amount"` + Address string `json:"Address"` + Opened string `json:"Opened"` + Authorized bool `json:"Authorized"` + PendingPayment bool `json:"PendingPayment"` + TxCost float64 `json:"TxCost"` + TxID string `json:"TxId"` + Canceled bool `json:"Canceled"` + InvalidAddress bool `json:"InvalidAddress"` +} diff --git a/exchanges/btcc/btcc_wrapper.go b/exchanges/btcc/btcc_wrapper.go index 0ed067a1..c7c868ca 100644 --- a/exchanges/btcc/btcc_wrapper.go +++ b/exchanges/btcc/btcc_wrapper.go @@ -52,6 +52,10 @@ func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) { var tickerPrice ticker.TickerPrice tick, err := b.GetTicker(p.Pair().Lower().String()) + if err != nil { + return tickerPrice, err + } + tickerPrice.Pair = p tickerPrice.Ask = tick.Sell tickerPrice.Bid = tick.Buy @@ -75,12 +79,12 @@ func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(ob.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(ob.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } @@ -92,8 +96,8 @@ func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err //TODO: Retrieve BTCC info //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Kraken exchange -func (e *BTCC) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +func (b *BTCC) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() + response.ExchangeName = b.GetName() return response, nil } diff --git a/exchanges/btce/btce_wrapper.go b/exchanges/btce/btce_wrapper.go index 04cdffa8..c3d6bed9 100644 --- a/exchanges/btce/btce_wrapper.go +++ b/exchanges/btce/btce_wrapper.go @@ -78,12 +78,12 @@ func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(ob.Bids, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(ob.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]}) } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 97903d82..f68d8eac 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -272,11 +272,10 @@ func (b *BTCMarkets) GetAccountBalance() ([]BTCMarketsAccountBalance, error) { return nil, err } + // All values are returned in Satoshis, even for fiat currencies. for i := range balance { - if balance[i].Currency == "LTC" || balance[i].Currency == "BTC" { - balance[i].Balance = balance[i].Balance / common.SatoshisPerBTC - balance[i].PendingFunds = balance[i].PendingFunds / common.SatoshisPerBTC - } + balance[i].Balance = balance[i].Balance / common.SatoshisPerBTC + balance[i].PendingFunds = balance[i].PendingFunds / common.SatoshisPerBTC } return balance, nil } @@ -317,7 +316,7 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interfa } if b.Verbose { - log.Printf("Recieved raw: %s\n", resp) + log.Printf("Received raw: %s\n", resp) } err = common.JSONDecode([]byte(resp), &result) diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 52c279a9..ee19d7be 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -73,12 +73,12 @@ func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBas return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index c85e857c..1b6fc0f8 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -14,7 +14,7 @@ import ( const ( COINUT_API_URL = "https://api.coinut.com" - COINUT_API_VERISON = "1" + COINUT_API_VERSION = "1" COINUT_INSTRUMENTS = "inst_list" COINUT_TICKER = "inst_tick" COINUT_ORDERBOOK = "inst_order_book" @@ -299,7 +299,7 @@ func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[stri resp, err := common.SendHTTPRequest("POST", COINUT_API_URL, headers, bytes.NewBuffer(payload)) if c.Verbose { - log.Printf("Recieved raw: \n%s", resp) + log.Printf("Received raw: \n%s", resp) } genResp := CoinutGenericResponse{} @@ -307,17 +307,17 @@ func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[stri err = common.JSONDecode([]byte(resp), &genResp) if err != nil { - return errors.New("Unable to JSON Unmarshal generic response.") + return errors.New("unable to JSON Unmarshal generic response") } if genResp.Status[0] != "OK" { - return errors.New("Status is not OK.") + return errors.New("status is not OK") } err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index 856f4f52..ba294a82 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -16,7 +16,7 @@ import ( const ( GDAX_API_URL = "https://api.gdax.com/" - GDAX_API_VERISON = "0" + GDAX_API_VERSION = "0" GDAX_PRODUCTS = "products" GDAX_ORDERBOOK = "book" GDAX_TICKER = "ticker" @@ -398,13 +398,13 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri resp, err := common.SendHTTPRequest(method, GDAX_API_URL+path, headers, bytes.NewBuffer(payload)) if g.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/gdax/gdax_types.go b/exchanges/gdax/gdax_types.go index 61118d4f..c14894c9 100644 --- a/exchanges/gdax/gdax_types.go +++ b/exchanges/gdax/gdax_types.go @@ -31,13 +31,13 @@ type GDAXOrderL3 struct { type GDAXOrderbookL1L2 struct { Sequence int64 `json:"sequence"` - Bids []GDAXOrderL1L2 `json:"asks"` + Bids []GDAXOrderL1L2 `json:"bids"` Asks []GDAXOrderL1L2 `json:"asks"` } type GDAXOrderbookL3 struct { Sequence int64 `json:"sequence"` - Bids []GDAXOrderL3 `json:"asks"` + Bids []GDAXOrderL3 `json:"bids"` Asks []GDAXOrderL3 `json:"asks"` } diff --git a/exchanges/gdax/gdax_wrapper.go b/exchanges/gdax/gdax_wrapper.go index 5953d5db..c68dab0c 100644 --- a/exchanges/gdax/gdax_wrapper.go +++ b/exchanges/gdax/gdax_wrapper.go @@ -63,10 +63,10 @@ func (g *GDAX) Run() { } //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the GDAX exchange -func (e *GDAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +func (g *GDAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() - accountBalance, err := e.GetAccounts() + response.ExchangeName = g.GetName() + accountBalance, err := g.GetAccounts() if err != nil { return response, err } @@ -122,11 +122,11 @@ func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, err obNew := orderbookNew.(GDAXOrderbookL1L2) - for x, _ := range obNew.Bids { + for x := range obNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) } - for x, _ := range obNew.Asks { + for x := range obNew.Asks { orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price}) } orderBook.Pair = p diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index a0d923fe..ef2f40a4 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -275,13 +275,13 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st resp, err := common.SendHTTPRequest(method, GEMINI_API_URL+path, headers, strings.NewReader("")) if g.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index c723a2ab..a392cebf 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -100,11 +100,11 @@ func (g *Gemini) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, e return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index deede501..672bb8d3 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -198,7 +198,7 @@ func (h *HUOBI) SendAuthenticatedRequest(method string, v url.Values) error { } if h.Verbose { - log.Printf("Recieved raw: %s\n", resp) + log.Printf("Received raw: %s\n", resp) } return nil diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index f4a57b1e..038d10fa 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -83,12 +83,12 @@ func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 9f275f6c..e051e099 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -51,27 +51,26 @@ func (i *ItBit) Setup(exch config.ExchangeConfig) { func (i *ItBit) GetFee(maker bool) float64 { if maker { return i.MakerFee - } else { - return i.TakerFee } + return i.TakerFee } -func (i *ItBit) GetTicker(currency string) (ItBitTicker, error) { +func (i *ItBit) GetTicker(currency string) (Ticker, error) { path := ITBIT_API_URL + "/markets/" + currency + "/ticker" - var itbitTicker ItBitTicker + var itbitTicker Ticker err := common.SendHTTPGetRequest(path, true, &itbitTicker) if err != nil { - return ItBitTicker{}, err + return Ticker{}, err } return itbitTicker, nil } -func (i *ItBit) GetOrderbook(currency string) (ItBitOrderbookResponse, error) { - response := ItBitOrderbookResponse{} +func (i *ItBit) GetOrderbook(currency string) (OrderbookResponse, error) { + response := OrderbookResponse{} path := ITBIT_API_URL + "/markets/" + currency + "/order_book" err := common.SendHTTPGetRequest(path, true, &response) if err != nil { - return ItBitOrderbookResponse{}, err + return OrderbookResponse{}, err } return response, nil } @@ -234,7 +233,7 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params return err } - nonce -= 1 + nonce-- request := make(map[string]interface{}) url := ITBIT_API_URL + path @@ -244,22 +243,22 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } } - PayloadJson := []byte("") + PayloadJSON := []byte("") if params != nil { - PayloadJson, err = common.JSONEncode(request) + PayloadJSON, err = common.JSONEncode(request) if err != nil { return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON Marshal request") } if i.Verbose { - log.Printf("Request JSON: %s\n", PayloadJson) + log.Printf("Request JSON: %s\n", PayloadJSON) } } nonceStr := strconv.Itoa(nonce) - message, err := common.JSONEncode([]string{method, url, string(PayloadJson), nonceStr, timestamp}) + message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), nonceStr, timestamp}) if err != nil { log.Println(err) return @@ -275,10 +274,10 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params headers["X-Auth-Nonce"] = nonceStr headers["Content-Type"] = "application/json" - resp, err := common.SendHTTPRequest(method, url, headers, bytes.NewBuffer([]byte(PayloadJson))) + resp, err := common.SendHTTPRequest(method, url, headers, bytes.NewBuffer([]byte(PayloadJSON))) if i.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } return nil } diff --git a/exchanges/itbit/itbit_types.go b/exchanges/itbit/itbit_types.go index fdfc96f8..9613404d 100644 --- a/exchanges/itbit/itbit_types.go +++ b/exchanges/itbit/itbit_types.go @@ -1,6 +1,6 @@ package itbit -type ItBitTicker struct { +type Ticker struct { Pair string Bid float64 `json:",string"` BidAmt float64 `json:",string"` @@ -20,7 +20,7 @@ type ItBitTicker struct { ServertimeUTC string } -type ItBitOrderbookResponse struct { +type OrderbookResponse struct { Bids [][]string `json:"bids"` Asks [][]string `json:"asks"` } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 99bec337..d6276475 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -73,7 +73,7 @@ func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] price, err := strconv.ParseFloat(data[0], 64) if err != nil { @@ -86,7 +86,7 @@ func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: amount, Price: price}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] price, err := strconv.ParseFloat(data[0], 64) if err != nil { @@ -105,8 +105,8 @@ func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er //TODO Get current holdings from ItBit //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the ItBit exchange -func (e *ItBit) GetExchangeAccountInfo() (exchange.AccountInfo, error) { +func (i *ItBit) GetExchangeAccountInfo() (exchange.AccountInfo, error) { var response exchange.AccountInfo - response.ExchangeName = e.GetName() + response.ExchangeName = i.GetName() return response, nil } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 36cf5dde..bee3ea15 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -535,7 +535,7 @@ func (k *Kraken) SendAuthenticatedHTTPRequest(method string, values url.Values) } if k.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } return resp, nil diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 83f2c01e..ecb50f29 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -301,7 +301,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int } if l.Verbose { - log.Printf("Recieved raw: %s\n", resp) + log.Printf("Received raw: %s\n", resp) } type ErrorResponse struct { @@ -311,7 +311,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int errResponse := ErrorResponse{} err = common.JSONDecode([]byte(resp), &errResponse) if err != nil { - return errors.New("Unable to check response for error.") + return errors.New("unable to check response for error") } if errResponse.Error != "" { @@ -321,7 +321,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/lakebtc/lakebtc_wrapper.go b/exchanges/lakebtc/lakebtc_wrapper.go index 36b65e5d..0d7ec64a 100644 --- a/exchanges/lakebtc/lakebtc_wrapper.go +++ b/exchanges/lakebtc/lakebtc_wrapper.go @@ -77,11 +77,11 @@ func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price}) } diff --git a/exchanges/liqui/liqui_wrapper.go b/exchanges/liqui/liqui_wrapper.go index 8eb59312..67a293e8 100644 --- a/exchanges/liqui/liqui_wrapper.go +++ b/exchanges/liqui/liqui_wrapper.go @@ -90,12 +90,12 @@ func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 47ffd5df..43ad14a7 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -286,13 +286,13 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values resp, err := common.SendHTTPRequest(method, LOCALBITCOINS_API_URL+path, headers, bytes.NewBuffer([]byte(payload))) if l.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index d13feb8f..6a7d531f 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -898,13 +898,13 @@ func (o *OKCoin) SendAuthenticatedHTTPRequest(method string, v url.Values, resul } if o.Verbose { - log.Printf("Recieved raw: \n%s\n", resp) + log.Printf("Received raw: \n%s\n", resp) } err = common.JSONDecode([]byte(resp), &result) if err != nil { - return errors.New("Unable to JSON Unmarshal response.") + return errors.New("unable to JSON Unmarshal response") } return nil diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 58094685..a538f9da 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -108,12 +108,12 @@ func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.Orderbook return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data[1], Price: data[0]}) } diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index af016140..a895d787 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -122,15 +122,15 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbo } ob := PoloniexOrderbook{} - for x, _ := range resp.Asks { + for x := range resp.Asks { data := resp.Asks[x] price, _ := strconv.ParseFloat(data[0].(string), 64) amount := data[1].(float64) ob.Asks = append(ob.Asks, PoloniexOrderbookItem{Price: price, Amount: amount}) } - for x, _ := range resp.Bids { - data := resp.Asks[x] + for x := range resp.Bids { + data := resp.Bids[x] price, _ := strconv.ParseFloat(data[0].(string), 64) amount := data[1].(float64) ob.Bids = append(ob.Bids, PoloniexOrderbookItem{Price: price, Amount: amount}) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 69c6ea74..ce04d372 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -81,12 +81,12 @@ func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair) (orderbook.Ord return orderBook, err } - for x, _ := range orderbookNew.Bids { + for x := range orderbookNew.Bids { data := orderbookNew.Bids[x] orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) } - for x, _ := range orderbookNew.Asks { + for x := range orderbookNew.Asks { data := orderbookNew.Asks[x] orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: data.Amount, Price: data.Price}) } diff --git a/exchanges/stats/stats.go b/exchanges/stats/stats.go index 3578211e..5d477e62 100644 --- a/exchanges/stats/stats.go +++ b/exchanges/stats/stats.go @@ -71,7 +71,7 @@ func AppendExchangeInfo(exchange, crypto, fiat string, price, volume float64) { } func ExchangeInfoAlreadyExists(exchange, crypto, fiat string, price, volume float64) bool { - for i, _ := range ExchInfo { + for i := range ExchInfo { if ExchInfo[i].Exchange == exchange && ExchInfo[i].FirstCurrency == crypto && ExchInfo[i].FiatCurrency == fiat { ExchInfo[i].Price, ExchInfo[i].Volume = price, volume return true diff --git a/main.go b/main.go index 5e30c2d7..935b8121 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "log" "net/http" "os" @@ -60,12 +61,13 @@ type ExchangeMain struct { // Bot contains configuration, portfolio, exchange & ticker data and is the // overarching type across this code base. type Bot struct { - config *config.Config - portfolio *portfolio.Base - exchange ExchangeMain - exchanges []exchange.IBotExchange - tickers []ticker.Ticker - shutdown chan bool + config *config.Config + portfolio *portfolio.Base + exchange ExchangeMain + exchanges []exchange.IBotExchange + tickers []ticker.Ticker + shutdown chan bool + configFile string } var bot Bot @@ -98,10 +100,15 @@ func setupBotExchanges() { func main() { HandleInterrupt() - bot.config = &config.Cfg - log.Printf("Loading config file %s..\n", config.ConfigFile) - err := bot.config.LoadConfig("") + //Handle flags + flag.StringVar(&bot.configFile, "config", config.GetFilePath(""), "config file to load") + flag.Parse() + + bot.config = &config.Cfg + log.Printf("Loading config file %s..\n", bot.configFile) + + err := bot.config.LoadConfig(bot.configFile) if err != nil { log.Fatal(err) } @@ -238,7 +245,7 @@ func HandleInterrupt() { func Shutdown() { log.Println("Bot shutting down..") bot.config.Portfolio = portfolio.Portfolio - err := bot.config.SaveConfig("") + err := bot.config.SaveConfig(bot.configFile) if err != nil { log.Println("Unable to save config.") @@ -266,18 +273,27 @@ func SeedExchangeAccountInfo(data []exchange.AccountInfo) { avail := data[i].Currencies[j].TotalValue total := onHold + avail - if total <= 0 { - continue - } - if !port.ExchangeAddressExists(exchangeName, currencyName) { + if total <= 0 { + continue + } + log.Printf("Portfolio: Adding new exchange address: %s, %s, %f, %s\n", + exchangeName, currencyName, total, portfolio.PortfolioAddressExchange) port.Addresses = append( port.Addresses, portfolio.Address{Address: exchangeName, CoinType: currencyName, - Balance: total, Decscription: portfolio.PortfolioAddressExchange}, + Balance: total, Description: portfolio.PortfolioAddressExchange}, ) } else { - port.UpdateExchangeAddressBalance(exchangeName, currencyName, total) + if total <= 0 { + log.Printf("Portfolio: Removing %s %s entry.\n", exchangeName, + currencyName) + port.RemoveExchangeAddress(exchangeName, currencyName) + } else { + log.Printf("Portfolio: Updating %s %s entry with balance %f.\n", + exchangeName, currencyName, total) + port.UpdateExchangeAddressBalance(exchangeName, currencyName, total) + } } } } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index 5b5f9a51..ddff2170 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -16,9 +16,10 @@ const ( etherchainAPIURL = "https://etherchain.org/api" etherchainAccountMultiple = "account/multiple" - // PortfolioAddressExchange holds the current portfolio address + // PortfolioAddressExchange is a label for an exchange address PortfolioAddressExchange = "Exchange" - portfolioAddressPersonal = "Personal" + // PortfolioAddressPersonal is a label for a personal/offline address + PortfolioAddressPersonal = "Personal" ) // Portfolio is variable store holding an array of portfolioAddress @@ -31,10 +32,10 @@ type Base struct { // Address sub type holding address information for portfolio type Address struct { - Address string - CoinType string - Balance float64 - Decscription string + Address string + CoinType string + Balance float64 + Description string } // BlockrAddress holds JSON incoming and outgoing data for BLOCKR with address @@ -172,9 +173,9 @@ func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBa // GetAddressBalance acceses the portfolio base and returns the balance by passed // in address func (p *Base) GetAddressBalance(address string) (float64, bool) { - for _, x := range p.Addresses { - if x.Address == address { - return x.Balance, true + for x := range p.Addresses { + if p.Addresses[x].Address == address { + return p.Addresses[x].Balance, true } } return 0, false @@ -182,8 +183,8 @@ func (p *Base) GetAddressBalance(address string) (float64, bool) { // ExchangeExists checks to see if an exchange exists in the portfolio base func (p *Base) ExchangeExists(exchangeName string) bool { - for _, x := range p.Addresses { - if x.Address == exchangeName { + for x := range p.Addresses { + if p.Addresses[x].Address == exchangeName { return true } } @@ -193,8 +194,8 @@ func (p *Base) ExchangeExists(exchangeName string) bool { // AddressExists checks to see if there is an address associated with the // portfolio base func (p *Base) AddressExists(address string) bool { - for _, x := range p.Addresses { - if x.Address == address { + for x := range p.Addresses { + if p.Addresses[x].Address == address { return true } } @@ -204,8 +205,8 @@ func (p *Base) AddressExists(address string) bool { // ExchangeAddressExists checks to see if there is an exchange address // associated with the portfolio base func (p *Base) ExchangeAddressExists(exchangeName, coinType string) bool { - for _, x := range p.Addresses { - if x.Address == exchangeName && x.CoinType == coinType { + for x := range p.Addresses { + if p.Addresses[x].Address == exchangeName && p.Addresses[x].CoinType == coinType { return true } } @@ -221,6 +222,16 @@ func (p *Base) UpdateAddressBalance(address string, amount float64) { } } +// RemoveExchangeAddress removes an exchange address from the portfolio. +func (p *Base) RemoveExchangeAddress(exchangeName, coinType string) { + for x := range p.Addresses { + if p.Addresses[x].Address == exchangeName && p.Addresses[x].CoinType == coinType { + p.Addresses = append(p.Addresses[:x], p.Addresses[x+1:]...) + return + } + } +} + // UpdateExchangeAddressBalance updates the portfolio balance when checked // against correct exchangeName and coinType. func (p *Base) UpdateExchangeAddressBalance(exchangeName, coinType string, balance float64) { @@ -236,16 +247,31 @@ func (p *Base) AddAddress(address, coinType, description string, balance float64 if !p.AddressExists(address) { p.Addresses = append( p.Addresses, Address{Address: address, CoinType: coinType, - Balance: balance, Decscription: description}, + Balance: balance, Description: description}, ) } else { - p.UpdateAddressBalance(address, balance) + if balance <= 0 { + p.RemoveAddress(address, coinType, description) + } else { + p.UpdateAddressBalance(address, balance) + } + } +} + +// RemoveAddress removes an address when checked against the correct address and +// coinType +func (p *Base) RemoveAddress(address, coinType, description string) { + for x := range p.Addresses { + if p.Addresses[x].Address == address && p.Addresses[x].CoinType == coinType && p.Addresses[x].Description == description { + p.Addresses = append(p.Addresses[:x], p.Addresses[x+1:]...) + return + } } } // UpdatePortfolio adds to the portfolio addresses by coin type func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool { - if common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressExchange) || common.StringContains(common.JoinStrings(addresses, ","), portfolioAddressPersonal) { + if common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressExchange) || common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressPersonal) { return true } @@ -256,7 +282,7 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool { } for _, x := range result.Data { - p.AddAddress(x.Address, coinType, portfolioAddressPersonal, x.Balance) + p.AddAddress(x.Address, coinType, PortfolioAddressPersonal, x.Balance) } return true } @@ -266,7 +292,7 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool { return false } for _, x := range result.Data { - p.AddAddress(x.Address, coinType, portfolioAddressPersonal, x.Balance) + p.AddAddress(x.Address, coinType, PortfolioAddressPersonal, x.Balance) } } else { result, err := GetBlockrBalanceSingle(addresses[0], coinType) @@ -274,17 +300,28 @@ func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool { return false } p.AddAddress( - addresses[0], coinType, portfolioAddressPersonal, result.Data.Balance, + addresses[0], coinType, PortfolioAddressPersonal, result.Data.Balance, ) } return true } +// GetPortfolioByExchange returns currency portfolio amount by exchange +func (p *Base) GetPortfolioByExchange(exchangeName string) map[string]float64 { + result := make(map[string]float64) + for x := range p.Addresses { + if common.StringContains(p.Addresses[x].Address, exchangeName) { + result[p.Addresses[x].CoinType] = p.Addresses[x].Balance + } + } + return result +} + // GetExchangePortfolio returns current portfolio base information func (p *Base) GetExchangePortfolio() map[string]float64 { result := make(map[string]float64) for _, x := range p.Addresses { - if x.Decscription != PortfolioAddressExchange { + if x.Description != PortfolioAddressExchange { continue } balance, ok := result[x.CoinType] @@ -301,7 +338,7 @@ func (p *Base) GetExchangePortfolio() map[string]float64 { func (p *Base) GetPersonalPortfolio() map[string]float64 { result := make(map[string]float64) for _, x := range p.Addresses { - if x.Decscription == PortfolioAddressExchange { + if x.Description == PortfolioAddressExchange { continue } balance, ok := result[x.CoinType] @@ -314,28 +351,167 @@ func (p *Base) GetPersonalPortfolio() map[string]float64 { return result } -// GetPortfolioSummary rpoves a summary for your portfolio base -func (p *Base) GetPortfolioSummary(coinFilter string) map[string]float64 { - result := make(map[string]float64) - for _, x := range p.Addresses { - if coinFilter != "" && coinFilter != x.CoinType { - continue +// getPercentage returns the percentage of the target coin amount against the +// total coin amount. +func getPercentage(input map[string]float64, target string, totals map[string]float64) float64 { + subtotal, _ := input[target] + total, _ := totals[target] + percentage := (subtotal / total) * 100 / 1 + return percentage +} + +// getPercentage returns the percentage a specific value of a target coin amount +// against the total coin amount. +func getPercentageSpecific(input float64, target string, totals map[string]float64) float64 { + total, _ := totals[target] + percentage := (input / total) * 100 / 1 + return percentage +} + +// Coin stores a coin type, balance, address and percentage relative to the total +// amount. +type Coin struct { + Coin string `json:"coin"` + Balance float64 `json:"balance"` + Address string `json:"address,omitempty"` + Percentage float64 `json:"percentage,omitempty"` +} + +// OfflineCoinSummary stores a coin types address, balance and percentage +// relative to the total amount. +type OfflineCoinSummary struct { + Address string `json:"address"` + Balance float64 `json:"balance"` + Percentage float64 `json:"percentage,omitempty"` +} + +// OnlineCoinSummary stores a coin types balance and percentage relative to the +// total amount. +type OnlineCoinSummary struct { + Balance float64 `json:"balance"` + Percentage float64 `json:"percentage,omitempty"` +} + +// Summary Stores the entire portfolio summary +type Summary struct { + Totals []Coin `json:"coin_totals"` + Offline []Coin `json:"coins_offline"` + OfflineSummary map[string][]OfflineCoinSummary `json:"offline_summary"` + Online []Coin `json:"coins_online"` + OnlineSummary map[string]map[string]OnlineCoinSummary `json:"online_summary"` +} + +// GetPortfolioSummary returns the complete portfolio summary, showing +// coin totals, offline and online summaries with their relative percentages. +func (p *Base) GetPortfolioSummary() Summary { + personalHoldings := p.GetPersonalPortfolio() + exchangeHoldings := p.GetExchangePortfolio() + totalCoins := make(map[string]float64) + + for x, y := range personalHoldings { + if x == "ETH" { + y = y / common.WeiPerEther + personalHoldings[x] = y } - balance, ok := result[x.CoinType] + balance, ok := totalCoins[x] if !ok { - result[x.CoinType] = x.Balance + totalCoins[x] = y } else { - result[x.CoinType] = x.Balance + balance + totalCoins[x] = y + balance } } - return result + + for x, y := range exchangeHoldings { + balance, ok := totalCoins[x] + if !ok { + totalCoins[x] = y + } else { + totalCoins[x] = y + balance + } + } + + var portfolioOutput Summary + for x, y := range totalCoins { + coins := Coin{Coin: x, Balance: y} + portfolioOutput.Totals = append(portfolioOutput.Totals, coins) + } + + for x, y := range personalHoldings { + coins := Coin{ + Coin: x, + Balance: y, + Percentage: getPercentage(personalHoldings, x, totalCoins), + } + portfolioOutput.Offline = append(portfolioOutput.Offline, coins) + } + + for x, y := range exchangeHoldings { + coins := Coin{ + Coin: x, + Balance: y, + Percentage: getPercentage(exchangeHoldings, x, totalCoins), + } + portfolioOutput.Online = append(portfolioOutput.Online, coins) + } + + var portfolioExchanges []string + for _, x := range p.Addresses { + if x.Description == PortfolioAddressExchange { + if !common.DataContains(portfolioExchanges, x.Address) { + portfolioExchanges = append(portfolioExchanges, x.Address) + } + } + } + + exchangeSummary := make(map[string]map[string]OnlineCoinSummary) + for x := range portfolioExchanges { + exchgName := portfolioExchanges[x] + result := p.GetPortfolioByExchange(exchgName) + + coinSummary := make(map[string]OnlineCoinSummary) + for y, z := range result { + coinSum := OnlineCoinSummary{ + Balance: z, + Percentage: getPercentageSpecific(z, y, totalCoins), + } + coinSummary[y] = coinSum + + } + exchangeSummary[exchgName] = coinSummary + } + portfolioOutput.OnlineSummary = exchangeSummary + + offlineSummary := make(map[string][]OfflineCoinSummary) + for _, x := range p.Addresses { + if x.Description != PortfolioAddressExchange { + if x.CoinType == "ETH" { + x.Balance = x.Balance / common.WeiPerEther + } + coinSummary := OfflineCoinSummary{ + Address: x.Address, + Balance: x.Balance, + Percentage: getPercentageSpecific(x.Balance, x.CoinType, + totalCoins), + } + result, ok := offlineSummary[x.CoinType] + if !ok { + offlineSummary[x.CoinType] = append(offlineSummary[x.CoinType], + coinSummary) + } else { + result = append(result, coinSummary) + offlineSummary[x.CoinType] = result + } + } + } + portfolioOutput.OfflineSummary = offlineSummary + return portfolioOutput } // GetPortfolioGroupedCoin returns portfolio base information grouped by coin func (p *Base) GetPortfolioGroupedCoin() map[string][]string { result := make(map[string][]string) for _, x := range p.Addresses { - if common.StringContains(x.Decscription, PortfolioAddressExchange) || common.StringContains(x.Decscription, portfolioAddressPersonal) { + if common.StringContains(x.Description, PortfolioAddressExchange) { continue } result[x.CoinType] = append(result[x.CoinType], x.Address) diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index c815a5ab..3a966481 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -153,12 +153,43 @@ func TestUpdateAddressBalance(t *testing.T) { newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) newbase.UpdateAddressBalance("someaddress", 0.03) - value := newbase.GetPortfolioSummary("LTC") - if value["LTC"] != 0.03 { + value := newbase.GetPortfolioSummary() + if value.Totals[0].Coin != "LTC" && value.Totals[0].Balance != 0.03 { t.Error("Test Failed - portfolio_test.go - UpdateUpdateAddressBalance error") } } +func TestRemoveAddress(t *testing.T) { + newbase := Base{} + newbase.AddAddress("someaddr", "LTC", "LTCWALLETTEST", 420) + + if !newbase.AddressExists("someaddr") { + t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + } + + newbase.RemoveAddress("someaddr", "LTC", "LTCWALLETTEST") + if newbase.AddressExists("someaddr") { + t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + } +} + +func TestRemoveExchangeAddress(t *testing.T) { + newbase := Base{} + exchangeName := "BallerExchange" + coinType := "LTC" + + newbase.AddAddress(exchangeName, coinType, PortfolioAddressExchange, 420) + + if !newbase.ExchangeAddressExists(exchangeName, coinType) { + t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + } + + newbase.RemoveExchangeAddress(exchangeName, coinType) + if newbase.ExchangeAddressExists(exchangeName, coinType) { + t.Error("Test failed - portfolio_test.go - TestRemoveAddress") + } +} + func TestUpdateExchangeAddressBalance(t *testing.T) { newbase := Base{} newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) @@ -166,18 +197,25 @@ func TestUpdateExchangeAddressBalance(t *testing.T) { portfolio.SeedPortfolio(newbase) portfolio.UpdateExchangeAddressBalance("someaddress", "LTC", 0.04) - value := portfolio.GetPortfolioSummary("LTC") - if value["LTC"] != 0.04 { + value := portfolio.GetPortfolioSummary() + if value.Totals[0].Coin != "LTC" && value.Totals[0].Balance != 0.04 { t.Error("Test Failed - portfolio_test.go - UpdateExchangeAddressBalance error") } } func TestAddAddress(t *testing.T) { newbase := Base{} - newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + newbase.AddAddress("Gibson", "LTC", "LTCWALLETTEST", 0.02) portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) - if !portfolio.AddressExists("someaddress") { + if !portfolio.AddressExists("Gibson") { + t.Error("Test Failed - portfolio_test.go - AddAddress error") + } + + // Test updating balance to <= 0, expected result is to remove the address. + // Fail if address still exists. + newbase.AddAddress("Gibson", "LTC", "LTCWALLETTEST", -1) + if newbase.AddressExists("Gibson") { t.Error("Test Failed - portfolio_test.go - AddAddress error") } } @@ -224,50 +262,128 @@ func TestUpdatePortfolio(t *testing.T) { if value { t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") } + + value = portfolio.UpdatePortfolio( + []string{PortfolioAddressExchange, PortfolioAddressPersonal}, "LTC") + + if !value { + t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + } +} + +func TestGetPortfolioByExchange(t *testing.T) { + newbase := Base{} + newbase.AddAddress("ANX", "LTC", PortfolioAddressExchange, 0.07) + newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 0.05) + newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 0.03) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + value := portfolio.GetPortfolioByExchange("ANX") + result, ok := value["LTC"] + if !ok { + t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange error") + } + + if result != 0.07 { + t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange result != 0.10") + } + + value = portfolio.GetPortfolioByExchange("Bitfinex") + result, ok = value["LTC"] + if !ok { + t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange error") + } + + if result != 0.05 { + t.Error("Test Failed - portfolio_test.go - GetPortfolioByExchange result != 0.05") + } } func TestGetExchangePortfolio(t *testing.T) { newbase := Base{} - newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + newbase.AddAddress("ANX", "LTC", PortfolioAddressExchange, 0.03) + newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 0.05) + newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 0.03) portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) value := portfolio.GetExchangePortfolio() - _, ok := value["ANX"] - if ok { + + result, ok := value["LTC"] + if !ok { t.Error("Test Failed - portfolio_test.go - GetExchangePortfolio error") } + + if result != 0.08 { + t.Error("Test Failed - portfolio_test.go - GetExchangePortfolio result != 0.08") + } } func TestGetPersonalPortfolio(t *testing.T) { newbase := Base{} newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + newbase.AddAddress("anotheraddress", "LTC", "LTCWALLETTEST", 0.03) + newbase.AddAddress("Exchange", "LTC", PortfolioAddressExchange, 0.01) portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) value := portfolio.GetPersonalPortfolio() - _, ok := value["LTC"] + result, ok := value["LTC"] if !ok { t.Error("Test Failed - portfolio_test.go - GetPersonalPortfolio error") } + + if result != 0.05 { + t.Error("Test Failed - portfolio_test.go - GetPersonalPortfolio result != 0.05") + } } func TestGetPortfolioSummary(t *testing.T) { newbase := Base{} - newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + // Personal holdings + newbase.AddAddress("someaddress", "LTC", PortfolioAddressPersonal, 1) + newbase.AddAddress("0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", "ETH", + PortfolioAddressPersonal, 865346880000000000) + newbase.AddAddress("0x9edc81c813b26165f607a8d1b8db87a02f34307f", "ETH", + PortfolioAddressPersonal, 165346880000000000) + + // Exchange holdings + newbase.AddAddress("Bitfinex", "LTC", PortfolioAddressExchange, 20) + newbase.AddAddress("Bitfinex", "BTC", PortfolioAddressExchange, 100) + newbase.AddAddress("ANX", "ETH", PortfolioAddressExchange, 42) + portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) - value := portfolio.GetPortfolioSummary("LTC") - if value["LTC"] != 0.02 { - t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error") + value := portfolio.GetPortfolioSummary() + + getTotalsVal := func(s string) Coin { + for x := range value.Totals { + if value.Totals[x].Coin == s { + return value.Totals[x] + } + } + return Coin{} + } + + if getTotalsVal("LTC").Coin != "LTC" { + t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") + } + + if getTotalsVal("ETH").Coin != "ETH" { + t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") + } + + if getTotalsVal("LTC").Balance != 101 { + t.Error("Test Failed - portfolio_test.go - TestGetPortfolioSummary error") } } func TestGetPortfolioGroupedCoin(t *testing.T) { newbase := Base{} newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + newbase.AddAddress("Exchange", "LTC", PortfolioAddressExchange, 0.05) portfolio := GetPortfolio() portfolio.SeedPortfolio(newbase) value := portfolio.GetPortfolioGroupedCoin() - if value["LTC"][0] != "someaddress" { + if value["LTC"][0] != "someaddress" && len(value["LTC"][0]) != 1 { t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error") } } diff --git a/portfolio_routes.go b/portfolio_routes.go new file mode 100644 index 00000000..d2117160 --- /dev/null +++ b/portfolio_routes.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "net/http" +) + +// RESTGetPortfolio replies to a request with an encoded JSON response of the +// portfolio +func RESTGetPortfolio(w http.ResponseWriter, r *http.Request) { + result := bot.portfolio.GetPortfolioSummary() + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(result); err != nil { + panic(err) + } +} + +// PortfolioRoutes declares the current routes for config_routes.go +var PortfolioRoutes = Routes{ + Route{ + "GetPortfolio", + "GET", + "/portfolio/all", + RESTGetPortfolio, + }, +} diff --git a/restful_router.go b/restful_router.go index bc97ff40..350ba9c9 100644 --- a/restful_router.go +++ b/restful_router.go @@ -13,6 +13,7 @@ func NewRouter(exchanges []exchange.IBotExchange) *mux.Router { router := mux.NewRouter().StrictSlash(true) allRoutes := append(routes, ExchangeRoutes...) allRoutes = append(allRoutes, ConfigRoutes...) + allRoutes = append(allRoutes, PortfolioRoutes...) allRoutes = append(allRoutes, WalletRoutes...) for _, route := range allRoutes { var handler http.Handler diff --git a/tools/portfolio/portfolio.go b/tools/portfolio/portfolio.go index 3df8cf1a..a7b806fb 100644 --- a/tools/portfolio/portfolio.go +++ b/tools/portfolio/portfolio.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "log" "net/url" @@ -12,89 +13,152 @@ import ( "github.com/thrasher-/gocryptotrader/portfolio" ) +var ( + priceMap map[string]float64 +) + +func printSummary(msg, from, to string, amount float64) { + log.Println() + log.Println(fmt.Sprintf("%s in USD: $%.2f", msg, amount)) + conv, err := currency.ConvertCurrency(amount, "USD", "AUD") + if err != nil { + log.Println(err) + } else { + log.Println(fmt.Sprintf("%s in AUD: $%.2f", msg, conv)) + } + log.Println() +} + +func getOnlineOfflinePortfolio(coins []portfolio.Coin, online bool) { + var totals float64 + for _, x := range coins { + value := priceMap[x.Coin] * x.Balance + totals += value + log.Printf("\t%v %v Subtotal: $%.2f Coin percentage: %.2f%%\n", x.Coin, + x.Balance, value, x.Percentage) + } + if !online { + printSummary("\tOffline balance", "USD", "AUD", totals) + } else { + printSummary("\tOnline balance", "USD", "AUD", totals) + } +} + func main() { var inFile, key string - var err error flag.StringVar(&inFile, "infile", "config.dat", "The config input file to process.") flag.StringVar(&key, "key", "", "The key to use for AES encryption.") flag.Parse() log.Println("GoCryptoTrader: portfolio tool.") - var data []byte var cfg config.Config - - data, err = common.ReadFile(inFile) + var err = cfg.ReadConfig(inFile) if err != nil { - log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err) - } - - if config.ConfirmECS(data) { - if key == "" { - result, errf := config.PromptForConfigKey() - if errf != nil { - log.Fatal("Unable to obtain encryption/decryption key.") - } - key = string(result) - } - data, err = config.DecryptConfigFile(data, []byte(key)) - if err != nil { - log.Fatalf("Unable to decrypt config data. Error: %s.", err) - } - - } - err = config.ConfirmConfigJSON(data, &cfg) - if err != nil { - log.Fatal("File isn't in JSON format") + log.Fatal(err) } + log.Println("Loaded config file.") port := portfolio.Base{} port.SeedPortfolio(cfg.Portfolio) - result := port.GetPortfolioSummary("") + result := port.GetPortfolioSummary() + + log.Println("Fetched portfolio data.") type PortfolioTemp struct { Balance float64 Subtotal float64 } - stuff := make(map[string]PortfolioTemp) + cfg.RetrieveConfigCurrencyPairs() + portfolioMap := make(map[string]PortfolioTemp) total := float64(0) - for x, y := range result { - if x == "ETH" { - y = y / common.WeiPerEther + log.Println("Fetching currency data..") + var fiatCurrencies []string + for _, y := range result.Totals { + if currency.IsFiatCurrency(y.Coin) { + fiatCurrencies = append(fiatCurrencies, y.Coin) } + } + err = currency.SeedCurrencyData(common.JoinStrings(fiatCurrencies, ",")) + if err != nil { + log.Fatal(err) + } + log.Println("Fetched currency data.") + log.Println("Fetching ticker data and calculating totals..") + priceMap = make(map[string]float64) + priceMap["USD"] = 1 + + for _, y := range result.Totals { pf := PortfolioTemp{} - pf.Balance = y + pf.Balance = y.Balance pf.Subtotal = 0 - bf := bitfinex.Bitfinex{} - - if currency.IsDefaultCurrency(x) { - continue - } - - ticker, errf := bf.GetTicker(x+"USD", url.Values{}) - if errf != nil { - log.Println(errf) + if currency.IsDefaultCurrency(y.Coin) { + if y.Coin != "USD" { + conv, err := currency.ConvertCurrency(y.Balance, y.Coin, "USD") + if err != nil { + log.Println(err) + } else { + priceMap[y.Coin] = conv / y.Balance + pf.Subtotal = conv + } + } else { + pf.Subtotal = y.Balance + } } else { - pf.Subtotal = ticker.Last * y + bf := bitfinex.Bitfinex{} + ticker, errf := bf.GetTicker(y.Coin+"USD", url.Values{}) + if errf != nil { + log.Println(errf) + } else { + priceMap[y.Coin] = ticker.Last + pf.Subtotal = ticker.Last * y.Balance + } } - stuff[x] = pf + portfolioMap[y.Coin] = pf total += pf.Subtotal } + log.Println("Done.") + log.Println() + log.Println("PORTFOLIO TOTALS:") + for x, y := range portfolioMap { + log.Printf("\t%s Amount: %f Subtotal: $%.2f USD (1 %s = $%.2f USD). Percentage of portfolio %.3f%%", x, y.Balance, y.Subtotal, x, y.Subtotal/y.Balance, y.Subtotal/total*100/1) + } + printSummary("\tTotal balance", "USD", "AUD", total) - for x, y := range stuff { - log.Printf("%s %f subtotal: %f USD (1 %s = %.2f USD). Percentage of portfolio %f", x, y.Balance, y.Subtotal, x, y.Subtotal/y.Balance, y.Subtotal/total*100/1) + log.Println("OFFLINE COIN TOTALS:") + getOnlineOfflinePortfolio(result.Offline, false) + + log.Println("ONLINE COIN TOTALS:") + getOnlineOfflinePortfolio(result.Online, true) + + log.Println("OFFLINE COIN SUMMARY:") + var totals float64 + for x, y := range result.OfflineSummary { + log.Printf("\t%s:", x) + totals = 0 + for z := range y { + value := priceMap[x] * y[z].Balance + totals += value + log.Printf("\t %s Amount: %f Subtotal: $%.2f Coin percentage: %.2f%%\n", + y[z].Address, y[z].Balance, value, y[z].Percentage) + } + printSummary(fmt.Sprintf("\t %s balance", x), "USD", "AUD", totals) } - log.Printf("Total balance in USD: %f.\n", total) - - conv, err := currency.ConvertCurrency(total, "USD", "AUD") - if err != nil { - log.Println(err) - } else { - log.Printf("Total balance in AUD: %f.\n", conv) + log.Println("ONLINE COINS SUMMARY:") + for x, y := range result.OnlineSummary { + log.Printf("\t%s:", x) + totals = 0 + for z, w := range y { + value := priceMap[z] * w.Balance + totals += value + log.Printf("\t %s Amount: %f Subtotal $%.2f Coin percentage: %.2f%%", + z, w.Balance, value, w.Percentage) + } + printSummary("\t Exchange balance", "USD", "AUD", totals) } }