mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-17 15:09:59 +00:00
30
README.md
30
README.md
@@ -3,6 +3,7 @@
|
||||
[](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-/gocryptotrader)
|
||||
[](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
|
||||
[](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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
//
|
||||
// }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
362
exchanges/bittrex/bittrex.go
Normal file
362
exchanges/bittrex/bittrex.go
Normal file
@@ -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)
|
||||
}
|
||||
262
exchanges/bittrex/bittrex_test.go
Normal file
262
exchanges/bittrex/bittrex_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
147
exchanges/bittrex/bittrex_types.go
Normal file
147
exchanges/bittrex/bittrex_types.go
Normal file
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
|
||||
@@ -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]})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]})
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
48
main.go
48
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
27
portfolio_routes.go
Normal file
27
portfolio_routes.go
Normal file
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user