Merge remote-tracking branch 'upstream/master'

This commit is contained in:
GloriousCode
2017-09-28 16:33:52 +10:00
124 changed files with 14120 additions and 6325 deletions

5
.gitignore vendored
View File

@@ -2,4 +2,7 @@ config.json
config.dat
node_modules
lib
.vscode
.vscode
testdata/dump
testdata/writefiletest

View File

@@ -1,14 +1,14 @@
language: go
go:
- 1.7
- tip
go:
- 1.8.x
#- master
before_install:
- go get -t -v ./...
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
- ./testdata/test.sh
install:
- go get github.com/gorilla/websocket
@@ -18,4 +18,4 @@ install:
- go get github.com/gorilla/mux
after_success:
- bash <(curl -s https://codecov.io/bash)
- bash <(curl -s https://codecov.io/bash)

View File

@@ -7,3 +7,7 @@ Cornel - cornelk
Łukasz Kurowski - crackcomm
Adrian Gallagher - thrasher-
Manuel Kreutz - 140am
libsora.so - if1live
Tong - tongxiaofeng
Jamie Cheng - starit
Jake - snipesjr

View File

@@ -1,8 +1,10 @@
## Cryptocurrency trading bot written in Golang
# Cryptocurrency trading bot written in Golang
[![Build Status](https://travis-ci.org/thrasher-/gocryptotrader.svg?branch=master)](https://travis-ci.org/thrasher-/gocryptotrader)
[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/thrasher-/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-/gocryptotrader)
[![Coverage Status](http://codecov.io/github/thrasher-/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
A cryptocurrency trading bot supporting multiple exchanges written in Golang.
@@ -10,7 +12,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
@@ -20,8 +22,8 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader]
| ANXPRO | Yes | No | NA |
| Bitfinex | Yes | Yes | NA |
| Bitstamp | Yes | Yes | NA |
| Bittrex | Yes | No | NA |
| BTCC | Yes | Yes | No |
| BTCE | Yes | NA | NA |
| BTCMarkets | Yes | NA | NA |
| COINUT | Yes | No | NA |
| GDAX(Coinbase) | Yes | Yes | No|
@@ -34,25 +36,29 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader]
| LocalBitcoins | Yes | NA | NA |
| OKCoin (both) | Yes | Yes | No |
| Poloniex | Yes | Yes | NA |
| WEX | Yes | NA | 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
@@ -60,18 +66,29 @@ Please feel free to submit any pull requests or suggest any desired features to
When submitting a PR, please abide by our coding guidelines:
* Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
* Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
* Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
* Pull requests need to be based on and opened against the `master` branch.
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
+ Pull requests need to be based on and opened against the `master` branch.
## Compiling instructions
Download 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.
Make any neccessary changes to the config file.
Run the application!
Download and install Go from [Go Downloads](https://golang.org/dl/)
```
go get github.com/thrasher-/gocryptotrader
cd $GOPATH/src/github.com/thrasher-/gocryptotrader
go install
cp $GOPATH/src/github.com/thrasher-/gocryptotrader/config_example.dat $GOPATH/bin/config.dat
```
Make any neccessary changes to the config file.
Run the application!
## 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.

View File

@@ -1,7 +1,6 @@
package common
import (
//"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
@@ -21,56 +20,63 @@ import (
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"time"
)
// Const declarations for common.go operations
const (
HASH_SHA1 = iota
HASH_SHA256
HASH_SHA512
HASH_SHA512_384
SATOSHIS_PER_BTC = 100000000
SATOSHIS_PER_LTC = 100000000
WEI_PER_ETHER = 1000000000000000000
HashSHA1 = iota
HashSHA256
HashSHA512
HashSHA512_384
SatoshisPerBTC = 100000000
SatoshisPerLTC = 100000000
WeiPerEther = 1000000000000000000
)
// GetMD5 returns a MD5 hash of a byte array
func GetMD5(input []byte) []byte {
hash := md5.New()
hash.Write(input)
return hash.Sum(nil)
}
// GetSHA512 returns a SHA512 hash of a byte array
func GetSHA512(input []byte) []byte {
sha := sha512.New()
sha.Write(input)
return sha.Sum(nil)
}
// GetSHA256 returns a SHA256 hash of a byte array
func GetSHA256(input []byte) []byte {
sha := sha256.New()
sha.Write(input)
return sha.Sum(nil)
}
// GetHMAC returns a keyed-hash message authentication code using the desired
// hashtype
func GetHMAC(hashType int, input, key []byte) []byte {
var hash func() hash.Hash
switch hashType {
case HASH_SHA1:
case HashSHA1:
{
hash = sha1.New
}
case HASH_SHA256:
case HashSHA256:
{
hash = sha256.New
}
case HASH_SHA512:
case HashSHA512:
{
hash = sha512.New
}
case HASH_SHA512_384:
case HashSHA512_384:
{
hash = sha512.New384
}
@@ -81,10 +87,12 @@ func GetHMAC(hashType int, input, key []byte) []byte {
return hmac.Sum(nil)
}
// HexEncodeToString takes in a hexadecimal byte array and returns a string
func HexEncodeToString(input []byte) string {
return hex.EncodeToString(input)
}
// Base64Decode takes in a Base64 string and returns a byte array and an error
func Base64Decode(input string) ([]byte, error) {
result, err := base64.StdEncoding.DecodeString(input)
if err != nil {
@@ -93,10 +101,13 @@ func Base64Decode(input string) ([]byte, error) {
return result, nil
}
// Base64Encode takes in a byte array then returns an encoded base64 string
func Base64Encode(input []byte) string {
return base64.StdEncoding.EncodeToString(input)
}
// StringSliceDifference concatenates slices together based on its index and
// returns an individual string array
func StringSliceDifference(slice1 []string, slice2 []string) []string {
var diff []string
for i := 0; i < 2; i++ {
@@ -119,35 +130,51 @@ func StringSliceDifference(slice1 []string, slice2 []string) []string {
return diff
}
// StringContains checks a substring if it contains your input then returns a
// bool
func StringContains(input, substring string) bool {
return strings.Contains(input, substring)
}
// DataContains checks the substring array with an input and returns a bool
func DataContains(haystack []string, needle string) bool {
data := strings.Join(haystack, ",")
return strings.Contains(data, needle)
}
func JoinStrings(input []string, seperator string) string {
return strings.Join(input, seperator)
// JoinStrings joins an array together with the required separator and returns
// it as a string
func JoinStrings(input []string, separator string) string {
return strings.Join(input, separator)
}
func SplitStrings(input, seperator string) []string {
return strings.Split(input, seperator)
// SplitStrings splits blocks of strings from string into a string array using
// a separator ie "," or "_"
func SplitStrings(input, separator string) []string {
return strings.Split(input, separator)
}
// TrimString trims unwanted prefixes or postfixes
func TrimString(input, cutset string) string {
return strings.Trim(input, cutset)
}
// ReplaceString replaces a string with another
func ReplaceString(input, old, new string, n int) string {
return strings.Replace(input, old, new, n)
}
// StringToUpper changes strings to uppercase
func StringToUpper(input string) string {
return strings.ToUpper(input)
}
// StringToLower changes strings to lowercase
func StringToLower(input string) string {
return strings.ToLower(input)
}
// RoundFloat rounds your floating point number to the desired decimal place
func RoundFloat(x float64, prec int) float64 {
var rounder float64
pow := math.Pow(10, float64(prec))
@@ -157,7 +184,7 @@ func RoundFloat(x float64, prec int) float64 {
x = .5
if frac < 0.0 {
x = -.5
intermed -= 1
intermed--
}
if frac >= x {
rounder = math.Ceil(intermed)
@@ -168,14 +195,32 @@ func RoundFloat(x float64, prec int) float64 {
return rounder / pow
}
// IsEnabled takes in a boolean param and returns a string if it is enabled
// or disabled
func IsEnabled(isEnabled bool) string {
if isEnabled {
return "Enabled"
} else {
return "Disabled"
}
return "Disabled"
}
// IsValidCryptoAddress validates your cryptocurrency address string using the
// 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) {
case "btc":
return regexp.MatchString("^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$", address)
case "ltc":
return regexp.MatchString("^[L3M][a-km-zA-HJ-NP-Z1-9]{25,34}$", address)
case "eth":
return regexp.MatchString("^0x[a-km-z0-9]{40}$", address)
default:
return false, errors.New("Invalid crypto currency")
}
}
// YesOrNo returns a boolean variable to check if input is "y" or "yes"
func YesOrNo(input string) bool {
if StringToLower(input) == "y" || StringToLower(input) == "yes" {
return true
@@ -183,31 +228,40 @@ func YesOrNo(input string) bool {
return false
}
// CalculateAmountWithFee returns a calculated fee included amount on fee
func CalculateAmountWithFee(amount, fee float64) float64 {
return amount + CalculateFee(amount, fee)
}
// CalculateFee returns a simple fee on amount
func CalculateFee(amount, fee float64) float64 {
return amount * (fee / 100)
}
// CalculatePercentageGainOrLoss returns the percentage rise over a certain
// period
func CalculatePercentageGainOrLoss(priceNow, priceThen float64) float64 {
return (priceNow - priceThen) / priceThen * 100
}
// CalculatePercentageDifference returns the percentage of difference between
// multiple time periods
func CalculatePercentageDifference(amount, secondAmount float64) float64 {
return (amount - secondAmount) / ((amount + secondAmount) / 2) * 100
}
// CalculateNetProfit returns net profit
func CalculateNetProfit(amount, priceThen, priceNow, costs float64) float64 {
return (priceNow * amount) - (priceThen * amount) - costs
}
// SendHTTPRequest sends a request using the http package and returns a response
// as a string and an error
func SendHTTPRequest(method, path string, headers map[string]string, body io.Reader) (string, error) {
result := strings.ToUpper(method)
if result != "POST" && result != "GET" && result != "DELETE" {
return "", errors.New("Invalid HTTP method specified.")
return "", errors.New("invalid HTTP method specified")
}
req, err := http.NewRequest(method, path, body)
@@ -237,20 +291,22 @@ func SendHTTPRequest(method, path string, headers map[string]string, body io.Rea
return string(contents), nil
}
func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) (err error) {
// SendHTTPGetRequest sends a simple get request using a url string & JSON
// decodes the response into a struct pointer you have supplied. Returns an error
// on failure.
func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error {
res, err := http.Get(url)
if err != nil {
return err
}
if res.StatusCode != 200 {
log.Printf("HTTP status code: %d\n", res.StatusCode)
return errors.New("Status code was not 200.")
log.Printf("URL: %s\n", url)
return errors.New("status code was not 200")
}
contents, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
@@ -270,14 +326,18 @@ func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) (err er
return nil
}
// JSONEncode encodes structure data into JSON
func JSONEncode(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
// JSONDecode decodes JSON data into a structure
func JSONDecode(data []byte, to interface{}) error {
return json.Unmarshal(data, to)
}
// EncodeURLValues concatenates url values onto a url string and returns a
// string
func EncodeURLValues(url string, values url.Values) string {
path := url
if len(values) > 0 {
@@ -286,6 +346,7 @@ func EncodeURLValues(url string, values url.Values) string {
return path
}
// ExtractHost returns the hostname out of a string
func ExtractHost(address string) string {
host := SplitStrings(address, ":")[0]
if host == "" {
@@ -294,13 +355,23 @@ func ExtractHost(address string) string {
return host
}
// ExtractPort returns the port name out of a string
func ExtractPort(host string) int {
portStr := SplitStrings(host, ":")[1]
port, _ := strconv.Atoi(portStr)
return port
}
// OutputCSV dumps data into a file as comma-separated values
func OutputCSV(path string, data [][]string) error {
_, err := ReadFile(path)
if err != nil {
errTwo := WriteFile(path, nil)
if errTwo != nil {
return errTwo
}
}
file, err := os.Create(path)
if err != nil {
return err
@@ -313,14 +384,17 @@ func OutputCSV(path string, data [][]string) error {
return err
}
defer writer.Flush()
writer.Flush()
file.Close()
return nil
}
// UnixTimestampToTime returns time.time
func UnixTimestampToTime(timeint64 int64) time.Time {
return time.Unix(timeint64, 0)
}
// UnixTimestampStrToTime returns a time.time and an error
func UnixTimestampStrToTime(timeStr string) (time.Time, error) {
i, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil {
@@ -330,6 +404,7 @@ func UnixTimestampStrToTime(timeStr string) (time.Time, error) {
return time.Unix(i, 0), nil
}
// ReadFile reads a file and returns read data as byte array.
func ReadFile(path string) ([]byte, error) {
file, err := ioutil.ReadFile(path)
if err != nil {
@@ -338,6 +413,7 @@ func ReadFile(path string) ([]byte, error) {
return file, nil
}
// WriteFile writes selected data to a file and returns an error
func WriteFile(file string, data []byte) error {
err := ioutil.WriteFile(file, data, 0644)
if err != nil {
@@ -346,7 +422,12 @@ func WriteFile(file string, data []byte) error {
return nil
}
// GetURIPath returns the path of a URL given a URL
// RemoveFile removes a file
func RemoveFile(file string) error {
return os.Remove(file)
}
// GetURIPath returns the path of a URL given a URI
func GetURIPath(uri string) string {
urip, err := url.Parse(uri)
if err != nil {

View File

@@ -3,7 +3,9 @@ package common
import (
"bytes"
"fmt"
"net/url"
"reflect"
"strings"
"testing"
"time"
)
@@ -13,13 +15,63 @@ func TestIsEnabled(t *testing.T) {
expected := "Enabled"
actual := IsEnabled(true)
if actual != expected {
t.Error(fmt.Sprintf("Test failed. Expected %s. Actual %s", expected, actual))
t.Error(fmt.Sprintf(
"Test failed. Expected %s. Actual %s", expected, actual),
)
}
expected = "Disabled"
actual = IsEnabled(false)
if actual != expected {
t.Error(fmt.Sprintf("Test failed. Expected %s. Actual %s", expected, actual))
t.Error(fmt.Sprintf(
"Test failed. Expected %s. Actual %s", expected, actual),
)
}
}
func TestIsValidCryptoAddress(t *testing.T) {
t.Parallel()
b, err := IsValidCryptoAddress("1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX", "bTC")
if err != nil && !b {
t.Errorf("Test Failed - Common IsValidCryptoAddress error: %s", err)
}
b, err = IsValidCryptoAddress("0Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX", "btc")
if err == nil && b {
t.Error("Test Failed - Common IsValidCryptoAddress error")
}
b, err = IsValidCryptoAddress("1Mz7153HMuxXTuR2R1t78mGSdzaAtNbBWX", "lTc")
if err == nil && b {
t.Error("Test Failed - Common IsValidCryptoAddress error")
}
b, err = IsValidCryptoAddress("3CDJNfdWX8m2NwuGUV3nhXHXEeLygMXoAj", "ltc")
if err != nil && !b {
t.Errorf("Test Failed - Common IsValidCryptoAddress error: %s", err)
}
b, err = IsValidCryptoAddress("NCDJNfdWX8m2NwuGUV3nhXHXEeLygMXoAj", "lTc")
if err == nil && b {
t.Error("Test Failed - Common IsValidCryptoAddress error")
}
b, err = IsValidCryptoAddress(
"0xb794f5ea0ba39494ce839613fffba74279579268",
"eth",
)
if err != nil && b {
t.Errorf("Test Failed - Common IsValidCryptoAddress error: %s", err)
}
b, err = IsValidCryptoAddress(
"xxb794f5ea0ba39494ce839613fffba74279579268",
"eTh",
)
if err == nil && b {
t.Error("Test Failed - Common IsValidCryptoAddress error")
}
b, err = IsValidCryptoAddress(
"xxb794f5ea0ba39494ce839613fffba74279579268",
"ding",
)
if err == nil && b {
t.Error("Test Failed - Common IsValidCryptoAddress error")
}
}
@@ -30,7 +82,10 @@ func TestGetMD5(t *testing.T) {
actualOutput := GetMD5(originalString)
actualStr := HexEncodeToString(actualOutput)
if !bytes.Equal(expectedOutput, []byte(actualStr)) {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, []byte(actualStr)))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'",
expectedOutput, []byte(actualStr)),
)
}
}
@@ -38,22 +93,77 @@ func TestGetMD5(t *testing.T) {
func TestGetSHA512(t *testing.T) {
t.Parallel()
var originalString = []byte("I am testing the GetSHA512 function in common!")
var expectedOutput = []byte("a2273f492ea73fddc4f25c267b34b3b74998bd8a6301149e1e1c835678e3c0b90859fce22e4e7af33bde1711cbb924809aedf5d759d648d61774b7185c5dc02b")
var expectedOutput = []byte(
`a2273f492ea73fddc4f25c267b34b3b74998bd8a6301149e1e1c835678e3c0b90859fce22e4e7af33bde1711cbb924809aedf5d759d648d61774b7185c5dc02b`,
)
actualOutput := GetSHA512(originalString)
actualStr := HexEncodeToString(actualOutput)
if !bytes.Equal(expectedOutput, []byte(actualStr)) {
t.Error(fmt.Sprintf("Test failed. Expected '%x'. Actual '%x'", expectedOutput, []byte(actualStr)))
t.Error(fmt.Sprintf("Test failed. Expected '%x'. Actual '%x'",
expectedOutput, []byte(actualStr)),
)
}
}
func TestGetSHA256(t *testing.T) {
t.Parallel()
var originalString = []byte("I am testing the GetSHA256 function in common!")
var expectedOutput = []byte("0962813d7a9f739cdcb7f0c0be0c2a13bd630167e6e54468266e4af6b1ad9303")
var expectedOutput = []byte(
"0962813d7a9f739cdcb7f0c0be0c2a13bd630167e6e54468266e4af6b1ad9303",
)
actualOutput := GetSHA256(originalString)
actualStr := HexEncodeToString(actualOutput)
if !bytes.Equal(expectedOutput, []byte(actualStr)) {
t.Error(fmt.Sprintf("Test failed. Expected '%x'. Actual '%x'", expectedOutput, []byte(actualStr)))
t.Error(fmt.Sprintf("Test failed. Expected '%x'. Actual '%x'",
expectedOutput, []byte(actualStr)),
)
}
}
func TestGetHMAC(t *testing.T) {
expectedSha1 := []byte{
74, 253, 245, 154, 87, 168, 110, 182, 172, 101, 177, 49, 142, 2, 253, 165,
100, 66, 86, 246,
}
expectedsha256 := []byte{
54, 68, 6, 12, 32, 158, 80, 22, 142, 8, 131, 111, 248, 145, 17, 202, 224,
59, 135, 206, 11, 170, 154, 197, 183, 28, 150, 79, 168, 105, 62, 102,
}
expectedsha512 := []byte{
249, 212, 31, 38, 23, 3, 93, 220, 81, 209, 214, 112, 92, 75, 126, 40, 109,
95, 247, 182, 210, 54, 217, 224, 199, 252, 129, 226, 97, 201, 245, 220, 37,
201, 240, 15, 137, 236, 75, 6, 97, 12, 190, 31, 53, 153, 223, 17, 214, 11,
153, 203, 49, 29, 158, 217, 204, 93, 179, 109, 140, 216, 202, 71,
}
expectedsha512384 := []byte{
121, 203, 109, 105, 178, 68, 179, 57, 21, 217, 76, 82, 94, 100, 210, 1, 55,
201, 8, 232, 194, 168, 165, 58, 192, 26, 193, 167, 254, 183, 172, 4, 189,
158, 158, 150, 173, 33, 119, 125, 94, 13, 125, 89, 241, 184, 166, 128,
}
sha1 := GetHMAC(HashSHA1, []byte("Hello,World"), []byte("1234"))
if string(sha1) != string(expectedSha1) {
t.Errorf("Test failed.Common GetHMAC error: Expected '%x'. Actual '%x'",
expectedSha1, sha1,
)
}
sha256 := GetHMAC(HashSHA256, []byte("Hello,World"), []byte("1234"))
if string(sha256) != string(expectedsha256) {
t.Errorf("Test failed.Common GetHMAC error: Expected '%x'. Actual '%x'",
expectedSha1, sha1,
)
}
sha512 := GetHMAC(HashSHA512, []byte("Hello,World"), []byte("1234"))
if string(sha512) != string(expectedsha512) {
t.Errorf("Test failed.Common GetHMAC error: Expected '%x'. Actual '%x'",
expectedSha1, sha1,
)
}
sha512384 := GetHMAC(HashSHA512_384, []byte("Hello,World"), []byte("1234"))
if string(sha512384) != string(expectedsha512384) {
t.Errorf("Test failed.Common GetHMAC error: Expected '%x'. Actual '%x'",
expectedSha1, sha1,
)
}
}
@@ -83,7 +193,9 @@ func TestHexEncodeToString(t *testing.T) {
expectedOutput := "737472696e67"
actualResult := HexEncodeToString(originalInput)
if actualResult != expectedOutput {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult))
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'",
expectedOutput, actualResult),
)
}
}
@@ -93,7 +205,14 @@ func TestBase64Decode(t *testing.T) {
expectedOutput := []byte("hello")
actualResult, err := Base64Decode(originalInput)
if !bytes.Equal(actualResult, expectedOutput) {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'. Error: %s", expectedOutput, actualResult, err))
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'. Error: %s",
expectedOutput, actualResult, err),
)
}
_, err = Base64Decode("-")
if err == nil {
t.Error("Test failed. Bad base64 string failed returned nil error")
}
}
@@ -103,18 +222,22 @@ func TestBase64Encode(t *testing.T) {
expectedOutput := "aGVsbG8="
actualResult := Base64Encode(originalInput)
if actualResult != expectedOutput {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult),
)
}
}
func TestStringSliceDifference(t *testing.T) {
t.Parallel()
originalInputOne := []string{"hello"}
originalInputTwo := []string{"moto"}
originalInputTwo := []string{"hello", "moto"}
expectedOutput := []string{"hello moto"}
actualResult := StringSliceDifference(originalInputOne, originalInputTwo)
if reflect.DeepEqual(expectedOutput, actualResult) {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult),
)
}
}
@@ -125,7 +248,9 @@ func TestStringContains(t *testing.T) {
expectedOutput := true
actualResult := StringContains(originalInput, originalInputSubstring)
if actualResult != expectedOutput {
t.Error(fmt.Sprintf("Test failed. Expected '%t'. Actual '%t'", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%t'. Actual '%t'", expectedOutput, actualResult),
)
}
}
@@ -138,44 +263,106 @@ func TestDataContains(t *testing.T) {
expectedOutputTwo := false
actualResult := DataContains(originalHaystack, originalNeedle)
if actualResult != expectedOutput {
t.Error(fmt.Sprintf("Test failed. Expected '%t'. Actual '%t'", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%t'. Actual '%t'", expectedOutput, actualResult),
)
}
actualResult = DataContains(originalHaystack, anotherNeedle)
if actualResult != expectedOutputTwo {
t.Error(fmt.Sprintf("Test failed. Expected '%t'. Actual '%t'", expectedOutputTwo, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%t'. Actual '%t'", expectedOutputTwo,
actualResult),
)
}
}
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))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult),
)
}
}
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))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult),
)
}
}
func TestTrimString(t *testing.T) {
t.Parallel()
originalInput := "abcd"
cutset := "ad"
expectedOutput := "bc"
actualResult := TrimString(originalInput, cutset)
if expectedOutput != actualResult {
t.Errorf(
"Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult,
)
}
}
// ReplaceString replaces a string with another
func TestReplaceString(t *testing.T) {
t.Parallel()
currency := "BTC-USD"
expectedOutput := "BTCUSD"
actualResult := ReplaceString(currency, "-", "", -1)
if expectedOutput != actualResult {
t.Errorf(
"Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult,
)
}
currency = "BTC-USD--"
actualResult = ReplaceString(currency, "-", "", 3)
if expectedOutput != actualResult {
t.Errorf(
"Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult,
)
}
}
func TestRoundFloat(t *testing.T) {
t.Parallel()
originalInput := float64(1.4545445445)
precisionInput := 2
expectedOutput := float64(1.45)
actualResult := RoundFloat(originalInput, precisionInput)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult))
// mapping of input vs expected result
testTable := map[float64]float64{
2.3232323: 2.32,
-2.3232323: -2.32,
}
for testInput, expectedOutput := range testTable {
actualOutput := RoundFloat(testInput, 2)
if actualOutput != expectedOutput {
t.Error(fmt.Sprintf("Test failed. RoundFloat Expected '%f'. Actual '%f'.",
expectedOutput, actualOutput))
}
}
}
func TestYesOrNo(t *testing.T) {
t.Parallel()
if !YesOrNo("y") {
t.Error("Test failed - Common YesOrNo Error.")
}
if !YesOrNo("yes") {
t.Error("Test failed - Common YesOrNo Error.")
}
if YesOrNo("ding") {
t.Error("Test failed - Common YesOrNo Error.")
}
}
@@ -186,7 +373,9 @@ func TestCalculateFee(t *testing.T) {
expectedOutput := float64(0.01)
actualResult := CalculateFee(originalInput, fee)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult),
)
}
}
@@ -197,7 +386,9 @@ func TestCalculateAmountWithFee(t *testing.T) {
expectedOutput := float64(1.01)
actualResult := CalculateAmountWithFee(originalInput, fee)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult),
)
}
}
@@ -208,7 +399,9 @@ func TestCalculatePercentageGainOrLoss(t *testing.T) {
expectedOutput := 3.3333333333333335
actualResult := CalculatePercentageGainOrLoss(originalInput, secondInput)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult),
)
}
}
@@ -219,7 +412,9 @@ func TestCalculatePercentageDifference(t *testing.T) {
expectedOutput := 66.66666666666666
actualResult := CalculatePercentageDifference(originalInput, secondAmount)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult),
)
}
}
@@ -232,24 +427,151 @@ func TestCalculateNetProfit(t *testing.T) {
expectedOutput := float64(44)
actualResult := CalculateNetProfit(amount, priceThen, priceNow, costs)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult),
)
}
}
func TestSendHTTPRequest(t *testing.T) {
methodPost := "pOst"
methodGet := "GeT"
methodDelete := "dEleTe"
methodGarbage := "ding"
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
_, err := SendHTTPRequest(
methodGarbage, "https://query.yahooapis.com/v1/public/yql", headers,
strings.NewReader(""),
)
if err == nil {
t.Error("Test failed. ")
}
_, err = SendHTTPRequest(
methodPost, "https://query.yahooapis.com/v1/public/yql", headers,
strings.NewReader(""),
)
if err != nil {
t.Errorf("Test failed. %s ", err)
}
_, err = SendHTTPRequest(
methodGet, "https://query.yahooapis.com/v1/public/yql", headers,
strings.NewReader(""),
)
if err != nil {
t.Errorf("Test failed. %s ", err)
}
_, err = SendHTTPRequest(
methodDelete, "https://query.yahooapis.com/v1/public/yql", headers,
strings.NewReader(""),
)
if err != nil {
t.Errorf("Test failed. %s ", err)
}
}
func TestSendHTTPGetRequest(t *testing.T) {
type test struct {
Status int `json:"status"`
Data []struct {
Address string `json:"address"`
Balance float64 `json:"balance"`
Nonce interface{} `json:"nonce"`
Code string `json:"code"`
Name interface{} `json:"name"`
Storage interface{} `json:"storage"`
FirstSeen interface{} `json:"firstSeen"`
} `json:"data"`
}
url := `https://etherchain.org/api/account/multiple/0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe`
result := test{}
err := SendHTTPGetRequest(url, true, &result)
if err != nil {
t.Errorf("Test failed - common SendHTTPGetRequest error: %s", err)
}
err = SendHTTPGetRequest("DINGDONG", true, &result)
if err == nil {
t.Error("Test failed - common SendHTTPGetRequest error")
}
err = SendHTTPGetRequest(url, false, &result)
if err != nil {
t.Error("Test failed - common SendHTTPGetRequest error")
}
}
func TestJSONEncode(t *testing.T) {
type test struct {
Status int `json:"status"`
Data []struct {
Address string `json:"address"`
Balance float64 `json:"balance"`
Nonce interface{} `json:"nonce"`
Code string `json:"code"`
Name interface{} `json:"name"`
Storage interface{} `json:"storage"`
FirstSeen interface{} `json:"firstSeen"`
} `json:"data"`
}
expectOutputString := `{"status":0,"data":null}`
v := test{}
bitey, err := JSONEncode(v)
if err != nil {
t.Errorf("Test failed - common JSONEncode error: %s", err)
}
if string(bitey) != expectOutputString {
t.Error("Test failed - common JSONEncode error")
}
_, err = JSONEncode("WigWham")
if err != nil {
t.Errorf("Test failed - common JSONEncode error: %s", err)
}
}
func TestEncodeURLValues(t *testing.T) {
urlstring := "https://www.test.com"
expectedOutput := `https://www.test.com?env=TEST%2FDATABASE&format=json&q=SELECT+%2A+from+yahoo.finance.xchange+WHERE+pair+in+%28%22BTC%2CUSD%22%29`
values := url.Values{}
values.Set("q", fmt.Sprintf(
"SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")", "BTC,USD"),
)
values.Set("format", "json")
values.Set("env", "TEST/DATABASE")
output := EncodeURLValues(urlstring, values)
if output != expectedOutput {
t.Error("Test Failed - common EncodeURLValues error")
}
}
func TestExtractHost(t *testing.T) {
t.Parallel()
address := "localhost:1337"
addresstwo := ":1337"
expectedOutput := "localhost"
actualResult := ExtractHost(address)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult),
)
}
actualResultTwo := ExtractHost(addresstwo)
if expectedOutput != actualResultTwo {
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult),
)
}
address = "192.168.1.100:1337"
expectedOutput = "192.168.1.100"
actualResult = ExtractHost(address)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult),
)
}
}
@@ -259,7 +581,23 @@ func TestExtractPort(t *testing.T) {
expectedOutput := 1337
actualResult := ExtractPort(address)
if expectedOutput != actualResult {
t.Error(fmt.Sprintf("Test failed. Expected '%d'. Actual '%d'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%d'. Actual '%d'.", expectedOutput, actualResult),
)
}
}
func TestOutputCSV(t *testing.T) {
path := "../testdata/dump"
data := [][]string{}
rowOne := []string{"Appended", "to", "two", "dimensional", "array"}
rowTwo := []string{"Appended", "to", "two", "dimensional", "array", "two"}
data = append(data, rowOne)
data = append(data, rowTwo)
err := OutputCSV(path, data)
if err != nil {
t.Errorf("Test failed - common OutputCSV error: %s", err)
}
}
@@ -270,21 +608,76 @@ func TestUnixTimestampToTime(t *testing.T) {
expectedOutput := "2017-03-13 21:17:11 +0000 UTC"
actualResult := UnixTimestampToTime(testTime)
if tm.String() != actualResult.String() {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult),
)
}
}
func TestUnixTimestampStrToTime(t *testing.T) {
t.Parallel()
testTime := "1489439831"
incorrectTime := "DINGDONG"
expectedOutput := "2017-03-13 21:17:11 +0000 UTC"
actualResult, err := UnixTimestampStrToTime(testTime)
if err != nil {
t.Error(err)
}
if actualResult.UTC().String() != expectedOutput {
t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult))
t.Error(fmt.Sprintf(
"Test failed. Expected '%s'. Actual '%s'.", expectedOutput, actualResult),
)
}
actualResult, err = UnixTimestampStrToTime(incorrectTime)
if err == nil {
t.Error("Test failed. Common UnixTimestampStrToTime error")
}
}
func TestReadFile(t *testing.T) {
pathCorrect := "../testdata/dump"
pathIncorrect := "testdata/dump"
_, err := ReadFile(pathCorrect)
if err != nil {
t.Errorf("Test failed - Common ReadFile error: %s", err)
}
_, err = ReadFile(pathIncorrect)
if err == nil {
t.Errorf("Test failed - Common ReadFile error")
}
}
func TestWriteFile(t *testing.T) {
path := "../testdata/writefiletest"
err := WriteFile(path, nil)
if err != nil {
t.Errorf("Test failed. Common WriteFile error: %s", err)
}
_, err = ReadFile(path)
if err != nil {
t.Errorf("Test failed. Common WriteFile error: %s", err)
}
err = WriteFile("", nil)
if err == nil {
t.Error("Test failed. Common WriteFile allowed bad path")
}
}
func TestRemoveFile(t *testing.T) {
TestWriteFile(t)
path := "../testdata/writefiletest"
err := RemoveFile(path)
if err != nil {
t.Errorf("Test failed. Common RemoveFile error: %s", err)
}
TestOutputCSV(t)
path = "../testdata/dump"
err = RemoveFile(path)
if err != nil {
t.Errorf("Test failed. Common RemoveFile error: %s", err)
}
}
@@ -292,9 +685,9 @@ func TestGetURIPath(t *testing.T) {
t.Parallel()
// mapping of input vs expected result
testTable := map[string]string{
"https://api.gdax.com/accounts": "/accounts",
"https://api.gdax.com/accounts?a=1&b=2": "/accounts?a=1&b=2",
"ht:tp:/invalidurl": "",
"https://api.gdax.com/accounts": "/accounts",
"https://api.gdax.com/accounts?a=1&b=2": "/accounts?a=1&b=2",
"http://www.google.com/accounts?!@#$%;^^": "",
}
for testInput, expectedOutput := range testTable {
actualOutput := GetURIPath(testInput)

View File

@@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"os"
@@ -13,18 +14,20 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/portfolio"
"github.com/thrasher-/gocryptotrader/smsglobal"
)
// Constants declared here are filename strings and test strings
const (
CONFIG_FILE = "config.dat"
OLD_CONFIG_FILE = "config.json"
CONFIG_TEST_FILE = "../testdata/configtest.dat"
CONFIG_FILE_ENCRYPTION_PROMPT = 0
CONFIG_FILE_ENCRYPTION_ENABLED = 1
CONFIG_FILE_ENCRYPTION_DISABLED = -1
ConfigFile = "config.dat"
OldConfigFile = "config.json"
ConfigTestFile = "../testdata/configtest.dat"
configFileEncryptionPrompt = 0
configFileEncryptionEnabled = 1
configFileEncryptionDisabled = -1
)
// Variables here are mainly alerts and a configuration object
var (
ErrExchangeNameEmpty = "Exchange #%d in config: Exchange name is empty."
ErrExchangeAvailablePairsEmpty = "Exchange %s: Available pairs is empty."
@@ -43,57 +46,78 @@ var (
WarningWebserverListenAddressInvalid = "WARNING -- Webserver support disabled due to invalid listen address."
WarningWebserverRootWebFolderNotFound = "WARNING -- Webserver support disabled due to missing web folder."
WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values."
WarningCurrencyExchangeProvider = "WARNING -- Currency exchange provider invalid valid. Reset to Fixer."
RenamingConfigFile = "Renaming config file %s to %s."
Cfg Config
)
// WebserverConfig struct holds the prestart variables for the webserver.
type WebserverConfig struct {
Enabled bool
AdminUsername string
AdminPassword string
ListenAddress string
Enabled bool
AdminUsername string
AdminPassword string
ListenAddress string
WebsocketConnectionLimit int
WebsocketAllowInsecureOrigin bool
}
// SMSGlobalConfig structure holds all the variables you need for instant
// messaging and broadcast used by SMSGlobal
type SMSGlobalConfig struct {
Enabled bool
Username string
Password string
Contacts []struct {
Name string
Number string
Enabled bool
}
Contacts []smsglobal.Contact
}
type ConfigPost struct {
// Post holds the bot configuration data
type Post struct {
Data Config `json:"Data"`
}
// CurrencyPairFormatConfig stores the users preferred currency pair display
type CurrencyPairFormatConfig struct {
Uppercase bool
Delimiter string `json:",omitempty"`
Separator string `json:",omitempty"`
Index string `json:",omitempty"`
}
// Config is the overarching object that holds all the information for
// prestart management of portfolio, SMSGlobal, webserver and enabled exchange
type Config struct {
Name string
EncryptConfig int
Cryptocurrencies string
Portfolio portfolio.PortfolioBase `json:"PortfolioAddresses"`
SMS SMSGlobalConfig `json:"SMSGlobal"`
Webserver WebserverConfig `json:"Webserver"`
Exchanges []ExchangeConfig `json:"Exchanges"`
Name string
EncryptConfig int
Cryptocurrencies string
CurrencyExchangeProvider string
CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat"`
FiatDisplayCurrency string
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.
type ExchangeConfig struct {
Name string
Enabled bool
Verbose bool
Websocket bool
RESTPollingDelay time.Duration
AuthenticatedAPISupport bool
APIKey string
APISecret string
ClientID string `json:",omitempty"`
AvailablePairs string
EnabledPairs string
BaseCurrencies string
Name string
Enabled bool
Verbose bool
Websocket bool
RESTPollingDelay time.Duration
AuthenticatedAPISupport bool
APIKey string
APISecret string
ClientID string `json:",omitempty"`
AvailablePairs string
EnabledPairs string
BaseCurrencies string
AssetTypes string
ConfigCurrencyPairFormat *CurrencyPairFormatConfig `json:"ConfigCurrencyPairFormat"`
RequestCurrencyPairFormat *CurrencyPairFormatConfig `json:"RequestCurrencyPairFormat"`
}
// GetConfigEnabledExchanges returns the number of exchanges that are enabled.
func (c *Config) GetConfigEnabledExchanges() int {
counter := 0
for i := range c.Exchanges {
@@ -104,8 +128,14 @@ func (c *Config) GetConfigEnabledExchanges() int {
return counter
}
// GetCurrencyPairDisplayConfig retrieves the currency pair display preference
func (c *Config) GetCurrencyPairDisplayConfig() *CurrencyPairFormatConfig {
return c.CurrencyPairFormat
}
// GetExchangeConfig returns your exchange configurations by its indivdual name
func (c *Config) GetExchangeConfig(name string) (ExchangeConfig, error) {
for i, _ := range c.Exchanges {
for i := range c.Exchanges {
if c.Exchanges[i].Name == name {
return c.Exchanges[i], nil
}
@@ -113,8 +143,9 @@ func (c *Config) GetExchangeConfig(name string) (ExchangeConfig, error) {
return ExchangeConfig{}, fmt.Errorf(ErrExchangeNotFound, name)
}
// UpdateExchangeConfig updates exchange configurations
func (c *Config) UpdateExchangeConfig(e ExchangeConfig) error {
for i, _ := range c.Exchanges {
for i := range c.Exchanges {
if c.Exchanges[i].Name == e.Name {
c.Exchanges[i] = e
return nil
@@ -123,6 +154,7 @@ func (c *Config) UpdateExchangeConfig(e ExchangeConfig) error {
return fmt.Errorf(ErrExchangeNotFound, e.Name)
}
// CheckSMSGlobalConfigValues checks concurrent SMSGlobal configurations
func (c *Config) CheckSMSGlobalConfigValues() error {
if c.SMS.Username == "" || c.SMS.Username == "Username" || c.SMS.Password == "" || c.SMS.Password == "Password" {
return errors.New(WarningSMSGlobalDefaultOrEmptyValues)
@@ -143,6 +175,8 @@ func (c *Config) CheckSMSGlobalConfigValues() error {
return nil
}
// CheckExchangeConfigValues returns configuation values for all enabled
// exchanges
func (c *Config) CheckExchangeConfigValues() error {
if c.Cryptocurrencies == "" {
return errors.New(ErrCryptocurrenciesEmpty)
@@ -185,6 +219,8 @@ func (c *Config) CheckExchangeConfigValues() error {
return nil
}
// CheckWebserverConfigValues checks information before webserver starts and
// returns an error if values are incorrect.
func (c *Config) CheckWebserverConfigValues() error {
if c.Webserver.AdminUsername == "" || c.Webserver.AdminPassword == "" {
return errors.New(WarningWebserverCredentialValuesEmpty)
@@ -203,12 +239,19 @@ func (c *Config) CheckWebserverConfigValues() error {
if port < 1 || port > 65355 {
return errors.New(WarningWebserverListenAddressInvalid)
}
if c.Webserver.WebsocketConnectionLimit <= 0 {
c.Webserver.WebsocketConnectionLimit = 1
}
return nil
}
// RetrieveConfigCurrencyPairs splits, assigns and verifies enabled currency
// pairs either cryptoCurrencies or fiatCurrencies
func (c *Config) RetrieveConfigCurrencyPairs() error {
cryptoCurrencies := common.SplitStrings(c.Cryptocurrencies, ",")
fiatCurrencies := common.SplitStrings(currency.DEFAULT_CURRENCIES, ",")
fiatCurrencies := common.SplitStrings(currency.DefaultCurrencies, ",")
for _, s := range cryptoCurrencies {
_, err := strconv.Atoi(s)
@@ -266,26 +309,36 @@ 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 {
_, err := common.ReadFile(OLD_CONFIG_FILE)
_, err := common.ReadFile(OldConfigFile)
if err == nil {
err = os.Rename(OLD_CONFIG_FILE, CONFIG_FILE)
err = os.Rename(OldConfigFile, ConfigFile)
if err != nil {
return err
}
log.Printf(RenamingConfigFile+"\n", OLD_CONFIG_FILE, CONFIG_FILE)
log.Printf(RenamingConfigFile+"\n", OldConfigFile, ConfigFile)
}
return nil
}
// ReadConfig verifies and checks for encryption and verifies the unencrypted
// file contains JSON.
func (c *Config) ReadConfig(configPath string) error {
defaultPath := ""
if configPath == "" {
defaultPath = CONFIG_FILE
} else {
defaultPath = configPath
}
defaultPath := GetFilePath(configPath)
err := CheckConfig()
if err != nil {
return err
@@ -302,13 +355,13 @@ func (c *Config) ReadConfig(configPath string) error {
return err
}
if c.EncryptConfig == CONFIG_FILE_ENCRYPTION_DISABLED {
if c.EncryptConfig == configFileEncryptionDisabled {
return nil
}
if c.EncryptConfig == CONFIG_FILE_ENCRYPTION_PROMPT {
if c.EncryptConfig == configFileEncryptionPrompt {
if c.PromptForConfigEncryption() {
c.EncryptConfig = CONFIG_FILE_ENCRYPTION_ENABLED
c.EncryptConfig = configFileEncryptionEnabled
return c.SaveConfig("")
}
}
@@ -331,19 +384,14 @@ func (c *Config) ReadConfig(configPath string) error {
return nil
}
// SaveConfig saves your configuration to your desired path
func (c *Config) SaveConfig(configPath string) error {
defaultPath := ""
if configPath == "" {
defaultPath = CONFIG_FILE
} else {
defaultPath = configPath
}
defaultPath := GetFilePath(configPath)
payload, err := json.MarshalIndent(c, "", " ")
if c.EncryptConfig == CONFIG_FILE_ENCRYPTION_ENABLED {
key, err := PromptForConfigKey()
if err != nil {
if c.EncryptConfig == configFileEncryptionEnabled {
key, err2 := PromptForConfigKey()
if err2 != nil {
return err
}
@@ -360,10 +408,11 @@ func (c *Config) SaveConfig(configPath string) error {
return nil
}
// LoadConfig loads your configuration file into your configuration object
func (c *Config) LoadConfig(configPath string) error {
err := c.ReadConfig(configPath)
if err != nil {
return fmt.Errorf(ErrFailureOpeningConfig, CONFIG_FILE, err)
return fmt.Errorf(ErrFailureOpeningConfig, configPath, err)
}
err = c.CheckExchangeConfigValues()
@@ -371,9 +420,83 @@ func (c *Config) LoadConfig(configPath string) error {
return fmt.Errorf(ErrCheckingConfigValues, err)
}
if c.SMS.Enabled {
err = c.CheckSMSGlobalConfigValues()
if err != nil {
log.Print(fmt.Errorf(ErrCheckingConfigValues, err))
c.SMS.Enabled = false
}
}
if c.Webserver.Enabled {
err = c.CheckWebserverConfigValues()
if err != nil {
log.Print(fmt.Errorf(ErrCheckingConfigValues, err))
c.Webserver.Enabled = false
}
}
if c.CurrencyExchangeProvider == "" {
c.CurrencyExchangeProvider = "fixer"
} else {
if c.CurrencyExchangeProvider != "yahoo" && c.CurrencyExchangeProvider != "fixer" {
log.Println(WarningCurrencyExchangeProvider)
c.CurrencyExchangeProvider = "fixer"
}
}
if c.CurrencyPairFormat == nil {
c.CurrencyPairFormat = &CurrencyPairFormatConfig{
Delimiter: "-",
Uppercase: true,
}
}
if c.FiatDisplayCurrency == "" {
c.FiatDisplayCurrency = "USD"
}
return nil
}
// UpdateConfig updates the config with a supplied config file
func (c *Config) UpdateConfig(configPath string, newCfg Config) error {
if c.Name != newCfg.Name && newCfg.Name != "" {
c.Name = newCfg.Name
}
err := newCfg.CheckExchangeConfigValues()
if err != nil {
return err
}
c.Exchanges = newCfg.Exchanges
if c.CurrencyPairFormat != newCfg.CurrencyPairFormat {
c.CurrencyPairFormat = newCfg.CurrencyPairFormat
}
c.Portfolio = newCfg.Portfolio
err = newCfg.CheckSMSGlobalConfigValues()
if err != nil {
return err
}
c.SMS = newCfg.SMS
err = c.SaveConfig(configPath)
if err != nil {
return err
}
err = c.LoadConfig(configPath)
if err != nil {
return err
}
return nil
}
// GetConfig returns a pointer to a confiuration object
func GetConfig() *Config {
return &Cfg
}

View File

@@ -15,11 +15,14 @@ import (
)
const (
CONFIG_ENCRYPTION_CONFIRMATION_STRING = "THORS-HAMMER"
ErrConfigDataLessThenRequiredAESBlockSize = "The config file data is too small for the AES required block size."
// EncryptConfirmString has a the general confirmation string to allow us to
// see if the file is correctly encrypted
EncryptConfirmString = "THORS-HAMMER"
errAESBlockSize = "The config file data is too small for the AES required block size"
errNotAPointer = "Error: parameter interface is not a pointer"
)
// PromptForConfigEncryption asks for encryption key
func (c *Config) PromptForConfigEncryption() bool {
log.Println("Would you like to encrypt your config file (y/n)?")
@@ -30,13 +33,14 @@ func (c *Config) PromptForConfigEncryption() bool {
}
if !common.YesOrNo(input) {
c.EncryptConfig = CONFIG_FILE_ENCRYPTION_DISABLED
c.EncryptConfig = configFileEncryptionDisabled
c.SaveConfig("")
return false
}
return true
}
// PromptForConfigKey asks for configuration key
func PromptForConfigKey() ([]byte, error) {
var cryptoKey []byte
@@ -60,6 +64,8 @@ func PromptForConfigKey() ([]byte, error) {
return cryptoKey, nil
}
// EncryptConfigFile encrypts configuration data that is parsed in with a key
// and returns it as a byte array with an error
func EncryptConfigFile(configData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
@@ -75,11 +81,13 @@ func EncryptConfigFile(configData, key []byte) ([]byte, error) {
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], configData)
appendedFile := []byte(CONFIG_ENCRYPTION_CONFIRMATION_STRING)
appendedFile := []byte(EncryptConfirmString)
appendedFile = append(appendedFile, ciphertext...)
return appendedFile, nil
}
// DecryptConfigFile decrypts configuration data with the supplied key and
// returns the un-encrypted file as a byte array with an error
func DecryptConfigFile(configData, key []byte) ([]byte, error) {
configData = RemoveECS(configData)
blockDecrypt, err := aes.NewCipher(key)
@@ -88,7 +96,7 @@ func DecryptConfigFile(configData, key []byte) ([]byte, error) {
}
if len(configData) < aes.BlockSize {
return nil, errors.New(ErrConfigDataLessThenRequiredAESBlockSize)
return nil, errors.New(errAESBlockSize)
}
iv := configData[:aes.BlockSize]
@@ -100,18 +108,21 @@ func DecryptConfigFile(configData, key []byte) ([]byte, error) {
return result, nil
}
// ConfirmConfigJSON confirms JSON in file
func ConfirmConfigJSON(file []byte, result interface{}) error {
if !common.StringContains(reflect.TypeOf(result).String(), "*") {
return errors.New("ConfirmConfigJSON Error: Parameter interface is not a pointer.")
return errors.New(errNotAPointer)
}
return common.JSONDecode(file, &result)
}
// ConfirmECS confirms that the encryption confirmation string is found
func ConfirmECS(file []byte) bool {
subslice := []byte(CONFIG_ENCRYPTION_CONFIRMATION_STRING)
subslice := []byte(EncryptConfirmString)
return bytes.Contains(file, subslice)
}
// RemoveECS removes encryption confirmation string
func RemoveECS(file []byte) []byte {
return bytes.Trim(file, CONFIG_ENCRYPTION_CONFIRMATION_STRING)
return bytes.Trim(file, EncryptConfirmString)
}

View File

@@ -1,7 +1,6 @@
package config
import (
"encoding/json"
"reflect"
"testing"
@@ -27,7 +26,8 @@ func TestPromptForConfigKey(t *testing.T) {
func TestEncryptDecryptConfigFile(t *testing.T) { //Dual function Test
testKey := []byte("12345678901234567890123456789012")
testConfigData, err := common.ReadFile(CONFIG_TEST_FILE)
testConfigData, err := common.ReadFile(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. EncryptConfigFile: %s", err)
}
@@ -46,16 +46,16 @@ func TestEncryptDecryptConfigFile(t *testing.T) { //Dual function Test
if reflect.TypeOf(decryptedFile).String() != "[]uint8" {
t.Errorf("Test failed. DecryptConfigFile: Incorrect Type")
}
unmarshalled := Config{}
err4 := json.Unmarshal(decryptedFile, &unmarshalled)
if err4 != nil {
t.Errorf("Test failed. DecryptConfigFile: %s", err3)
}
// unmarshalled := Config{} // racecondition
// err4 := json.Unmarshal(decryptedFile, &unmarshalled)
// if err4 != nil {
// t.Errorf("Test failed. DecryptConfigFile: %s", err3)
// }
}
func TestConfirmJson(t *testing.T) {
func TestConfirmConfigJSON(t *testing.T) {
var result interface{}
testConfirmJSON, err := common.ReadFile(CONFIG_TEST_FILE)
testConfirmJSON, err := common.ReadFile(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. testConfirmJSON: %s", err)
}
@@ -67,12 +67,16 @@ func TestConfirmJson(t *testing.T) {
if result == nil {
t.Errorf("Test failed. testConfirmJSON: Error Unmarshalling JSON")
}
err3 := ConfirmConfigJSON(testConfirmJSON, result)
if err3 == nil {
t.Errorf("Test failed. testConfirmJSON: %s", err3)
}
}
func TestConfirmECS(t *testing.T) {
t.Parallel()
ECStest := []byte(CONFIG_ENCRYPTION_CONFIRMATION_STRING)
ECStest := []byte(EncryptConfirmString)
if !ConfirmECS(ECStest) {
t.Errorf("Test failed. TestConfirmECS: Error finding ECS.")
}
@@ -81,7 +85,7 @@ func TestConfirmECS(t *testing.T) {
func TestRemoveECS(t *testing.T) {
t.Parallel()
ECStest := []byte(CONFIG_ENCRYPTION_CONFIRMATION_STRING)
ECStest := []byte(EncryptConfirmString)
isremoved := RemoveECS(ECStest)
if string(isremoved) != "" {

View File

@@ -5,13 +5,13 @@ import (
)
func TestGetConfigEnabledExchanges(t *testing.T) {
t.Parallel()
defaultEnabledExchanges := 17
defaultEnabledExchanges := 18
GetConfigEnabledExchanges := GetConfig()
err := GetConfigEnabledExchanges.LoadConfig(CONFIG_TEST_FILE)
err := GetConfigEnabledExchanges.LoadConfig(ConfigTestFile)
if err != nil {
t.Error("Test failed. GetConfigEnabledExchanges load config error: " + err.Error())
t.Error(
"Test failed. GetConfigEnabledExchanges load config error: " + err.Error(),
)
}
enabledExch := GetConfigEnabledExchanges.GetConfigEnabledExchanges()
if enabledExch != defaultEnabledExchanges {
@@ -19,36 +19,67 @@ func TestGetConfigEnabledExchanges(t *testing.T) {
}
}
func TestGetExchangeConfig(t *testing.T) {
t.Parallel()
GetExchangeConfig := GetConfig()
err := GetExchangeConfig.LoadConfig(CONFIG_TEST_FILE)
func TestGetCurrencyPairDisplayConfig(t *testing.T) {
cfg := GetConfig()
err := cfg.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. GetExchangeConfig.LoadConfig Error: %s", err.Error())
t.Errorf(
"Test failed. GetCurrencyPairDisplayConfig. LoadConfig Error: %s", err.Error(),
)
}
settings := cfg.GetCurrencyPairDisplayConfig()
if settings.Delimiter != "-" || !settings.Uppercase {
t.Errorf(
"Test failed. GetCurrencyPairDisplayConfi. Invalid values",
)
}
}
func TestGetExchangeConfig(t *testing.T) {
GetExchangeConfig := GetConfig()
err := GetExchangeConfig.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf(
"Test failed. GetExchangeConfig.LoadConfig Error: %s", err.Error(),
)
}
r, err := GetExchangeConfig.GetExchangeConfig("ANX")
if err != nil && (ExchangeConfig{}) == r {
t.Errorf("Test failed. GetExchangeConfig.GetExchangeConfig Error: %s", err.Error())
t.Errorf(
"Test failed. GetExchangeConfig.GetExchangeConfig Error: %s", err.Error(),
)
}
r, err = GetExchangeConfig.GetExchangeConfig("Testy")
if err == nil && (ExchangeConfig{}) == r {
t.Error("Test failed. GetExchangeConfig.GetExchangeConfig Error")
}
}
func TestUpdateExchangeConfig(t *testing.T) {
t.Parallel()
UpdateExchangeConfig := GetConfig()
err := UpdateExchangeConfig.LoadConfig(CONFIG_TEST_FILE)
err := UpdateExchangeConfig.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. UpdateExchangeConfig.LoadConfig Error: %s", err.Error())
t.Errorf(
"Test failed. UpdateExchangeConfig.LoadConfig Error: %s", err.Error(),
)
}
e, err2 := UpdateExchangeConfig.GetExchangeConfig("ANX")
if err2 != nil {
t.Errorf("Test failed. UpdateExchangeConfig.GetExchangeConfig: %s", err.Error())
t.Errorf(
"Test failed. UpdateExchangeConfig.GetExchangeConfig: %s", err.Error(),
)
}
e.APIKey = "test1234"
err3 := UpdateExchangeConfig.UpdateExchangeConfig(e)
if err3 != nil {
t.Errorf("Test failed. UpdateExchangeConfig.UpdateExchangeConfig: %s", err.Error())
t.Errorf(
"Test failed. UpdateExchangeConfig.UpdateExchangeConfig: %s", err.Error(),
)
}
e.Name = "testyTest"
err = UpdateExchangeConfig.UpdateExchangeConfig(e)
if err == nil {
t.Error("Test failed. UpdateExchangeConfig.UpdateExchangeConfig Error")
}
}
@@ -56,87 +87,251 @@ func TestCheckSMSGlobalConfigValues(t *testing.T) {
t.Parallel()
checkSMSGlobalConfigValues := GetConfig()
err := checkSMSGlobalConfigValues.LoadConfig(CONFIG_TEST_FILE)
err := checkSMSGlobalConfigValues.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. checkSMSGlobalConfigValues.LoadConfig: %s", err)
}
err2 := checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
if err2 != nil {
t.Error("Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value")
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
if err != nil {
t.Error(
`Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value`,
)
}
checkSMSGlobalConfigValues.SMS.Username = "Username"
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
if err == nil {
t.Error(
"Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value",
)
}
checkSMSGlobalConfigValues.SMS.Username = "1234"
checkSMSGlobalConfigValues.SMS.Contacts[0].Name = "Bob"
checkSMSGlobalConfigValues.SMS.Contacts[0].Number = "12345"
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
if err == nil {
t.Error(
"Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value",
)
}
checkSMSGlobalConfigValues.SMS.Contacts = checkSMSGlobalConfigValues.SMS.Contacts[:0]
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
if err == nil {
t.Error(
"Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value",
)
}
}
func TestCheckExchangeConfigValues(t *testing.T) {
t.Parallel()
checkExchangeConfigValues := Config{}
err := checkExchangeConfigValues.LoadConfig(CONFIG_TEST_FILE)
err := checkExchangeConfigValues.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. checkExchangeConfigValues.LoadConfig: %s", err.Error())
t.Errorf(
"Test failed. checkExchangeConfigValues.LoadConfig: %s", err.Error(),
)
}
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err != nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues: %s",
err.Error(),
)
}
err3 := checkExchangeConfigValues.CheckExchangeConfigValues()
if err3 != nil {
t.Errorf("Test failed. checkExchangeConfigValues.CheckExchangeConfigValues: %s", err.Error())
checkExchangeConfigValues.Exchanges[0].APIKey = "Key"
checkExchangeConfigValues.Exchanges[0].APISecret = "Secret"
checkExchangeConfigValues.Exchanges[0].AuthenticatedAPISupport = true
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err != nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
checkExchangeConfigValues.Exchanges[0].AuthenticatedAPISupport = true
checkExchangeConfigValues.Exchanges[0].APIKey = "TESTYTEST"
checkExchangeConfigValues.Exchanges[0].APISecret = "TESTYTEST"
checkExchangeConfigValues.Exchanges[0].Name = "ITBIT"
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err != nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
checkExchangeConfigValues.Exchanges[0].BaseCurrencies = ""
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err == nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
checkExchangeConfigValues.Exchanges[0].EnabledPairs = ""
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err == nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
checkExchangeConfigValues.Exchanges[0].AvailablePairs = ""
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err == nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
checkExchangeConfigValues.Exchanges[0].Name = ""
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err == nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
checkExchangeConfigValues.Cryptocurrencies = ""
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err == nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
checkExchangeConfigValues.Exchanges = checkExchangeConfigValues.Exchanges[:0]
checkExchangeConfigValues.Cryptocurrencies = "TESTYTEST"
err = checkExchangeConfigValues.CheckExchangeConfigValues()
if err == nil {
t.Errorf(
"Test failed. checkExchangeConfigValues.CheckExchangeConfigValues Error",
)
}
}
func TestCheckWebserverConfigValues(t *testing.T) {
t.Parallel()
checkWebserverConfigValues := GetConfig()
err := checkWebserverConfigValues.LoadConfig(CONFIG_TEST_FILE)
err := checkWebserverConfigValues.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. checkWebserverConfigValues.LoadConfig: %s", err.Error())
t.Errorf(
"Test failed. checkWebserverConfigValues.LoadConfig: %s", err.Error(),
)
}
err2 := checkWebserverConfigValues.CheckWebserverConfigValues()
if err2 != nil {
t.Errorf("Test failed. checkWebserverConfigValues.CheckWebserverConfigValues: %s", err2.Error())
err = checkWebserverConfigValues.CheckWebserverConfigValues()
if err != nil {
t.Errorf(
"Test failed. checkWebserverConfigValues.CheckWebserverConfigValues: %s",
err.Error(),
)
}
checkWebserverConfigValues.Webserver.ListenAddress = ":0"
err = checkWebserverConfigValues.CheckWebserverConfigValues()
if err == nil {
t.Error(
"Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error",
)
}
checkWebserverConfigValues.Webserver.ListenAddress = ":LOLOLOL"
err = checkWebserverConfigValues.CheckWebserverConfigValues()
if err == nil {
t.Error(
"Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error",
)
}
checkWebserverConfigValues.Webserver.ListenAddress = "LOLOLOL"
err = checkWebserverConfigValues.CheckWebserverConfigValues()
if err == nil {
t.Error(
"Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error",
)
}
checkWebserverConfigValues.Webserver.AdminUsername = ""
err = checkWebserverConfigValues.CheckWebserverConfigValues()
if err == nil {
t.Error(
"Test failed. checkWebserverConfigValues.CheckWebserverConfigValues error",
)
}
}
func TestRetrieveConfigCurrencyPairs(t *testing.T) {
t.Parallel()
retrieveConfigCurrencyPairs := GetConfig()
err := retrieveConfigCurrencyPairs.LoadConfig(CONFIG_TEST_FILE)
err := retrieveConfigCurrencyPairs.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. checkWebserverConfigValues.LoadConfig: %s", err.Error())
t.Errorf(
"Test failed. checkWebserverConfigValues.LoadConfig: %s", err.Error(),
)
}
err2 := retrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs()
if err2 != nil {
t.Errorf("Test failed. checkWebserverConfigValues.RetrieveConfigCurrencyPairs: %s", err2.Error())
err = retrieveConfigCurrencyPairs.RetrieveConfigCurrencyPairs()
if err != nil {
t.Errorf(
"Test failed. checkWebserverConfigValues.RetrieveConfigCurrencyPairs: %s",
err.Error(),
)
}
}
func TestReadConfig(t *testing.T) {
t.Parallel()
readConfig := GetConfig()
err := readConfig.ReadConfig(CONFIG_TEST_FILE)
err := readConfig.ReadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. TestReadConfig %s", err.Error())
}
err = readConfig.ReadConfig("bla")
if err == nil {
t.Error("Test failed. TestReadConfig " + err.Error())
}
err = readConfig.ReadConfig("")
if err != nil {
t.Error("Test failed. TestReadConfig error")
}
}
func TestLoadConfig(t *testing.T) {
t.Parallel()
loadConfig := GetConfig()
err := loadConfig.LoadConfig(CONFIG_TEST_FILE)
err := loadConfig.LoadConfig(ConfigTestFile)
if err != nil {
t.Error("Test failed. TestLoadConfig " + err.Error())
}
err = loadConfig.LoadConfig("testy")
if err == nil {
t.Error("Test failed. TestLoadConfig ")
}
}
func TestSaveConfig(t *testing.T) {
saveConfig := GetConfig()
err := saveConfig.LoadConfig(CONFIG_TEST_FILE)
err := saveConfig.LoadConfig(ConfigTestFile)
if err != nil {
t.Errorf("Test failed. TestSaveConfig.LoadConfig: %s", err.Error())
}
err2 := saveConfig.SaveConfig(CONFIG_TEST_FILE)
err2 := saveConfig.SaveConfig(ConfigTestFile)
if err2 != nil {
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)
}
}

View File

@@ -2,27 +2,35 @@
"Name": "Skynet",
"EncryptConfig": 0,
"Cryptocurrencies": "BTC,LTC,ETH,XRP,NMC,NVC,PPC,XBT,DOGE,DASH",
"CurrencyPairFormat": {
"Uppercase": true,
"Delimiter": "-"
},
"PortfolioAddresses": {
"Addresses": [
{
"Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy",
"CoinType": "BTC",
"Balance": 124178.0002442
"Balance": 124178.00647714,
"Description": ""
},
{
"Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v",
"CoinType": "BTC",
"Balance": 103439.83659727
"Balance": 107843.84030984,
"Description": ""
},
{
"Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh",
"CoinType": "LTC",
"Balance": 3.00000005e+06
"Balance": 100000.052,
"Description": ""
},
{
"Address": "0xb794f5ea0ba39494ce839613fffba74279579268",
"CoinType": "ETH",
"Balance": 5.774999820458524e+06
"Balance": 3.224999915984445e+24,
"Description": ""
}
]
},
@@ -54,10 +62,18 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC",
"EnabledPairs": "BTCUSD,BTCHKD,BTCEUR,BTCCAD,BTCAUD,BTCSGD,BTCJPY,BTCGBP,BTCNZD,LTCBTC,DOGEBTC,STRBTC,XRPBTC",
"BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD"
"BaseCurrencies": "USD,HKD,EUR,CAD,AUD,SGD,JPY,GBP,NZD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true,
"Index": "BTC"
},
"RequestCurrencyPairFormat": {
"Uppercase": true,
"Index": "BTC"
}
},
{
"Name": "Bitfinex",
@@ -68,10 +84,16 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,BFXUSD,BFXBTC,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC",
"AvailablePairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BCCBTC,BCUBTC,BCCUSD,BCUUSD,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,BCHUSD,BCHBTC,BCHETH",
"EnabledPairs": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC",
"BaseCurrencies": "USD"
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Bitstamp",
@@ -85,7 +107,36 @@
"ClientID": "ClientID",
"AvailablePairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR",
"EnabledPairs": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR",
"BaseCurrencies": "USD,EUR"
"BaseCurrencies": "USD,EUR",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Bittrex",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "BTC-LTC,BTC-DOGE,BTC-VTC,BTC-PPC,BTC-FTC,BTC-RDD,BTC-NXT,BTC-DASH,BTC-POT,BTC-BLK,BTC-EMC2,BTC-XMY,BTC-AUR,BTC-EFL,BTC-GLD,BTC-SLR,BTC-PTC,BTC-GRS,BTC-NLG,BTC-RBY,BTC-XWC,BTC-MONA,BTC-THC,BTC-ENRG,BTC-ERC,BTC-VRC,BTC-CURE,BTC-XBB,BTC-XMR,BTC-CLOAK,BTC-START,BTC-KORE,BTC-XDN,BTC-TRUST,BTC-NAV,BTC-XST,BTC-BTCD,BTC-VIA,BTC-UNO,BTC-PINK,BTC-IOC,BTC-CANN,BTC-SYS,BTC-NEOS,BTC-DGB,BTC-BURST,BTC-EXCL,BTC-SWIFT,BTC-DOPE,BTC-BLOCK,BTC-ABY,BTC-BYC,BTC-XMG,BTC-BLITZ,BTC-BAY,BTC-BTS,BTC-FAIR,BTC-SPR,BTC-VTR,BTC-XRP,BTC-GAME,BTC-COVAL,BTC-NXS,BTC-XCP,BTC-BITB,BTC-GEO,BTC-FLDC,BTC-GRC,BTC-FLO,BTC-NBT,BTC-MUE,BTC-XEM,BTC-CLAM,BTC-DMD,BTC-GAM,BTC-SPHR,BTC-OK,BTC-SNRG,BTC-PKB,BTC-CPC,BTC-AEON,BTC-ETH,BTC-GCR,BTC-TX,BTC-BCY,BTC-EXP,BTC-INFX,BTC-OMNI,BTC-AMP,BTC-AGRS,BTC-XLM,BTC-BTA,USDT-BTC,BTC-CLUB,BTC-VOX,BTC-EMC,BTC-FCT,BTC-MAID,BTC-EGC,BTC-SLS,BTC-RADS,BTC-DCR,BTC-SAFEX,BTC-BSD,BTC-XVG,BTC-PIVX,BTC-XVC,BTC-MEME,BTC-STEEM,BTC-2GIVE,BTC-LSK,BTC-PDC,BTC-BRK,BTC-DGD,ETH-DGD,BTC-WAVES,BTC-RISE,BTC-LBC,BTC-SBD,BTC-BRX,BTC-DRACO,BTC-ETC,ETH-ETC,BTC-STRAT,BTC-UNB,BTC-SYNX,BTC-TRIG,BTC-EBST,BTC-VRM,BTC-SEQ,BTC-XAUR,BTC-SNGLS,BTC-REP,BTC-SHIFT,BTC-ARDR,BTC-XZC,BTC-NEO,BTC-ZEC,BTC-ZCL,BTC-IOP,BTC-DAR,BTC-GOLOS,BTC-HKG,BTC-UBQ,BTC-KMD,BTC-GBG,BTC-SIB,BTC-ION,BTC-LMC,BTC-QWARK,BTC-CRW,BTC-SWT,BTC-TIME,BTC-MLN,BTC-ARK,BTC-DYN,BTC-TKS,BTC-MUSIC,BTC-DTB,BTC-INCNT,BTC-GBYTE,BTC-GNT,BTC-NXC,BTC-EDG,BTC-LGD,BTC-TRST,ETH-GNT,ETH-REP,USDT-ETH,ETH-WINGS,BTC-WINGS,BTC-RLC,BTC-GNO,BTC-GUP,BTC-LUN,ETH-GUP,ETH-RLC,ETH-LUN,ETH-SNGLS,ETH-GNO,BTC-APX,BTC-TKN,ETH-TKN,BTC-HMQ,ETH-HMQ,BTC-ANT,ETH-TRST,ETH-ANT,BTC-SC,ETH-BAT,BTC-BAT,BTC-ZEN,BTC-1ST,BTC-QRL,ETH-1ST,ETH-QRL,BTC-CRB,ETH-CRB,ETH-LGD,BTC-PTOY,ETH-PTOY,BTC-MYST,ETH-MYST,BTC-CFI,ETH-CFI,BTC-BNT,ETH-BNT,BTC-NMR,ETH-NMR,ETH-TIME,ETH-LTC,ETH-XRP,BTC-SNT,ETH-SNT,BTC-DCT,BTC-XEL,BTC-MCO,ETH-MCO,BTC-ADT,ETH-ADT,BTC-FUN,ETH-FUN,BTC-PAY,ETH-PAY,BTC-MTL,ETH-MTL,BTC-STORJ,ETH-STORJ,BTC-ADX,ETH-ADX,ETH-DASH,ETH-SC,ETH-ZEC,USDT-ZEC,USDT-LTC,USDT-ETC,USDT-XRP,BTC-OMG,ETH-OMG,BTC-CVC,ETH-CVC,BTC-PART,BTC-QTUM,ETH-QTUM,ETH-XMR,ETH-XEM,ETH-XLM,ETH-NEO,USDT-XMR,USDT-DASH,ETH-BCC,USDT-BCC,BTC-BCC,USDT-NEO,ETH-WAVES,ETH-STRAT,ETH-DGB,ETH-FCT,ETH-BTS",
"EnabledPairs": "USDT-BTC",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true,
"Delimiter": "-"
},
"RequestCurrencyPairFormat": {
"Uppercase": true,
"Delimiter": "-"
}
},
{
"Name": "BTCC",
@@ -96,24 +147,16 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCCNY,LTCCNY,LTCBTC",
"EnabledPairs": "BTCCNY,LTCCNY,LTCBTC",
"BaseCurrencies": "CNY"
},
{
"Name": "BTCE",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD",
"EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD",
"BaseCurrencies": "USD,RUR,EUR"
"BaseCurrencies": "CNY",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": false
}
},
{
"Name": "BTC Markets",
@@ -124,10 +167,16 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "LTC,BTC",
"EnabledPairs": "LTC,BTC",
"BaseCurrencies": "AUD"
"AvailablePairs": "LTCAUD,BTCAUD",
"EnabledPairs": "LTCAUD,BTCAUD",
"BaseCurrencies": "AUD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "COINUT",
@@ -141,7 +190,14 @@
"ClientID": "ClientID",
"AvailablePairs": "LTCBTC,ETCBTC,ETHBTC",
"EnabledPairs": "LTCBTC,ETCBTC,ETHBTC",
"BaseCurrencies": "USD"
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "GDAX",
@@ -153,9 +209,17 @@
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "ClientID",
"AvailablePairs": "BTCGBP,BTCEUR,ETHUSD,ETHBTC,LTCUSD,LTCBTC,BTCUSD",
"AvailablePairs": "LTCEUR,LTCBTC,BTCGBP,BTCEUR,ETHEUR,ETHBTC,LTCUSD,BTCUSD,ETHUSD",
"EnabledPairs": "BTCUSD,BTCGBP,BTCEUR",
"BaseCurrencies": "USD,GBP,EUR"
"BaseCurrencies": "USD,GBP,EUR",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true,
"Delimiter": "-"
}
},
{
"Name": "Gemini",
@@ -166,10 +230,16 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCUSD,ETHBTC,ETHUSD",
"EnabledPairs": "BTCUSD",
"BaseCurrencies": "USD"
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Huobi",
@@ -180,10 +250,16 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCCNY,LTCCNY",
"EnabledPairs": "BTCCNY,LTCCNY",
"BaseCurrencies": "CNY"
"BaseCurrencies": "CNY",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": false
}
},
{
"Name": "ITBIT",
@@ -197,7 +273,14 @@
"ClientID": "ClientID",
"AvailablePairs": "XBTUSD,XBTSGD,XBTEUR",
"EnabledPairs": "XBTUSD,XBTSGD,XBTEUR",
"BaseCurrencies": "USD,SGD,EUR"
"BaseCurrencies": "USD,SGD,EUR",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Kraken",
@@ -208,10 +291,17 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "ETCUSD,ICNETH,REPXBT,ZECXBT,ETHXBT,ETHXBT.d,ETHGBP,LTCXBT,XBTGBP.d,XDGXBT,XMRUSD,ZECUSD,ETCETH,ETHJPY,XBTCAD.d,XBTJPY.d,XBTUSD.d,XLMXBT,XLMEUR,XLMUSD,XMREUR,ETCXBT,ETHCAD.d,ETHEUR.d,ETHJPY.d,XBTEUR.d,ETHEUR,ETHGBP.d,ICNXBT,LTCEUR,REPEUR,XBTGBP,XBTJPY,ETHUSD,ETHUSD.d,LTCUSD,REPETH,XBTUSD,XMRXBT,ETCEUR,ETHCAD,REPUSD,XBTCAD,XBTEUR,XRPXBT,ZECEUR",
"AvailablePairs": "BCHEUR,REPEUR,XBTGBP,XBTUSD,ETHXBT,MLNXBT,ETCEUR,ETHGBP,ICNXBT,ZECEUR,EOSETH,GNOXBT,ETHCAD.D,ETHGBP.D,XRPEUR,BCHXBT,EOSXBT,LTCXBT,XBTEUR.D,XBTUSD.D,DASHUSD,GNOETH,ETHJPY,ETHUSD.D,REPETH,USDTUSD,ETHEUR,XLMXBT,BCHUSD,ETHCAD,XBTEUR,XMRUSD,ZECXBT,LTCUSD,XBTCAD,XMRXBT,ETHJPY.D,ICNETH,XBTCAD.D,XBTJPY,XRPUSD,ZECUSD,DASHEUR,ETCETH,ETCUSD,MLNETH,XMREUR,DASHXBT,ETHXBT.D,XDGXBT,XBTGBP.D,XRPXBT,XBTJPY.D,ETCXBT,ETHEUR.D,ETHUSD,LTCEUR,REPXBT",
"EnabledPairs": "ETCUSD,XBTUSD,ETHUSD",
"BaseCurrencies": "EUR,USD,CAD,GBP,JPY"
"BaseCurrencies": "EUR,USD,CAD,GBP,JPY",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true,
"Separator": ","
}
},
{
"Name": "LakeBTC",
@@ -222,10 +312,16 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCUSD,BTCEUR,USDHKD,AUDUSD,BTCGBP,BTCNZD,USDJPY,BTCSGD,BTCNGN,EURUSD,USDSGD,NZDUSD,USDNGN,USDCHF,BTCJPY,BTCAUD,BTCCAD,BTCCHF,GBPUSD,USDCAD",
"EnabledPairs": "BTCUSD,BTCAUD",
"BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD"
"BaseCurrencies": "USD,EUR,HKD,AUD,GBP,NZD,JPY,SGD,NGN,CHF,CAD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "Liqui",
@@ -236,10 +332,19 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "TIME_BTC,ETH_BTC,GNT_BTC,WAVES_BTC,ICN_BTC,1ST_BTC,WINGS_BTC,MLN_BTC,ROUND_BTC,VSL_BTC,LTC_BTC,DCT_BTC,INCNT_BTC,PLU_BTC,DASH_BTC",
"AvailablePairs": "LUN_BTC,BCAP_ETH,NET_USDT,WAVES_ETH,GNO_ETH,CVC_ETH,GNO_BTC,XID_BTC,TAAS_BTC,MGO_ETH,STORJ_BTC,ADX_USDT,BCC_BTC,ICN_ETH,ETH_USDT,LUN_ETH,SNGLS_BTC,OMG_USDT,STX_BTC,RLC_USDT,TRST_BTC,STX_USDT,INCNT_ETH,EOS_BTC,CVC_USDT,NET_ETH,DGD_BTC,OAX_ETH,DNT_ETH,DASH_USDT,QTUM_BTC,TKN_USDT,SNM_USDT,MCO_ETH,SAN_ETH,TNT_ETH,ROUND_BTC,VSL_ETH,SAN_USDT,VSL_BTC,INCNT_BTC,STORJ_ETH,ZRX_ETH,BCAP_BTC,PTOY_ETH,PAY_BTC,MGO_USDT,EOS_USDT,TIME_USDT,INCNT_USDT,ANT_BTC,MYST_ETH,CFI_ETH,SNM_BTC,DASH_BTC,MLN_BTC,OMG_BTC,SAN_BTC,QTUM_ETH,LTC_ETH,QRL_ETH,QRL_USDT,BNT_ETH,QTUM_USDT,WAVES_USDT,REP_ETH,BNT_BTC,ETH_BTC,WINGS_USDT,SNGLS_ETH,XID_USDT,TNT_BTC,GNT_ETH,WINGS_ETH,BTC_USDT,GUP_USDT,TAAS_ETH,LUN_USDT,HMQ_ETH,MYST_BTC,WAVES_BTC,MLN_ETH,TNT_USDT,STORJ_USDT,OMG_ETH,EDG_BTC,GNO_USDT,BAT_ETH,SNT_USDT,DNT_BTC,PLU_ETH,REP_BTC,ADX_BTC,PAY_ETH,DGD_USDT,ZRX_BTC,WINGS_BTC,QRL_BTC,MCO_BTC,VSL_USDT,BAT_BTC,ANT_USDT,PAY_USDT,XID_ETH,TKN_BTC,EOS_ETH,NET_BTC,RLC_BTC,PTOY_BTC,SNM_ETH,OAX_BTC,1ST_ETH,BCAP_USDT,TRST_USDT,PLU_USDT,GUP_ETH,MCO_USDT,BCC_ETH,ROUND_ETH,TIME_ETH,TIME_BTC,ICN_USDT,GUP_BTC,SNGLS_USDT,PLU_BTC,MYST_USDT,CFI_USDT,SNT_BTC,SNT_ETH,ZRX_USDT,ICN_BTC,BAT_USDT,REP_USDT,HMQ_BTC,OAX_USDT,LTC_BTC,EDG_ETH,GNT_USDT,ROUND_USDT,BNT_USDT,CFI_BTC,CVC_BTC,BCC_USDT,GNT_BTC,STX_ETH,1ST_BTC,MGO_BTC,DNT_USDT,DASH_ETH,1ST_USDT,EDG_USDT,TKN_ETH,PTOY_USDT,ADX_ETH,LTC_USDT,RLC_ETH,HMQ_USDT,ANT_ETH,DGD_ETH,MLN_USDT,TRST_ETH,TAAS_USDT",
"EnabledPairs": "ETH_BTC,LTC_BTC,DASH_BTC",
"BaseCurrencies": "USD"
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true,
"Delimiter": "_"
},
"RequestCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_",
"Separator": "-"
}
},
{
"Name": "LocalBitcoins",
@@ -250,10 +355,16 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR",
"EnabledPairs": "BTCARS,BTCAUD,BTCBRL,BTCCAD,BTCCHF,BTCCZK,BTCDKK,BTCEUR,BTCGBP,BTCHKD,BTCILS,BTCINR,BTCMXN,BTCNOK,BTCNZD,BTCPLN,BTCRUB,BTCSEK,BTCSGD,BTCTHB,BTCUSD,BTCZAR",
"BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR"
"BaseCurrencies": "ARS,AUD,BRL,CAD,CHF,CZK,DKK,EUR,GBP,HKD,ILS,INR,MXN,NOK,NZD,PLN,RUB,SEK,SGD,THB,USD,ZAR",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "OKCOIN China",
@@ -264,10 +375,17 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCCNY,LTCCNY",
"EnabledPairs": "BTCCNY,LTCCNY",
"BaseCurrencies": "CNY"
"BaseCurrencies": "CNY",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_"
}
},
{
"Name": "OKCOIN International",
@@ -278,10 +396,17 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTCUSD,LTCUSD",
"EnabledPairs": "BTCUSD,LTCUSD",
"BaseCurrencies": "USD"
"BaseCurrencies": "USD",
"AssetTypes": "SPOT,this_week,next_week,quarter",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_"
}
},
{
"Name": "Poloniex",
@@ -292,10 +417,40 @@
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "",
"AvailablePairs": "BTC_XUSD,BTC_FCT,BTC_MMNXT,BTC_NMC,BTC_BITUSD,BTC_RDD,BTC_XMR,BTC_XST,BTC_DSH,BTC_MAID,BTC_DGB,BTC_NEOS,BTC_BLK,BTC_NAUT,BTC_NBT,BTC_XCP,BTC_STR,BTC_BTCD,BTC_GRC,BTC_HUC,BTC_BBR,BTC_XDN,BTC_INDEX,BTC_IOC,BTC_SWARM,BTC_EMC2,BTC_MCN,BTC_NOXT,BTC_MINT,BTC_PTS,BTC_SC,BTC_GEO,BTC_XRP,BTC_FLO,BTC_BITS,BTC_HYP,BTC_XCR,BTC_LTBC,BTC_SYS,BTC_GMC,BTC_ETH,BTC_SYNC,BTC_GAP,BTC_BCN,BTC_C2,BTC_PINK,BTC_FIBRE,BTC_POT,BTC_QTL,BTC_SDC,BTC_XC,BTC_DASH,BTC_SILK,BTC_CLAM,BTC_NAV,BTC_PIGGY,BTC_BCY,BTC_MIL,BTC_XCN,BTC_YACC,BTC_BTS,BTC_QBK,BTC_SJCX,BTC_LQD,BTC_BURST,BTC_RIC,BTC_VRC,BTC_LTC,BTC_XPB,BTC_GRS,BTC_XCH,BTC_ARCH,BTC_QORA,BTC_HZ,BTC_NSR,BTC_XPM,BTC_BITCNY,BTC_EXE,BTC_XMG,BTC_BTC,BTC_BTM,BTC_NOBL,BTC_NXT,BTC_DOGE,BTC_CURE,BTC_MNTA,BTC_ADN,BTC_EXP,BTC_VTC,BTC_FLDC,BTC_MRS,BTC_MYR,BTC_OMNI,BTC_VNL,BTC_USDT,BTC_NOTE,BTC_WDC,BTC_BELA,BTC_VIA,BTC_CGA,BTC_DIEM,BTC_IFC,BTC_XDP,BTC_BLOCK,BTC_MMC,BTC_1CR,BTC_UNITY,BTC_XBC,BTC_GEMZ,BTC_FLT,BTC_PPC,BTC_XEM,BTC_RBY,BTC_CNMT,BTC_ABY,XMR_XDN,XMR_IFC,XMR_DIEM,XMR_BBR,XMR_DSH,XMR_BCN,XMR_LTC,XMR_MAID,XMR_DASH,XMR_BTCD,XMR_HYP,XMR_BLK,XMR_QORA,XMR_MNTA,XMR_NXT,USDT_BTC,USDT_ETH,USDT_XRP,USDT_DASH,USDT_LTC,USDT_NXT,USDT_XMR,USDT_STR",
"EnabledPairs": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP",
"BaseCurrencies": "USD"
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true,
"Delimiter": "_"
},
"RequestCurrencyPairFormat": {
"Uppercase": true,
"Delimiter": "_"
}
},
{
"Name": "WEX",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"AvailablePairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD",
"EnabledPairs": "BTCUSD,BTCRUR,BTCEUR,LTCBTC,LTCUSD,LTCRUR,LTCEUR,NMCBTC,NMCUSD,NVCBTC,NVCUSD,USDRUR,EURUSD,EURRUR,PPCBTC,PPCUSD",
"BaseCurrencies": "USD,RUR,EUR",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": false,
"Delimiter": "_",
"Separator": "-"
}
}
]
}
}

View File

@@ -1,69 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"github.com/thrasher-/gocryptotrader/config"
)
func GetAllSettings(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(bot.config); err != nil {
panic(err)
}
}
func SaveAllSettings(w http.ResponseWriter, r *http.Request) {
//Get the data from the request
decoder := json.NewDecoder(r.Body)
var responseData config.ConfigPost
jsonerr := decoder.Decode(&responseData)
if jsonerr != nil {
panic(jsonerr)
}
//Save change the settings
for x, _ := range bot.config.Exchanges {
for i := 0; i < len(responseData.Data.Exchanges); i++ {
if responseData.Data.Exchanges[i].Name == bot.config.Exchanges[x].Name {
bot.config.Exchanges[x].Enabled = responseData.Data.Exchanges[i].Enabled
bot.config.Exchanges[x].APIKey = responseData.Data.Exchanges[i].APIKey
bot.config.Exchanges[x].APISecret = responseData.Data.Exchanges[i].APISecret
bot.config.Exchanges[x].EnabledPairs = responseData.Data.Exchanges[i].EnabledPairs
}
}
}
//Reload the configuration
err := bot.config.SaveConfig("")
if err != nil {
panic(err)
}
err = bot.config.LoadConfig("")
if err != nil {
panic(err)
}
setupBotExchanges()
//Return response status
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(bot.config); err != nil {
panic(err)
}
}
var ConfigRoutes = Routes{
Route{
"GetAllSettings",
"GET",
"/config/all",
GetAllSettings,
},
Route{
"SaveAllSettings",
"POST",
"/config/all/save",
SaveAllSettings,
},
}

View File

@@ -1,99 +0,0 @@
mode: atomic
github.com/thrasher-/gocryptotrader/common/common.go:37.34,41.2 3 1
github.com/thrasher-/gocryptotrader/common/common.go:43.37,47.2 3 1
github.com/thrasher-/gocryptotrader/common/common.go:49.37,53.2 3 1
github.com/thrasher-/gocryptotrader/common/common.go:55.54,58.18 2 0
github.com/thrasher-/gocryptotrader/common/common.go:77.2,79.22 3 0
github.com/thrasher-/gocryptotrader/common/common.go:59.2,60.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:63.2,64.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:67.2,68.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:71.2,72.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:60.3,62.4 1 0
github.com/thrasher-/gocryptotrader/common/common.go:64.3,66.4 1 0
github.com/thrasher-/gocryptotrader/common/common.go:68.3,70.4 1 0
github.com/thrasher-/gocryptotrader/common/common.go:72.3,74.4 1 0
github.com/thrasher-/gocryptotrader/common/common.go:82.45,84.2 1 4
github.com/thrasher-/gocryptotrader/common/common.go:86.49,88.16 2 1
github.com/thrasher-/gocryptotrader/common/common.go:91.2,91.20 1 1
github.com/thrasher-/gocryptotrader/common/common.go:88.16,90.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:94.40,96.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:98.71,100.25 2 1
github.com/thrasher-/gocryptotrader/common/common.go:117.2,117.13 1 1
github.com/thrasher-/gocryptotrader/common/common.go:100.25,101.29 1 2
github.com/thrasher-/gocryptotrader/common/common.go:113.3,113.13 1 2
github.com/thrasher-/gocryptotrader/common/common.go:101.29,103.30 2 2
github.com/thrasher-/gocryptotrader/common/common.go:109.4,109.14 1 2
github.com/thrasher-/gocryptotrader/common/common.go:103.30,104.17 1 2
github.com/thrasher-/gocryptotrader/common/common.go:104.17,106.11 2 0
github.com/thrasher-/gocryptotrader/common/common.go:109.14,111.5 1 2
github.com/thrasher-/gocryptotrader/common/common.go:113.13,115.4 1 1
github.com/thrasher-/gocryptotrader/common/common.go:120.51,122.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:124.59,126.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:128.53,130.2 1 4
github.com/thrasher-/gocryptotrader/common/common.go:132.46,134.2 1 0
github.com/thrasher-/gocryptotrader/common/common.go:136.41,138.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:140.41,142.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:144.46,151.16 7 1
github.com/thrasher-/gocryptotrader/common/common.go:155.2,155.15 1 1
github.com/thrasher-/gocryptotrader/common/common.go:161.2,161.22 1 1
github.com/thrasher-/gocryptotrader/common/common.go:151.16,154.3 2 0
github.com/thrasher-/gocryptotrader/common/common.go:155.15,157.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:157.3,159.3 1 1
github.com/thrasher-/gocryptotrader/common/common.go:164.39,165.15 1 2
github.com/thrasher-/gocryptotrader/common/common.go:165.15,167.3 1 1
github.com/thrasher-/gocryptotrader/common/common.go:167.3,169.3 1 1
github.com/thrasher-/gocryptotrader/common/common.go:172.33,173.66 1 0
github.com/thrasher-/gocryptotrader/common/common.go:176.2,176.14 1 0
github.com/thrasher-/gocryptotrader/common/common.go:173.66,175.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:179.58,181.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:183.48,185.2 1 2
github.com/thrasher-/gocryptotrader/common/common.go:187.73,189.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:191.74,193.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:195.77,197.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:199.102,202.63 2 0
github.com/thrasher-/gocryptotrader/common/common.go:206.2,208.16 2 0
github.com/thrasher-/gocryptotrader/common/common.go:212.2,212.28 1 0
github.com/thrasher-/gocryptotrader/common/common.go:216.2,219.16 3 0
github.com/thrasher-/gocryptotrader/common/common.go:223.2,226.16 3 0
github.com/thrasher-/gocryptotrader/common/common.go:230.2,230.30 1 0
github.com/thrasher-/gocryptotrader/common/common.go:202.63,204.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:208.16,210.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:212.28,214.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:219.16,221.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:226.16,228.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:233.86,236.16 2 0
github.com/thrasher-/gocryptotrader/common/common.go:240.2,240.27 1 0
github.com/thrasher-/gocryptotrader/common/common.go:245.2,247.16 2 0
github.com/thrasher-/gocryptotrader/common/common.go:251.2,253.16 2 0
github.com/thrasher-/gocryptotrader/common/common.go:263.2,263.12 1 0
github.com/thrasher-/gocryptotrader/common/common.go:236.16,238.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:240.27,243.3 2 0
github.com/thrasher-/gocryptotrader/common/common.go:247.16,249.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:253.16,256.17 2 0
github.com/thrasher-/gocryptotrader/common/common.go:256.17,258.4 1 0
github.com/thrasher-/gocryptotrader/common/common.go:259.3,261.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:266.48,268.2 1 0
github.com/thrasher-/gocryptotrader/common/common.go:270.52,272.2 1 0
github.com/thrasher-/gocryptotrader/common/common.go:274.60,276.21 2 0
github.com/thrasher-/gocryptotrader/common/common.go:279.2,279.13 1 0
github.com/thrasher-/gocryptotrader/common/common.go:276.21,278.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:282.41,284.16 2 2
github.com/thrasher-/gocryptotrader/common/common.go:287.2,287.13 1 2
github.com/thrasher-/gocryptotrader/common/common.go:284.16,286.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:290.35,294.2 3 1
github.com/thrasher-/gocryptotrader/common/common.go:296.52,298.16 2 0
github.com/thrasher-/gocryptotrader/common/common.go:302.2,305.16 3 0
github.com/thrasher-/gocryptotrader/common/common.go:309.2,310.12 2 0
github.com/thrasher-/gocryptotrader/common/common.go:298.16,300.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:305.16,307.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:313.53,315.2 1 1
github.com/thrasher-/gocryptotrader/common/common.go:317.64,319.16 2 1
github.com/thrasher-/gocryptotrader/common/common.go:323.2,323.29 1 1
github.com/thrasher-/gocryptotrader/common/common.go:319.16,321.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:326.44,328.16 2 0
github.com/thrasher-/gocryptotrader/common/common.go:331.2,331.18 1 0
github.com/thrasher-/gocryptotrader/common/common.go:328.16,330.3 1 0
github.com/thrasher-/gocryptotrader/common/common.go:334.48,336.16 2 0
github.com/thrasher-/gocryptotrader/common/common.go:339.2,339.12 1 0
github.com/thrasher-/gocryptotrader/common/common.go:336.16,338.3 1 0

View File

@@ -11,8 +11,9 @@ import (
"github.com/thrasher-/gocryptotrader/common"
)
// Rate holds the current exchange rates for the currency pair.
type Rate struct {
Id string `json:"id"`
ID string `json:"id"`
Name string `json:"Name"`
Rate float64 `json:",string"`
Date string `json:"Date"`
@@ -21,12 +22,14 @@ type Rate struct {
Bid float64 `json:",string"`
}
// YahooJSONResponseInfo is a sub type that holds JSON response info
type YahooJSONResponseInfo struct {
Count int `json:"count"`
Created time.Time `json:"created"`
Lang string `json:"lang"`
}
// YahooJSONResponse holds Yahoo API responses
type YahooJSONResponse struct {
Query struct {
YahooJSONResponseInfo
@@ -36,46 +39,105 @@ type YahooJSONResponse struct {
}
}
// FixerResponse contains the data fields for the Fixer API response
type FixerResponse struct {
Base string `json:"base"`
Date string `json:"date"`
Rates map[string]float64 `json:"rates"`
}
const (
MAX_CURRENCY_PAIRS_PER_REQUEST = 350
YAHOO_YQL_URL = "http://query.yahooapis.com/v1/public/yql"
YAHOO_DATABASE = "store://datatables.org/alltableswithkeys"
DEFAULT_CURRENCIES = "USD,AUD,EUR,CNY"
DEFAULT_CRYPTOCURRENCIES = "BTC,LTC,ETH,DOGE,DASH,XRP,XMR"
maxCurrencyPairsPerRequest = 350
yahooYQLURL = "https://query.yahooapis.com/v1/public/yql?"
yahooDatabase = "store://datatables.org/alltableswithkeys"
fixerAPI = "http://api.fixer.io/latest"
// DefaultCurrencies has the default minimum of FIAT values
DefaultCurrencies = "USD,AUD,EUR,CNY"
// DefaultCryptoCurrencies has the default minimum of crytpocurrency values
DefaultCryptoCurrencies = "BTC,LTC,ETH,DOGE,DASH,XRP,XMR"
)
// Variables for package which includes base error strings & exportable
// queries
var (
CurrencyStore map[string]Rate
CurrencyStoreFixer map[string]float64
BaseCurrencies string
CryptoCurrencies string
ErrCurrencyDataNotFetched = errors.New("Yahoo currency data has not been fetched yet.")
ErrCurrencyNotFound = errors.New("Unable to find specified currency.")
ErrQueryingYahoo = errors.New("Unable to query Yahoo currency values.")
ErrQueryingYahooZeroCount = errors.New("Yahoo returned zero currency data.")
ErrCurrencyDataNotFetched = errors.New("yahoo currency data has not been fetched yet")
ErrCurrencyNotFound = errors.New("unable to find specified currency")
ErrQueryingYahoo = errors.New("unable to query Yahoo currency values")
ErrQueryingYahooZeroCount = errors.New("yahoo returned zero currency data")
YahooEnabled = true
)
// SetProvider sets the currency exchange service used by the currency
// converter
func SetProvider(yahooEnabled bool) {
if yahooEnabled {
YahooEnabled = true
return
}
YahooEnabled = false
}
// SwapProvider swaps the currency exchange service used by the curency
// converter
func SwapProvider() {
if YahooEnabled {
YahooEnabled = false
return
}
YahooEnabled = true
}
// GetProvider returns the currency exchange service used by the currency
// converter
func GetProvider() string {
if YahooEnabled {
return "yahoo"
}
return "fixer"
}
// IsDefaultCurrency checks if the currency passed in matches the default
// FIAT currency
func IsDefaultCurrency(currency string) bool {
return common.StringContains(DEFAULT_CURRENCIES, common.StringToUpper(currency))
return common.StringContains(
DefaultCurrencies, common.StringToUpper(currency),
)
}
// IsDefaultCryptocurrency checks if the currency passed in matches the default
// CRYPTO currency
func IsDefaultCryptocurrency(currency string) bool {
return common.StringContains(DEFAULT_CRYPTOCURRENCIES, common.StringToUpper(currency))
return common.StringContains(
DefaultCryptoCurrencies, common.StringToUpper(currency),
)
}
// IsFiatCurrency checks if the currency passed is an enabled FIAT currency
func IsFiatCurrency(currency string) bool {
if BaseCurrencies == "" {
log.Println("IsFiatCurrency: BaseCurrencies string variable not populated")
return false
}
return common.StringContains(BaseCurrencies, common.StringToUpper(currency))
}
// IsCryptocurrency checks if the currency passed is an enabled CRYPTO currency.
func IsCryptocurrency(currency string) bool {
if CryptoCurrencies == "" {
log.Println("IsCryptocurrency: CryptoCurrencies string variable not populated")
log.Println(
"IsCryptocurrency: CryptoCurrencies string variable not populated",
)
return false
}
return common.StringContains(CryptoCurrencies, common.StringToUpper(currency))
}
// ContainsSeparator checks to see if the string passed contains "-" or "_"
// separated strings and returns what the separators were.
func ContainsSeparator(input string) (bool, string) {
separators := []string{"-", "_"}
var separatorsContainer []string
@@ -87,11 +149,12 @@ func ContainsSeparator(input string) (bool, string) {
}
if len(separatorsContainer) == 0 {
return false, ""
} else {
return true, strings.Join(separatorsContainer, ",")
}
return true, strings.Join(separatorsContainer, ",")
}
// ContainsBaseCurrencyIndex checks the currency against the baseCurrencies and
// returns a bool and its corresponding basecurrency.
func ContainsBaseCurrencyIndex(baseCurrencies []string, currency string) (bool, string) {
for _, x := range baseCurrencies {
if common.StringContains(currency, x) {
@@ -101,6 +164,8 @@ func ContainsBaseCurrencyIndex(baseCurrencies []string, currency string) (bool,
return false, ""
}
// ContainsBaseCurrency checks the currency against the baseCurrencies and
// returns a bool
func ContainsBaseCurrency(baseCurrencies []string, currency string) bool {
for _, x := range baseCurrencies {
if common.StringContains(currency, x) {
@@ -110,6 +175,9 @@ func ContainsBaseCurrency(baseCurrencies []string, currency string) bool {
return false
}
// CheckAndAddCurrency checks the string you passed with the input string array,
// if not already added, checks to see if it is part of the default currency
// list and returns the appended string.
func CheckAndAddCurrency(input []string, check string) []string {
for _, x := range input {
if IsDefaultCurrency(x) {
@@ -118,40 +186,39 @@ func CheckAndAddCurrency(input []string, check string) []string {
return input
}
continue
} else {
return input
}
return input
} else if IsDefaultCryptocurrency(x) {
if IsDefaultCryptocurrency(check) {
if check == x {
return input
}
continue
} else {
return input
}
} else {
return input
}
return input
}
input = append(input, check)
return input
}
// SeedCurrencyData takes the desired FIAT currency string, if not defined the
// function will assign it the default values. The function will query
// yahoo for the currency values and will seed currency data.
func SeedCurrencyData(fiatCurrencies string) error {
if fiatCurrencies == "" {
fiatCurrencies = DEFAULT_CURRENCIES
fiatCurrencies = DefaultCurrencies
}
err := QueryYahooCurrencyValues(fiatCurrencies)
if err != nil {
return ErrQueryingYahoo
if YahooEnabled {
return QueryYahooCurrencyValues(fiatCurrencies)
}
return nil
return FetchFixerCurrencyData()
}
// MakecurrencyPairs takes all supported currency and turns them into pairs.
func MakecurrencyPairs(supportedCurrencies string) string {
currencies := common.SplitStrings(supportedCurrencies, ",")
pairs := []string{}
@@ -167,42 +234,120 @@ func MakecurrencyPairs(supportedCurrencies string) string {
return common.JoinStrings(pairs, ",")
}
// ConvertCurrency for example converts $1 USD to the equivalent Japanese Yen
// or vice versa.
func ConvertCurrency(amount float64, from, to string) (float64, error) {
currency := common.StringToUpper(from + to)
from = common.StringToUpper(from)
to = common.StringToUpper(to)
if CurrencyStore[currency].Name != currency {
err := SeedCurrencyData(currency[:len(from)] + "," + currency[len(to):])
if from == to {
return amount, nil
}
if YahooEnabled {
currency := from + to
_, ok := CurrencyStore[currency]
if !ok {
err := SeedCurrencyData(currency[:len(from)] + "," + currency[len(to):])
if err != nil {
return 0, err
}
}
result, ok := CurrencyStore[currency]
if !ok {
return 0, ErrCurrencyNotFound
}
return amount * result.Rate, nil
}
_, ok := CurrencyStoreFixer[from]
if !ok {
err := FetchFixerCurrencyData()
if err != nil {
return 0, err
}
}
for x, y := range CurrencyStore {
if x == currency {
return amount * y.Rate, nil
var resultFrom float64
var resultTo float64
// First check if we're converting to USD, USD doesn't exist in the rates map
if to == "USD" {
resultFrom, ok = CurrencyStoreFixer[from]
if !ok {
return 0, ErrCurrencyNotFound
}
return amount / resultFrom, nil
}
return 0, ErrCurrencyNotFound
// Check to see if we're converting from USD
if from == "USD" {
resultTo, ok = CurrencyStoreFixer[to]
if !ok {
return 0, ErrCurrencyNotFound
}
return resultTo * amount, nil
}
// Otherwise convert to USD, then to the target currency
resultFrom, ok = CurrencyStoreFixer[from]
if !ok {
return 0, ErrCurrencyNotFound
}
converted := amount / resultFrom
resultTo, ok = CurrencyStoreFixer[to]
if !ok {
return 0, ErrCurrencyNotFound
}
return converted * resultTo, nil
}
func FetchYahooCurrencyData(currencyPairs []string) error {
// FetchFixerCurrencyData seeds the variable C
func FetchFixerCurrencyData() error {
var result FixerResponse
values := url.Values{}
values.Set("q", fmt.Sprintf("SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")", common.JoinStrings(currencyPairs, ",")))
values.Set("format", "json")
values.Set("env", YAHOO_DATABASE)
values.Set("base", "USD")
url := common.EncodeURLValues(fixerAPI, values)
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
resp, err := common.SendHTTPRequest("POST", YAHOO_YQL_URL, headers, strings.NewReader(values.Encode()))
CurrencyStoreFixer = make(map[string]float64)
err := common.SendHTTPGetRequest(url, true, &result)
if err != nil {
return err
}
CurrencyStoreFixer = result.Rates
return nil
}
// FetchYahooCurrencyData seeds the variable CurrencyStore; this is a
// map[string]Rate
func FetchYahooCurrencyData(currencyPairs []string) error {
values := url.Values{}
values.Set(
"q", fmt.Sprintf("SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")",
common.JoinStrings(currencyPairs, ",")),
)
values.Set("format", "json")
values.Set("env", yahooDatabase)
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
resp, err := common.SendHTTPRequest(
"POST", yahooYQLURL, headers, strings.NewReader(values.Encode()),
)
if err != nil {
return err
}
log.Printf("Currency recv: %s", resp)
yahooResp := YahooJSONResponse{}
err = common.JSONDecode([]byte(resp), &yahooResp)
if err != nil {
return err
}
@@ -212,41 +357,19 @@ func FetchYahooCurrencyData(currencyPairs []string) error {
}
for i := 0; i < yahooResp.Query.YahooJSONResponseInfo.Count; i++ {
CurrencyStore[yahooResp.Query.Results.Rate[i].Id] = yahooResp.Query.Results.Rate[i]
CurrencyStore[yahooResp.Query.Results.Rate[i].ID] = yahooResp.Query.Results.Rate[i]
}
return nil
}
// QueryYahooCurrencyValues takes in desired currencies, creates pairs then
// uses FetchYahooCurrencyData to seed CurrencyStore
func QueryYahooCurrencyValues(currencies string) error {
CurrencyStore = make(map[string]Rate)
currencyPairs := common.SplitStrings(MakecurrencyPairs(currencies), ",")
log.Printf("%d fiat currency pairs generated. Fetching Yahoo currency data (this may take a minute)..\n", len(currencyPairs))
var err error
var pairs []string
index := 0
if len(currencyPairs) > MAX_CURRENCY_PAIRS_PER_REQUEST {
for index < len(currencyPairs) {
if len(currencyPairs)-index > MAX_CURRENCY_PAIRS_PER_REQUEST {
pairs = currencyPairs[index : index+MAX_CURRENCY_PAIRS_PER_REQUEST]
index += MAX_CURRENCY_PAIRS_PER_REQUEST
} else {
pairs = currencyPairs[index:len(currencyPairs)]
index += (len(currencyPairs) - index)
}
err = FetchYahooCurrencyData(pairs)
if err != nil {
return err
}
}
} else {
pairs = currencyPairs[index:len(currencyPairs)]
err = FetchYahooCurrencyData(pairs)
if err != nil {
return err
}
}
return nil
log.Printf(
"%d fiat currency pairs generated. Fetching Yahoo currency data (this may take a minute)..\n",
len(currencyPairs),
)
return FetchYahooCurrencyData(currencyPairs)
}

View File

@@ -7,19 +7,85 @@ import (
"github.com/thrasher-/gocryptotrader/common"
)
func TestSetProvider(t *testing.T) {
defaultVal := YahooEnabled
expected := "yahoo"
SetProvider(true)
actual := GetProvider()
if expected != actual {
t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual)
}
SetProvider(false)
expected = "fixer"
actual = GetProvider()
if expected != actual {
t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual)
}
SetProvider(defaultVal)
}
func TestSwapProvider(t *testing.T) {
defaultVal := YahooEnabled
expected := "fixer"
SetProvider(true)
SwapProvider()
actual := GetProvider()
if expected != actual {
t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual)
}
SetProvider(false)
SwapProvider()
expected = "yahoo"
actual = GetProvider()
if expected != actual {
t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual)
}
SetProvider(defaultVal)
}
func TestGetProvider(t *testing.T) {
defaultVal := YahooEnabled
SetProvider(true)
expected := "yahoo"
actual := GetProvider()
if expected != actual {
t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual)
}
SetProvider(false)
expected = "fixer"
actual = GetProvider()
if expected != actual {
t.Errorf("Test failed. TestGetProvider expected %s got %s", expected, actual)
}
SetProvider(defaultVal)
}
func TestIsDefaultCurrency(t *testing.T) {
t.Parallel()
var str1, str2, str3 string = "USD", "usd", "cats123"
if !IsDefaultCurrency(str1) {
t.Errorf("Test Failed. TestIsDefaultCurrency: \nCannot match currency, %s.", str1)
t.Errorf(
"Test Failed. TestIsDefaultCurrency: \nCannot match currency, %s.", str1,
)
}
if !IsDefaultCurrency(str2) {
t.Errorf("Test Failed. TestIsDefaultCurrency: \nCannot match currency, %s.", str2)
t.Errorf(
"Test Failed. TestIsDefaultCurrency: \nCannot match currency, %s.", str2,
)
}
if IsDefaultCurrency(str3) {
t.Errorf("Test Failed. TestIsDefaultCurrency: \nFunction return is incorrect with, %s.", str3)
t.Errorf(
"Test Failed. TestIsDefaultCurrency: \nFunction return is incorrect with, %s.",
str3,
)
}
}
@@ -29,47 +95,76 @@ func TestIsDefaultCryptocurrency(t *testing.T) {
var str1, str2, str3 string = "BTC", "btc", "dogs123"
if !IsDefaultCryptocurrency(str1) {
t.Errorf("Test Failed. TestIsDefaultCryptocurrency: \nCannot match currency, %s.", str1)
t.Errorf(
"Test Failed. TestIsDefaultCryptocurrency: \nCannot match currency, %s.",
str1,
)
}
if !IsDefaultCryptocurrency(str2) {
t.Errorf("Test Failed. TestIsDefaultCryptocurrency: \nCannot match currency, %s.", str2)
t.Errorf(
"Test Failed. TestIsDefaultCryptocurrency: \nCannot match currency, %s.",
str2,
)
}
if IsDefaultCryptocurrency(str3) {
t.Errorf("Test Failed. TestIsDefaultCryptocurrency: \nFunction return is incorrect with, %s.", str3)
t.Errorf(
"Test Failed. TestIsDefaultCryptocurrency: \nFunction return is incorrect with, %s.",
str3,
)
}
}
func TestIsFiatCurrency(t *testing.T) {
t.Parallel()
if IsFiatCurrency("") {
t.Error("Test failed. TestIsFiatCurrency returned true on an empty string")
}
BaseCurrencies = "USD,AUD"
var str1, str2, str3 string = "BTC", "USD", "birds123"
if IsFiatCurrency(str1) {
t.Errorf("Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str1)
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str1,
)
}
if !IsFiatCurrency(str2) {
t.Errorf("Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str2)
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str2,
)
}
if IsFiatCurrency(str3) {
t.Errorf("Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str3)
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str3,
)
}
}
func TestIsCryptocurrency(t *testing.T) {
t.Parallel()
if IsCryptocurrency("") {
t.Error("Test failed. TestIsCryptocurrency returned true on an empty string")
}
CryptoCurrencies = "BTC,LTC,DASH"
var str1, str2, str3 string = "USD", "BTC", "pterodactyl123"
if IsCryptocurrency(str1) {
t.Errorf("Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str1)
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str1,
)
}
if !IsCryptocurrency(str2) {
t.Errorf("Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str2)
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str2,
)
}
if IsCryptocurrency(str3) {
t.Errorf("Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str3)
t.Errorf(
"Test Failed. TestIsFiatCurrency: \nCannot match currency, %s.", str3,
)
}
}
@@ -80,19 +175,28 @@ func TestContainsSeparator(t *testing.T) {
doesIt, whatIsIt := ContainsSeparator(str1)
if doesIt != true || whatIsIt != "-" {
t.Errorf("Test Failed. ContainsSeparator: \nCannot find separator, %s.", str1)
t.Errorf(
"Test Failed. ContainsSeparator: \nCannot find separator, %s.", str1,
)
}
doesIt2, whatIsIt2 := ContainsSeparator(str2)
if doesIt2 != true || whatIsIt2 != "_" {
t.Errorf("Test Failed. ContainsSeparator: \nCannot find separator, %s.", str2)
t.Errorf(
"Test Failed. ContainsSeparator: \nCannot find separator, %s.", str2,
)
}
doesIt3, whatIsIt3 := ContainsSeparator(str3)
if doesIt3 != true || len(whatIsIt3) != 3 {
t.Errorf("Test Failed. ContainsSeparator: \nCannot find or incorrect separator, %s.", str3)
t.Errorf(
"Test Failed. ContainsSeparator: \nCannot find or incorrect separator, %s.",
str3,
)
}
doesIt4, whatIsIt4 := ContainsSeparator(str4)
if doesIt4 != false || whatIsIt4 != "" {
t.Errorf("Test Failed. ContainsSeparator: \nReturn Issues with string, %s.", str3)
t.Errorf(
"Test Failed. ContainsSeparator: \nReturn Issues with string, %s.", str3,
)
}
}
@@ -104,11 +208,17 @@ func TestContainsBaseCurrencyIndex(t *testing.T) {
isIt, whatIsIt := ContainsBaseCurrencyIndex(baseCurrencies, currency1)
if !isIt && whatIsIt != "USD" {
t.Errorf("Test Failed. ContainsBaseCurrencyIndex: \nReturned: %t & %s, with Currency as %s.", isIt, whatIsIt, currency1)
t.Errorf(
"Test Failed. ContainsBaseCurrencyIndex: \nReturned: %t & %s, with Currency as %s.",
isIt, whatIsIt, currency1,
)
}
isIt2, whatIsIt2 := ContainsBaseCurrencyIndex(baseCurrencies, currency2)
if isIt2 && whatIsIt2 != "DINGDONG" {
t.Errorf("Test Failed. ContainsBaseCurrencyIndex: \nReturned: %t & %s, with Currency as %s.", isIt2, whatIsIt2, currency2)
t.Errorf(
"Test Failed. ContainsBaseCurrencyIndex: \nReturned: %t & %s, with Currency as %s.",
isIt2, whatIsIt2, currency2,
)
}
}
@@ -120,11 +230,15 @@ func TestContainsBaseCurrency(t *testing.T) {
isIt := ContainsBaseCurrency(baseCurrencies, currency1)
if !isIt {
t.Errorf("Test Failed. ContainsBaseCurrency: \nReturned: %t, with Currency as %s.", isIt, currency1)
t.Errorf("Test Failed. ContainsBaseCurrency: \nReturned: %t, with Currency as %s.",
isIt, currency1,
)
}
isIt2 := ContainsBaseCurrency(baseCurrencies, currency2)
if isIt2 {
t.Errorf("Test Failed. ContainsBaseCurrency: \nReturned: %t, with Currency as %s.", isIt2, currency2)
t.Errorf("Test Failed. ContainsBaseCurrency: \nReturned: %t, with Currency as %s.",
isIt2, currency2,
)
}
}
@@ -133,6 +247,7 @@ func TestCheckAndAddCurrency(t *testing.T) {
inputFiat := []string{"USD", "AUD", "EUR"}
inputCrypto := []string{"BTC", "LTC", "ETH", "DOGE", "DASH", "XRP"}
testError := []string{"Testy"}
fiat := "USD"
fiatIncrease := "CNY"
crypto := "LTC"
@@ -141,66 +256,114 @@ func TestCheckAndAddCurrency(t *testing.T) {
appendedString := CheckAndAddCurrency(inputFiat, fiat)
if len(appendedString) > len(inputFiat) {
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.", fiat)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.",
fiat,
)
}
appendedString = CheckAndAddCurrency(inputFiat, fiatIncrease)
if len(appendedString) <= len(inputFiat) {
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.", fiatIncrease)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.",
fiatIncrease,
)
}
appendedString = CheckAndAddCurrency(inputFiat, crypto)
if len(appendedString) > len(inputFiat) {
t.Log(appendedString)
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.", crypto)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.",
crypto,
)
}
appendedString = CheckAndAddCurrency(inputFiat, obtuse)
if len(appendedString) > len(inputFiat) {
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.", obtuse)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputFiat, currency as %s.",
obtuse,
)
}
appendedString = CheckAndAddCurrency(inputCrypto, crypto)
if len(appendedString) > len(inputCrypto) {
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.", crypto)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.",
crypto,
)
}
appendedString = CheckAndAddCurrency(inputCrypto, cryptoIncrease)
if len(appendedString) <= len(inputCrypto) {
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.", cryptoIncrease)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.",
cryptoIncrease,
)
}
appendedString = CheckAndAddCurrency(inputCrypto, fiat)
if len(appendedString) > len(inputCrypto) {
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.", fiat)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.",
fiat,
)
}
appendedString = CheckAndAddCurrency(inputCrypto, obtuse)
if len(appendedString) > len(inputCrypto) {
t.Errorf("Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.", obtuse)
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputCrytpo, currency as %s.",
obtuse,
)
}
appendedString = CheckAndAddCurrency(testError, "USD")
if appendedString[0] != testError[0] {
t.Errorf(
"Test Failed. CheckAndAddCurrency: Error with inputCrytpo, basecurrency as %s.",
testError,
)
}
}
func TestSeedCurrencyData(t *testing.T) {
t.Parallel()
SetProvider(true)
currencyRequestDefault := ""
currencyRequestUSDAUD := "USD,AUD"
currencyRequestObtuse := "WigWham"
err := SeedCurrencyData(currencyRequestDefault)
if err != nil {
t.Errorf("Test Failed. SeedCurrencyData: Error %s with currency as %s.", err, currencyRequestDefault)
t.Errorf(
"Test Failed. SeedCurrencyData: Error %s with currency as %s.",
err, currencyRequestDefault,
)
}
err2 := SeedCurrencyData(currencyRequestUSDAUD)
if err2 != nil {
t.Errorf("Test Failed. SeedCurrencyData: Error %s with currency as %s.", err2, currencyRequestUSDAUD)
t.Errorf(
"Test Failed. SeedCurrencyData: Error %s with currency as %s.",
err2, currencyRequestUSDAUD,
)
}
err3 := SeedCurrencyData(currencyRequestObtuse)
if err3 == nil {
t.Errorf("Test Failed. SeedCurrencyData: Error %s with currency as %s.", err3, currencyRequestObtuse)
t.Errorf(
"Test Failed. SeedCurrencyData: Error %s with currency as %s.",
err3, currencyRequestObtuse,
)
}
SetProvider(false)
err = SeedCurrencyData("")
if err != nil {
t.Errorf("Test failed. SeedCurrencyData via Fixer. Error: %s", err)
}
}
func TestMakecurrencyPairs(t *testing.T) {
t.Parallel()
lengthDefault := len(common.SplitStrings(DEFAULT_CURRENCIES, ","))
fiatPairsLength := len(common.SplitStrings(MakecurrencyPairs(DEFAULT_CURRENCIES), ","))
lengthDefault := len(common.SplitStrings(DefaultCurrencies, ","))
fiatPairsLength := len(
common.SplitStrings(MakecurrencyPairs(DefaultCurrencies), ","),
)
if lengthDefault*(lengthDefault-1) > fiatPairsLength {
t.Error("Test Failed. MakecurrencyPairs: Error, mismatched length")
@@ -208,34 +371,78 @@ func TestMakecurrencyPairs(t *testing.T) {
}
func TestConvertCurrency(t *testing.T) {
t.Parallel()
fiatCurrencies := DEFAULT_CURRENCIES
SetProvider(true)
fiatCurrencies := DefaultCurrencies
for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") {
for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") {
if currencyFrom == currencyTo {
continue
} else {
floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo)
if err != nil {
t.Errorf("Test Failed. ConvertCurrency: Error %s with return: %.2f Currency 1: %s Currency 2: %s",
err, floatyMcfloat, currencyFrom, currencyTo)
}
if reflect.TypeOf(floatyMcfloat).String() != "float64" {
t.Error("Test Failed. ConvertCurrency: Error, incorrect return type")
}
if floatyMcfloat <= 0 {
t.Error("Test Failed. ConvertCurrency: Error, negative return or a serious issue with current fiat")
}
floatyMcfloat, err := ConvertCurrency(1000, currencyFrom, currencyTo)
if err != nil {
t.Errorf(
"Test Failed. ConvertCurrency: Error %s with return: %.2f Currency 1: %s Currency 2: %s",
err, floatyMcfloat, currencyFrom, currencyTo,
)
}
if reflect.TypeOf(floatyMcfloat).String() != "float64" {
t.Error("Test Failed. ConvertCurrency: Error, incorrect return type")
}
if floatyMcfloat <= 0 {
t.Error(
"Test Failed. ConvertCurrency: Error, negative return or a serious issue with current fiat",
)
}
}
}
SetProvider(false)
_, err := ConvertCurrency(1000, "USD", "AUD")
if err != nil {
t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err)
}
_, err = ConvertCurrency(1000, "AUD", "USD")
if err != nil {
t.Errorf("Test failed. ConvertCurrency AUD -> AUD. Error %s", err)
}
_, err = ConvertCurrency(1000, "CNY", "AUD")
if err != nil {
t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err)
}
// Test non-existant currencies
_, err = ConvertCurrency(1000, "ASDF", "USD")
if err == nil {
t.Errorf("Test failed. ConvertCurrency non-existant currency -> USD. Error %s", err)
}
_, err = ConvertCurrency(1000, "USD", "ASDF")
if err == nil {
t.Errorf("Test failed. ConvertCurrency USD -> non-existant currency. Error %s", err)
}
_, err = ConvertCurrency(1000, "CNY", "UAHF")
if err == nil {
t.Errorf("Test failed. ConvertCurrency non-USD currency CNY -> non-existant currency. Error %s", err)
}
_, err = ConvertCurrency(1000, "UASF", "UAHF")
if err == nil {
t.Errorf("Test failed. ConvertCurrency non-existant currency -> non-existant currency. Error %s", err)
}
}
func TestFetchFixerCurrencyData(t *testing.T) {
err := FetchFixerCurrencyData()
if err != nil {
t.Errorf("Test failed. FetchFixerCurrencyData returned %s", err)
}
}
func TestFetchYahooCurrencyData(t *testing.T) {
t.Parallel()
var fetchData []string
fiatCurrencies := DEFAULT_CURRENCIES
fiatCurrencies := DefaultCurrencies
for _, currencyOne := range common.SplitStrings(fiatCurrencies, ",") {
for _, currencyTwo := range common.SplitStrings(fiatCurrencies, ",") {
@@ -253,16 +460,13 @@ func TestFetchYahooCurrencyData(t *testing.T) {
}
func TestQueryYahooCurrencyValues(t *testing.T) {
t.Parallel()
err := QueryYahooCurrencyValues(DEFAULT_CURRENCIES)
err := QueryYahooCurrencyValues(DefaultCurrencies)
if err != nil {
t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err)
}
err2 := QueryYahooCurrencyValues(DEFAULT_CRYPTOCURRENCIES)
if err2 == nil {
t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err2)
err = QueryYahooCurrencyValues(DefaultCryptoCurrencies)
if err == nil {
t.Errorf("Test Failed. QueryYahooCurrencyValues: Error, %s", err)
}
}

View File

@@ -1,39 +1,78 @@
package pair
import "strings"
import (
"strings"
)
// CurrencyItem is an exported string with methods to manipulate the data instead
// of using array/slice access modifiers
type CurrencyItem string
// Lower converts the CurrencyItem object c to lowercase
func (c CurrencyItem) Lower() CurrencyItem {
return CurrencyItem(strings.ToLower(string(c)))
}
// Upper converts the CurrencyItem object c to uppercase
func (c CurrencyItem) Upper() CurrencyItem {
return CurrencyItem(strings.ToUpper(string(c)))
}
// String converts the CurrencyItem object c to string
func (c CurrencyItem) String() string {
return string(c)
}
// CurrencyPair holds currency pair information
type CurrencyPair struct {
Delimiter string `json:"delimiter"`
FirstCurrency CurrencyItem `json:"first_currency"`
SecondCurrency CurrencyItem `json:"second_currency"`
}
// GetFirstCurrency returns the first currency item
func (c CurrencyPair) GetFirstCurrency() CurrencyItem {
return c.FirstCurrency
}
// GetSecondCurrency returns the second currency item
func (c CurrencyPair) GetSecondCurrency() CurrencyItem {
return c.SecondCurrency
}
// Pair returns a currency pair string
func (c CurrencyPair) Pair() CurrencyItem {
return c.FirstCurrency + CurrencyItem(c.Delimiter) + c.SecondCurrency
}
// Display formats and returns the currency based on user preferences,
// overriding the default Pair() display
func (c CurrencyPair) Display(delimiter string, uppercase bool) CurrencyItem {
var pair CurrencyItem
if delimiter != "" {
pair = c.FirstCurrency + CurrencyItem(delimiter) + c.SecondCurrency
} else {
pair = c.FirstCurrency + c.SecondCurrency
}
if uppercase {
return pair.Upper()
}
return pair.Lower()
}
// Equal compares two currency pairs and returns whether or not they are equal
func (c CurrencyPair) Equal(p CurrencyPair) bool {
if c.FirstCurrency.Upper() == p.FirstCurrency.Upper() &&
c.SecondCurrency.Upper() == p.SecondCurrency.Upper() {
return true
}
return false
}
// NewCurrencyPairDelimiter splits the desired currency string at delimeter,
// the returns a CurrencyPair struct
func NewCurrencyPairDelimiter(currency, delimiter string) CurrencyPair {
result := strings.Split(currency, delimiter)
return CurrencyPair{
@@ -43,6 +82,7 @@ func NewCurrencyPairDelimiter(currency, delimiter string) CurrencyPair {
}
}
// NewCurrencyPair returns a CurrencyPair without a delimiter
func NewCurrencyPair(firstCurrency, secondCurrency string) CurrencyPair {
return CurrencyPair{
FirstCurrency: CurrencyItem(firstCurrency),
@@ -50,10 +90,22 @@ func NewCurrencyPair(firstCurrency, secondCurrency string) CurrencyPair {
}
}
// NewCurrencyPairFromIndex returns a CurrencyPair via a currency string and
// specific index
func NewCurrencyPairFromIndex(currency, index string) CurrencyPair {
i := strings.Index(currency, index)
if i == 0 {
return NewCurrencyPair(currency[0:len(index)], currency[len(index):])
}
return NewCurrencyPair(currency[0:i], currency[i:])
}
// NewCurrencyPairFromString converts currency string into a new CurrencyPair
// with or without delimeter
func NewCurrencyPairFromString(currency string) CurrencyPair {
delmiters := []string{"_", "-"}
delimiters := []string{"_", "-"}
var delimiter string
for _, x := range delmiters {
for _, x := range delimiters {
if strings.Contains(currency, x) {
delimiter = x
return NewCurrencyPairDelimiter(currency, delimiter)

View File

@@ -1,8 +1,6 @@
package pair
import (
"testing"
)
import "testing"
func TestLower(t *testing.T) {
t.Parallel()
@@ -43,7 +41,10 @@ func TestGetFirstCurrency(t *testing.T) {
actual := pair.GetFirstCurrency()
expected := CurrencyItem("BTC")
if actual != expected {
t.Errorf("Test failed. GetFirstCurrency(): %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. GetFirstCurrency(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
@@ -53,7 +54,10 @@ func TestGetSecondCurrency(t *testing.T) {
actual := pair.GetSecondCurrency()
expected := CurrencyItem("USD")
if actual != expected {
t.Errorf("Test failed. GetSecondCurrency(): %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. GetSecondCurrency(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
@@ -63,7 +67,65 @@ func TestPair(t *testing.T) {
actual := pair.Pair()
expected := CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf("Test failed. Pair(): %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestDisplay(t *testing.T) {
t.Parallel()
pair := NewCurrencyPairDelimiter("BTC-USD", "-")
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Display("", false)
expected = CurrencyItem("btcusd")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = pair.Display("~", true)
expected = CurrencyItem("BTC~USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
func TestEqual(t *testing.T) {
t.Parallel()
pair := NewCurrencyPair("BTC", "USD")
secondPair := NewCurrencyPair("btc", "uSd")
actual := pair.Equal(secondPair)
expected := true
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
secondPair.SecondCurrency = "ETH"
actual = pair.Equal(secondPair)
expected = false
if actual != expected {
t.Errorf(
"Test failed. Equal(): %v was not equal to expected value: %v",
actual, expected,
)
}
}
@@ -73,7 +135,10 @@ func TestNewCurrencyPair(t *testing.T) {
actual := pair.Pair()
expected := CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf("Test failed. Pair(): %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
@@ -83,13 +148,53 @@ func TestNewCurrencyPairDelimiter(t *testing.T) {
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf("Test failed. Pair(): %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
actual = CurrencyItem(pair.Delimiter)
expected = "-"
if actual != expected {
t.Errorf("Test failed. Delmiter: %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. Delmiter: %s was not equal to expected value: %s",
actual, expected,
)
}
}
// NewCurrencyPairFromIndex returns a CurrencyPair via a currency string and
// specific index
func TestNewCurrencyPairFromIndex(t *testing.T) {
t.Parallel()
currency := "BTCUSD"
index := "BTC"
pair := NewCurrencyPairFromIndex(currency, index)
pair.Delimiter = "-"
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
currency = "DOGEBTC"
pair = NewCurrencyPairFromIndex(currency, index)
pair.Delimiter = "-"
actual = pair.Pair()
expected = CurrencyItem("DOGE-BTC")
if actual != expected {
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}
@@ -100,7 +205,10 @@ func TestNewCurrencyPairFromString(t *testing.T) {
actual := pair.Pair()
expected := CurrencyItem("BTC-USD")
if actual != expected {
t.Errorf("Test failed. Pair(): %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
pairStr = "BTCUSD"
@@ -108,6 +216,9 @@ func TestNewCurrencyPairFromString(t *testing.T) {
actual = pair.Pair()
expected = CurrencyItem("BTCUSD")
if actual != expected {
t.Errorf("Test failed. Pair(): %s was not equal to expected value: %s", actual, expected)
t.Errorf(
"Test failed. Pair(): %s was not equal to expected value: %s",
actual, expected,
)
}
}

125
currency/symbol/symbol.go Normal file
View File

@@ -0,0 +1,125 @@
package symbol
import "errors"
// symbols map holds the currency name and symbol mappings
var symbols = map[string]string{
"ALL": "Lek",
"AFN": "؋",
"ARS": "$",
"AWG": "ƒ",
"AUD": "$",
"AZN": "ман",
"BSD": "$",
"BBD": "$",
"BYN": "Br",
"BZD": "BZ$",
"BMD": "$",
"BOB": "$b",
"BAM": "KM",
"BWP": "P",
"BGN": "лв",
"BRL": "R$",
"BND": "$",
"KHR": "៛",
"CAD": "$",
"KYD": "$",
"CLP": "$",
"CNY": "¥",
"COP": "$",
"CRC": "₡",
"HRK": "kn",
"CUP": "₱",
"CZK": "Kč",
"DKK": "kr",
"DOP": "RD$",
"XCD": "$",
"EGP": "£",
"SVC": "$",
"EUR": "€",
"FKP": "£",
"FJD": "$",
"GHS": "¢",
"GIP": "£",
"GTQ": "Q",
"GGP": "£",
"GYD": "$",
"HNL": "L",
"HKD": "$",
"HUF": "Ft",
"ISK": "kr",
"INR": "₹",
"IDR": "Rp",
"IRR": "﷼",
"IMP": "£",
"ILS": "₪",
"JMD": "J$",
"JPY": "¥",
"JEP": "£",
"KZT": "лв",
"KPW": "₩",
"KRW": "₩",
"KGS": "лв",
"LAK": "₭",
"LBP": "£",
"LRD": "$",
"MKD": "ден",
"MYR": "RM",
"MUR": "₨",
"MXN": "$",
"MNT": "₮",
"MZN": "MT",
"NAD": "$",
"NPR": "₨",
"ANG": "ƒ",
"NZD": "$",
"NIO": "C$",
"NGN": "₦",
"NOK": "kr",
"OMR": "﷼",
"PKR": "₨",
"PAB": "B/.",
"PYG": "Gs",
"PEN": "S/.",
"PHP": "₱",
"PLN": "zł",
"QAR": "﷼",
"RON": "lei",
"RUB": "₽",
"SHP": "£",
"SAR": "﷼",
"RSD": "Дин.",
"SCR": "₨",
"SGD": "$",
"SBD": "$",
"SOS": "S",
"ZAR": "R",
"LKR": "₨",
"SEK": "kr",
"CHF": "CHF",
"SRD": "$",
"SYP": "£",
"TWD": "NT$",
"THB": "฿",
"TTD": "TT$",
"TRY": "₺",
"TVD": "$",
"UAH": "₴",
"GBP": "£",
"USD": "$",
"UYU": "$U",
"UZS": "лв",
"VEF": "Bs",
"VND": "₫",
"YER": "﷼",
"ZWD": "Z$",
}
// GetSymbolByCurrencyName returns a currency symbol
func GetSymbolByCurrencyName(currency string) (string, error) {
result, ok := symbols[currency]
if !ok {
return "", errors.New("currency symbol not found")
}
return result, nil
}

View File

@@ -0,0 +1,21 @@
package symbol
import "testing"
func TestGetSymbolByCurrencyName(t *testing.T) {
expected := "₩"
actual, err := GetSymbolByCurrencyName("KPW")
if err != nil {
t.Errorf("Test failed. TestGetSymbolByCurrencyName error: %s", err)
}
if actual != expected {
t.Errorf("Test failed. TestGetSymbolByCurrencyName differing values")
}
_, err = GetSymbolByCurrencyName("BLAH")
if err == nil {
t.Errorf("Test failed. TestGetSymbolByCurrencyNam returned nil on non-existant currency")
}
}

View File

@@ -7,7 +7,7 @@ In order to maintain a consistent style across the codebase, the following codin
- Function names using acronyms are capitilised (func SendHTTPRequest()).
- Variable names use CamelCase (var someVar()).
- Coding style uses gofmt.
- Const variables are capitilised.
- Const variables are CamelCase depending on exported items.
- In line with gofmt, for loops and if statements don't require paranthesis.
Block style example:

View File

@@ -2,67 +2,98 @@ package events
import (
"testing"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/smsglobal"
)
var (
loaded = false
)
func testSetup(t *testing.T) {
if !loaded {
cfg := config.GetConfig()
err := cfg.LoadConfig("")
if err != nil {
t.Fatalf("Test failed. Failed to load config %s", err)
}
smsglobal.New(cfg.SMS.Username, cfg.SMS.Password, cfg.Name, cfg.SMS.Contacts)
loaded = true
}
}
func TestAddEvent(t *testing.T) {
eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
testSetup(t)
pair := pair.NewCurrencyPair("BTC", "USD")
eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil && eventID != 0 {
t.Errorf("Test Failed. AddEvent: Error, %s", err)
}
eventID, err = AddEvent("ANXX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
eventID, err = AddEvent("ANXX", "price", ">,==", pair, "SPOT", actionTest)
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Exchange")
}
eventID, err = AddEvent("ANX", "prices", ">,==", "BTC", "LTC", ACTION_TEST)
eventID, err = AddEvent("ANX", "prices", ">,==", pair, "SPOT", actionTest)
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Item")
}
eventID, err = AddEvent("ANX", "price", "3===D", "BTC", "LTC", ACTION_TEST)
eventID, err = AddEvent("ANX", "price", "3===D", pair, "SPOT", actionTest)
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Condition")
}
eventID, err = AddEvent("ANX", "price", ">,==", "BTC", "LTC", "console_prints")
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Action")
}
eventID, err = AddEvent("ANX", "price", ">,==", "BATMAN", "ROBIN", ACTION_TEST)
eventID, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", "console_prints")
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Action")
}
if !RemoveEvent(eventID) {
t.Error("Test Failed. RemoveEvent: Error, error removing event")
}
}
func TestRemoveEvent(t *testing.T) {
eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
testSetup(t)
pair := pair.NewCurrencyPair("BTC", "USD")
eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil && eventID != 0 {
t.Errorf("Test Failed. RemoveEvent: Error, %s", err)
}
if !RemoveEvent(eventID) {
t.Error("Test Failed. RemoveEvent: Error, error removing event")
}
if RemoveEvent(1234) {
t.Error("Test Failed. RemoveEvent: Error, error removing event")
}
}
func TestGetEventCounter(t *testing.T) {
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
testSetup(t)
pair := pair.NewCurrencyPair("BTC", "USD")
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
}
two, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
two, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
}
three, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
three, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
}
Events[three-1].Executed = true
total, _ := GetEventCounter()
if total <= 0 {
t.Errorf("Test Failed. GetEventCounter: Total = %d", total)
}
if !RemoveEvent(one) {
t.Error("Test Failed. GetEventCounter: Error, error removing event")
}
@@ -80,84 +111,205 @@ func TestGetEventCounter(t *testing.T) {
}
func TestExecuteAction(t *testing.T) {
t.Parallel()
testSetup(t)
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
pair := pair.NewCurrencyPair("BTC", "USD")
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. ExecuteAction: Error, %s", err)
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
}
isExecuted := Events[one].ExecuteAction()
if !isExecuted {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
if !RemoveEvent(one) {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
action := actionSMSNotify + "," + "ALL"
one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
if err != nil {
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
}
isExecuted = Events[one].ExecuteAction()
if !isExecuted {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
if !RemoveEvent(one) {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
action = actionSMSNotify + "," + "StyleGherkin"
one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
if err != nil {
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
}
isExecuted = Events[one].ExecuteAction()
if !isExecuted {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
if !RemoveEvent(one) {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
// More tests when ExecuteAction is expanded
}
func TestEventToString(t *testing.T) {
t.Parallel()
testSetup(t)
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
pair := pair.NewCurrencyPair("BTC", "USD")
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. EventToString: Error, %s", err)
}
eventString := Events[one].EventToString()
if eventString != "If the BTCLTC price on ANX is > == then ACTION_TEST." {
eventString := Events[one].String()
if eventString != "If the BTCUSD [SPOT] price on ANX is > == then ACTION_TEST." {
t.Error("Test Failed. EventToString: Error, incorrect return string")
}
if !RemoveEvent(one) {
t.Error("Test Failed. EventToString: Error, error removing event")
}
}
func TestCheckCondition(t *testing.T) { //error handling needs to be implemented
t.Parallel()
func TestCheckCondition(t *testing.T) {
testSetup(t)
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST)
// Test invalid currency pair
newPair := pair.NewCurrencyPair("A", "B")
one, err := AddEvent("ANX", "price", ">=,10", newPair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. EventToString: Error, %s", err)
t.Errorf("Test Failed. CheckCondition: Error, %s", err)
}
conditionBool := Events[one].CheckCondition()
if conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
conditionBool := Events[one].CheckCondition()
if conditionBool { //check once error handling is implemented
t.Error("Test Failed. EventToString: Error, wrong conditional.")
// Test last price == 0
var tickerNew ticker.Price
tickerNew.Last = 0
newPair = pair.NewCurrencyPair("BTC", "USD")
ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
Events[one].Pair = newPair
conditionBool = Events[one].CheckCondition()
if conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last pricce > 0 and conditional logic
tickerNew.Last = 11
ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
Events[one].Condition = ">,10"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last price >= 10
Events[one].Condition = ">=,10"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last price <= 10
Events[one].Condition = "<,100"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last price <= 10
Events[one].Condition = "<=,100"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
Events[one].Condition = "==,11"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
Events[one].Condition = "^,11"
conditionBool = Events[one].CheckCondition()
if conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
if !RemoveEvent(one) {
t.Error("Test Failed. EventToString: Error, error removing event")
t.Error("Test Failed. CheckCondition: Error, error removing event")
}
}
func TestIsValidEvent(t *testing.T) {
err := IsValidEvent("ANX", "price", ">,==", ACTION_TEST)
testSetup(t)
err := IsValidEvent("ANX", "price", ">,==", actionTest)
if err != nil {
t.Errorf("Test Failed. IsValidExchange: Error %s", err)
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
err = IsValidEvent("ANX", "price", ">,", actionTest)
if err == nil {
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
err = IsValidEvent("ANX", "Testy", ">,==", actionTest)
if err == nil {
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
err = IsValidEvent("Testys", "price", ">,==", actionTest)
if err == nil {
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
action := "blah,blah"
err = IsValidEvent("ANX", "price", ">=,10", action)
if err == nil {
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
action = "SMS,blah"
err = IsValidEvent("ANX", "price", ">=,10", action)
if err == nil {
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
//Function tests need to appended to this function when more actions are
//implemented
}
func TestCheckEvents(t *testing.T) { //Add error handling
//CheckEvents() //check once error handling is implemented
func TestCheckEvents(t *testing.T) {
testSetup(t)
pair := pair.NewCurrencyPair("BTC", "USD")
_, err := AddEvent("ANX", "price", ">=,10", pair, "SPOT", actionTest)
if err != nil {
t.Fatal("Test failed. TestChcheckEvents add event")
}
go CheckEvents()
}
func TestIsValidExchange(t *testing.T) {
boolean := IsValidExchange("ANX", CONFIG_PATH_TEST)
testSetup(t)
boolean := IsValidExchange("ANX")
if !boolean {
t.Error("Test Failed. IsValidExchange: Error, incorrect Exchange")
}
boolean = IsValidExchange("OBTUSE", CONFIG_PATH_TEST)
boolean = IsValidExchange("OBTUSE")
if boolean {
t.Error("Test Failed. IsValidExchange: Error, incorrect return")
}
}
func TestIsValidCondition(t *testing.T) {
t.Parallel()
testSetup(t)
boolean := IsValidCondition(">")
if !boolean {
@@ -186,13 +338,13 @@ func TestIsValidCondition(t *testing.T) {
}
func TestIsValidAction(t *testing.T) {
t.Parallel()
testSetup(t)
boolean := IsValidAction("sms")
if !boolean {
t.Error("Test Failed. IsValidAction: Error, incorrect Action")
}
boolean = IsValidAction(ACTION_TEST)
boolean = IsValidAction(actionTest)
if !boolean {
t.Error("Test Failed. IsValidAction: Error, incorrect Action")
}
@@ -203,7 +355,7 @@ func TestIsValidAction(t *testing.T) {
}
func TestIsValidItem(t *testing.T) {
t.Parallel()
testSetup(t)
boolean := IsValidItem("price")
if !boolean {

View File

@@ -8,56 +8,54 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/smsglobal"
)
const (
ITEM_PRICE = "PRICE"
GREATER_THAN = ">"
GREATER_THAN_OR_EQUAL = ">="
LESS_THAN = "<"
LESS_THAN_OR_EQUAL = "<="
IS_EQUAL = "=="
ACTION_SMS_NOTIFY = "SMS"
ACTION_CONSOLE_PRINT = "CONSOLE_PRINT"
ACTION_TEST = "ACTION_TEST"
CONFIG_PATH_TEST = config.CONFIG_TEST_FILE
itemPrice = "PRICE"
greaterThan = ">"
greaterThanOrEqual = ">="
lessThan = "<"
lessThanOrEqual = "<="
isEqual = "=="
actionSMSNotify = "SMS"
actionConsolePrint = "CONSOLE_PRINT"
actionTest = "ACTION_TEST"
)
var (
ErrInvalidItem = errors.New("Invalid item.")
ErrInvalidCondition = errors.New("Invalid conditional option.")
ErrInvalidAction = errors.New("Invalid action.")
ErrExchangeDisabled = errors.New("Desired exchange is disabled.")
ErrCurrencyInvalid = errors.New("Invalid currency.")
errInvalidItem = errors.New("invalid item")
errInvalidCondition = errors.New("invalid conditional option")
errInvalidAction = errors.New("invalid action")
errExchangeDisabled = errors.New("desired exchange is disabled")
)
// Event struct holds the event variables
type Event struct {
ID int
Exchange string
Item string
Condition string
FirstCurrency string
SecondCurrency string
Action string
Executed bool
ID int
Exchange string
Item string
Condition string
Pair pair.CurrencyPair
Asset string
Action string
Executed bool
}
// Events variable is a pointer array to the event structures that will be
// appended
var Events []*Event
func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Action string) (int, error) {
// AddEvent adds an event to the Events chain and returns an index/eventID
// and an error
func AddEvent(Exchange, Item, Condition string, CurrencyPair pair.CurrencyPair, Asset, Action string) (int, error) {
err := IsValidEvent(Exchange, Item, Condition, Action)
if err != nil {
return 0, err
}
if !IsValidCurrency(FirstCurrency, SecondCurrency) {
return 0, ErrCurrencyInvalid
}
Event := &Event{}
if len(Events) == 0 {
@@ -69,14 +67,15 @@ func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Action s
Event.Exchange = Exchange
Event.Item = Item
Event.Condition = Condition
Event.FirstCurrency = FirstCurrency
Event.SecondCurrency = SecondCurrency
Event.Pair = CurrencyPair
Event.Asset = Asset
Event.Action = Action
Event.Executed = false
Events = append(Events, Event)
return Event.ID, nil
}
// RemoveEvent deletes and event by its ID
func RemoveEvent(EventID int) bool {
for i, x := range Events {
if x.ID == EventID {
@@ -87,6 +86,8 @@ func RemoveEvent(EventID int) bool {
return false
}
// GetEventCounter displays the emount of total events on the chain and the
// events that have been executed.
func GetEventCounter() (int, int) {
total := len(Events)
executed := 0
@@ -99,70 +100,78 @@ func GetEventCounter() (int, int) {
return total, executed
}
// ExecuteAction will execute the action pending on the chain
func (e *Event) ExecuteAction() bool {
if common.StringContains(e.Action, ",") {
action := common.SplitStrings(e.Action, ",")
if action[0] == ACTION_SMS_NOTIFY {
message := fmt.Sprintf("Event triggered: %s", e.EventToString())
if action[0] == actionSMSNotify {
message := fmt.Sprintf("Event triggered: %s", e.String())
s := smsglobal.SMSGlobal
if action[1] == "ALL" {
smsglobal.SMSSendToAll(message, config.Cfg)
s.SendMessageToAll(message)
} else {
smsglobal.SMSNotify(smsglobal.SMSGetNumberByName(action[1], config.Cfg.SMS), message, config.Cfg)
contact, _ := s.GetContactByName(action[1])
s.SendMessage(contact.Number, message)
}
}
} else {
log.Printf("Event triggered: %s", e.EventToString())
log.Printf("Event triggered: %s", e.String())
}
return true
}
func (e *Event) EventToString() string {
// EventToString turns the structure event into a string
func (e *Event) String() string {
condition := common.SplitStrings(e.Condition, ",")
return fmt.Sprintf("If the %s%s %s on %s is %s then %s.", e.FirstCurrency, e.SecondCurrency, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action)
return fmt.Sprintf(
"If the %s%s [%s] %s on %s is %s then %s.", e.Pair.FirstCurrency.String(),
e.Pair.SecondCurrency.String(), e.Asset, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action,
)
}
func (e *Event) CheckCondition() bool { //Add error handling
lastPrice := 0.00
// CheckCondition will check the event structure to see if there is a condition
// met
func (e *Event) CheckCondition() bool {
condition := common.SplitStrings(e.Condition, ",")
targetPrice, _ := strconv.ParseFloat(condition[1], 64)
ticker, err := ticker.GetTickerByExchange(e.Exchange)
t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset)
if err != nil {
return false
}
lastPrice = ticker.Price[pair.CurrencyItem(e.FirstCurrency)][pair.CurrencyItem(e.SecondCurrency)].Last
lastPrice := t.Last
if lastPrice == 0 {
return false
}
switch condition[0] {
case GREATER_THAN:
case greaterThan:
{
if lastPrice > targetPrice {
return e.ExecuteAction()
}
}
case GREATER_THAN_OR_EQUAL:
case greaterThanOrEqual:
{
if lastPrice >= targetPrice {
return e.ExecuteAction()
}
}
case LESS_THAN:
case lessThan:
{
if lastPrice < targetPrice {
return e.ExecuteAction()
}
}
case LESS_THAN_OR_EQUAL:
case lessThanOrEqual:
{
if lastPrice <= targetPrice {
return e.ExecuteAction()
}
}
case IS_EQUAL:
case isEqual:
{
if lastPrice == targetPrice {
return e.ExecuteAction()
@@ -172,52 +181,54 @@ func (e *Event) CheckCondition() bool { //Add error handling
return false
}
// IsValidEvent checks the actions to be taken and returns an error if incorrect
func IsValidEvent(Exchange, Item, Condition, Action string) error {
Exchange = common.StringToUpper(Exchange)
Item = common.StringToUpper(Item)
Action = common.StringToUpper(Action)
configPath := ""
if Action == ACTION_TEST {
configPath = CONFIG_PATH_TEST
}
if !IsValidExchange(Exchange, configPath) {
return ErrExchangeDisabled
if !IsValidExchange(Exchange) {
return errExchangeDisabled
}
if !IsValidItem(Item) {
return ErrInvalidItem
return errInvalidItem
}
if !common.StringContains(Condition, ",") {
return ErrInvalidCondition
return errInvalidCondition
}
condition := common.SplitStrings(Condition, ",")
if !IsValidCondition(condition[0]) || len(condition[1]) == 0 {
return ErrInvalidCondition
return errInvalidCondition
}
if common.StringContains(Action, ",") {
action := common.SplitStrings(Action, ",")
if action[0] != ACTION_SMS_NOTIFY {
return ErrInvalidAction
if action[0] != actionSMSNotify {
return errInvalidAction
}
if action[1] != "ALL" && smsglobal.SMSGetNumberByName(action[1], config.Cfg.SMS) == smsglobal.ErrSMSContactNotFound {
return ErrInvalidAction
if action[1] != "ALL" {
s := smsglobal.SMSGlobal
_, err := s.GetContactByName(action[1])
if err != nil {
return errInvalidAction
}
}
} else {
if Action != ACTION_CONSOLE_PRINT && Action != ACTION_TEST {
return ErrInvalidAction
if Action != actionConsolePrint && Action != actionTest {
return errInvalidAction
}
}
return nil
}
// CheckEvents is the overarching routine that will iterate through the Events
// chain
func CheckEvents() {
for {
total, executed := GetEventCounter()
@@ -226,7 +237,10 @@ func CheckEvents() {
if !event.Executed {
success := event.CheckCondition()
if success {
log.Printf("Event %d triggered on %s successfully.\n", event.ID, event.Exchange)
log.Printf(
"Event %d triggered on %s successfully.\n", event.ID,
event.Exchange,
)
event.Executed = true
}
}
@@ -235,27 +249,10 @@ func CheckEvents() {
}
}
func IsValidCurrency(currencies ...string) bool {
for _, whatIsIt := range currencies {
whatIsIt = common.StringToUpper(whatIsIt)
if currency.IsDefaultCryptocurrency(whatIsIt) {
return true
}
if currency.IsDefaultCurrency(whatIsIt) {
return true
}
}
return false
}
func IsValidExchange(Exchange, configPath string) bool {
// IsValidExchange validates the exchange
func IsValidExchange(Exchange string) bool {
Exchange = common.StringToUpper(Exchange)
cfg := config.GetConfig()
if len(cfg.Exchanges) == 0 {
cfg.LoadConfig(configPath)
}
for _, x := range cfg.Exchanges {
if x.Name == Exchange && x.Enabled {
return true
@@ -264,27 +261,30 @@ func IsValidExchange(Exchange, configPath string) bool {
return false
}
// IsValidCondition validates passed in condition
func IsValidCondition(Condition string) bool {
switch Condition {
case GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, IS_EQUAL:
case greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, isEqual:
return true
}
return false
}
// IsValidAction validates passed in action
func IsValidAction(Action string) bool {
Action = common.StringToUpper(Action)
switch Action {
case ACTION_SMS_NOTIFY, ACTION_CONSOLE_PRINT, ACTION_TEST:
case actionSMSNotify, actionConsolePrint, actionTest:
return true
}
return false
}
// IsValidItem validates passed in Item
func IsValidItem(Item string) bool {
Item = common.StringToUpper(Item)
switch Item {
case ITEM_PRICE:
case itemPrice:
return true
}
return false

View File

@@ -11,47 +11,56 @@ import (
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
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.ExchangeBase
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
a.AssetTypes = []string{ticker.Spot}
}
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 +70,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 +93,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 +114,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 +131,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 +145,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 +159,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 +178,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 +280,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 +306,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 +322,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,186 +363,209 @@ 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 {
if !a.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, a.Name)
}
if a.Nonce.Get() == 0 {
a.Nonce.Set(time.Now().UnixNano())
} else {
a.Nonce.Inc()
}
headers := make(map[string]string)
headers["Content-Type"] = "application/json"
data["apiKey"] = a.APIKey
nonce := time.Now().UnixNano()
nonceStr := strconv.FormatInt(nonce, 10)
data["apiNonce"] = nonce
hmac := common.GetHMAC(common.HASH_SHA256, []byte(nonceStr+a.ClientID+a.APIKey), []byte(a.APISecret))
data["apiNonce"] = a.Nonce.Get()
hmac := common.GetHMAC(common.HashSHA256, []byte(a.Nonce.String()+a.ClientID+a.APIKey), []byte(a.APISecret))
data["apiSig"] = common.StringToUpper(common.HexEncodeToString(hmac))
path = fmt.Sprintf("%s/ajax/v%s/%s", a.APIUrl, 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
}

View File

@@ -1,8 +1,16 @@
package alphapoint
import (
"reflect"
"testing"
"github.com/thrasher-/gocryptotrader/common"
)
const (
onlineTest = false
testAPIKey = ""
testAPISecret = ""
)
func TestSetDefaults(t *testing.T) {
@@ -18,227 +26,121 @@ func TestSetDefaults(t *testing.T) {
}
}
func testSetAPIKey(a *Alphapoint) {
a.APIKey = testAPIKey
a.APISecret = testAPISecret
a.AuthenticatedAPISupport = true
}
func testIsAPIKeysSet(a *Alphapoint) bool {
if testAPIKey != "" && testAPISecret != "" && a.AuthenticatedAPISupport {
return true
}
return false
}
func TestGetTicker(t *testing.T) {
GetTicker := Alphapoint{}
GetTicker.SetDefaults()
alpha := Alphapoint{}
alpha.SetDefaults()
response, err := GetTicker.GetTicker("BTCUSD")
if err != nil {
t.Error("Test Failed - Alphapoint GetTicker init error: ", err)
}
if reflect.ValueOf(response).NumField() != 13 {
t.Error("Test Failed - Alphapoint GetTicker struct change/or updated")
}
if reflect.TypeOf(response.Ask).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.Ask value is not a float64")
}
if reflect.TypeOf(response.Bid).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.Bid value is not a float64")
}
if reflect.TypeOf(response.BuyOrderCount).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.BuyOrderCount value is not a float64")
}
if reflect.TypeOf(response.High).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.High value is not a float64")
}
if reflect.TypeOf(response.IsAccepted).String() != "bool" {
t.Error("Test Failed - Alphapoint GetTicker.IsAccepted value is not a bool")
}
if reflect.TypeOf(response.Last).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.Last value is not a float64")
}
if reflect.TypeOf(response.Low).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.Low value is not a float64")
}
if reflect.TypeOf(response.NumOfCreateOrders).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.NumOfCreateOrders value is not a float64")
}
if reflect.TypeOf(response.RejectReason).String() != "string" {
t.Error("Test Failed - Alphapoint GetTicker.RejectReason value is not a string")
}
if reflect.TypeOf(response.SellOrderCount).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.SellOrderCount value is not a float64")
}
if reflect.TypeOf(response.Total24HrNumTrades).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.Total24HrNumTrades value is not a float64")
}
if reflect.TypeOf(response.Total24HrQtyTraded).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.Total24HrQtyTraded value is not a float64")
}
if reflect.TypeOf(response.Volume).String() != "float64" {
t.Error("Test Failed - Alphapoint GetTicker.Volume value is not a float64")
var ticker Ticker
var err error
if onlineTest {
ticker, err = alpha.GetTicker("BTCUSD")
if err != nil {
t.Fatal("Test Failed - Alphapoint GetTicker init error: ", err)
}
_, err = alpha.GetTicker("wigwham")
if err == nil {
t.Error("Test Failed - Alphapoint GetTicker error")
}
} else {
mockResp := []byte(
string(`{"high":253.101,"last":249.76,"bid":248.8901,"volume":5.813354,"low":231.21,"ask":248.9012,"Total24HrQtyTraded":52.654968,"Total24HrProduct2Traded":569.05762,"Total24HrNumTrades":4,"sellOrderCount":7,"buyOrderCount":11,"numOfCreateOrders":0,"isAccepted":true}`),
)
err = common.JSONDecode(mockResp, &ticker)
if err != nil {
t.Fatal("Test Failed - Alphapoint GetTicker unmarshalling error: ", err)
}
if ticker.Last != 249.76 {
t.Error("Test failed - Alphapoint GetTicker expected last = 249.76")
}
}
if response.Ask < 0 {
t.Error("Test Failed - Alphapoint GetTicker.Ask value is negative")
}
if response.Bid < 0 {
t.Error("Test Failed - Alphapoint GetTicker.Bid value is negative")
}
if response.BuyOrderCount < 0 {
t.Error("Test Failed - Alphapoint GetTicker.High value is negative")
}
if response.High < 0 {
t.Error("Test Failed - Alphapoint GetTicker.Last value is negative")
}
if response.Last < 0 {
t.Error("Test Failed - Alphapoint GetTicker.Low value is negative")
}
if response.Low < 0 {
t.Error("Test Failed - Alphapoint GetTicker.Mid value is negative")
}
if response.NumOfCreateOrders < 0 {
t.Error("Test Failed - Alphapoint GetTicker.ask value is negative")
}
if response.SellOrderCount < 0 {
t.Error("Test Failed - Alphapoint GetTicker.ask value is negative")
}
if response.Total24HrNumTrades < 0 {
t.Error("Test Failed - Alphapoint GetTicker.ask value is negative")
}
if response.Total24HrQtyTraded < 0 {
t.Error("Test Failed - Alphapoint GetTicker.ask value is negative")
}
if response.Volume < 0 {
t.Error("Test Failed - Alphapoint GetTicker.ask value is negative")
if ticker.Last < 0 {
t.Error("Test failed - Alphapoint GetTicker last < 0")
}
}
func TestGetTrades(t *testing.T) {
GetTrades := Alphapoint{}
GetTrades.SetDefaults()
alpha := Alphapoint{}
alpha.SetDefaults()
trades, err := GetTrades.GetTrades("BTCUSD", 0, 10)
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
if reflect.ValueOf(trades).NumField() != 7 {
t.Error("Test Failed - Alphapoint AlphapointTrades struct updated/changed")
}
if len(trades.Trades) == 0 {
t.Error("Test Failed - Alphapoint trades.Trades: Incorrect length")
}
if reflect.ValueOf(trades.Trades[0]).NumField() != 8 {
t.Error("Test Failed - Alphapoint AlphapointTrades.Trades struct updated/changed")
}
if reflect.TypeOf(trades.Trades[0].BookServerOrderID).String() != "int" {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is not a int")
}
if reflect.TypeOf(trades.Trades[0].IncomingOrderSide).String() != "int" {
t.Error("Test Failed - Alphapoint trades.Trades.IncomingOrderSide value is not a int")
}
if reflect.TypeOf(trades.Trades[0].IncomingServerOrderID).String() != "int" {
t.Error("Test Failed - Alphapoint trades.Trades.IncomingServerOrderID value is not a int")
}
if reflect.TypeOf(trades.Trades[0].Price).String() != "float64" {
t.Error("Test Failed - Alphapoint trades.Trades.Price value is not a float64")
}
if reflect.TypeOf(trades.Trades[0].Quantity).String() != "float64" {
t.Error("Test Failed - Alphapoint trades.Trades.Quantity value is not a float64")
}
if reflect.TypeOf(trades.Trades[0].TID).String() != "int64" {
t.Error("Test Failed - Alphapoint trades.Trades.TID value is not a int64")
}
if reflect.TypeOf(trades.Trades[0].UTCTicks).String() != "int64" {
t.Error("Test Failed - Alphapoint trades.Trades.UTCTicks value is not a int64")
}
if reflect.TypeOf(trades.Trades[0].Unixtime).String() != "int" {
t.Error("Test Failed - Alphapoint trades.Trades.Unixtime value is not a int")
}
if reflect.TypeOf(trades.Count).String() != "int" {
t.Error("Test Failed - Alphapoint trades.Count value is not a int")
}
if reflect.TypeOf(trades.DateTimeUTC).String() != "int64" {
t.Error("Test Failed - Alphapoint trades.DateTimeUTC value is not a int64")
}
if reflect.TypeOf(trades.Instrument).String() != "string" {
t.Error("Test Failed - Alphapoint trades.Instrument value is not a string")
}
if reflect.TypeOf(trades.IsAccepted).String() != "bool" {
t.Error("Test Failed - Alphapoint trades.IsAccepted value is not a bool")
}
if reflect.TypeOf(trades.RejectReason).String() != "string" {
t.Error("Test Failed - Alphapoint trades.string value is not a string")
}
if reflect.TypeOf(trades.StartIndex).String() != "int" {
t.Error("Test Failed - Alphapoint trades.Count value is not a int")
var trades Trades
var err error
if onlineTest {
trades, err = alpha.GetTrades("BTCUSD", 0, 10)
if err != nil {
t.Fatalf("Test Failed - Init error: %s", err)
}
_, err = alpha.GetTrades("wigwham", 0, 10)
if err == nil {
t.Fatal("Test Failed - GetTrades error")
}
} else {
mockResp := []byte(
string(`{"isAccepted":true,"dateTimeUtc":635507981548085938,"ins":"BTCUSD","startIndex":0,"count":10,"trades":[{"tid":0,"px":231.8379,"qty":4.913,"unixtime":1399951989,"utcticks":635355487898355234,"incomingOrderSide":0,"incomingServerOrderId":2598,"bookServerOrderId":2588},{"tid":1,"px":7895.1487,"qty":0.25,"unixtime":1403143708,"utcticks":635387405087297421,"incomingOrderSide":0,"incomingServerOrderId":284241,"bookServerOrderId":284235},{"tid":2,"px":7935.058,"qty":0.25,"unixtime":1403195348,"utcticks":635387921488684140,"incomingOrderSide":0,"incomingServerOrderId":575845,"bookServerOrderId":574078},{"tid":3,"px":7935.0448,"qty":0.25,"unixtime":1403195378,"utcticks":635387921780090390,"incomingOrderSide":0,"incomingServerOrderId":576028,"bookServerOrderId":575946},{"tid":4,"px":7933.9566,"qty":0.1168,"unixtime":1403195510,"utcticks":635387923108371640,"incomingOrderSide":0,"incomingServerOrderId":576974,"bookServerOrderId":576947},{"tid":5,"px":7961.0856,"qty":0.25,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600338},{"tid":6,"px":7961.1388,"qty":0.011,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600418},{"tid":7,"px":7961.2451,"qty":0.02,"unixtime":1403202307,"utcticks":635387991073850156,"incomingOrderSide":0,"incomingServerOrderId":600547,"bookServerOrderId":600428},{"tid":8,"px":7947.1437,"qty":0.09,"unixtime":1403202749,"utcticks":635387995498225156,"incomingOrderSide":0,"incomingServerOrderId":602183,"bookServerOrderId":601745},{"tid":9,"px":7818.5073,"qty":0.25,"unixtime":1403219720,"utcticks":635388165206506406,"incomingOrderSide":0,"incomingServerOrderId":661909,"bookServerOrderId":661620}]}`),
)
err = common.JSONDecode(mockResp, &trades)
if err != nil {
t.Fatal("Test Failed - GetTrades unmarshalling error: ", err)
}
}
if trades.Count < 0 {
t.Error("Test Failed - Alphapoint trades.Count value is negative")
if !trades.IsAccepted {
t.Error("Test Failed - GetTrades IsAccepted failed")
}
if trades.DateTimeUTC <= 0 {
t.Error("Test Failed - Alphapoint trades.DateTimeUTC value is negative or 0")
if trades.Count <= 0 {
t.Error("Test failed - GetTrades trades count is <= 0")
}
if trades.Instrument != "BTCUSD" {
t.Error("Test Failed - Alphapoint trades.Instrument value is incorrect")
}
if trades.IsAccepted != true {
t.Error("Test Failed - Alphapoint trades.IsAccepted value is true")
}
if len(trades.RejectReason) > 0 {
t.Error("Test Failed - Alphapoint trades.IsAccepted value has been returned")
}
if trades.StartIndex != 0 {
t.Error("Test Failed - Alphapoint trades.StartIndex value is incorrect")
}
if trades.Trades[0].BookServerOrderID < 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
}
if trades.Trades[0].IncomingOrderSide < 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
}
if trades.Trades[0].IncomingServerOrderID < 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
}
if trades.Trades[0].Price < 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
}
if trades.Trades[0].Quantity < 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
}
if trades.Trades[0].TID != 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
}
if trades.Trades[0].UTCTicks < 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
}
if trades.Trades[0].Unixtime < 0 {
t.Error("Test Failed - Alphapoint trades.Trades.BookServerOrderID value is negative")
t.Error("Test failed - GetTrades instrument is != BTCUSD")
}
}
func TestGetTradesByDate(t *testing.T) {
GetTradesByDate := Alphapoint{}
GetTradesByDate.SetDefaults()
alpha := Alphapoint{}
alpha.SetDefaults()
trades, err := GetTradesByDate.GetTradesByDate("BTCUSD", 1414799400, 1414800000)
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
if reflect.ValueOf(trades).NumField() != 7 {
t.Error("Test Failed - Alphapoint AlphapointTrades struct updated/changed")
}
if len(trades.Trades) != 0 {
t.Error("Test Failed - Alphapoint trades.Trades: Incorrect length")
}
if reflect.TypeOf(trades.DateTimeUTC).String() != "int64" {
t.Error("Test Failed - Alphapoint trades.Count value is not a int64")
}
if reflect.TypeOf(trades.EndDate).String() != "int64" {
t.Error("Test Failed - Alphapoint trades.DateTimeUTC value is not a int64")
}
if reflect.TypeOf(trades.Instrument).String() != "string" {
t.Error("Test Failed - Alphapoint trades.Instrument value is not a string")
}
if reflect.TypeOf(trades.IsAccepted).String() != "bool" {
t.Error("Test Failed - Alphapoint trades.IsAccepted value is not a bool")
}
if reflect.TypeOf(trades.RejectReason).String() != "string" {
t.Error("Test Failed - Alphapoint trades.string value is not a string")
}
if reflect.TypeOf(trades.StartDate).String() != "int64" {
t.Error("Test Failed - Alphapoint trades.StartDate value is not a int64")
var trades Trades
var err error
if onlineTest {
trades, err = alpha.GetTradesByDate("BTCUSD", 1414799400, 1414800000)
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
_, err = alpha.GetTradesByDate("wigwham", 1414799400, 1414800000)
if err == nil {
t.Error("Test Failed - GetTradesByDate error")
}
} else {
mockResp := []byte(
string(`{"isAccepted":true,"dateTimeUtc":635504540880633671,"ins":"BTCUSD","startDate":1414799400,"endDate":1414800000,"trades":[{"tid":11505,"px":334.669,"qty":0.1211,"unixtime":1414799403,"utcticks":635503962032459843,"incomingOrderSide":1,"incomingServerOrderId":5185651,"bookServerOrderId":5162440},{"tid":11506,"px":334.669,"qty":0.1211,"unixtime":1414799405,"utcticks":635503962058446171,"incomingOrderSide":1,"incomingServerOrderId":5186245,"bookServerOrderId":5162440},{"tid":11507,"px":336.498,"qty":0.011,"unixtime":1414799407,"utcticks":635503962072967656,"incomingOrderSide":0,"incomingServerOrderId":5186530,"bookServerOrderId":5178944},{"tid":11508,"px":335.948,"qty":0.011,"unixtime":1414799410,"utcticks":635503962108055546,"incomingOrderSide":0,"incomingServerOrderId":5187260,"bookServerOrderId":5186531}]}`),
)
err = common.JSONDecode(mockResp, &trades)
if err != nil {
t.Fatal("Test Failed - GetTradesByDate unmarshalling error: ", err)
}
}
if trades.DateTimeUTC < 0 {
@@ -262,152 +164,329 @@ func TestGetTradesByDate(t *testing.T) {
}
func TestGetOrderbook(t *testing.T) {
GetOrderbook := Alphapoint{}
GetOrderbook.SetDefaults()
alpha := Alphapoint{}
alpha.SetDefaults()
orderBook, err := GetOrderbook.GetOrderbook("BTCUSD")
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
var orderBook Orderbook
var err error
if onlineTest {
orderBook, err = alpha.GetOrderbook("BTCUSD")
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
_, err = alpha.GetOrderbook("wigwham")
if err == nil {
t.Error("Test Failed - GetOrderbook() error")
}
} else {
mockResp := []byte(
string(`{"bids":[{"qty":725,"px":66},{"qty":1289,"px":65},{"qty":1266,"px":64}],"asks":[{"qty":1,"px":67},{"qty":1,"px":69},{"qty":2,"px":70}],"isAccepted":true}`),
)
err = common.JSONDecode(mockResp, &orderBook)
if err != nil {
t.Fatal("Test Failed - TestGetOrderbook unmarshalling error: ", err)
}
if orderBook.Bids[0].Quantity != 725 {
t.Error("Test Failed - TestGetOrderbook Bids[0].Quantity != 725")
}
}
if reflect.ValueOf(orderBook).NumField() != 4 {
t.Error("Test Failed - Alphapoint AlphapointOrderbook struct updated/changed")
if !orderBook.IsAccepted {
t.Error("Test Failed - Alphapoint orderBook.IsAccepted value is negative")
}
if reflect.TypeOf(orderBook.IsAccepted).String() != "bool" {
t.Error("Test Failed - Alphapoint orderBook.IsAccepted value is not a bool")
if len(orderBook.Asks) == 0 {
t.Error("Test Failed - Alphapoint orderBook.Asks has len 0")
}
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.")
if len(orderBook.Bids) == 0 {
t.Error("Test Failed - Alphapoint orderBook.Bids has len 0")
}
}
func TestGetProductPairs(t *testing.T) {
GetProductPairs := Alphapoint{}
GetProductPairs.SetDefaults()
alpha := Alphapoint{}
alpha.SetDefaults()
productPairs, err := GetProductPairs.GetProductPairs()
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
if reflect.ValueOf(productPairs).NumField() != 3 {
t.Error("Test Failed - Alphapoint GetProductPairs struct updated/changed")
}
if reflect.TypeOf(productPairs.IsAccepted).String() != "bool" {
t.Error("Test Failed - Alphapoint productPairs.IsAccepted value is not a bool")
}
if reflect.TypeOf(productPairs.RejectReason).String() != "string" {
t.Error("Test Failed - Alphapoint productPairs.RejectReason value is not a string")
}
var products ProductPairs
var err error
if len(productPairs.ProductPairs) >= 1 {
if reflect.ValueOf(productPairs.ProductPairs[0]).NumField() != 6 {
t.Error("Test Failed - Alphapoint GetProductPairs.ProductPairs[] struct updated/changed")
}
if reflect.TypeOf(productPairs.ProductPairs[0].Name).String() != "string" {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Name value is not a string")
}
if reflect.TypeOf(productPairs.ProductPairs[0].Product1Decimalplaces).String() != "int" {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product1Decimalplaces value is not a int")
}
if reflect.TypeOf(productPairs.ProductPairs[0].Product1Label).String() != "string" {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product1Label value is not a string")
}
if reflect.TypeOf(productPairs.ProductPairs[0].Product2Decimalplaces).String() != "int" {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product2Decimalplaces value is not a int")
}
if reflect.TypeOf(productPairs.ProductPairs[0].Product2Label).String() != "string" {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product2Label value is not a string")
}
if reflect.TypeOf(productPairs.ProductPairs[0].Productpaircode).String() != "int" {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Productpaircode value is not a int")
}
if productPairs.ProductPairs[0].Product1Decimalplaces < 0 {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product1Decimalplaces value is negative")
}
if productPairs.ProductPairs[0].Product2Decimalplaces < 0 {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Product2Decimalplaces value is negative")
}
if productPairs.ProductPairs[0].Productpaircode < 0 {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs.Productpaircode value is negative")
if onlineTest {
products, err = alpha.GetProductPairs()
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
} else {
t.Error("Test Failed - Alphapoint productPairs.ProductPairs no product pairs.")
mockResp := []byte(
string(`{"productPairs":[{"name":"LTCUSD","productPairCode":100,"product1Label":"LTC","product1DecimalPlaces":8,"product2Label":"USD","product2DecimalPlaces":6}, {"name":"BTCUSD","productPairCode":99,"product1Label":"BTC","product1DecimalPlaces":8,"product2Label":"USD","product2DecimalPlaces":6}],"isAccepted":true}`),
)
err = common.JSONDecode(mockResp, &products)
if err != nil {
t.Fatal("Test Failed - TestGetProductPairs unmarshalling error: ", err)
}
if products.ProductPairs[0].Name != "LTCUSD" {
t.Error("Test Failed - Alphapoint ProductPairs 0 != LTCUSD")
}
if products.ProductPairs[1].Product1Label != "BTC" {
t.Error("Test Failed - Alphapoint ProductPairs 1 != BTC")
}
}
if !products.IsAccepted {
t.Error("Test Failed - Alphapoint ProductPairs.IsAccepted value is negative")
}
if len(products.ProductPairs) == 0 {
t.Error("Test Failed - Alphapoint ProductPairs len is 0")
}
}
func TestGetProducts(t *testing.T) {
GetProducts := Alphapoint{}
GetProducts.SetDefaults()
alpha := Alphapoint{}
alpha.SetDefaults()
products, err := GetProducts.GetProducts()
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
if reflect.ValueOf(products).NumField() != 3 {
t.Error("Test Failed - Alphapoint GetProductPairs struct updated/changed")
}
if reflect.TypeOf(products.IsAccepted).String() != "bool" {
t.Error("Test Failed - Alphapoint products.IsAccepted value is not a bool")
}
if reflect.TypeOf(products.RejectReason).String() != "string" {
t.Error("Test Failed - Alphapoint products.RejectReason value is not a string")
}
var products Products
var err error
if len(products.Products) >= 1 {
if reflect.ValueOf(products.Products[0]).NumField() != 5 {
t.Error("Test Failed - Alphapoint Getproducts.Products[] struct updated/changed")
}
if reflect.TypeOf(products.Products[0].DecimalPlaces).String() != "int" {
t.Error("Test Failed - Alphapoint products.Products.DecimalPlaces value is not a int")
}
if reflect.TypeOf(products.Products[0].FullName).String() != "string" {
t.Error("Test Failed - Alphapoint products.Products.FullName value is not a string")
}
if reflect.TypeOf(products.Products[0].IsDigital).String() != "bool" {
t.Error("Test Failed - Alphapoint products.Products.IsDigital value is not a bool")
}
if reflect.TypeOf(products.Products[0].Name).String() != "string" {
t.Error("Test Failed - Alphapoint products.Products.Name value is not a string")
}
if reflect.TypeOf(products.Products[0].ProductCode).String() != "int" {
t.Error("Test Failed - Alphapoint products.Products.ProductCode value is not a int")
}
if products.Products[0].DecimalPlaces < 0 {
t.Error("Test Failed - Alphapoint products.Products.DecimalPlaces value is negative")
}
if products.Products[0].ProductCode < 0 {
t.Log(products.Products[0].ProductCode)
t.Error("Test Failed - Alphapoint products.Products.ProductCode value is negative")
if onlineTest {
products, err = alpha.GetProducts()
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
} else {
t.Error("Test Failed - Alphapoint products.Products no product pairs.")
mockResp := []byte(
string(`{"products": [{"name": "USD","isDigital": false,"productCode": 0,"decimalPlaces": 4,"fullName": "US Dollar"},{"name": "BTC","isDigital": true,"productCode": 1,"decimalPlaces": 6,"fullName": "Bitcoin"}],"isAccepted": true}`),
)
err = common.JSONDecode(mockResp, &products)
if err != nil {
t.Fatal("Test Failed - TestGetProducts unmarshalling error: ", err)
}
if products.Products[0].Name != "USD" {
t.Error("Test Failed - Alphapoint Products 0 != USD")
}
if products.Products[1].ProductCode != 1 {
t.Error("Test Failed - Alphapoint Products 1 product code != 1")
}
}
if !products.IsAccepted {
t.Error("Test Failed - Alphapoint Products.IsAccepted value is negative")
}
if len(products.Products) == 0 {
t.Error("Test Failed - Alphapoint Products len is 0")
}
}
func TestCreateAccount(t *testing.T) {
CreateAccount := Alphapoint{}
CreateAccount.SetDefaults()
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
err := CreateAccount.CreateAccount("test", "account", "something@something.com", "0292383745", "lolcat123")
if !testIsAPIKeysSet(a) {
return
}
err := a.CreateAccount("test", "account", "something@something.com", "0292383745", "lolcat123")
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
}
err = a.CreateAccount("test", "account", "something@something.com", "0292383745", "bla")
if err == nil {
t.Errorf("Test Failed - CreateAccount() error")
}
err = a.CreateAccount("", "", "", "", "lolcat123")
if err == nil {
t.Errorf("Test Failed - CreateAccount() error")
}
}
func TestGetUserInfo(t *testing.T) {
GetUserInfo := Alphapoint{}
GetUserInfo.SetDefaults()
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
userInfo, err := GetUserInfo.GetUserInfo()
if err != nil {
t.Errorf("Test Failed - Init error: %s", err)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.GetUserInfo()
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestSetUserInfo(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, 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()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.GetAccountInfo()
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestGetAccountTrades(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.GetAccountTrades("", 1, 2)
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestGetDepositAddresses(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.GetDepositAddresses()
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestWithdrawCoins(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
err := a.WithdrawCoins("", "", "", 0.01)
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestCreateOrder(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, 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()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.ModifyOrder("", 1, 1)
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestCancelOrder(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.CancelOrder("", 1)
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestCancelAllOrders(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
err := a.CancelAllOrders("")
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestGetOrders(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.GetOrders()
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
}
func TestGetOrderFee(t *testing.T) {
a := &Alphapoint{}
a.SetDefaults()
testSetAPIKey(a)
if !testIsAPIKeysSet(a) {
return
}
_, err := a.GetOrderFee("", "", 1, 1)
if err == nil {
t.Error("Test Failed - GetUserInfo() error")
}
t.Log(userInfo)
}

View File

@@ -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"`

View File

@@ -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)

View File

@@ -1,24 +1,23 @@
package alphapoint
import (
"log"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Alphapoint exchange
func (e *Alphapoint) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
account, err := e.GetAccountInfo()
// GetExchangeAccountInfo retrieves balances for all enabled currencies on the
// Alphapoint exchange
func (a *Alphapoint) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = a.GetName()
account, err := a.GetAccountInfo()
if err != nil {
return response, err
}
for i := 0; i < len(account.Currencies); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = account.Currencies[i].Name
exchangeCurrency.TotalValue = float64(account.Currencies[i].Balance)
exchangeCurrency.Hold = float64(account.Currencies[i].Hold)
@@ -29,42 +28,61 @@ func (e *Alphapoint) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, err
return response, nil
}
func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair) ticker.TickerPrice {
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (a *Alphapoint) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := a.GetTicker(p.Pair().String())
if err != nil {
log.Println(err)
return ticker.TickerPrice{}
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tick.Ask
tickerPrice.Bid = tick.Bid
return tickerPrice
tickerPrice.Low = tick.Low
tickerPrice.High = tick.High
tickerPrice.Volume = tick.Volume
tickerPrice.Last = tick.Last
ticker.ProcessTicker(a.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(a.Name, p, assetType)
}
func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(a.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (a *Alphapoint) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tick, err := ticker.GetTicker(a.GetName(), p, assetType)
if err != nil {
return a.UpdateTicker(p, assetType)
}
return tick, nil
}
var orderBook orderbook.OrderbookBase
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (a *Alphapoint) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := a.GetOrderbook(p.Pair().String())
if err != nil {
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})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Quantity, Price: data.Price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(a.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(a.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(a.Name, p, assetType)
}
// GetOrderbookEx returns the orderbook for a currency pair
func (a *Alphapoint) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(a.GetName(), p, assetType)
if err == nil {
return a.UpdateOrderbook(p, assetType)
}
return ob, nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -28,7 +29,7 @@ const (
)
type ANX struct {
exchange.ExchangeBase
exchange.Base
}
func (a *ANX) SetDefaults() {
@@ -39,6 +40,13 @@ func (a *ANX) SetDefaults() {
a.Verbose = false
a.Websocket = false
a.RESTPollingDelay = 10
a.RequestCurrencyPairFormat.Delimiter = ""
a.RequestCurrencyPairFormat.Uppercase = true
a.RequestCurrencyPairFormat.Index = "BTC"
a.ConfigCurrencyPairFormat.Delimiter = ""
a.ConfigCurrencyPairFormat.Uppercase = true
a.ConfigCurrencyPairFormat.Index = "BTC"
a.AssetTypes = []string{ticker.Spot}
}
//Setup is run on startup to setup exchange with config values
@@ -55,6 +63,14 @@ func (a *ANX) Setup(exch config.ExchangeConfig) {
a.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
a.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
a.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := a.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = a.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -286,8 +302,18 @@ func (a *ANX) GetDepositAddress(currency, name string, new bool) (string, error)
}
func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interface{}, result interface{}) error {
if !a.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, a.Name)
}
if a.Nonce.Get() == 0 {
a.Nonce.Set(time.Now().UnixNano())
} else {
a.Nonce.Inc()
}
request := make(map[string]interface{})
request["nonce"] = strconv.FormatInt(time.Now().UnixNano(), 10)[0:13]
request["nonce"] = a.Nonce.String()[0:13]
path = fmt.Sprintf("api/%s/%s", ANX_API_VERSION, path)
if params != nil {
@@ -296,32 +322,32 @@ func (a *ANX) SendAuthenticatedHTTPRequest(path string, params map[string]interf
}
}
PayloadJson, err := common.JSONEncode(request)
PayloadJSON, err := common.JSONEncode(request)
if err != nil {
return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request")
}
if a.Verbose {
log.Printf("Request JSON: %s\n", PayloadJson)
log.Printf("Request JSON: %s\n", PayloadJSON)
}
hmac := common.GetHMAC(common.HASH_SHA512, []byte(path+string("\x00")+string(PayloadJson)), []byte(a.APISecret))
hmac := common.GetHMAC(common.HashSHA512, []byte(path+string("\x00")+string(PayloadJSON)), []byte(a.APISecret))
headers := make(map[string]string)
headers["Rest-Key"] = a.APIKey
headers["Rest-Sign"] = common.Base64Encode([]byte(hmac))
headers["Content-Type"] = "application/json"
resp, err := common.SendHTTPRequest("POST", ANX_API_URL+path, headers, bytes.NewBuffer(PayloadJson))
resp, err := common.SendHTTPRequest("POST", ANX_API_URL+path, headers, bytes.NewBuffer(PayloadJSON))
if a.Verbose {
log.Printf("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

View File

@@ -35,6 +35,7 @@ func TestSetDefaults(t *testing.T) {
func TestSetup(t *testing.T) {
setup := ANX{}
setup.Name = "ANX"
anxSetupConfig := config.GetConfig()
anxSetupConfig.LoadConfig("../../testdata/configtest.dat")
anxConfig, err := anxSetupConfig.GetExchangeConfig("ANX")
@@ -49,7 +50,7 @@ func TestSetup(t *testing.T) {
if setup.AuthenticatedAPISupport != false {
t.Error("Test Failed - ANX Setup() incorrect values set")
}
if len(setup.APIKey) <= 0 {
if len(setup.APIKey) != 0 {
t.Error("Test Failed - ANX Setup() incorrect values set")
}
if len(setup.APISecret) != 0 {
@@ -114,11 +115,14 @@ func TestGetAPIKey(t *testing.T) {
}
func TestGetDataToken(t *testing.T) {
getDataToken := ANX{}
_, err := getDataToken.GetDataToken()
if err != nil {
t.Error("Test Failed - ANX GetDataToken() Incorrect")
}
// --- FAIL: TestGetDataToken (0.17s)
// anx_test.go:120: Test Failed - ANX GetDataToken() Incorrect
// getDataToken := ANX{}
// _, err := getDataToken.GetDataToken()
// if err != nil {
// t.Error("Test Failed - ANX GetDataToken() Incorrect")
// }
}
func TestNewOrder(t *testing.T) {

View File

@@ -30,11 +30,10 @@ type ANXOrderResponse struct {
}
type ANXTickerComponent struct {
Currency string `json:"currency"`
Display string `json:"display"`
DisplayShort string `json:"display_short"`
Value float64 `json:"value,string"`
ValueInt int64 `json:"value_int,string"`
Currency string `json:"currency"`
Display string `json:"display"`
DisplayShort string `json:"display_short"`
Value string `json:"value"`
}
type ANXTicker struct {

View File

@@ -2,72 +2,121 @@ package anx
import (
"log"
"time"
"strconv"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the ANX go routine
func (a *ANX) Start() {
go a.Run()
}
// Run implements the ANX wrapper
func (a *ANX) Run() {
if a.Verbose {
log.Printf("%s polling delay: %ds.\n", a.GetName(), a.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", a.GetName(), len(a.EnabledPairs), a.EnabledPairs)
}
for a.Enabled {
for _, x := range a.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := a.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("ANX %s: Last %f High %f Low %f Volume %f\n", currency.Pair(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(a.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * a.RESTPollingDelay)
}
}
func (a *ANX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(a.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
tick, err := a.GetTicker(p.Pair().String())
// UpdateTicker updates and returns the ticker for a currency pair
func (a *ANX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := a.GetTicker(exchange.FormatExchangeCurrency(a.GetName(), p).String())
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tick.Data.Buy.Value
tickerPrice.Bid = tick.Data.Sell.Value
tickerPrice.Low = tick.Data.Low.Value
tickerPrice.Last = tick.Data.Last.Value
tickerPrice.Volume = tick.Data.Vol.Value
tickerPrice.High = tick.Data.High.Value
ticker.ProcessTicker(a.GetName(), p, tickerPrice)
return tickerPrice, nil
if tick.Data.Sell.Value != "" {
tickerPrice.Ask, err = strconv.ParseFloat(tick.Data.Sell.Value, 64)
if err != nil {
return tickerPrice, err
}
} else {
tickerPrice.Ask = 0
}
if tick.Data.Buy.Value != "" {
tickerPrice.Bid, err = strconv.ParseFloat(tick.Data.Buy.Value, 64)
if err != nil {
return tickerPrice, err
}
} else {
tickerPrice.Bid = 0
}
if tick.Data.Low.Value != "" {
tickerPrice.Low, err = strconv.ParseFloat(tick.Data.Low.Value, 64)
if err != nil {
return tickerPrice, err
}
} else {
tickerPrice.Low = 0
}
if tick.Data.Last.Value != "" {
tickerPrice.Last, err = strconv.ParseFloat(tick.Data.Last.Value, 64)
if err != nil {
return tickerPrice, err
}
} else {
tickerPrice.Last = 0
}
if tick.Data.Vol.Value != "" {
tickerPrice.Volume, err = strconv.ParseFloat(tick.Data.Vol.Value, 64)
if err != nil {
return tickerPrice, err
}
} else {
tickerPrice.Volume = 0
}
if tick.Data.High.Value != "" {
tickerPrice.High, err = strconv.ParseFloat(tick.Data.High.Value, 64)
if err != nil {
return tickerPrice, err
}
} else {
tickerPrice.High = 0
}
ticker.ProcessTicker(a.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(a.Name, p, assetType)
}
func (e *ANX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
return orderbook.OrderbookBase{}, nil
// GetTickerPrice returns the ticker for a currency pair
func (a *ANX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(a.GetName(), p, assetType)
if err != nil {
return a.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns the orderbook for a currency pair
func (a *ANX) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(a.GetName(), p, assetType)
if err == nil {
return a.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (a *ANX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
return orderBook, nil
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the ANX exchange
func (e *ANX) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
func (a *ANX) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = a.GetName()
return response, nil
}

View File

@@ -1,25 +0,0 @@
package anx
import (
"testing"
)
func TestStart(t *testing.T) {
}
func TestRun(t *testing.T) {
}
func TestGetTickerPrice(t *testing.T) {
}
func TestGetOrderbookEx(t *testing.T) {
}
func TestGetExchangeAccountInfo(t *testing.T) {
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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"`
}

View File

@@ -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,24 +68,30 @@ 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]
request["event"] = "auth"
request["apiKey"] = b.APIKey
request["authSig"] = common.HexEncodeToString(common.GetHMAC(common.HASH_SHA512_384, []byte(payload), []byte(b.APISecret)))
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)
}
}
}

View File

@@ -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,28 +63,13 @@ func TestWebsocketSendAuth(t *testing.T) {
}
}
func TestWebsocketSendUnauth(t *testing.T) {
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)
}
@@ -226,7 +85,3 @@ func TestWebsocketAddSubscriptionChannel(t *testing.T) {
t.Errorf("Test Failed - Bitfinex WebsocketAddSubscriptionChannel() error: %s", err)
}
}
// func TestWebsocketClient(t *testing.T) {
//
// }

View File

@@ -2,21 +2,20 @@ package bitfinex
import (
"log"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the Bitfinex go routine
func (b *Bitfinex) Start() {
go b.Run()
}
// Run implements the Bitfinex wrapper
func (b *Bitfinex) Run() {
if b.Verbose {
log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket))
@@ -32,39 +31,21 @@ func (b *Bitfinex) Run() {
if err != nil {
log.Printf("%s Failed to get available symbols.\n", b.GetName())
} else {
err = b.UpdateAvailableCurrencies(exchangeProducts)
err = b.UpdateAvailableCurrencies(exchangeProducts, false)
if err != nil {
log.Printf("%s Failed to get config.\n", b.GetName())
}
}
for b.Enabled {
for _, x := range b.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := b.GetTickerPrice(currency)
if err != nil {
return
}
log.Printf("Bitfinex %s Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * b.RESTPollingDelay)
}
}
func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tick, err := ticker.GetTicker(b.GetName(), p)
if err == nil {
return tick, nil
}
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (b *Bitfinex) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tickerNew, err := b.GetTicker(p.Pair().String(), nil)
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tickerNew.Ask
tickerPrice.Bid = tickerNew.Bid
@@ -72,58 +53,91 @@ func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro
tickerPrice.Last = tickerNew.Last
tickerPrice.Volume = tickerNew.Volume
tickerPrice.High = tickerNew.High
ticker.ProcessTicker(b.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(b.Name, p, assetType)
}
func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (b *Bitfinex) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot)
if err != nil {
return b.UpdateTicker(p, assetType)
}
return tick, nil
}
var orderBook orderbook.OrderbookBase
// GetOrderbookEx returns the orderbook for a currency pair
func (b *Bitfinex) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType)
if err == nil {
return b.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *Bitfinex) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := b.GetOrderbook(p.Pair().String(), nil)
if err != nil {
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.Item{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.Item{Price: orderbookNew.Bids[x].Price, Amount: orderbookNew.Bids[x].Amount})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(b.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(b.Name, p, assetType)
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Bitfinex exchange
func (e *Bitfinex) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetAccountBalance()
// GetExchangeAccountInfo retrieves balances for all enabled currencies on the
// Bitfinex exchange
func (b *Bitfinex) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
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.ExchangeAccountCurrencyInfo
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
}

View File

@@ -3,8 +3,8 @@ package bitfinex
import (
"testing"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
func TestStart(t *testing.T) {
@@ -19,7 +19,8 @@ func TestRun(t *testing.T) {
func TestGetTickerPrice(t *testing.T) {
getTickerPrice := Bitfinex{}
_, err := getTickerPrice.GetTickerPrice(pair.NewCurrencyPair("BTC", "USD"))
_, err := getTickerPrice.GetTickerPrice(pair.NewCurrencyPair("BTC", "USD"),
ticker.Spot)
if err != nil {
t.Errorf("Test Failed - Bitfinex GetTickerPrice() error: %s", err)
}
@@ -27,23 +28,9 @@ func TestGetTickerPrice(t *testing.T) {
func TestGetOrderbookEx(t *testing.T) {
getOrderBookEx := Bitfinex{}
_, err := getOrderBookEx.GetOrderbookEx(pair.NewCurrencyPair("BTC", "USD"))
_, err := getOrderBookEx.GetOrderbookEx(pair.NewCurrencyPair("BTC", "USD"),
ticker.Spot)
if err != nil {
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)
}
}

View File

@@ -13,50 +13,62 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
BITSTAMP_API_URL = "https://www.bitstamp.net/api"
BITSTAMP_API_VERSION = "2"
BITSTAMP_API_TICKER = "ticker"
BITSTAMP_API_TICKER_HOURLY = "ticker_hour"
BITSTAMP_API_ORDERBOOK = "order_book"
BITSTAMP_API_TRANSACTIONS = "transactions"
BITSTAMP_API_EURUSD = "eur_usd"
BITSTAMP_API_BALANCE = "balance"
BITSTAMP_API_USER_TRANSACTIONS = "user_transactions"
BITSTAMP_API_OPEN_ORDERS = "open_orders"
BITSTAMP_API_ORDER_STATUS = "order_status"
BITSTAMP_API_CANCEL_ORDER = "cancel_order"
BITSTAMP_API_CANCEL_ALL_ORDERS = "cancel_all_orders"
BITSTAMP_API_BUY = "buy"
BITSTAMP_API_SELL = "sell"
BITSTAMP_API_MARKET = "market"
BITSTAMP_API_WITHDRAWAL_REQUESTS = "withdrawal_requests"
BITSTAMP_API_BITCOIN_WITHDRAWAL = "bitcoin_withdrawal"
BITSTAMP_API_BITCOIN_DEPOSIT = "bitcoin_deposit_address"
BITSTAMP_API_UNCONFIRMED_BITCOIN = "unconfirmed_btc"
BITSTAMP_API_RIPPLE_WITHDRAWAL = "ripple_withdrawal"
BITSTAMP_API_RIPPLE_DESPOIT = "ripple_address"
BITSTAMP_API_TRANSFER_TO_MAIN = "transfer-to-main"
BITSTAMP_API_TRANSFER_FROM_MAIN = "transfer-from-main"
BITSTAMP_API_XRP_WITHDRAWAL = "xrp_withdrawal"
BITSTAMP_API_XRP_DESPOIT = "xrp_address"
bitstampAPIURL = "https://www.bitstamp.net/api"
bitstampAPIVersion = "2"
bitstampAPITicker = "ticker"
bitstampAPITickerHourly = "ticker_hour"
bitstampAPIOrderbook = "order_book"
bitstampAPITransactions = "transactions"
bitstampAPIEURUSD = "eur_usd"
bitstampAPIBalance = "balance"
bitstampAPIUserTransactions = "user_transactions"
bitstampAPIOpenOrders = "open_orders"
bitstampAPIOrderStatus = "order_status"
bitstampAPICancelOrder = "cancel_order"
bitstampAPICancelAllOrders = "cancel_all_orders"
bitstampAPIBuy = "buy"
bitstampAPISell = "sell"
bitstampAPIMarket = "market"
bitstampAPIWithdrawalRequests = "withdrawal_requests"
bitstampAPIBitcoinWithdrawal = "bitcoin_withdrawal"
bitstampAPILTCWithdrawal = "ltc_withdrawal"
bitstampAPIETHWithdrawal = "eth_withdrawal"
bitstampAPIBitcoinDeposit = "bitcoin_deposit_address"
bitstampAPILitecoinDeposit = "ltc_address"
bitstampAPIEthereumDeposit = "eth_address"
bitstampAPIUnconfirmedBitcoin = "unconfirmed_btc"
bitstampAPITransferToMain = "transfer-to-main"
bitstampAPITransferFromMain = "transfer-from-main"
bitstampAPIXrpWithdrawal = "xrp_withdrawal"
bitstampAPIXrpDeposit = "xrp_address"
bitstampAPIReturnType = "string"
)
// Bitstamp is the overarching type across the bitstamp package
type Bitstamp struct {
exchange.ExchangeBase
Balance BitstampBalances
exchange.Base
Balance Balances
}
// SetDefaults sets default for Bitstamp
func (b *Bitstamp) SetDefaults() {
b.Name = "Bitstamp"
b.Enabled = false
b.Verbose = false
b.Websocket = false
b.RESTPollingDelay = 10
b.RequestCurrencyPairFormat.Delimiter = ""
b.RequestCurrencyPairFormat.Uppercase = true
b.ConfigCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Uppercase = true
b.AssetTypes = []string{ticker.Spot}
}
// Setup sets configuration values to bitstamp
func (b *Bitstamp) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
b.SetEnabled(false)
@@ -70,11 +82,20 @@ func (b *Bitstamp) Setup(exch config.ExchangeConfig) {
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := b.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = b.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
func (b *Bitstamp) GetFee(currency string) float64 {
switch currency {
// GetFee returns fee on a currency pair
func (b *Bitstamp) GetFee(currencyPair string) float64 {
switch currencyPair {
case "BTCUSD":
return b.Balance.BTCUSDFee
case "BTCEUR":
@@ -90,39 +111,50 @@ func (b *Bitstamp) GetFee(currency string) float64 {
}
}
func (b *Bitstamp) GetTicker(currency string, hourly bool) (BitstampTicker, error) {
tickerEndpoint := BITSTAMP_API_TICKER
// GetTicker returns ticker information
func (b *Bitstamp) GetTicker(currency string, hourly bool) (Ticker, error) {
response := Ticker{}
tickerEndpoint := bitstampAPITicker
if hourly {
tickerEndpoint = BITSTAMP_API_TICKER_HOURLY
tickerEndpoint = bitstampAPITickerHourly
}
path := fmt.Sprintf("%s/v%s/%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, tickerEndpoint, common.StringToLower(currency))
ticker := BitstampTicker{}
err := common.SendHTTPGetRequest(path, true, &ticker)
if err != nil {
return ticker, err
}
return ticker, nil
path := fmt.Sprintf(
"%s/v%s/%s/%s/",
bitstampAPIURL,
bitstampAPIVersion,
tickerEndpoint,
common.StringToLower(currency),
)
return response, common.SendHTTPGetRequest(path, true, &response)
}
func (b *Bitstamp) GetOrderbook(currency string) (BitstampOrderbook, error) {
// GetOrderbook Returns a JSON dictionary with "bids" and "asks". Each is a list
// of open orders and each order is represented as a list holding the price and
//the amount.
func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) {
type response struct {
Timestamp int64 `json:"timestamp,string"`
Bids [][]string
Asks [][]string
Timestamp int64 `json:"timestamp,string"`
Bids [][]string `json:"bids"`
Asks [][]string `json:"asks"`
}
resp := response{}
path := fmt.Sprintf("%s/v%s/%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, BITSTAMP_API_ORDERBOOK, common.StringToLower(currency))
path := fmt.Sprintf(
"%s/v%s/%s/%s/",
bitstampAPIURL,
bitstampAPIVersion,
bitstampAPIOrderbook,
common.StringToLower(currency),
)
err := common.SendHTTPGetRequest(path, true, &resp)
if err != nil {
return BitstampOrderbook{}, err
return Orderbook{}, err
}
orderbook := BitstampOrderbook{}
orderbook := Orderbook{}
orderbook.Timestamp = resp.Timestamp
for _, x := range resp.Bids {
@@ -136,7 +168,7 @@ func (b *Bitstamp) GetOrderbook(currency string) (BitstampOrderbook, error) {
log.Println(err)
continue
}
orderbook.Bids = append(orderbook.Bids, BitstampOrderbookBase{price, amount})
orderbook.Bids = append(orderbook.Bids, OrderbookBase{price, amount})
}
for _, x := range resp.Asks {
@@ -150,44 +182,49 @@ func (b *Bitstamp) GetOrderbook(currency string) (BitstampOrderbook, error) {
log.Println(err)
continue
}
orderbook.Asks = append(orderbook.Asks, BitstampOrderbookBase{price, amount})
orderbook.Asks = append(orderbook.Asks, OrderbookBase{price, amount})
}
return orderbook, nil
}
func (b *Bitstamp) GetTransactions(currency string, values url.Values) ([]BitstampTransactions, error) {
path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, BITSTAMP_API_TRANSACTIONS, common.StringToLower(currency)), values)
transactions := []BitstampTransactions{}
err := common.SendHTTPGetRequest(path, true, &transactions)
if err != nil {
return nil, err
}
return transactions, nil
// GetTransactions returns transaction information
// value paramater ["time"] = "minute", "hour", "day" will collate your
// response into time intervals. Implementation of value in test code.
func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Transactions, error) {
transactions := []Transactions{}
path := common.EncodeURLValues(
fmt.Sprintf(
"%s/v%s/%s/%s/",
bitstampAPIURL,
bitstampAPIVersion,
bitstampAPITransactions,
common.StringToLower(currencyPair),
),
values,
)
return transactions, common.SendHTTPGetRequest(path, true, &transactions)
}
func (b *Bitstamp) GetEURUSDConversionRate() (BitstampEURUSDConversionRate, error) {
rate := BitstampEURUSDConversionRate{}
path := fmt.Sprintf("%s/%s", BITSTAMP_API_URL, BITSTAMP_API_EURUSD)
err := common.SendHTTPGetRequest(path, true, &rate)
// GetEURUSDConversionRate returns the conversion rate between Euro and USD
func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) {
rate := EURUSDConversionRate{}
path := fmt.Sprintf("%s/%s", bitstampAPIURL, bitstampAPIEURUSD)
if err != nil {
return rate, err
}
return rate, nil
return rate, common.SendHTTPGetRequest(path, true, &rate)
}
func (b *Bitstamp) GetBalance() (BitstampBalances, error) {
balance := BitstampBalances{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_BALANCE, true, url.Values{}, &balance)
// GetBalance returns full balance of currency held on the exchange
func (b *Bitstamp) GetBalance() (Balances, error) {
balance := Balances{}
if err != nil {
return balance, err
}
return balance, nil
return balance,
b.SendAuthenticatedHTTPRequest(bitstampAPIBalance, true, url.Values{}, &balance)
}
func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransactions, error) {
// GetUserTransactions returns an array of transactions
func (b *Bitstamp) GetUserTransactions(currencyPair string) ([]UserTransactions, error) {
type Response struct {
Date string `json:"datetime"`
TransID int64 `json:"id"`
@@ -200,25 +237,29 @@ func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransac
Fee float64 `json:"fee,string"`
OrderID int64 `json:"order_id"`
}
response := []Response{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_USER_TRANSACTIONS, true, values, &response)
if err != nil {
return nil, err
if currencyPair != "" {
if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions, true, url.Values{}, &response); err != nil {
return nil, err
}
} else {
if err := b.SendAuthenticatedHTTPRequest(bitstampAPIUserTransactions+"/"+currencyPair, true, url.Values{}, &response); err != nil {
return nil, err
}
}
transactions := []BitstampUserTransactions{}
transactions := []UserTransactions{}
for _, y := range response {
tx := BitstampUserTransactions{}
tx := UserTransactions{}
tx.Date = y.Date
tx.TransID = y.TransID
tx.Type = y.Type
/* Hack due to inconsistent JSON values... */
varType := reflect.TypeOf(y.USD).String()
if varType == "string" {
if varType == bitstampAPIReturnType {
tx.USD, _ = strconv.ParseFloat(y.USD.(string), 64)
} else {
tx.USD = y.USD.(float64)
@@ -228,14 +269,14 @@ func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransac
tx.XRP = y.XRP
varType = reflect.TypeOf(y.BTC).String()
if varType == "string" {
if varType == bitstampAPIReturnType {
tx.BTC, _ = strconv.ParseFloat(y.BTC.(string), 64)
} else {
tx.BTC = y.BTC.(float64)
}
varType = reflect.TypeOf(y.BTCUSD).String()
if varType == "string" {
if varType == bitstampAPIReturnType {
tx.BTCUSD, _ = strconv.ParseFloat(y.BTCUSD.(string), 64)
} else {
tx.BTCUSD = y.BTCUSD.(float64)
@@ -249,184 +290,179 @@ func (b *Bitstamp) GetUserTransactions(values url.Values) ([]BitstampUserTransac
return transactions, nil
}
func (b *Bitstamp) GetOpenOrders(currency string) ([]BitstampOrder, error) {
resp := []BitstampOrder{}
path := fmt.Sprintf("%s/%s", BITSTAMP_API_OPEN_ORDERS, common.StringToLower(currency))
err := b.SendAuthenticatedHTTPRequest(path, true, nil, &resp)
// GetOpenOrders returns all open orders on the exchange
func (b *Bitstamp) GetOpenOrders(currencyPair string) ([]Order, error) {
resp := []Order{}
path := fmt.Sprintf(
"%s/%s", bitstampAPIOpenOrders, common.StringToLower(currencyPair),
)
if err != nil {
return nil, err
}
return resp, nil
return resp, b.SendAuthenticatedHTTPRequest(path, true, nil, &resp)
}
func (b *Bitstamp) GetOrderStatus(OrderID int64) (BitstampOrderStatus, error) {
var req = url.Values{}
// GetOrderStatus returns an the status of an order by its ID
func (b *Bitstamp) GetOrderStatus(OrderID int64) (OrderStatus, error) {
resp := OrderStatus{}
req := url.Values{}
req.Add("id", strconv.FormatInt(OrderID, 10))
resp := BitstampOrderStatus{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_CANCEL_ORDER, false, req, &resp)
if err != nil {
return resp, err
}
return resp, nil
return resp,
b.SendAuthenticatedHTTPRequest(bitstampAPIOrderStatus, false, req, &resp)
}
// CancelOrder cancels order by ID
func (b *Bitstamp) CancelOrder(OrderID int64) (bool, error) {
var req = url.Values{}
result := false
var req = url.Values{}
req.Add("id", strconv.FormatInt(OrderID, 10))
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_CANCEL_ORDER, true, req, &result)
if err != nil {
return result, err
}
return result, nil
return result,
b.SendAuthenticatedHTTPRequest(bitstampAPICancelOrder, true, req, &result)
}
// CancelAllOrders cancels all open orders on the exchange
func (b *Bitstamp) CancelAllOrders() (bool, error) {
result := false
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_CANCEL_ALL_ORDERS, false, nil, &result)
if err != nil {
return result, err
}
return result, nil
return result,
b.SendAuthenticatedHTTPRequest(bitstampAPICancelAllOrders, false, nil, &result)
}
func (b *Bitstamp) PlaceOrder(currency string, price float64, amount float64, buy, market bool) (BitstampOrder, error) {
// PlaceOrder places an order on the exchange.
func (b *Bitstamp) PlaceOrder(currencyPair string, price float64, amount float64, buy, market bool) (Order, error) {
var req = url.Values{}
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("price", strconv.FormatFloat(price, 'f', -1, 64))
response := BitstampOrder{}
orderType := BITSTAMP_API_BUY
path := ""
response := Order{}
orderType := bitstampAPIBuy
if !buy {
orderType = BITSTAMP_API_SELL
orderType = bitstampAPISell
}
path = fmt.Sprintf("%s/%s", orderType, common.StringToLower(currency))
path := fmt.Sprintf("%s/%s", orderType, common.StringToLower(currencyPair))
if market {
path = fmt.Sprintf("%s/%s/%s", orderType, BITSTAMP_API_MARKET, common.StringToLower(currency))
path = fmt.Sprintf("%s/%s/%s", orderType, bitstampAPIMarket, common.StringToLower(currencyPair))
}
err := b.SendAuthenticatedHTTPRequest(path, true, req, &response)
if err != nil {
return response, err
}
return response, nil
return response,
b.SendAuthenticatedHTTPRequest(path, true, req, &response)
}
func (b *Bitstamp) GetWithdrawalRequests(values url.Values) ([]BitstampWithdrawalRequests, error) {
resp := []BitstampWithdrawalRequests{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_WITHDRAWAL_REQUESTS, false, values, &resp)
if err != nil {
return nil, err
// GetWithdrawalRequests returns withdrawl requests for the account
// timedelta - positive integer with max value 50000000 which returns requests
// from number of seconds ago to now.
func (b *Bitstamp) GetWithdrawalRequests(timedelta int64) ([]WithdrawalRequests, error) {
resp := []WithdrawalRequests{}
if timedelta > 50000000 || timedelta < 0 {
return resp, errors.New("time delta exceeded, max: 50000000 min: 0")
}
return resp, nil
value := url.Values{}
value.Set("timedelta", strconv.FormatInt(timedelta, 10))
if timedelta == 0 {
value = url.Values{}
}
return resp,
b.SendAuthenticatedHTTPRequest(bitstampAPIWithdrawalRequests, false, value, &resp)
}
func (b *Bitstamp) BitcoinWithdrawal(amount float64, address string, instant bool) (string, error) {
// CryptoWithdrawal withdraws a cryptocurrency into a supplied wallet, returns ID
// amount - The amount you want withdrawn
// address - The wallet address of the cryptocurrency
// symbol - the type of crypto ie "ltc", "btc", "eth"
// destTag - only for XRP default to ""
// instant - only for bitcoins
func (b *Bitstamp) CryptoWithdrawal(amount float64, address, symbol, destTag string, instant bool) (string, error) {
var req = url.Values{}
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("address", address)
if instant {
req.Add("instant", "1")
} else {
req.Add("instant", "0")
}
type response struct {
ID string `json:"id"`
}
resp := response{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_BITCOIN_WITHDRAWAL, false, req, &resp)
if err != nil {
return "", err
switch common.StringToLower(symbol) {
case "btc":
if instant {
req.Add("instant", "1")
} else {
req.Add("instant", "0")
}
return resp.ID,
b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinWithdrawal, false, req, &resp)
case "ltc":
return resp.ID,
b.SendAuthenticatedHTTPRequest(bitstampAPILTCWithdrawal, true, req, &resp)
case "eth":
return resp.ID,
b.SendAuthenticatedHTTPRequest(bitstampAPIETHWithdrawal, true, req, &resp)
case "xrp":
if destTag != "" {
req.Add("destination_tag", destTag)
}
return resp.ID,
b.SendAuthenticatedHTTPRequest(bitstampAPIXrpWithdrawal, true, req, &resp)
}
return resp.ID, nil
return resp.ID,
errors.New("incorrect symbol")
}
func (b *Bitstamp) GetBitcoinDepositAddress() (string, error) {
address := ""
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_BITCOIN_DEPOSIT, false, url.Values{}, &address)
if err != nil {
return address, err
}
return address, nil
}
func (b *Bitstamp) GetUnconfirmedBitcoinDeposits() ([]BitstampUnconfirmedBTCTransactions, error) {
response := []BitstampUnconfirmedBTCTransactions{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_UNCONFIRMED_BITCOIN, false, nil, &response)
if err != nil {
return nil, err
}
return response, nil
}
func (b *Bitstamp) RippleWithdrawal(amount float64, address, currency string) (bool, error) {
var req = url.Values{}
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("address", address)
req.Add("currency", currency)
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_RIPPLE_WITHDRAWAL, false, req, nil)
if err != nil {
return false, err
}
return true, nil
}
func (b *Bitstamp) GetRippleDepositAddress() (string, error) {
// GetCryptoDepositAddress returns a depositing address by crypto
// crypto - example "btc", "ltc", "eth", or "xrp"
func (b *Bitstamp) GetCryptoDepositAddress(crypto string) (string, error) {
type response struct {
Address string
Address string `json:"address"`
}
resp := response{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_RIPPLE_DESPOIT, false, nil, &resp)
if err != nil {
return "", err
switch common.StringToLower(crypto) {
case "btc":
return resp.Address,
b.SendAuthenticatedHTTPRequest(bitstampAPIBitcoinDeposit, false, nil, &resp.Address)
case "ltc":
return resp.Address,
b.SendAuthenticatedHTTPRequest(bitstampAPILitecoinDeposit, true, nil, &resp)
case "eth":
return resp.Address,
b.SendAuthenticatedHTTPRequest(bitstampAPIEthereumDeposit, true, nil, &resp)
case "xrp":
return resp.Address,
b.SendAuthenticatedHTTPRequest(bitstampAPIXrpDeposit, true, nil, &resp)
}
return resp.Address, nil
return resp.Address, errors.New("incorrect cryptocurrency string")
}
// GetUnconfirmedBitcoinDeposits returns unconfirmed transactions
func (b *Bitstamp) GetUnconfirmedBitcoinDeposits() ([]UnconfirmedBTCTransactions, error) {
response := []UnconfirmedBTCTransactions{}
return response,
b.SendAuthenticatedHTTPRequest(bitstampAPIUnconfirmedBitcoin, false, nil, &response)
}
// TransferAccountBalance transfers funds from either a main or sub account
// amount - to transfers
// currency - which currency to transfer
// subaccount - name of account
// toMain - bool either to or from account
func (b *Bitstamp) TransferAccountBalance(amount float64, currency, subAccount string, toMain bool) (bool, error) {
var req = url.Values{}
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("currency", currency)
req.Add("subAccount", subAccount)
path := BITSTAMP_API_TRANSFER_TO_MAIN
path := bitstampAPITransferToMain
if !toMain {
path = BITSTAMP_API_TRANSFER_FROM_MAIN
path = bitstampAPITransferFromMain
}
err := b.SendAuthenticatedHTTPRequest(path, true, req, nil)
if err != nil {
return false, err
}
@@ -434,55 +470,31 @@ func (b *Bitstamp) TransferAccountBalance(amount float64, currency, subAccount s
return true, nil
}
func (b *Bitstamp) XRPWithdrawal(amount float64, address, destTag string) (string, error) {
var req = url.Values{}
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("address", address)
if destTag != "" {
req.Add("destination_tag", destTag)
}
type response struct {
ID string `json:"id"`
}
resp := response{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_XRP_WITHDRAWAL, true, req, &resp)
if err != nil {
return "", err
}
return resp.ID, nil
}
func (b *Bitstamp) GetXRPDepositAddress() (BitstampXRPDepositResponse, error) {
resp := BitstampXRPDepositResponse{}
err := b.SendAuthenticatedHTTPRequest(BITSTAMP_API_XRP_DESPOIT, true, nil, &resp)
if err != nil {
return BitstampXRPDepositResponse{}, err
}
return resp, nil
}
// SendAuthenticatedHTTPRequest sends an authenticated request
func (b *Bitstamp) SendAuthenticatedHTTPRequest(path string, v2 bool, values url.Values, result interface{}) (err error) {
nonce := strconv.FormatInt(time.Now().UnixNano(), 10)
if !b.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name)
}
if b.Nonce.Get() == 0 {
b.Nonce.Set(time.Now().UnixNano())
} else {
b.Nonce.Inc()
}
if values == nil {
values = url.Values{}
}
values.Set("key", b.APIKey)
values.Set("nonce", nonce)
hmac := common.GetHMAC(common.HASH_SHA256, []byte(nonce+b.ClientID+b.APIKey), []byte(b.APISecret))
values.Set("nonce", b.Nonce.String())
hmac := common.GetHMAC(common.HashSHA256, []byte(b.Nonce.String()+b.ClientID+b.APIKey), []byte(b.APISecret))
values.Set("signature", common.StringToUpper(common.HexEncodeToString(hmac)))
if v2 {
path = fmt.Sprintf("%s/v%s/%s/", BITSTAMP_API_URL, BITSTAMP_API_VERSION, path)
path = fmt.Sprintf("%s/v%s/%s/", bitstampAPIURL, bitstampAPIVersion, path)
} else {
path = fmt.Sprintf("%s/%s/", BITSTAMP_API_URL, path)
path = fmt.Sprintf("%s/%s/", bitstampAPIURL, path)
}
if b.Verbose {
@@ -498,14 +510,20 @@ 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.")
/* inconsistent errors, needs to be improved when in production*/
if common.StringContains(resp, "500 error") {
return errors.New("internal server: code 500")
}
return nil
capture := CaptureError{}
if err = common.JSONDecode([]byte(resp), &capture); err == nil {
if capture.Code != nil || capture.Error != nil || capture.Reason != nil || capture.Status != nil {
errstring := fmt.Sprint("Status: ", capture.Status, ", Issue: ", capture.Error, ", Reason: ", capture.Reason, ", Code: ", capture.Code)
return errors.New(errstring)
}
}
return common.JSONDecode([]byte(resp), &result)
}

View File

@@ -0,0 +1,358 @@
package bitstamp
import (
"net/url"
"testing"
"time"
"github.com/thrasher-/gocryptotrader/config"
)
// Please add your private keys and customerID for better tests
const (
apiKey = ""
apiSecret = ""
customerID = ""
)
func TestSetDefaults(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.SetDefaults()
if b.Name != "Bitstamp" {
t.Error("Test Failed - SetDefaults() error")
}
if b.Enabled != false {
t.Error("Test Failed - SetDefaults() error")
}
if b.Verbose != false {
t.Error("Test Failed - SetDefaults() error")
}
if b.Websocket != false {
t.Error("Test Failed - SetDefaults() error")
}
if b.RESTPollingDelay != 10 {
t.Error("Test Failed - SetDefaults() error")
}
}
func TestSetup(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.Name = "Bitstamp"
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.dat")
bConfig, err := cfg.GetExchangeConfig("Bitstamp")
if err != nil {
t.Error("Test Failed - Bitstamp Setup() init error")
}
b.SetDefaults()
b.Setup(bConfig)
if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) ||
b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 ||
len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 {
t.Error("Test Failed - Bitstamp Setup values not set correctly")
}
bConfig.Enabled = false
b.Setup(bConfig)
if b.IsEnabled() {
t.Error("Test failed - Bitstamp TestSetup incorrect value")
}
}
func TestGetFee(t *testing.T) {
t.Parallel()
b := Bitstamp{}
if resp := b.GetFee("BTCUSD"); resp != 0 {
t.Error("Test Failed - GetFee() error")
}
if resp := b.GetFee("BTCEUR"); resp != 0 {
t.Error("Test Failed - GetFee() error")
}
if resp := b.GetFee("XRPEUR"); resp != 0 {
t.Error("Test Failed - GetFee() error")
}
if resp := b.GetFee("XRPUSD"); resp != 0 {
t.Error("Test Failed - GetFee() error")
}
if resp := b.GetFee("EURUSD"); resp != 0 {
t.Error("Test Failed - GetFee() error")
}
if resp := b.GetFee("bla"); resp != 0 {
t.Error("Test Failed - GetFee() error")
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
b := Bitstamp{}
_, err := b.GetTicker("BTCUSD", false)
if err != nil {
t.Error("Test Failed - GetTicker() error", err)
}
_, err = b.GetTicker("BTCUSD", true)
if err != nil {
t.Error("Test Failed - GetTicker() error", err)
}
}
func TestGetOrderbook(t *testing.T) {
t.Parallel()
b := Bitstamp{}
_, err := b.GetOrderbook("BTCUSD")
if err != nil {
t.Error("Test Failed - GetOrderbook() error", err)
}
}
func TestGetTransactions(t *testing.T) {
t.Parallel()
b := Bitstamp{}
value := url.Values{}
value.Set("time", "hour")
_, err := b.GetTransactions("BTCUSD", value)
if err != nil {
t.Error("Test Failed - GetTransactions() error", err)
}
_, err = b.GetTransactions("wigwham", value)
if err == nil {
t.Error("Test Failed - GetTransactions() error")
}
}
func TestGetEURUSDConversionRate(t *testing.T) {
t.Parallel()
b := Bitstamp{}
_, err := b.GetEURUSDConversionRate()
if err != nil {
t.Error("Test Failed - GetEURUSDConversionRate() error", err)
}
}
func TestGetBalance(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.GetBalance()
if err == nil {
t.Error("Test Failed - GetBalance() error", err)
}
}
func TestGetUserTransactions(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.GetUserTransactions("")
if err == nil {
t.Error("Test Failed - GetUserTransactions() error", err)
}
_, err = b.GetUserTransactions("btcusd")
if err == nil {
t.Error("Test Failed - GetUserTransactions() error", err)
}
}
func TestGetOpenOrders(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.GetOpenOrders("btcusd")
if err == nil {
t.Error("Test Failed - GetOpenOrders() error", err)
}
_, err = b.GetOpenOrders("wigwham")
if err == nil {
t.Error("Test Failed - GetOpenOrders() error")
}
}
func TestGetOrderStatus(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.GetOrderStatus(1337)
if err == nil {
t.Error("Test Failed - GetOpenOrders() error")
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
resp, err := b.CancelOrder(1337)
if err == nil || resp != false {
t.Error("Test Failed - CancelOrder() error")
}
}
func TestCancelAllOrders(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.CancelAllOrders()
if err == nil {
t.Error("Test Failed - CancelAllOrders() error", err)
}
}
func TestPlaceOrder(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.PlaceOrder("btcusd", 0.01, 1, true, true)
if err == nil {
t.Error("Test Failed - PlaceOrder() error")
}
_, err = b.PlaceOrder("btcusd", 0.01, 1, true, false)
if err == nil {
t.Error("Test Failed - PlaceOrder() error")
}
_, err = b.PlaceOrder("btcusd", 0.01, 1, false, false)
if err == nil {
t.Error("Test Failed - PlaceOrder() error")
}
_, err = b.PlaceOrder("wigwham", 0.01, 1, false, false)
if err == nil {
t.Error("Test Failed - PlaceOrder() error")
}
}
func TestGetWithdrawalRequests(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.GetWithdrawalRequests(0)
if err == nil {
t.Error("Test Failed - GetWithdrawalRequests() error", err)
}
_, err = b.GetWithdrawalRequests(-1)
if err == nil {
t.Error("Test Failed - GetWithdrawalRequests() error")
}
}
func TestCryptoWithdrawal(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.CryptoWithdrawal(0, "bla", "btc", "", true)
if err == nil {
t.Error("Test Failed - CryptoWithdrawal() error", err)
}
_, err = b.CryptoWithdrawal(0, "bla", "btc", "", false)
if err == nil {
t.Error("Test Failed - CryptoWithdrawal() error", err)
}
_, err = b.CryptoWithdrawal(0, "bla", "ltc", "", false)
if err == nil {
t.Error("Test Failed - CryptoWithdrawal() error", err)
}
_, err = b.CryptoWithdrawal(0, "bla", "eth", "", false)
if err == nil {
t.Error("Test Failed - CryptoWithdrawal() error", err)
}
_, err = b.CryptoWithdrawal(0, "bla", "xrp", "someplace", false)
if err == nil {
t.Error("Test Failed - CryptoWithdrawal() error", err)
}
_, err = b.CryptoWithdrawal(0, "bla", "ding!", "", false)
if err == nil {
t.Error("Test Failed - CryptoWithdrawal() error", err)
}
}
func TestGetBitcoinDepositAddress(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.GetCryptoDepositAddress("btc")
if err == nil {
t.Error("Test Failed - GetCryptoDepositAddress() error", err)
}
_, err = b.GetCryptoDepositAddress("LTc")
if err == nil {
t.Error("Test Failed - GetCryptoDepositAddress() error", err)
}
_, err = b.GetCryptoDepositAddress("eth")
if err == nil {
t.Error("Test Failed - GetCryptoDepositAddress() error", err)
}
_, err = b.GetCryptoDepositAddress("xrp")
if err == nil {
t.Error("Test Failed - GetCryptoDepositAddress() error", err)
}
_, err = b.GetCryptoDepositAddress("wigwham")
if err == nil {
t.Error("Test Failed - GetCryptoDepositAddress() error")
}
}
func TestGetUnconfirmedBitcoinDeposits(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.GetUnconfirmedBitcoinDeposits()
if err == nil {
t.Error("Test Failed - GetUnconfirmedBitcoinDeposits() error", err)
}
}
func TestTransferAccountBalance(t *testing.T) {
t.Parallel()
b := Bitstamp{}
b.APIKey = apiKey
b.APISecret = apiSecret
b.ClientID = customerID
_, err := b.TransferAccountBalance(1, "", "", true)
if err == nil {
t.Error("Test Failed - TransferAccountBalance() error", err)
}
_, err = b.TransferAccountBalance(1, "btc", "", false)
if err == nil {
t.Error("Test Failed - TransferAccountBalance() error", err)
}
}

View File

@@ -1,6 +1,7 @@
package bitstamp
type BitstampTicker struct {
// Ticker holds ticker information
type Ticker struct {
Last float64 `json:"last,string"`
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
@@ -12,38 +13,21 @@ type BitstampTicker struct {
Open float64 `json:"open,string"`
}
type BitstampBalances struct {
BTCReserved float64 `json:"btc_reserved,string"`
BTCEURFee float64 `json:"btceur_fee,string"`
BTCAvailable float64 `json:"btc_available,string"`
XRPAvailable float64 `json:"xrp_available,string"`
EURAvailable float64 `json:"eur_available,string"`
USDReserved float64 `json:"usd_reserved,string"`
EURReserved float64 `json:"eur_reserved,string"`
XRPEURFee float64 `json:"xrpeur_fee,string"`
XRPReserved float64 `json:"xrp_reserved,string"`
XRPBalance float64 `json:"xrp_balance,string"`
XRPUSDFee float64 `json:"xrpusd_fee,string"`
EURBalance float64 `json:"eur_balance,string"`
BTCBalance float64 `json:"btc_balance,string"`
BTCUSDFee float64 `json:"btcusd_fee,string"`
USDBalance float64 `json:"usd_balance,string"`
USDAvailable float64 `json:"usd_available,string"`
EURUSDFee float64 `json:"eurusd_fee,string"`
}
type BitstampOrderbookBase struct {
// OrderbookBase holds singular price information
type OrderbookBase struct {
Price float64
Amount float64
}
type BitstampOrderbook struct {
// Orderbook holds orderbook information
type Orderbook struct {
Timestamp int64 `json:"timestamp,string"`
Bids []BitstampOrderbookBase
Asks []BitstampOrderbookBase
Bids []OrderbookBase
Asks []OrderbookBase
}
type BitstampTransactions struct {
// Transactions holds transaction data
type Transactions struct {
Date int64 `json:"date,string"`
TradeID int64 `json:"tid,string"`
Price float64 `json:"price,string"`
@@ -51,12 +35,37 @@ type BitstampTransactions struct {
Amount float64 `json:"amount,string"`
}
type BitstampEURUSDConversionRate struct {
// EURUSDConversionRate holds buy sell conversion rate information
type EURUSDConversionRate struct {
Buy float64 `json:"buy,string"`
Sell float64 `json:"sell,string"`
}
type BitstampUserTransactions struct {
// Balances holds full balance information with the supplied APIKEYS
type Balances struct {
USDBalance float64 `json:"usd_balance,string"`
BTCBalance float64 `json:"btc_balance,string"`
EURBalance float64 `json:"eur_balance,string"`
XRPBalance float64 `json:"xrp_balance,string"`
USDReserved float64 `json:"usd_reserved,string"`
BTCReserved float64 `json:"btc_reserved,string"`
EURReserved float64 `json:"eur_reserved,string"`
XRPReserved float64 `json:"xrp_reserved,string"`
USDAvailable float64 `json:"usd_available,string"`
BTCAvailable float64 `json:"btc_available,string"`
EURAvailable float64 `json:"eur_available,string"`
XRPAvailable float64 `json:"xrp_available,string"`
BTCUSDFee float64 `json:"btcusd_fee,string"`
BTCEURFee float64 `json:"btceur_fee,string"`
EURUSDFee float64 `json:"eurusd_fee,string"`
XRPUSDFee float64 `json:"xrpusd_fee,string"`
XRPEURFee float64 `json:"xrpeur_fee,string"`
XRPBTCFee float64 `json:"xrpbtc_fee,string"`
Fee float64 `json:"fee,string"`
}
// UserTransactions holds user transaction information
type UserTransactions struct {
Date string `json:"datetime"`
TransID int64 `json:"id"`
Type int `json:"type,string"`
@@ -69,7 +78,8 @@ type BitstampUserTransactions struct {
OrderID int64 `json:"order_id"`
}
type BitstampOrder struct {
// Order holds current open order data
type Order struct {
ID int64 `json:"id"`
Date string `json:"datetime"`
Type int `json:"type"`
@@ -77,7 +87,8 @@ type BitstampOrder struct {
Amount float64 `json:"amount"`
}
type BitstampOrderStatus struct {
// OrderStatus holds order status information
type OrderStatus struct {
Status string
Transactions []struct {
TradeID int64 `json:"tid"`
@@ -88,22 +99,30 @@ type BitstampOrderStatus struct {
}
}
type BitstampWithdrawalRequests struct {
OrderID int64 `json:"id"`
Date string `json:"datetime"`
Type int `json:"type"`
Amount float64 `json:"amount,string"`
Status int `json:"status"`
Data interface{}
// WithdrawalRequests holds request information on withdrawals
type WithdrawalRequests struct {
OrderID int64 `json:"id"`
Date string `json:"datetime"`
Type int `json:"type"`
Amount float64 `json:"amount,string"`
Status int `json:"status"`
Data interface{}
Address string `json:"address"` // Bitcoin withdrawals only
TransactionID string `json:"transaction_id"` // Bitcoin withdrawals only
}
type BitstampUnconfirmedBTCTransactions struct {
// UnconfirmedBTCTransactions holds address information about unconfirmed
// transactions
type UnconfirmedBTCTransactions struct {
Amount float64 `json:"amount,string"`
Address string `json:"address"`
Confirmations int `json:"confirmations"`
}
type BitstampXRPDepositResponse struct {
Address string `json:"address"`
DestinationTag int64 `json:"destination_tag"`
// CaptureError is used to capture unmarshalled errors
type CaptureError struct {
Status interface{} `json:"status"`
Reason interface{} `json:"reason"`
Code interface{} `json:"code"`
Error interface{} `json:"error"`
}

View File

@@ -7,23 +7,28 @@ import (
"github.com/toorop/go-pusher"
)
type BitstampPusherOrderbook struct {
// PusherOrderbook holds order book information to be pushed
type PusherOrderbook struct {
Asks [][]string `json:"asks"`
Bids [][]string `json:"bids"`
}
type BitstampPusherTrade struct {
// PusherTrade holds trade information to be pushed
type PusherTrade struct {
Price float64 `json:"price"`
Amount float64 `json:"amount"`
ID int64 `json:"id"`
}
const (
BITSTAMP_PUSHER_KEY = "de504dc5763aeef9ff52"
// BitstampPusherKey holds the current pusher key
BitstampPusherKey = "de504dc5763aeef9ff52"
)
// PusherClient starts the push mechanism
func (b *Bitstamp) PusherClient() {
for b.Enabled && b.Websocket {
pusherClient, err := pusher.NewClient(BITSTAMP_PUSHER_KEY)
pusherClient, err := pusher.NewClient(BitstampPusherKey)
if err != nil {
log.Printf("%s Unable to connect to Websocket. Error: %s\n", b.GetName(), err)
continue
@@ -55,13 +60,13 @@ func (b *Bitstamp) PusherClient() {
for b.Websocket {
select {
case data := <-dataChannelTrade:
result := BitstampPusherOrderbook{}
result := PusherOrderbook{}
err := common.JSONDecode([]byte(data.Data), &result)
if err != nil {
log.Println(err)
}
case trade := <-tradeChannelTrade:
result := BitstampPusherTrade{}
result := PusherTrade{}
err := common.JSONDecode([]byte(trade.Data), &result)
if err != nil {
log.Println(err)

View File

@@ -2,20 +2,20 @@ package bitstamp
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the Bitstamp go routine
func (b *Bitstamp) Start() {
go b.Run()
}
// Run implements the Bitstamp wrapper
func (b *Bitstamp) Run() {
if b.Verbose {
log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket))
@@ -26,31 +26,11 @@ func (b *Bitstamp) Run() {
if b.Websocket {
go b.PusherClient()
}
for b.Enabled {
for _, x := range b.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := b.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("Bitstamp %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * b.RESTPollingDelay)
}
}
func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(b.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (b *Bitstamp) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := b.GetTicker(p.Pair().String(), false)
if err != nil {
return tickerPrice, err
@@ -63,65 +43,79 @@ func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, erro
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Volume
tickerPrice.High = tick.High
ticker.ProcessTicker(b.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(b.Name, p, assetType)
}
func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (b *Bitstamp) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tick, err := ticker.GetTicker(b.GetName(), p, assetType)
if err != nil {
return b.UpdateTicker(p, assetType)
}
return tick, nil
}
var orderBook orderbook.OrderbookBase
// GetOrderbookEx returns the orderbook for a currency pair
func (b *Bitstamp) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType)
if err == nil {
return b.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *Bitstamp) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := b.GetOrderbook(p.Pair().String())
if err != nil {
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})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(b.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(b.Name, p, assetType)
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Bitstamp exchange
func (e *Bitstamp) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetBalance()
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// Bitstamp exchange
func (b *Bitstamp) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = b.GetName()
accountBalance, err := b.GetBalance()
if err != nil {
return response, err
}
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "BTC",
TotalValue: accountBalance.BTCAvailable,
Hold: accountBalance.BTCReserved,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "XRP",
TotalValue: accountBalance.XRPAvailable,
Hold: accountBalance.XRPReserved,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "USD",
TotalValue: accountBalance.USDAvailable,
Hold: accountBalance.USDReserved,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "EUR",
TotalValue: accountBalance.EURAvailable,
Hold: accountBalance.EURReserved,

View File

@@ -0,0 +1,384 @@
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"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
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
b.RequestCurrencyPairFormat.Delimiter = "-"
b.RequestCurrencyPairFormat.Uppercase = true
b.ConfigCurrencyPairFormat.Delimiter = "-"
b.ConfigCurrencyPairFormat.Uppercase = true
b.AssetTypes = []string{ticker.Spot}
}
// 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, ",")
err := b.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = b.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
// 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{}, &currencies)
}
// 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, bittrexAPIBuyLimit)
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, bittrexAPISellLimit)
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, bittrexAPIGetOpenOrders)
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, bittrexAPICancel)
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)
}
// GetWithdrawalHistory is used to retrieve your withdrawal history. If currency
// omitted it will return the entire history
func (b *Bittrex) GetWithdrawalHistory(currency string) ([]WithdrawalHistory, error) {
var history []WithdrawalHistory
values := url.Values{}
if !(currency == "" || currency == " ") {
values.Set("currency", currency)
}
path := fmt.Sprintf("%s/%s", bittrexAPIURL, bittrexAPIGetWithdrawalHistory)
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, bittrexAPIGetDepositHistory)
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) {
if !b.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name)
}
if b.Nonce.Get() == 0 {
b.Nonce.Set(time.Now().UnixNano())
} else {
b.Nonce.Inc()
}
values.Set("apikey", b.APIKey)
values.Set("apisecret", b.APISecret)
values.Set("nonce", b.Nonce.String())
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)
}

View File

@@ -0,0 +1,293 @@
package bittrex
import (
"testing"
"time"
"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) {
t.Parallel()
b := Bittrex{}
b.SetDefaults()
if b.GetName() != "Bittrex" {
t.Error("Test Failed - Bittrex - SetDefaults() error")
}
}
func TestSetup(t *testing.T) {
t.Parallel()
b := Bittrex{}
b.Name = "Bittrex"
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.dat")
bConfig, err := cfg.GetExchangeConfig("Bittrex")
if err != nil {
t.Error("Test Failed - Bittrex Setup() init error")
}
b.SetDefaults()
b.Setup(bConfig)
if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) ||
b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 ||
len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 {
t.Error("Test Failed - Bittrex Setup values not set correctly")
}
bConfig.Enabled = false
b.Setup(bConfig)
if b.IsEnabled() {
t.Error("Test failed - Bittrex TestSetup incorrect value")
}
}
func TestGetMarkets(t *testing.T) {
t.Parallel()
obj := Bittrex{}
_, err := obj.GetMarkets()
if err != nil {
t.Errorf("Test Failed - Bittrex - GetMarkets() error: %s", err)
}
}
func TestGetCurrencies(t *testing.T) {
t.Parallel()
obj := Bittrex{}
_, err := obj.GetCurrencies()
if err != nil {
t.Errorf("Test Failed - Bittrex - GetCurrencies() error: %s", err)
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
obj := Bittrex{}
_, err := obj.GetMarketSummaries()
if err != nil {
t.Errorf("Test Failed - Bittrex - GetMarketSummaries() error: %s", err)
}
}
func TestGetMarketSummary(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
obj := Bittrex{}
obj.APIKey = apiKey
obj.APISecret = apiSecret
_, err := obj.GetWithdrawalHistory("")
if err == nil {
t.Error("Test Failed - Bittrex - GetWithdrawalHistory() error")
}
_, err = obj.GetWithdrawalHistory("btc-ltc")
if err == nil {
t.Error("Test Failed - Bittrex - GetWithdrawalHistory() error")
}
}
func TestGetDepositHistory(t *testing.T) {
t.Parallel()
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")
}
}

View 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"`
}

View File

@@ -0,0 +1,139 @@
package bittrex
import (
"log"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the Bittrex go routine
func (b *Bittrex) Start() {
go b.Run()
}
// Run implements the Bittrex wrapper
func (b *Bittrex) Run() {
if b.Verbose {
log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs)
}
exchangeProducts, err := b.GetMarkets()
if err != nil {
log.Printf("%s Failed to get available symbols.\n", b.GetName())
} else {
forceUpgrade := false
if !common.DataContains(b.EnabledPairs, "-") || !common.DataContains(b.AvailablePairs, "-") {
forceUpgrade = true
}
var currencies []string
for x := range exchangeProducts {
if !exchangeProducts[x].IsActive || exchangeProducts[x].MarketName == "" {
continue
}
currencies = append(currencies, exchangeProducts[x].MarketName)
}
if forceUpgrade {
enabledPairs := []string{"USDT-BTC"}
log.Println("WARNING: Available pairs for Bittrex reset due to config upgrade, please enable the ones you would like again")
err = b.UpdateEnabledCurrencies(enabledPairs, true)
if err != nil {
log.Printf("%s Failed to get config.\n", b.GetName())
}
}
err = b.UpdateAvailableCurrencies(currencies, forceUpgrade)
if err != nil {
log.Printf("%s Failed to get config.\n", b.GetName())
}
}
}
// GetExchangeAccountInfo Retrieves balances for all enabled currencies for the
// Bittrex exchange
func (b *Bittrex) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = b.GetName()
accountBalance, err := b.GetAccountBalances()
if err != nil {
return response, err
}
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Balance
exchangeCurrency.Hold = accountBalance[i].Balance - accountBalance[i].Available
response.Currencies = append(response.Currencies, exchangeCurrency)
}
return response, nil
}
// UpdateTicker updates and returns the ticker for a currency pair
func (b *Bittrex) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := b.GetMarketSummary(exchange.FormatExchangeCurrency(b.GetName(), p).String())
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tick[0].Ask
tickerPrice.Bid = tick[0].Bid
tickerPrice.Last = tick[0].Last
tickerPrice.Volume = tick[0].Volume
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(b.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (b *Bittrex) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tick, err := ticker.GetTicker(b.GetName(), p, ticker.Spot)
if err != nil {
return b.UpdateTicker(p, assetType)
}
return tick, nil
}
// GetOrderbookEx returns the orderbook for a currency pair
func (b *Bittrex) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType)
if err == nil {
return b.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *Bittrex) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := b.GetOrderbook(exchange.FormatExchangeCurrency(b.GetName(), p).String())
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Buy {
orderBook.Bids = append(orderBook.Bids,
orderbook.Item{
Amount: orderbookNew.Buy[x].Quantity,
Price: orderbookNew.Buy[x].Rate,
},
)
}
for x := range orderbookNew.Sell {
orderBook.Asks = append(orderBook.Asks,
orderbook.Item{
Amount: orderbookNew.Sell[x].Quantity,
Price: orderbookNew.Sell[x].Rate,
},
)
}
orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(b.Name, p, assetType)
}

View File

@@ -12,40 +12,43 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
BTCC_API_URL = "https://api.btcchina.com/"
BTCC_API_AUTHENTICATED_METHOD = "api_trade_v1.php"
BTCC_API_VER = "2.0.1.3"
BTCC_ORDER_BUY = "buyOrder2"
BTCC_ORDER_SELL = "sellOrder2"
BTCC_ORDER_CANCEL = "cancelOrder"
BTCC_ICEBERG_BUY = "buyIcebergOrder"
BTCC_ICEBERG_SELL = "sellIcebergOrder"
BTCC_ICEBERG_ORDER = "getIcebergOrder"
BTCC_ICEBERG_ORDERS = "getIcebergOrders"
BTCC_ICEBERG_CANCEL = "cancelIcebergOrder"
BTCC_ACCOUNT_INFO = "getAccountInfo"
BTCC_DEPOSITS = "getDeposits"
BTCC_MARKETDEPTH = "getMarketDepth2"
BTCC_ORDER = "getOrder"
BTCC_ORDERS = "getOrders"
BTCC_TRANSACTIONS = "getTransactions"
BTCC_WITHDRAWAL = "getWithdrawal"
BTCC_WITHDRAWALS = "getWithdrawals"
BTCC_WITHDRAWAL_REQUEST = "requestWithdrawal"
BTCC_STOPORDER_BUY = "buyStopOrder"
BTCC_STOPORDER_SELL = "sellStopOrder"
BTCC_STOPORDER_CANCEL = "cancelStopOrder"
BTCC_STOPORDER = "getStopOrder"
BTCC_STOPORDERS = "getStopOrders"
btccAPIUrl = "https://api.btcchina.com/"
btccAPIAuthenticatedMethod = "api_trade_v1.php"
btccAPIVersion = "2.0.1.3"
btccOrderBuy = "buyOrder2"
btccOrderSell = "sellOrder2"
btccOrderCancel = "cancelOrder"
btccIcebergBuy = "buyIcebergOrder"
btccIcebergSell = "sellIcebergOrder"
btccIcebergOrder = "getIcebergOrder"
btccIcebergOrders = "getIcebergOrders"
btccIcebergCancel = "cancelIcebergOrder"
btccAccountInfo = "getAccountInfo"
btccDeposits = "getDeposits"
btccMarketdepth = "getMarketDepth2"
btccOrder = "getOrder"
btccOrders = "getOrders"
btccTransactions = "getTransactions"
btccWithdrawal = "getWithdrawal"
btccWithdrawals = "getWithdrawals"
btccWithdrawalRequest = "requestWithdrawal"
btccStoporderBuy = "buyStopOrder"
btccStoporderSell = "sellStopOrder"
btccStoporderCancel = "cancelStopOrder"
btccStoporder = "getStopOrder"
btccStoporders = "getStopOrders"
)
// BTCC is the main overaching type across the BTCC package
type BTCC struct {
exchange.ExchangeBase
exchange.Base
}
// SetDefaults sets default values for the exchange
func (b *BTCC) SetDefaults() {
b.Name = "BTCC"
b.Enabled = false
@@ -53,9 +56,14 @@ func (b *BTCC) SetDefaults() {
b.Verbose = false
b.Websocket = false
b.RESTPollingDelay = 10
b.RequestCurrencyPairFormat.Delimiter = ""
b.RequestCurrencyPairFormat.Uppercase = false
b.ConfigCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Uppercase = true
b.AssetTypes = []string{ticker.Spot}
}
//Setup is run on startup to setup exchange with config values
// Setup is run on startup to setup exchange with config values
func (b *BTCC) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
b.SetEnabled(false)
@@ -69,39 +77,51 @@ func (b *BTCC) Setup(exch config.ExchangeConfig) {
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := b.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = b.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
// GetFee returns the fees associated with transactions
func (b *BTCC) GetFee() float64 {
return b.Fee
}
func (b *BTCC) GetTicker(symbol string) (BTCCTicker, error) {
type Response struct {
Ticker BTCCTicker
}
// GetTicker returns ticker information
// currencyPair - Example "btccny", "ltccny" or "ltcbtc"
func (b *BTCC) GetTicker(currencyPair string) (Ticker, error) {
resp := Response{}
req := fmt.Sprintf("%sdata/ticker?market=%s", BTCC_API_URL, symbol)
err := common.SendHTTPGetRequest(req, true, &resp)
if err != nil {
return BTCCTicker{}, err
}
return resp.Ticker, nil
req := fmt.Sprintf("%sdata/ticker?market=%s", btccAPIUrl, currencyPair)
return resp.Ticker, common.SendHTTPGetRequest(req, true, &resp)
}
func (b *BTCC) GetTradesLast24h(symbol string) bool {
req := fmt.Sprintf("%sdata/trades?market=%s", BTCC_API_URL, symbol)
err := common.SendHTTPGetRequest(req, true, nil)
if err != nil {
log.Println(err)
return false
}
return true
// GetTradesLast24h returns the trades executed on the exchange over the past
// 24 hours by currency pair
// currencyPair - Example "btccny", "ltccny" or "ltcbtc"
func (b *BTCC) GetTradesLast24h(currencyPair string) ([]Trade, error) {
trades := []Trade{}
req := fmt.Sprintf("%sdata/trades?market=%s", btccAPIUrl, currencyPair)
return trades, common.SendHTTPGetRequest(req, true, &trades)
}
func (b *BTCC) GetTradeHistory(symbol string, limit, sinceTid int64, time time.Time) bool {
req := fmt.Sprintf("%sdata/historydata?market=%s", BTCC_API_URL, symbol)
// GetTradeHistory returns trade history data
// currencyPair - Example "btccny", "ltccny" or "ltcbtc"
// limit - limits the returned trades example "10"
// sinceTid - returns trade records starting from id supplied example "5000"
// time - returns trade records starting from unix time 1406794449
func (b *BTCC) GetTradeHistory(currencyPair string, limit, sinceTid int64, time time.Time) ([]Trade, error) {
trades := []Trade{}
req := fmt.Sprintf("%sdata/historydata?market=%s", btccAPIUrl, currencyPair)
v := url.Values{}
if limit > 0 {
@@ -115,37 +135,33 @@ func (b *BTCC) GetTradeHistory(symbol string, limit, sinceTid int64, time time.T
}
req = common.EncodeURLValues(req, v)
err := common.SendHTTPGetRequest(req, true, nil)
if err != nil {
log.Println(err)
return false
}
return true
return trades, common.SendHTTPGetRequest(req, true, &trades)
}
func (b *BTCC) GetOrderBook(symbol string, limit int) (BTCCOrderbook, error) {
result := BTCCOrderbook{}
req := fmt.Sprintf("%sdata/orderbook?market=%s&limit=%d", BTCC_API_URL, symbol, limit)
err := common.SendHTTPGetRequest(req, true, &result)
if err != nil {
return BTCCOrderbook{}, err
// GetOrderBook returns current market order book
// currencyPair - Example "btccny", "ltccny" or "ltcbtc"
// limit - limits the returned trades example "10" if 0 will return full
// orderbook
func (b *BTCC) GetOrderBook(currencyPair string, limit int) (Orderbook, error) {
result := Orderbook{}
req := fmt.Sprintf("%sdata/orderbook?market=%s&limit=%d", btccAPIUrl, currencyPair, limit)
if limit == 0 {
req = fmt.Sprintf("%sdata/orderbook?market=%s", btccAPIUrl, currencyPair)
}
return result, nil
return result, common.SendHTTPGetRequest(req, true, &result)
}
func (b *BTCC) GetAccountInfo(infoType string) {
func (b *BTCC) GetAccountInfo(infoType string) error {
params := make([]interface{}, 0)
if len(infoType) > 0 {
params = append(params, infoType)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_ACCOUNT_INFO, params)
if err != nil {
log.Println(err)
}
return b.SendAuthenticatedHTTPRequest(btccAccountInfo, params)
}
func (b *BTCC) PlaceOrder(buyOrder bool, price, amount float64, market string) {
@@ -157,9 +173,9 @@ func (b *BTCC) PlaceOrder(buyOrder bool, price, amount float64, market string) {
params = append(params, market)
}
req := BTCC_ORDER_BUY
req := btccOrderBuy
if !buyOrder {
req = BTCC_ORDER_SELL
req = btccOrderSell
}
err := b.SendAuthenticatedHTTPRequest(req, params)
@@ -177,7 +193,7 @@ func (b *BTCC) CancelOrder(orderID int64, market string) {
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_ORDER_CANCEL, params)
err := b.SendAuthenticatedHTTPRequest(btccOrderCancel, params)
if err != nil {
log.Println(err)
@@ -192,7 +208,7 @@ func (b *BTCC) GetDeposits(currency string, pending bool) {
params = append(params, pending)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_DEPOSITS, params)
err := b.SendAuthenticatedHTTPRequest(btccDeposits, params)
if err != nil {
log.Println(err)
@@ -210,7 +226,7 @@ func (b *BTCC) GetMarketDepth(market string, limit int64) {
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_MARKETDEPTH, params)
err := b.SendAuthenticatedHTTPRequest(btccMarketdepth, params)
if err != nil {
log.Println(err)
@@ -229,7 +245,7 @@ func (b *BTCC) GetOrder(orderID int64, market string, detailed bool) {
params = append(params, detailed)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_ORDER, params)
err := b.SendAuthenticatedHTTPRequest(btccOrder, params)
if err != nil {
log.Println(err)
@@ -263,7 +279,7 @@ func (b *BTCC) GetOrders(openonly bool, market string, limit, offset, since int6
params = append(params, detailed)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_ORDERS, params)
err := b.SendAuthenticatedHTTPRequest(btccOrders, params)
if err != nil {
log.Println(err)
@@ -293,7 +309,7 @@ func (b *BTCC) GetTransactions(transType string, limit, offset, since int64, sin
params = append(params, sinceType)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_TRANSACTIONS, params)
err := b.SendAuthenticatedHTTPRequest(btccTransactions, params)
if err != nil {
log.Println(err)
@@ -308,7 +324,7 @@ func (b *BTCC) GetWithdrawal(withdrawalID int64, currency string) {
params = append(params, currency)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWAL, params)
err := b.SendAuthenticatedHTTPRequest(btccWithdrawal, params)
if err != nil {
log.Println(err)
@@ -323,7 +339,7 @@ func (b *BTCC) GetWithdrawals(currency string, pending bool) {
params = append(params, pending)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWALS, params)
err := b.SendAuthenticatedHTTPRequest(btccWithdrawals, params)
if err != nil {
log.Println(err)
@@ -335,7 +351,7 @@ func (b *BTCC) RequestWithdrawal(currency string, amount float64) {
params = append(params, currency)
params = append(params, amount)
err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWAL_REQUEST, params)
err := b.SendAuthenticatedHTTPRequest(btccWithdrawalRequest, params)
if err != nil {
log.Println(err)
@@ -353,9 +369,9 @@ func (b *BTCC) IcebergOrder(buyOrder bool, price, amount, discAmount, variance f
params = append(params, market)
}
req := BTCC_ICEBERG_BUY
req := btccIcebergBuy
if !buyOrder {
req = BTCC_ICEBERG_SELL
req = btccIcebergSell
}
err := b.SendAuthenticatedHTTPRequest(req, params)
@@ -373,7 +389,7 @@ func (b *BTCC) GetIcebergOrder(orderID int64, market string) {
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_ORDER, params)
err := b.SendAuthenticatedHTTPRequest(btccIcebergOrder, params)
if err != nil {
log.Println(err)
@@ -395,7 +411,7 @@ func (b *BTCC) GetIcebergOrders(limit, offset int64, market string) {
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_ORDERS, params)
err := b.SendAuthenticatedHTTPRequest(btccIcebergOrders, params)
if err != nil {
log.Println(err)
@@ -410,7 +426,7 @@ func (b *BTCC) CancelIcebergOrder(orderID int64, market string) {
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_CANCEL, params)
err := b.SendAuthenticatedHTTPRequest(btccIcebergCancel, params)
if err != nil {
log.Println(err)
@@ -439,9 +455,9 @@ func (b *BTCC) PlaceStopOrder(buyOder bool, stopPrice, price, amount, trailingAm
params = append(params, market)
}
req := BTCC_STOPORDER_BUY
req := btccStoporderBuy
if !buyOder {
req = BTCC_STOPORDER_SELL
req = btccStoporderSell
}
err := b.SendAuthenticatedHTTPRequest(req, params)
@@ -459,7 +475,7 @@ func (b *BTCC) GetStopOrder(orderID int64, market string) {
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDER, params)
err := b.SendAuthenticatedHTTPRequest(btccStoporder, params)
if err != nil {
log.Println(err)
@@ -493,7 +509,7 @@ func (b *BTCC) GetStopOrders(status, orderType string, stopPrice float64, limit,
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDERS, params)
err := b.SendAuthenticatedHTTPRequest(btccStoporders, params)
if err != nil {
log.Println(err)
@@ -508,7 +524,7 @@ func (b *BTCC) CancelStopOrder(orderID int64, market string) {
params = append(params, market)
}
err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDER_CANCEL, params)
err := b.SendAuthenticatedHTTPRequest(btccStoporderCancel, params)
if err != nil {
log.Println(err)
@@ -516,8 +532,16 @@ func (b *BTCC) CancelStopOrder(orderID int64, market string) {
}
func (b *BTCC) SendAuthenticatedHTTPRequest(method string, params []interface{}) (err error) {
nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:16]
encoded := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=%d&method=%s&params=", nonce, b.APIKey, 1, method)
if !b.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name)
}
if b.Nonce.Get() == 0 {
b.Nonce.Set(time.Now().UnixNano())
} else {
b.Nonce.Inc()
}
encoded := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=%d&method=%s&params=", b.Nonce.String()[0:16], b.APIKey, 1, method)
if len(params) == 0 {
params = make([]interface{}, 0)
@@ -558,12 +582,12 @@ func (b *BTCC) SendAuthenticatedHTTPRequest(method string, params []interface{})
log.Println(encoded)
}
hmac := common.GetHMAC(common.HASH_SHA1, []byte(encoded), []byte(b.APISecret))
hmac := common.GetHMAC(common.HashSHA1, []byte(encoded), []byte(b.APISecret))
postData := make(map[string]interface{})
postData["method"] = method
postData["params"] = params
postData["id"] = 1
apiURL := BTCC_API_URL + BTCC_API_AUTHENTICATED_METHOD
apiURL := btccAPIUrl + btccAPIAuthenticatedMethod
data, err := common.JSONEncode(postData)
if err != nil {
@@ -577,7 +601,7 @@ func (b *BTCC) SendAuthenticatedHTTPRequest(method string, params []interface{})
headers := make(map[string]string)
headers["Content-type"] = "application/json-rpc"
headers["Authorization"] = "Basic " + common.Base64Encode([]byte(b.APIKey+":"+common.HexEncodeToString(hmac)))
headers["Json-Rpc-Tonce"] = nonce
headers["Json-Rpc-Tonce"] = b.Nonce.String()
resp, err := common.SendHTTPRequest("POST", apiURL, headers, strings.NewReader(string(data)))

View File

@@ -0,0 +1,93 @@
package btcc
import (
"testing"
"time"
"github.com/thrasher-/gocryptotrader/config"
)
// Please supply your own APIkeys here to do better tests
const (
apiKey = ""
apiSecret = ""
)
var b BTCC
func TestSetDefaults(t *testing.T) {
b.SetDefaults()
}
func TestSetup(t *testing.T) {
t.Parallel()
b := BTCC{}
b.Name = "BTCC"
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.dat")
bConfig, err := cfg.GetExchangeConfig("BTCC")
if err != nil {
t.Error("Test Failed - BTCC Setup() init error")
}
b.SetDefaults()
b.Setup(bConfig)
if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) ||
b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 ||
len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 {
t.Error("Test Failed - BTCC Setup values not set correctly")
}
bConfig.Enabled = false
b.Setup(bConfig)
if b.IsEnabled() {
t.Error("Test failed - BTCC TestSetup incorrect value")
}
}
func TestGetFee(t *testing.T) {
if b.GetFee() != 0 {
t.Error("Test failed - GetFee() error")
}
}
func TestGetTicker(t *testing.T) {
_, err := b.GetTicker("ltccny")
if err != nil {
t.Error("Test failed - GetTicker() error", err)
}
}
func TestGetTradesLast24h(t *testing.T) {
_, err := b.GetTradesLast24h("ltccny")
if err != nil {
t.Error("Test failed - GetTradesLast24h() error", err)
}
}
func TestGetTradeHistory(t *testing.T) {
_, err := b.GetTradeHistory("ltccny", 0, 0, time.Time{})
if err != nil {
t.Error("Test failed - GetTradeHistory() error", err)
}
}
func TestGetOrderBook(t *testing.T) {
_, err := b.GetOrderBook("ltccny", 100)
if err != nil {
t.Error("Test failed - GetOrderBook() error", err)
}
_, err = b.GetOrderBook("ltccny", 0)
if err != nil {
t.Error("Test failed - GetOrderBook() error", err)
}
}
func TestGetAccountInfo(t *testing.T) {
err := b.GetAccountInfo("")
if err == nil {
t.Error("Test failed - GetAccountInfo() error", err)
}
}

View File

@@ -1,19 +1,45 @@
package btcc
type BTCCTicker struct {
High float64 `json:",string"`
Low float64 `json:",string"`
Buy float64 `json:",string"`
Sell float64 `json:",string"`
Last float64 `json:",string"`
Vol float64 `json:",string"`
Date int64
Vwap float64 `json:",string"`
Prev_close float64 `json:",string"`
Open float64 `json:",string"`
// Response is the generalized response type
type Response struct {
Ticker Ticker `json:"ticker"`
BtcCny Ticker `json:"ticker_btccny"`
LtcCny Ticker `json:"ticker_ltccny"`
LtcBtc Ticker `json:"ticker_ltcbtc"`
}
type BTCCProfile struct {
// Ticker holds basic ticker information
type Ticker struct {
High float64 `json:"high,string"`
Low float64 `json:"low,string"`
Buy float64 `json:"buy,string"`
Sell float64 `json:"sell,string"`
Last float64 `json:"last,string"`
Vol float64 `json:"vol,string"`
Date int64 `json:"date"`
Vwap float64 `json:"vwap,string"`
PrevClose float64 `json:"prev_close,string"`
Open float64 `json:"open,string"`
}
// Trade holds executed trade data
type Trade struct {
Date int64 `json:"date,string"`
Price float64 `json:"price"`
Amount float64 `json:"amount"`
TID int64 `json:"tid,string"`
Type string `json:"type"`
}
// Orderbook holds orderbook data
type Orderbook struct {
Bids [][]float64 `json:"bids"`
Asks [][]float64 `json:"asks"`
Date int64 `json:"date"`
}
// Profile holds profile information
type Profile struct {
Username string
TradePasswordEnabled bool `json:"trade_password_enabled,bool"`
OTPEnabled bool `json:"otp_enabled,bool"`
@@ -29,13 +55,8 @@ type BTCCProfile struct {
APIKeyPermission int64 `json:"api_key_permission"`
}
type BTCCOrderbook struct {
Bids [][]float64 `json:"bids"`
Asks [][]float64 `json:"asks"`
Date int64 `json:"date"`
}
type BTCCCurrencyGeneric struct {
// CurrencyGeneric holds currency information
type CurrencyGeneric struct {
Currency string
Symbol string
Amount string
@@ -43,7 +64,8 @@ type BTCCCurrencyGeneric struct {
AmountDecimal float64 `json:"amount_decimal"`
}
type BTCCOrder struct {
// Order holds order information
type Order struct {
ID int64
Type string
Price float64
@@ -52,16 +74,18 @@ type BTCCOrder struct {
AmountOrig float64 `json:"amount_original"`
Date int64
Status string
Detail BTCCOrderDetail
Detail OrderDetail
}
type BTCCOrderDetail struct {
// OrderDetail holds order detail information
type OrderDetail struct {
Dateline int64
Price float64
Amount float64
}
type BTCCWithdrawal struct {
// Withdrawal holds withdrawal transaction information
type Withdrawal struct {
ID int64
Address string
Currency string
@@ -71,7 +95,8 @@ type BTCCWithdrawal struct {
Status string
}
type BTCCDeposit struct {
// Deposit holds deposit address information
type Deposit struct {
ID int64
Address string
Currency string
@@ -80,17 +105,20 @@ type BTCCDeposit struct {
Status string
}
type BTCCBidAsk struct {
// BidAsk holds bid and ask information
type BidAsk struct {
Price float64
Amount float64
}
type BTCCDepth struct {
Bid []BTCCBidAsk
Ask []BTCCBidAsk
// Depth holds order book depth
type Depth struct {
Bid []BidAsk
Ask []BidAsk
}
type BTCCTransaction struct {
// Transaction holds transaction information
type Transaction struct {
ID int64
Type string
BTCAmount float64 `json:"btc_amount"`
@@ -99,7 +127,8 @@ type BTCCTransaction struct {
Date int64
}
type BTCCIcebergOrder struct {
// IcebergOrder holds iceberg lettuce
type IcebergOrder struct {
ID int64
Type string
Price float64
@@ -112,7 +141,8 @@ type BTCCIcebergOrder struct {
Status string
}
type BTCCStopOrder struct {
// StopOrder holds stop order information
type StopOrder struct {
ID int64
Type string
StopPrice float64 `json:"stop_price"`
@@ -126,19 +156,22 @@ type BTCCStopOrder struct {
OrderID int64 `json:"order_id"`
}
type BTCCWebsocketOrder struct {
// WebsocketOrder holds websocket order information
type WebsocketOrder struct {
Price float64 `json:"price"`
TotalAmount float64 `json:"totalamount"`
Type string `json:"type"`
}
type BTCCWebsocketGroupOrder struct {
Asks []BTCCWebsocketOrder `json:"ask"`
Bids []BTCCWebsocketOrder `json:"bid"`
Market string `json:"market"`
// WebsocketGroupOrder holds websocket group order book information
type WebsocketGroupOrder struct {
Asks []WebsocketOrder `json:"ask"`
Bids []WebsocketOrder `json:"bid"`
Market string `json:"market"`
}
type BTCCWebsocketTrade struct {
// WebsocketTrade holds websocket trade information
type WebsocketTrade struct {
Amount float64 `json:"amount"`
Date float64 `json:"date"`
Market string `json:"market"`
@@ -147,7 +180,8 @@ type BTCCWebsocketTrade struct {
Type string `json:"type"`
}
type BTCCWebsocketTicker struct {
// WebsocketTicker holds websocket ticker information
type WebsocketTicker struct {
Buy float64 `json:"buy"`
Date float64 `json:"date"`
High float64 `json:"high"`

View File

@@ -56,7 +56,7 @@ func (b *BTCC) OnMessage(message []byte, output chan socketio.Message) {
func (b *BTCC) OnTicker(message []byte, output chan socketio.Message) {
type Response struct {
Ticker BTCCWebsocketTicker `json:"ticker"`
Ticker WebsocketTicker `json:"ticker"`
}
var resp Response
err := common.JSONDecode(message, &resp)
@@ -69,7 +69,7 @@ func (b *BTCC) OnTicker(message []byte, output chan socketio.Message) {
func (b *BTCC) OnGroupOrder(message []byte, output chan socketio.Message) {
type Response struct {
GroupOrder BTCCWebsocketGroupOrder `json:"grouporder"`
GroupOrder WebsocketGroupOrder `json:"grouporder"`
}
var resp Response
err := common.JSONDecode(message, &resp)
@@ -81,7 +81,7 @@ func (b *BTCC) OnGroupOrder(message []byte, output chan socketio.Message) {
}
func (b *BTCC) OnTrade(message []byte, output chan socketio.Message) {
trade := BTCCWebsocketTrade{}
trade := WebsocketTrade{}
err := common.JSONDecode(message, &trade)
if err != nil {

View File

@@ -2,20 +2,20 @@ package btcc
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the BTCC go routine
func (b *BTCC) Start() {
go b.Run()
}
// Run implements the BTCC wrapper
func (b *BTCC) Run() {
if b.Verbose {
log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket))
@@ -26,32 +26,15 @@ func (b *BTCC) Run() {
if b.Websocket {
go b.WebsocketClient()
}
for b.Enabled {
for _, x := range b.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := b.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("BTCC %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(b.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * b.RESTPollingDelay)
}
}
func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(b.GetName(), p)
if err == nil {
return tickerNew, nil
// UpdateTicker updates and returns the ticker for a currency pair
func (b *BTCC) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := b.GetTicker(exchange.FormatExchangeCurrency(b.GetName(), p).String())
if err != nil {
return tickerPrice, err
}
var tickerPrice ticker.TickerPrice
tick, err := b.GetTicker(p.Pair().Lower().String())
tickerPrice.Pair = p
tickerPrice.Ask = tick.Sell
tickerPrice.Bid = tick.Buy
@@ -59,41 +42,54 @@ func (b *BTCC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Vol
tickerPrice.High = tick.High
ticker.ProcessTicker(b.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(b.Name, p, assetType)
}
func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (b *BTCC) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType)
if err != nil {
return b.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := b.GetOrderBook(p.Pair().Lower().String(), 100)
// GetOrderbookEx returns the orderbook for a currency pair
func (b *BTCC) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType)
if err == nil {
return b.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *BTCC) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := b.GetOrderBook(exchange.FormatExchangeCurrency(b.GetName(), p).String(), 100)
if err != nil {
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]})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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]})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: data[0], Amount: data[1]})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(b.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(b.Name, p, assetType)
}
//TODO: Retrieve BTCC info
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Kraken exchange
func (e *BTCC) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
// GetExchangeAccountInfo : Retrieves balances for all enabled currencies for
// the Kraken exchange - TODO
func (b *BTCC) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = b.GetName()
return response, nil
}

View File

@@ -1,341 +0,0 @@
package btce
import (
"errors"
"fmt"
"log"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
)
const (
BTCE_API_PUBLIC_URL = "https://btc-e.com/api"
BTCE_API_PRIVATE_URL = "https://btc-e.com/tapi"
BTCE_API_PUBLIC_VERSION = "3"
BTCE_API_PRIVATE_VERSION = "1"
BTCE_INFO = "info"
BTCE_TICKER = "ticker"
BTCE_DEPTH = "depth"
BTCE_TRADES = "trades"
BTCE_ACCOUNT_INFO = "getInfo"
BTCE_TRADE = "Trade"
BTCE_ACTIVE_ORDERS = "ActiveOrders"
BTCE_ORDER_INFO = "OrderInfo"
BTCE_CANCEL_ORDER = "CancelOrder"
BTCE_TRADE_HISTORY = "TradeHistory"
BTCE_TRANSACTION_HISTORY = "TransHistory"
BTCE_WITHDRAW_COIN = "WithdrawCoin"
BTCE_CREATE_COUPON = "CreateCoupon"
BTCE_REDEEM_COUPON = "RedeemCoupon"
)
type BTCE struct {
exchange.ExchangeBase
Ticker map[string]BTCeTicker
}
func (b *BTCE) SetDefaults() {
b.Name = "BTCE"
b.Enabled = false
b.Fee = 0.2
b.Verbose = false
b.Websocket = false
b.RESTPollingDelay = 10
b.Ticker = make(map[string]BTCeTicker)
}
func (b *BTCE) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
b.SetEnabled(false)
} else {
b.Enabled = true
b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
b.SetAPIKeys(exch.APIKey, exch.APISecret, "", 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, ",")
}
}
func (b *BTCE) GetFee() float64 {
return b.Fee
}
func (b *BTCE) GetInfo() (BTCEInfo, error) {
req := fmt.Sprintf("%s/%s/%s/", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_INFO)
resp := BTCEInfo{}
err := common.SendHTTPGetRequest(req, true, &resp)
if err != nil {
return resp, err
}
return resp, nil
}
func (b *BTCE) GetTicker(symbol string) (map[string]BTCeTicker, error) {
type Response struct {
Data map[string]BTCeTicker
}
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_TICKER, symbol)
err := common.SendHTTPGetRequest(req, true, &response.Data)
if err != nil {
return nil, err
}
return response.Data, nil
}
func (b *BTCE) GetDepth(symbol string) (BTCEOrderbook, error) {
type Response struct {
Data map[string]BTCEOrderbook
}
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_DEPTH, symbol)
err := common.SendHTTPGetRequest(req, true, &response.Data)
if err != nil {
return BTCEOrderbook{}, err
}
depth := response.Data[symbol]
return depth, nil
}
func (b *BTCE) GetTrades(symbol string) ([]BTCETrades, error) {
type Response struct {
Data map[string][]BTCETrades
}
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", BTCE_API_PUBLIC_URL, BTCE_API_PUBLIC_VERSION, BTCE_TRADES, symbol)
err := common.SendHTTPGetRequest(req, true, &response.Data)
if err != nil {
return []BTCETrades{}, err
}
trades := response.Data[symbol]
return trades, nil
}
func (b *BTCE) GetAccountInfo() (BTCEAccountInfo, error) {
var result BTCEAccountInfo
err := b.SendAuthenticatedHTTPRequest(BTCE_ACCOUNT_INFO, url.Values{}, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) GetActiveOrders(pair string) (map[string]BTCEActiveOrders, error) {
req := url.Values{}
req.Add("pair", pair)
var result map[string]BTCEActiveOrders
err := b.SendAuthenticatedHTTPRequest(BTCE_ACTIVE_ORDERS, req, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) GetOrderInfo(OrderID int64) (map[string]BTCEOrderInfo, error) {
req := url.Values{}
req.Add("order_id", strconv.FormatInt(OrderID, 10))
var result map[string]BTCEOrderInfo
err := b.SendAuthenticatedHTTPRequest(BTCE_ORDER_INFO, req, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) CancelOrder(OrderID int64) (bool, error) {
req := url.Values{}
req.Add("order_id", strconv.FormatInt(OrderID, 10))
var result BTCECancelOrder
err := b.SendAuthenticatedHTTPRequest(BTCE_CANCEL_ORDER, req, &result)
if err != nil {
return false, err
}
return true, nil
}
//to-do: convert orderid to int64
func (b *BTCE) Trade(pair, orderType string, amount, price float64) (float64, error) {
req := url.Values{}
req.Add("pair", pair)
req.Add("type", orderType)
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64))
var result BTCETrade
err := b.SendAuthenticatedHTTPRequest(BTCE_TRADE, req, &result)
if err != nil {
return 0, err
}
return result.OrderID, nil
}
func (b *BTCE) GetTransactionHistory(TIDFrom, Count, TIDEnd int64, order, since, end string) (map[string]BTCETransHistory, error) {
req := url.Values{}
req.Add("from", strconv.FormatInt(TIDFrom, 10))
req.Add("count", strconv.FormatInt(Count, 10))
req.Add("from_id", strconv.FormatInt(TIDFrom, 10))
req.Add("end_id", strconv.FormatInt(TIDEnd, 10))
req.Add("order", order)
req.Add("since", since)
req.Add("end", end)
var result map[string]BTCETransHistory
err := b.SendAuthenticatedHTTPRequest(BTCE_TRANSACTION_HISTORY, req, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) GetTradeHistory(TIDFrom, Count, TIDEnd int64, order, since, end, pair string) (map[string]BTCETradeHistory, error) {
req := url.Values{}
req.Add("from", strconv.FormatInt(TIDFrom, 10))
req.Add("count", strconv.FormatInt(Count, 10))
req.Add("from_id", strconv.FormatInt(TIDFrom, 10))
req.Add("end_id", strconv.FormatInt(TIDEnd, 10))
req.Add("order", order)
req.Add("since", since)
req.Add("end", end)
req.Add("pair", pair)
var result map[string]BTCETradeHistory
err := b.SendAuthenticatedHTTPRequest(BTCE_TRADE_HISTORY, req, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) WithdrawCoins(coin string, amount float64, address string) (BTCEWithdrawCoins, error) {
req := url.Values{}
req.Add("coinName", coin)
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("address", address)
var result BTCEWithdrawCoins
err := b.SendAuthenticatedHTTPRequest(BTCE_WITHDRAW_COIN, req, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) CreateCoupon(currency string, amount float64) (BTCECreateCoupon, error) {
req := url.Values{}
req.Add("currency", currency)
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
var result BTCECreateCoupon
err := b.SendAuthenticatedHTTPRequest(BTCE_CREATE_COUPON, req, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) RedeemCoupon(coupon string) (BTCERedeemCoupon, error) {
req := url.Values{}
req.Add("coupon", coupon)
var result BTCERedeemCoupon
err := b.SendAuthenticatedHTTPRequest(BTCE_REDEEM_COUPON, req, &result)
if err != nil {
return result, err
}
return result, nil
}
func (b *BTCE) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) {
nonce := strconv.FormatInt(time.Now().Unix(), 10)
values.Set("nonce", nonce)
values.Set("method", method)
encoded := values.Encode()
hmac := common.GetHMAC(common.HASH_SHA512, []byte(encoded), []byte(b.APISecret))
if b.Verbose {
log.Printf("Sending POST request to %s calling method %s with params %s\n", BTCE_API_PRIVATE_URL, method, encoded)
}
headers := make(map[string]string)
headers["Key"] = b.APIKey
headers["Sign"] = common.HexEncodeToString(hmac)
headers["Content-Type"] = "application/x-www-form-urlencoded"
resp, err := common.SendHTTPRequest("POST", BTCE_API_PRIVATE_URL, headers, strings.NewReader(encoded))
if err != nil {
return err
}
response := BTCEResponse{}
err = common.JSONDecode([]byte(resp), &response)
if err != nil {
return err
}
if response.Success != 1 {
return errors.New(response.Error)
}
JSONEncoded, err := common.JSONEncode(response.Return)
if err != nil {
return err
}
err = common.JSONDecode(JSONEncoded, &result)
if err != nil {
return err
}
return nil
}

View File

@@ -1,114 +0,0 @@
package btce
import (
"errors"
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
func (b *BTCE) Start() {
go b.Run()
}
func (b *BTCE) Run() {
if b.Verbose {
log.Printf("%s Websocket: %s.", b.GetName(), common.IsEnabled(b.Websocket))
log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs)
}
pairs := []string{}
for _, x := range b.EnabledPairs {
x = common.StringToLower(x[0:3] + "_" + x[3:6])
pairs = append(pairs, x)
}
pairsString := common.JoinStrings(pairs, "-")
for b.Enabled {
go func() {
ticker, err := b.GetTicker(pairsString)
if err != nil {
log.Println(err)
return
}
for x, y := range ticker {
x = common.StringToUpper(x[0:3] + x[4:])
log.Printf("BTC-e %s: Last %f High %f Low %f Volume %f\n", x, y.Last, y.High, y.Low, y.Vol_cur)
b.Ticker[x] = y
stats.AddExchangeInfo(b.GetName(), common.StringToUpper(x[0:3]), common.StringToUpper(x[4:]), y.Last, y.Vol_cur)
}
}()
time.Sleep(time.Second * b.RESTPollingDelay)
}
}
func (b *BTCE) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
var tickerPrice ticker.TickerPrice
tick, ok := b.Ticker[p.Pair().Lower().String()]
if !ok {
return tickerPrice, errors.New("Unable to get currency.")
}
tickerPrice.Pair = p
tickerPrice.Ask = tick.Buy
tickerPrice.Bid = tick.Sell
tickerPrice.Low = tick.Low
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Vol_cur
tickerPrice.High = tick.High
ticker.ProcessTicker(b.GetName(), p, tickerPrice)
return tickerPrice, nil
}
func (b *BTCE) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p)
if err == nil {
return ob, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := b.GetDepth(p.Pair().Lower().String())
if err != nil {
return orderBook, err
}
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 {
data := orderbookNew.Asks[x]
orderBook.Asks = append(ob.Asks, orderbook.OrderbookItem{Price: data[0], Amount: data[1]})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(b.GetName(), p, orderBook)
return orderBook, nil
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the BTCE exchange
func (e *BTCE) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetAccountInfo()
if err != nil {
return response, err
}
for x, y := range accountBalance.Funds {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
exchangeCurrency.CurrencyName = common.StringToUpper(x)
exchangeCurrency.TotalValue = y
exchangeCurrency.Hold = 0
response.Currencies = append(response.Currencies, exchangeCurrency)
}
return response, nil
}

View File

@@ -6,31 +6,45 @@ import (
"fmt"
"log"
"net/url"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
BTCMARKETS_API_URL = "https://api.btcmarkets.net"
BTCMARKETS_API_VERSION = "0"
BTCMARKETS_ACCOUNT_BALANCE = "/account/balance"
BTCMARKETS_ORDER_CREATE = "/order/create"
BTCMARKETS_ORDER_CANCEL = "/order/cancel"
BTCMARKETS_ORDER_HISTORY = "/order/history"
BTCMARKETS_ORDER_OPEN = "/order/open"
BTCMARKETS_ORDER_TRADE_HISTORY = "/order/trade/history"
BTCMARKETS_ORDER_DETAIL = "/order/detail"
btcMarketsAPIURL = "https://api.btcmarkets.net"
btcMarketsAPIVersion = "0"
btcMarketsAccountBalance = "/account/balance"
btcMarketsOrderCreate = "/order/create"
btcMarketsOrderCancel = "/order/cancel"
btcMarketsOrderHistory = "/order/history"
btcMarketsOrderOpen = "/order/open"
btcMarketsOrderTradeHistory = "/order/trade/history"
btcMarketsOrderDetail = "/order/detail"
btcMarketsWithdrawCrypto = "/fundtransfer/withdrawCrypto"
btcMarketsWithdrawAud = "/fundtransfer/withdrawEFT"
//Status Values
orderStatusNew = "New"
orderStatusPlaced = "Placed"
orderStatusFailed = "Failed"
orderStatusError = "Error"
orderStatusCancelled = "Cancelled"
orderStatusPartiallyCancelled = "Partially Cancelled"
orderStatusFullyMatched = "Fully Matched"
orderStatusPartiallyMatched = "Partially Matched"
)
// BTCMarkets is the overarching type across the BTCMarkets package
type BTCMarkets struct {
exchange.ExchangeBase
Ticker map[string]BTCMarketsTicker
exchange.Base
Ticker map[string]Ticker
}
// SetDefaults sets basic defaults
func (b *BTCMarkets) SetDefaults() {
b.Name = "BTC Markets"
b.Enabled = false
@@ -38,9 +52,15 @@ func (b *BTCMarkets) SetDefaults() {
b.Verbose = false
b.Websocket = false
b.RESTPollingDelay = 10
b.Ticker = make(map[string]BTCMarketsTicker)
b.Ticker = make(map[string]Ticker)
b.RequestCurrencyPairFormat.Delimiter = ""
b.RequestCurrencyPairFormat.Uppercase = true
b.ConfigCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Uppercase = true
b.AssetTypes = []string{ticker.Spot}
}
// Setup takes in an exchange configuration and sets all paramaters
func (b *BTCMarkets) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
b.SetEnabled(false)
@@ -54,113 +74,101 @@ func (b *BTCMarkets) Setup(exch config.ExchangeConfig) {
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := b.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = b.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
// GetFee returns the BTCMarkets fee on transactions
func (b *BTCMarkets) GetFee() float64 {
return b.Fee
}
func (b *BTCMarkets) GetTicker(symbol string) (BTCMarketsTicker, error) {
ticker := BTCMarketsTicker{}
path := fmt.Sprintf("/market/%s/AUD/tick", symbol)
err := common.SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &ticker)
if err != nil {
return BTCMarketsTicker{}, err
}
return ticker, nil
// GetTicker returns a ticker
// symbol - example "btc" or "ltc"
func (b *BTCMarkets) GetTicker(symbol string) (Ticker, error) {
ticker := Ticker{}
path := fmt.Sprintf("/market/%s/AUD/tick", common.StringToUpper(symbol))
return ticker,
common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &ticker)
}
func (b *BTCMarkets) GetOrderbook(symbol string) (BTCMarketsOrderbook, error) {
orderbook := BTCMarketsOrderbook{}
path := fmt.Sprintf("/market/%s/AUD/orderbook", symbol)
err := common.SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &orderbook)
if err != nil {
return BTCMarketsOrderbook{}, err
}
return orderbook, nil
// GetOrderbook returns current orderbook
// symbol - example "btc" or "ltc"
func (b *BTCMarkets) GetOrderbook(symbol string) (Orderbook, error) {
orderbook := Orderbook{}
path := fmt.Sprintf("/market/%s/AUD/orderbook", common.StringToUpper(symbol))
return orderbook,
common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &orderbook)
}
func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]BTCMarketsTrade, error) {
trades := []BTCMarketsTrade{}
path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", BTCMARKETS_API_URL, symbol), values)
err := common.SendHTTPGetRequest(path, true, &trades)
if err != nil {
return nil, err
}
return trades, nil
// GetTrades returns executed trades on the exchange
// symbol - example "btc" or "ltc"
// values - optional paramater "since" example values.Set(since, "59868345231")
func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]Trade, error) {
trades := []Trade{}
path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", btcMarketsAPIURL, symbol), values)
return trades, common.SendHTTPGetRequest(path, true, &trades)
}
func (b *BTCMarkets) Order(currency, instrument string, price, amount int64, orderSide, orderType, clientReq string) (int, error) {
type Order struct {
Currency string `json:"currency"`
Instrument string `json:"instrument"`
Price int64 `json:"price"`
Volume int64 `json:"volume"`
OrderSide string `json:"orderSide"`
OrderType string `json:"ordertype"`
ClientRequestId string `json:"clientRequestId"`
// NewOrder requests a new order and returns an ID
// currency - example "AUD"
// instrument - example "BTC"
// price - example 13000000000 (i.e x 100000000)
// amount - example 100000000 (i.e x 100000000)
// orderside - example "Bid" or "Ask"
// orderType - example "limit"
// clientReq - example "abc-cdf-1000"
func (b *BTCMarkets) NewOrder(currency, instrument string, price, amount int64, orderSide, orderType, clientReq string) (int, error) {
order := OrderToGo{
Currency: common.StringToUpper(currency),
Instrument: common.StringToUpper(instrument),
Price: price * common.SatoshisPerBTC,
Volume: amount * common.SatoshisPerBTC,
OrderSide: orderSide,
OrderType: orderType,
ClientRequestID: clientReq,
}
order := Order{}
order.Currency = currency
order.Instrument = instrument
order.Price = price * common.SATOSHIS_PER_BTC
order.Volume = amount * common.SATOSHIS_PER_BTC
order.OrderSide = orderSide
order.OrderType = orderType
order.ClientRequestId = clientReq
type Response struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
ID int `json:"id"`
ClientRequestID string `json:"clientRequestId"`
}
var resp Response
err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CREATE, order, &resp)
resp := Response{}
err := b.SendAuthenticatedRequest("POST", btcMarketsOrderCreate, order, &resp)
if err != nil {
return 0, err
}
if !resp.Success {
return 0, fmt.Errorf("%s Unable to place order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
return 0, fmt.Errorf("%s Unable to place order. Error message: %s", b.GetName(), resp.ErrorMessage)
}
return resp.ID, nil
}
// CancelOrder cancels an order by its ID
// orderID - id for order example "1337"
func (b *BTCMarkets) CancelOrder(orderID []int64) (bool, error) {
resp := Response{}
type CancelOrder struct {
OrderIDs []int64 `json:"orderIds"`
}
orders := CancelOrder{}
orders.OrderIDs = append(orders.OrderIDs, orderID...)
type Response struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
Responses []struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
ID int64 `json:"id"`
}
ClientRequestID string `json:"clientRequestId"`
}
var resp Response
err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CANCEL, orders, &resp)
err := b.SendAuthenticatedRequest("POST", btcMarketsOrderCancel, orders, &resp)
if err != nil {
return false, err
}
if !resp.Success {
return false, fmt.Errorf("%s Unable to cancel order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
return false, fmt.Errorf("%s Unable to cancel order. Error message: %s", b.GetName(), resp.ErrorMessage)
}
ordersToBeCancelled := len(orderID)
@@ -170,39 +178,37 @@ func (b *BTCMarkets) CancelOrder(orderID []int64) (bool, error) {
ordersCancelled++
log.Printf("%s Cancelled order %d.\n", b.GetName(), y.ID)
} else {
log.Printf("%s Unable to cancel order %d. Error message: %s\n", b.GetName(), y.ID, y.ErrorMessage)
log.Printf("%s Unable to cancel order %d. Error message: %s", b.GetName(), y.ID, y.ErrorMessage)
}
}
if ordersCancelled == ordersToBeCancelled {
return true, nil
} else {
return false, fmt.Errorf("%s Unable to cancel order(s).", b.GetName())
}
return false, fmt.Errorf("%s Unable to cancel order(s)", b.GetName())
}
func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) ([]BTCMarketsOrder, error) {
// GetOrders returns current order information on the exchange
// currency - example "AUD"
// instrument - example "BTC"
// limit - example "10"
// since - since a time example "33434568724"
// historic - if false just normal Orders open
func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) ([]Order, error) {
request := make(map[string]interface{})
request["currency"] = currency
request["instrument"] = instrument
request["currency"] = common.StringToUpper(currency)
request["instrument"] = common.StringToUpper(instrument)
request["limit"] = limit
request["since"] = since
path := BTCMARKETS_ORDER_OPEN
path := btcMarketsOrderOpen
if historic {
path = BTCMARKETS_ORDER_HISTORY
path = btcMarketsOrderHistory
}
type response struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
Orders []BTCMarketsOrder `json:"orders"`
}
resp := Response{}
resp := response{}
err := b.SendAuthenticatedRequest("POST", path, request, &resp)
if err != nil {
return nil, err
}
@@ -212,36 +218,31 @@ func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64,
}
for i := range resp.Orders {
resp.Orders[i].Price = resp.Orders[i].Price / common.SATOSHIS_PER_BTC
resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / common.SATOSHIS_PER_BTC
resp.Orders[i].Volume = resp.Orders[i].Volume / common.SATOSHIS_PER_BTC
resp.Orders[i].Price = resp.Orders[i].Price / common.SatoshisPerBTC
resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / common.SatoshisPerBTC
resp.Orders[i].Volume = resp.Orders[i].Volume / common.SatoshisPerBTC
for x := range resp.Orders[i].Trades {
resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / common.SATOSHIS_PER_BTC
resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / common.SATOSHIS_PER_BTC
resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / common.SATOSHIS_PER_BTC
resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / common.SatoshisPerBTC
resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / common.SatoshisPerBTC
resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / common.SatoshisPerBTC
}
}
return resp.Orders, nil
}
func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]BTCMarketsOrder, error) {
// GetOrderDetail returns order information an a specific order
// orderID - example "1337"
func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]Order, error) {
type OrderDetail struct {
OrderIDs []int64 `json:"orderIds"`
}
orders := OrderDetail{}
orders.OrderIDs = append(orders.OrderIDs, orderID...)
type response struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
Orders []BTCMarketsOrder `json:"orders"`
}
resp := response{}
err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_DETAIL, orders, &resp)
resp := Response{}
err := b.SendAuthenticatedRequest("POST", btcMarketsOrderDetail, orders, &resp)
if err != nil {
return nil, err
}
@@ -251,39 +252,94 @@ func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]BTCMarketsOrder, error)
}
for i := range resp.Orders {
resp.Orders[i].Price = resp.Orders[i].Price / common.SATOSHIS_PER_BTC
resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / common.SATOSHIS_PER_BTC
resp.Orders[i].Volume = resp.Orders[i].Volume / common.SATOSHIS_PER_BTC
resp.Orders[i].Price = resp.Orders[i].Price / common.SatoshisPerBTC
resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / common.SatoshisPerBTC
resp.Orders[i].Volume = resp.Orders[i].Volume / common.SatoshisPerBTC
for x := range resp.Orders[i].Trades {
resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / common.SATOSHIS_PER_BTC
resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / common.SATOSHIS_PER_BTC
resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / common.SATOSHIS_PER_BTC
resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / common.SatoshisPerBTC
resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / common.SatoshisPerBTC
resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / common.SatoshisPerBTC
}
}
return resp.Orders, nil
}
func (b *BTCMarkets) GetAccountBalance() ([]BTCMarketsAccountBalance, error) {
balance := []BTCMarketsAccountBalance{}
err := b.SendAuthenticatedRequest("GET", BTCMARKETS_ACCOUNT_BALANCE, nil, &balance)
// GetAccountBalance returns the full account balance
func (b *BTCMarkets) GetAccountBalance() ([]AccountBalance, error) {
balance := []AccountBalance{}
err := b.SendAuthenticatedRequest("GET", btcMarketsAccountBalance, nil, &balance)
if err != nil {
return nil, err
}
// 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.SATOSHIS_PER_BTC
balance[i].PendingFunds = balance[i].PendingFunds / common.SATOSHIS_PER_BTC
}
balance[i].Balance = balance[i].Balance / common.SatoshisPerBTC
balance[i].PendingFunds = balance[i].PendingFunds / common.SatoshisPerBTC
}
return balance, nil
}
// WithdrawCrypto withdraws cryptocurrency into a designated address
func (b *BTCMarkets) WithdrawCrypto(amount int64, currency, address string) (string, error) {
req := WithdrawRequestCrypto{
Amount: amount,
Currency: common.StringToUpper(currency),
Address: address,
}
resp := Response{}
err := b.SendAuthenticatedRequest("POST", btcMarketsWithdrawCrypto, req, &resp)
if err != nil {
return "", err
}
if !resp.Success {
return "", errors.New(resp.ErrorMessage)
}
return resp.Status, nil
}
// WithdrawAUD withdraws AUD into a designated bank address
// Does not return a TxID!
func (b *BTCMarkets) WithdrawAUD(accountName, accountNumber, bankName, bsbNumber, currency string, amount int64) (string, error) {
req := WithdrawRequestAUD{
AccountName: accountName,
AccountNumber: accountNumber,
BankName: bankName,
BSBNumber: bsbNumber,
Amount: amount,
Currency: common.StringToUpper(currency),
}
resp := Response{}
err := b.SendAuthenticatedRequest("POST", btcMarketsWithdrawAud, req, &resp)
if err != nil {
return "", err
}
if !resp.Success {
return "", errors.New(resp.ErrorMessage)
}
return resp.Status, nil
}
// SendAuthenticatedRequest sends an authenticated HTTP request
func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interface{}, result interface{}) (err error) {
nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13]
request := ""
if !b.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name)
}
if b.Nonce.Get() == 0 {
b.Nonce.Set(time.Now().UnixNano())
} else {
b.Nonce.Inc()
}
var request string
payload := []byte("")
if data != nil {
@@ -291,15 +347,15 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interfa
if err != nil {
return err
}
request = path + "\n" + nonce + "\n" + string(payload)
request = path + "\n" + b.Nonce.String()[0:13] + "\n" + string(payload)
} else {
request = path + "\n" + nonce + "\n"
request = path + "\n" + b.Nonce.String()[0:13] + "\n"
}
hmac := common.GetHMAC(common.HASH_SHA512, []byte(request), []byte(b.APISecret))
hmac := common.GetHMAC(common.HashSHA512, []byte(request), []byte(b.APISecret))
if b.Verbose {
log.Printf("Sending %s request to URL %s with params %s\n", reqType, BTCMARKETS_API_URL+path, request)
log.Printf("Sending %s request to URL %s with params %s\n", reqType, btcMarketsAPIURL+path, request)
}
headers := make(map[string]string)
@@ -307,17 +363,17 @@ func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interfa
headers["Accept-Charset"] = "UTF-8"
headers["Content-Type"] = "application/json"
headers["apikey"] = b.APIKey
headers["timestamp"] = nonce
headers["timestamp"] = b.Nonce.String()[0:13]
headers["signature"] = common.Base64Encode(hmac)
resp, err := common.SendHTTPRequest(reqType, BTCMARKETS_API_URL+path, headers, bytes.NewBuffer(payload))
resp, err := common.SendHTTPRequest(reqType, btcMarketsAPIURL+path, headers, bytes.NewBuffer(payload))
if err != nil {
return err
}
if b.Verbose {
log.Printf("Recieved raw: %s\n", resp)
log.Printf("Received raw: %s\n", resp)
}
err = common.JSONDecode([]byte(resp), &result)

View File

@@ -0,0 +1,147 @@
package btcmarkets
import (
"net/url"
"testing"
"time"
"github.com/thrasher-/gocryptotrader/config"
)
var bm BTCMarkets
// Please supply your own keys here to do better tests
const (
apiKey = ""
apiSecret = ""
)
func TestSetDefaults(t *testing.T) {
bm.SetDefaults()
}
func TestSetup(t *testing.T) {
t.Parallel()
b := BTCMarkets{}
b.Name = "BTC Markets"
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.dat")
bConfig, err := cfg.GetExchangeConfig("BTC Markets")
if err != nil {
t.Error("Test Failed - BTC Markets Setup() init error")
}
b.SetDefaults()
b.Setup(bConfig)
if !b.IsEnabled() || b.AuthenticatedAPISupport || b.RESTPollingDelay != time.Duration(10) ||
b.Verbose || b.Websocket || len(b.BaseCurrencies) < 1 ||
len(b.AvailablePairs) < 1 || len(b.EnabledPairs) < 1 {
t.Error("Test Failed - BTC Markets Setup values not set correctly")
}
bConfig.Enabled = false
b.Setup(bConfig)
if b.IsEnabled() {
t.Error("Test failed - BTC Markets TestSetup incorrect value")
}
}
func TestGetFee(t *testing.T) {
t.Parallel()
if fee := bm.GetFee(); fee == 0 {
t.Error("Test failed - GetFee() error")
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
_, err := bm.GetTicker("BTC")
if err != nil {
t.Error("Test failed - GetTicker() error", err)
}
}
func TestGetOrderbook(t *testing.T) {
t.Parallel()
_, err := bm.GetOrderbook("BTC")
if err != nil {
t.Error("Test failed - GetOrderbook() error", err)
}
}
func TestGetTrades(t *testing.T) {
t.Parallel()
_, err := bm.GetTrades("BTC", nil)
if err != nil {
t.Error("Test failed - GetTrades() error", err)
}
val := url.Values{}
val.Set("since", "0")
_, err = bm.GetTrades("BTC", val)
if err != nil {
t.Error("Test failed - GetTrades() error", err)
}
}
func TestNewOrder(t *testing.T) {
t.Parallel()
_, err := bm.NewOrder("AUD", "BTC", 0, 0, "Bid", "limit", "testTest")
if err == nil {
t.Error("Test failed - NewOrder() error", err)
}
}
func TestCancelOrder(t *testing.T) {
t.Parallel()
_, err := bm.CancelOrder([]int64{1337})
if err == nil {
t.Error("Test failed - CancelOrder() error", err)
}
}
func TestGetOrders(t *testing.T) {
t.Parallel()
_, err := bm.GetOrders("AUD", "BTC", 10, 0, false)
if err == nil {
t.Error("Test failed - GetOrders() error", err)
}
_, err = bm.GetOrders("AUD", "BTC", 10, 0, true)
if err == nil {
t.Error("Test failed - GetOrders() error", err)
}
}
func TestGetOrderDetail(t *testing.T) {
t.Parallel()
_, err := bm.GetOrderDetail([]int64{1337})
if err == nil {
t.Error("Test failed - GetOrderDetail() error", err)
}
}
func TestGetAccountBalance(t *testing.T) {
t.Parallel()
_, err := bm.GetAccountBalance()
if err == nil {
t.Error("Test failed - GetAccountBalance() error", err)
}
}
func TestWithdrawCrypto(t *testing.T) {
t.Parallel()
_, err := bm.WithdrawCrypto(0, "BTC", "LOLOLOL")
if err == nil {
t.Error("Test failed - WithdrawCrypto() error", err)
}
}
func TestWithdrawAUD(t *testing.T) {
t.Parallel()
_, err := bm.WithdrawAUD("BLA", "1337", "blawest", "1336", "BTC", 10000000)
if err == nil {
t.Error("Test failed - WithdrawAUD() error", err)
}
}

View File

@@ -1,22 +1,35 @@
package btcmarkets
type BTCMarketsTicker struct {
BestBID float64
BestAsk float64
LastPrice float64
Currency string
Instrument string
Timestamp int64
// Response is the genralized response type
type Response struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
ID int `json:"id"`
Responses []struct {
Success bool `json:"success"`
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
ID int64 `json:"id"`
}
ClientRequestID string `json:"clientRequestId"`
Orders []Order `json:"orders"`
Status string `json:"status"`
}
type BTCMarketsTrade struct {
TradeID int64 `json:"tid"`
Amount float64 `json:"amount"`
Price float64 `json:"price"`
Date int64 `json:"date"`
// Ticker holds ticker information
type Ticker struct {
BestBID float64 `json:"bestBid"`
BestAsk float64 `json:"bestAsk"`
LastPrice float64 `json:"lastPrice"`
Currency string `json:"currency"`
Instrument string `json:"instrument"`
Timestamp int64 `json:"timestamp"`
Volume float64 `json:"volume24h"`
}
type BTCMarketsOrderbook struct {
// Orderbook holds current orderbook information returned from the exchange
type Orderbook struct {
Currency string `json:"currency"`
Instrument string `json:"instrument"`
Timestamp int64 `json:"timestamp"`
@@ -24,7 +37,44 @@ type BTCMarketsOrderbook struct {
Bids [][]float64 `json:"bids"`
}
type BTCMarketsTradeResponse struct {
// Trade holds trade information
type Trade struct {
TradeID int64 `json:"tid"`
Amount float64 `json:"amount"`
Price float64 `json:"price"`
Date int64 `json:"date"`
}
// OrderToGo holds order information to be sent to the exchange
type OrderToGo struct {
Currency string `json:"currency"`
Instrument string `json:"instrument"`
Price int64 `json:"price"`
Volume int64 `json:"volume"`
OrderSide string `json:"orderSide"`
OrderType string `json:"ordertype"`
ClientRequestID string `json:"clientRequestId"`
}
// Order holds order information
type Order struct {
ID int64 `json:"id"`
Currency string `json:"currency"`
Instrument string `json:"instrument"`
OrderSide string `json:"orderSide"`
OrderType string `json:"ordertype"`
CreationTime float64 `json:"creationTime"`
Status string `json:"status"`
ErrorMessage string `json:"errorMessage"`
Price float64 `json:"price"`
Volume float64 `json:"volume"`
OpenVolume float64 `json:"openVolume"`
ClientRequestID string `json:"clientRequestId"`
Trades []TradeResponse `json:"trades"`
}
// TradeResponse holds trade information
type TradeResponse struct {
ID int64 `json:"id"`
CreationTime float64 `json:"creationTime"`
Description string `json:"description"`
@@ -33,24 +83,26 @@ type BTCMarketsTradeResponse struct {
Fee float64 `json:"fee"`
}
type BTCMarketsOrder struct {
ID int64 `json:"id"`
Currency string `json:"currency"`
Instrument string `json:"instrument"`
OrderSide string `json:"orderSide"`
OrderType string `json:"ordertype"`
CreationTime float64 `json:"creationTime"`
Status string `json:"status"`
ErrorMessage string `json:"errorMessage"`
Price float64 `json:"price"`
Volume float64 `json:"volume"`
OpenVolume float64 `json:"openVolume"`
ClientRequestId string `json:"clientRequestId"`
Trades []BTCMarketsTradeResponse `json:"trades"`
}
type BTCMarketsAccountBalance struct {
// AccountBalance holds account balance details
type AccountBalance struct {
Balance float64 `json:"balance"`
PendingFunds float64 `json:"pendingFunds"`
Currency string `json:"currency"`
}
// WithdrawRequestCrypto is a generalized withdraw request type
type WithdrawRequestCrypto struct {
Amount int64 `json:"amount"`
Currency string `json:"currency"`
Address string `json:"address"`
}
// WithdrawRequestAUD is a generalized withdraw request type
type WithdrawRequestAUD struct {
Amount int64 `json:"amount"`
Currency string `json:"currency"`
AccountName string `json:"accountName"`
AccountNumber string `json:"accountNumber"`
BankName string `json:"bankName"`
BSBNumber string `json:"bsbNumber"`
}

View File

@@ -2,53 +2,57 @@ package btcmarkets
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the BTC Markets go routine
func (b *BTCMarkets) Start() {
go b.Run()
}
// Run implements the BTC Markets wrapper
func (b *BTCMarkets) Run() {
if b.Verbose {
log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs)
}
for b.Enabled {
for _, x := range b.EnabledPairs {
curr := pair.NewCurrencyPair(x, "AUD")
go func() {
ticker, err := b.GetTickerPrice(curr)
if err != nil {
return
}
BTCMarketsLastUSD, _ := currency.ConvertCurrency(ticker.Last, "AUD", "USD")
BTCMarketsBestBidUSD, _ := currency.ConvertCurrency(ticker.Bid, "AUD", "USD")
BTCMarketsBestAskUSD, _ := currency.ConvertCurrency(ticker.Ask, "AUD", "USD")
log.Printf("BTC Markets %s: Last %f (%f) Bid %f (%f) Ask %f (%f)\n", curr.Pair().String(), BTCMarketsLastUSD, ticker.Last, BTCMarketsBestBidUSD, ticker.Bid, BTCMarketsBestAskUSD, ticker.Ask)
stats.AddExchangeInfo(b.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, 0)
stats.AddExchangeInfo(b.GetName(), curr.GetFirstCurrency().String(), "USD", BTCMarketsLastUSD, 0)
}()
if !common.DataContains(b.EnabledPairs, "AUD") || !common.DataContains(b.EnabledPairs, "AUD") {
enabledPairs := []string{}
for x := range b.EnabledPairs {
enabledPairs = append(enabledPairs, b.EnabledPairs[x]+"AUD")
}
availablePairs := []string{}
for x := range b.AvailablePairs {
availablePairs = append(availablePairs, b.AvailablePairs[x]+"AUD")
}
log.Println("BTCMarkets: Upgrading available and enabled pairs")
err := b.UpdateEnabledCurrencies(enabledPairs, true)
if err != nil {
log.Printf("%s Failed to get config.\n", b.GetName())
return
}
err = b.UpdateAvailableCurrencies(availablePairs, true)
if err != nil {
log.Printf("%s Failed to get config.\n", b.GetName())
return
}
time.Sleep(time.Second * b.RESTPollingDelay)
}
}
func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(b.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (b *BTCMarkets) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := b.GetTicker(p.GetFirstCurrency().String())
if err != nil {
return tickerPrice, err
@@ -57,47 +61,61 @@ func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, er
tickerPrice.Ask = tick.BestAsk
tickerPrice.Bid = tick.BestBID
tickerPrice.Last = tick.LastPrice
ticker.ProcessTicker(b.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(b.Name, p, assetType)
}
func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (b *BTCMarkets) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType)
if err != nil {
return b.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
// GetOrderbookEx returns orderbook base on the currency pair
func (b *BTCMarkets) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType)
if err == nil {
return b.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *BTCMarkets) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := b.GetOrderbook(p.GetFirstCurrency().String())
if err != nil {
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]})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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]})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(b.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(b.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(b.Name, p, assetType)
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the BTCMarkets exchange
func (e *BTCMarkets) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetAccountBalance()
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// BTCMarkets exchange
func (b *BTCMarkets) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = b.GetName()
accountBalance, err := b.GetAccountBalance()
if err != nil {
return response, err
}
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Balance
exchangeCurrency.Hold = accountBalance[i].PendingFunds

View File

@@ -3,6 +3,7 @@ package coinut
import (
"bytes"
"errors"
"fmt"
"log"
"time"
@@ -10,11 +11,12 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
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"
@@ -33,7 +35,7 @@ const (
)
type COINUT struct {
exchange.ExchangeBase
exchange.Base
WebsocketConn *websocket.Conn
InstrumentMap map[string]int
}
@@ -47,6 +49,11 @@ func (c *COINUT) SetDefaults() {
c.Verbose = false
c.Websocket = false
c.RESTPollingDelay = 10
c.RequestCurrencyPairFormat.Delimiter = ""
c.RequestCurrencyPairFormat.Uppercase = true
c.ConfigCurrencyPairFormat.Delimiter = ""
c.ConfigCurrencyPairFormat.Uppercase = true
c.AssetTypes = []string{ticker.Spot}
}
func (c *COINUT) Setup(exch config.ExchangeConfig) {
@@ -62,6 +69,14 @@ func (c *COINUT) Setup(exch config.ExchangeConfig) {
c.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
c.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
c.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := c.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = c.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -271,13 +286,21 @@ func (c *COINUT) GetOpenPosition(instrumentID int) ([]CoinutOpenPosition, error)
//to-do: user position update via websocket
func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[string]interface{}, result interface{}) (err error) {
timestamp := time.Now().Unix()
if !c.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, c.Name)
}
if c.Nonce.Get() == 0 {
c.Nonce.Set(time.Now().Unix())
} else {
c.Nonce.Inc()
}
payload := []byte("")
if params == nil {
params = map[string]interface{}{}
}
params["nonce"] = timestamp
params["nonce"] = c.Nonce.Get()
params["request"] = apiRequest
payload, err = common.JSONEncode(params)
@@ -290,7 +313,7 @@ func (c *COINUT) SendAuthenticatedHTTPRequest(apiRequest string, params map[stri
log.Printf("Request JSON: %s\n", payload)
}
hmac := common.GetHMAC(common.HASH_SHA256, []byte(payload), []byte(c.APIKey))
hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(c.APIKey))
headers := make(map[string]string)
headers["X-USER"] = c.ClientID
headers["X-SIGNATURE"] = common.HexEncodeToString(hmac)
@@ -299,7 +322,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 +330,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

View File

@@ -34,7 +34,7 @@ type CoinutTicker struct {
type CoinutOrderbookBase struct {
Count int `json:"count"`
Price float64 `json:"price,string"`
Quantity float64 `json:"quantity,string"`
Quantity float64 `json:"qty,string"`
}
type CoinutOrderbook struct {

View File

@@ -2,20 +2,20 @@ package coinut
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the COINUT go routine
func (c *COINUT) Start() {
go c.Run()
}
// Run implements the COINUT wrapper
func (c *COINUT) Run() {
if c.Verbose {
log.Printf("%s Websocket: %s. (url: %s).\n", c.GetName(), common.IsEnabled(c.Websocket), COINUT_WEBSOCKET_URL)
@@ -40,31 +40,16 @@ func (c *COINUT) Run() {
currencies = append(currencies, x)
}
err = c.UpdateAvailableCurrencies(currencies)
err = c.UpdateAvailableCurrencies(currencies, false)
if err != nil {
log.Printf("%s Failed to get config.\n", c.GetName())
}
for c.Enabled {
for _, x := range c.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := c.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("COINUT %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(c.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * c.RESTPollingDelay)
}
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the COINUT exchange
func (e *COINUT) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// COINUT exchange
func (c *COINUT) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
/*
response.ExchangeName = e.GetName()
accountBalance, err := e.GetAccounts()
@@ -72,7 +57,7 @@ func (e *COINUT) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error)
return response, err
}
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Available
exchangeCurrency.Hold = accountBalance[i].Hold
@@ -83,16 +68,12 @@ func (e *COINUT) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error)
return response, nil
}
func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(c.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (c *COINUT) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := c.GetInstrumentTicker(c.InstrumentMap[p.Pair().String()])
if err != nil {
return ticker.TickerPrice{}, err
return ticker.Price{}, err
}
tickerPrice.Pair = p
@@ -100,30 +81,45 @@ func (c *COINUT) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error)
tickerPrice.Last = tick.Last
tickerPrice.High = tick.HighestBuy
tickerPrice.Low = tick.LowestSell
ticker.ProcessTicker(c.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(c.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(c.Name, p, assetType)
}
func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(c.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (c *COINUT) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(c.GetName(), p, assetType)
if err != nil {
return c.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
// GetOrderbookEx returns orderbook base on the currency pair
func (c *COINUT) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(c.GetName(), p, assetType)
if err == nil {
return c.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (c *COINUT) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := c.GetInstrumentOrderbook(c.InstrumentMap[p.Pair().String()], 200)
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Buy {
orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: orderbookNew.Buy[x].Quantity, Price: orderbookNew.Buy[x].Price})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Buy[x].Quantity, Price: orderbookNew.Buy[x].Price})
}
for x := range orderbookNew.Sell {
orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Price})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Sell[x].Quantity, Price: orderbookNew.Sell[x].Price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(c.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(c.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(c.Name, p, assetType)
}

View File

@@ -7,29 +7,36 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/nonce"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
WarningBase64DecryptSecretKeyFailed = "WARNING -- Exchange %s unable to base64 decode secret key.. Disabling Authenticated API support."
ErrExchangeNotFound = "Exchange not found in dataset."
warningBase64DecryptSecretKeyFailed = "WARNING -- Exchange %s unable to base64 decode secret key.. Disabling Authenticated API support."
// WarningAuthenticatedRequestWithoutCredentialsSet error message for authenticated request without credentails set
WarningAuthenticatedRequestWithoutCredentialsSet = "WARNING -- Exchange %s authenticated HTTP request called but not supported due to unset/default API keys."
// ErrExchangeNotFound is a constant for an error message
ErrExchangeNotFound = "Exchange not found in dataset."
)
//ExchangeAccountInfo : Generic type to hold each exchange's holdings in all enabled currencies
type ExchangeAccountInfo struct {
// AccountInfo is a Generic type to hold each exchange's holdings in
// all enabled currencies
type AccountInfo struct {
ExchangeName string
Currencies []ExchangeAccountCurrencyInfo
Currencies []AccountCurrencyInfo
}
//ExchangeAccountCurrencyInfo : Sub type to store currency name and value
type ExchangeAccountCurrencyInfo struct {
// AccountCurrencyInfo is a sub type to store currency name and value
type AccountCurrencyInfo struct {
CurrencyName string
TotalValue float64
Hold float64
}
type ExchangeBase struct {
// Base stores the individual exchange information
type Base struct {
Name string
Enabled bool
Verbose bool
@@ -37,42 +44,279 @@ type ExchangeBase struct {
RESTPollingDelay time.Duration
AuthenticatedAPISupport bool
APISecret, APIKey, ClientID string
Nonce nonce.Nonce
TakerFee, MakerFee, Fee float64
BaseCurrencies []string
AvailablePairs []string
EnabledPairs []string
AssetTypes []string
WebsocketURL string
APIUrl string
RequestCurrencyPairFormat config.CurrencyPairFormatConfig
ConfigCurrencyPairFormat config.CurrencyPairFormatConfig
}
//IBotExchange : Enforces standard functions for all exchanges supported in gocryptotrader
// IBotExchange enforces standard functions for all exchanges supported in
// GoCryptoTrader
type IBotExchange interface {
Setup(exch config.ExchangeConfig)
Start()
SetDefaults()
GetName() string
IsEnabled() bool
GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error)
GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error)
GetEnabledCurrencies() []string
GetExchangeAccountInfo() (ExchangeAccountInfo, error)
GetTickerPrice(currency pair.CurrencyPair, assetType string) (ticker.Price, error)
UpdateTicker(currency pair.CurrencyPair, assetType string) (ticker.Price, error)
GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error)
UpdateOrderbook(currency pair.CurrencyPair, assetType string) (orderbook.Base, error)
GetEnabledCurrencies() []pair.CurrencyPair
GetExchangeAccountInfo() (AccountInfo, error)
GetAuthenticatedAPISupport() bool
}
func (e *ExchangeBase) GetName() string {
// SetAssetTypes checks the exchange asset types (whether it supports SPOT,
// Binary or Futures) and sets it to a default setting if it doesn't exist
func (e *Base) SetAssetTypes() error {
cfg := config.GetConfig()
exch, err := cfg.GetExchangeConfig(e.Name)
if err != nil {
return err
}
update := false
if exch.AssetTypes == "" {
exch.AssetTypes = common.JoinStrings(e.AssetTypes, ",")
update = true
} else {
e.AssetTypes = common.SplitStrings(exch.AssetTypes, ",")
}
if update {
return cfg.UpdateExchangeConfig(exch)
}
return nil
}
// GetExchangeAssetTypes returns the asset types the exchange supports (SPOT,
// binary, futures)
func GetExchangeAssetTypes(exchName string) ([]string, error) {
cfg := config.GetConfig()
exch, err := cfg.GetExchangeConfig(exchName)
if err != nil {
return nil, err
}
return common.SplitStrings(exch.AssetTypes, ","), nil
}
// SetCurrencyPairFormat checks the exchange request and config currency pair
// formats and sets it to a default setting if it doesn't exist
func (e *Base) SetCurrencyPairFormat() error {
cfg := config.GetConfig()
exch, err := cfg.GetExchangeConfig(e.Name)
if err != nil {
return err
}
update := false
if exch.RequestCurrencyPairFormat == nil {
exch.RequestCurrencyPairFormat = &config.CurrencyPairFormatConfig{
Delimiter: e.RequestCurrencyPairFormat.Delimiter,
Uppercase: e.RequestCurrencyPairFormat.Uppercase,
Separator: e.RequestCurrencyPairFormat.Separator,
Index: e.RequestCurrencyPairFormat.Index,
}
update = true
} else {
e.RequestCurrencyPairFormat = *exch.RequestCurrencyPairFormat
}
if exch.ConfigCurrencyPairFormat == nil {
exch.ConfigCurrencyPairFormat = &config.CurrencyPairFormatConfig{
Delimiter: e.ConfigCurrencyPairFormat.Delimiter,
Uppercase: e.ConfigCurrencyPairFormat.Uppercase,
Separator: e.ConfigCurrencyPairFormat.Separator,
Index: e.ConfigCurrencyPairFormat.Index,
}
update = true
} else {
e.ConfigCurrencyPairFormat = *exch.ConfigCurrencyPairFormat
}
if update {
return cfg.UpdateExchangeConfig(exch)
}
return nil
}
// GetAuthenticatedAPISupport returns whether the exchange supports
// authenticated API requests
func (e *Base) GetAuthenticatedAPISupport() bool {
return e.AuthenticatedAPISupport
}
// GetName is a method that returns the name of the exchange base
func (e *Base) GetName() string {
return e.Name
}
func (e *ExchangeBase) GetEnabledCurrencies() []string {
return e.EnabledPairs
// GetEnabledCurrencies is a method that returns the enabled currency pairs of
// the exchange base
func (e *Base) GetEnabledCurrencies() []pair.CurrencyPair {
var pairs []pair.CurrencyPair
for x := range e.EnabledPairs {
var currencyPair pair.CurrencyPair
if e.RequestCurrencyPairFormat.Delimiter != "" {
if e.ConfigCurrencyPairFormat.Delimiter != "" {
if e.ConfigCurrencyPairFormat.Delimiter == e.RequestCurrencyPairFormat.Delimiter {
currencyPair = pair.NewCurrencyPairDelimiter(e.EnabledPairs[x],
e.RequestCurrencyPairFormat.Delimiter)
} else {
currencyPair = pair.NewCurrencyPairDelimiter(e.EnabledPairs[x],
e.ConfigCurrencyPairFormat.Delimiter)
currencyPair.Delimiter = "-"
}
} else {
if e.ConfigCurrencyPairFormat.Index != "" {
currencyPair = pair.NewCurrencyPairFromIndex(e.EnabledPairs[x],
e.ConfigCurrencyPairFormat.Index)
} else {
currencyPair = pair.NewCurrencyPair(e.EnabledPairs[x][0:3],
e.EnabledPairs[x][3:])
}
}
} else {
if e.ConfigCurrencyPairFormat.Delimiter != "" {
currencyPair = pair.NewCurrencyPairDelimiter(e.EnabledPairs[x],
e.ConfigCurrencyPairFormat.Delimiter)
} else {
if e.ConfigCurrencyPairFormat.Index != "" {
currencyPair = pair.NewCurrencyPairFromIndex(e.EnabledPairs[x],
e.ConfigCurrencyPairFormat.Index)
} else {
currencyPair = pair.NewCurrencyPair(e.EnabledPairs[x][0:3],
e.EnabledPairs[x][3:])
}
}
}
pairs = append(pairs, currencyPair)
}
return pairs
}
func (e *ExchangeBase) SetEnabled(enabled bool) {
// GetAvailableCurrencies is a method that returns the available currency pairs
// of the exchange base
func (e *Base) GetAvailableCurrencies() []pair.CurrencyPair {
var pairs []pair.CurrencyPair
for x := range e.AvailablePairs {
var currencyPair pair.CurrencyPair
if e.RequestCurrencyPairFormat.Delimiter != "" {
if e.ConfigCurrencyPairFormat.Delimiter != "" {
if e.ConfigCurrencyPairFormat.Delimiter == e.RequestCurrencyPairFormat.Delimiter {
currencyPair = pair.NewCurrencyPairDelimiter(e.AvailablePairs[x],
e.RequestCurrencyPairFormat.Delimiter)
} else {
currencyPair = pair.NewCurrencyPairDelimiter(e.AvailablePairs[x],
e.ConfigCurrencyPairFormat.Delimiter)
currencyPair.Delimiter = "-"
}
} else {
if e.ConfigCurrencyPairFormat.Index != "" {
currencyPair = pair.NewCurrencyPairFromIndex(e.AvailablePairs[x],
e.ConfigCurrencyPairFormat.Index)
} else {
currencyPair = pair.NewCurrencyPair(e.AvailablePairs[x][0:3],
e.AvailablePairs[x][3:])
}
}
} else {
if e.ConfigCurrencyPairFormat.Delimiter != "" {
currencyPair = pair.NewCurrencyPairDelimiter(e.AvailablePairs[x],
e.ConfigCurrencyPairFormat.Delimiter)
} else {
if e.ConfigCurrencyPairFormat.Index != "" {
currencyPair = pair.NewCurrencyPairFromIndex(e.AvailablePairs[x],
e.ConfigCurrencyPairFormat.Index)
} else {
currencyPair = pair.NewCurrencyPair(e.AvailablePairs[x][0:3],
e.AvailablePairs[x][3:])
}
}
}
pairs = append(pairs, currencyPair)
}
return pairs
}
// GetExchangeFormatCurrencySeperator returns whether or not a specific
// exchange contains a separator used for API requests
func GetExchangeFormatCurrencySeperator(exchName string) bool {
cfg := config.GetConfig()
exch, err := cfg.GetExchangeConfig(exchName)
if err != nil {
return false
}
if exch.RequestCurrencyPairFormat.Separator != "" {
return true
}
return false
}
// GetAndFormatExchangeCurrencies returns a pair.CurrencyItem string containing
// the exchanges formatted currency pairs
func GetAndFormatExchangeCurrencies(exchName string, pairs []pair.CurrencyPair) (pair.CurrencyItem, error) {
var currencyItems pair.CurrencyItem
cfg := config.GetConfig()
exch, err := cfg.GetExchangeConfig(exchName)
if err != nil {
return currencyItems, err
}
for x := range pairs {
currencyItems += FormatExchangeCurrency(exchName, pairs[x])
if x == len(pairs)-1 {
continue
}
currencyItems += pair.CurrencyItem(exch.RequestCurrencyPairFormat.Separator)
}
return currencyItems, nil
}
// FormatExchangeCurrency is a method that formats and returns a currency pair
// based on the user currency display preferences
func FormatExchangeCurrency(exchName string, p pair.CurrencyPair) pair.CurrencyItem {
cfg := config.GetConfig()
exch, _ := cfg.GetExchangeConfig(exchName)
return p.Display(exch.RequestCurrencyPairFormat.Delimiter,
exch.RequestCurrencyPairFormat.Uppercase)
}
// FormatCurrency is a method that formats and returns a currency pair
// based on the user currency display preferences
func FormatCurrency(p pair.CurrencyPair) pair.CurrencyItem {
cfg := config.GetConfig()
return p.Display(cfg.CurrencyPairFormat.Delimiter,
cfg.CurrencyPairFormat.Uppercase)
}
// SetEnabled is a method that sets if the exchange is enabled
func (e *Base) SetEnabled(enabled bool) {
e.Enabled = enabled
}
func (e *ExchangeBase) IsEnabled() bool {
// IsEnabled is a method that returns if the current exchange is enabled
func (e *Base) IsEnabled() bool {
return e.Enabled
}
func (e *ExchangeBase) SetAPIKeys(APIKey, APISecret, ClientID string, b64Decode bool) {
// SetAPIKeys is a method that sets the current API keys for the exchange
func (e *Base) SetAPIKeys(APIKey, APISecret, ClientID string, b64Decode bool) {
if !e.AuthenticatedAPISupport {
return
}
e.APIKey = APIKey
e.ClientID = ClientID
@@ -80,7 +324,7 @@ func (e *ExchangeBase) SetAPIKeys(APIKey, APISecret, ClientID string, b64Decode
result, err := common.Base64Decode(APISecret)
if err != nil {
e.AuthenticatedAPISupport = false
log.Printf(WarningBase64DecryptSecretKeyFailed, e.Name)
log.Printf(warningBase64DecryptSecretKeyFailed, e.Name)
}
e.APISecret = string(result)
} else {
@@ -88,19 +332,50 @@ func (e *ExchangeBase) SetAPIKeys(APIKey, APISecret, ClientID string, b64Decode
}
}
func (e *ExchangeBase) UpdateAvailableCurrencies(exchangeProducts []string) error {
// UpdateEnabledCurrencies is a method that sets new pairs to the current
// exchange. Setting force to true upgrades the enabled currencies
func (e *Base) UpdateEnabledCurrencies(exchangeProducts []string, force bool) error {
exchangeProducts = common.SplitStrings(common.StringToUpper(common.JoinStrings(exchangeProducts, ",")), ",")
diff := common.StringSliceDifference(e.AvailablePairs, exchangeProducts)
if len(diff) > 0 {
diff := common.StringSliceDifference(e.EnabledPairs, exchangeProducts)
if force || len(diff) > 0 {
cfg := config.GetConfig()
exch, err := cfg.GetExchangeConfig(e.Name)
if err != nil {
return err
}
if force {
log.Printf("%s forced update of enabled pairs.", e.Name)
} else {
log.Printf("%s Updating available pairs. Difference: %s.\n", e.Name, diff)
exch.AvailablePairs = common.JoinStrings(exchangeProducts, ",")
cfg.UpdateExchangeConfig(exch)
}
exch.EnabledPairs = common.JoinStrings(exchangeProducts, ",")
e.EnabledPairs = exchangeProducts
return cfg.UpdateExchangeConfig(exch)
}
return nil
}
// UpdateAvailableCurrencies is a method that sets new pairs to the current
// exchange. Setting force to true upgrades the available currencies
func (e *Base) UpdateAvailableCurrencies(exchangeProducts []string, force bool) error {
exchangeProducts = common.SplitStrings(common.StringToUpper(common.JoinStrings(exchangeProducts, ",")), ",")
diff := common.StringSliceDifference(e.AvailablePairs, exchangeProducts)
if force || len(diff) > 0 {
cfg := config.GetConfig()
exch, err := cfg.GetExchangeConfig(e.Name)
if err != nil {
return err
}
if force {
log.Printf("%s forced update of available pairs.", e.Name)
} else {
log.Printf("%s Updating available pairs. Difference: %s.\n", e.Name, diff)
}
exch.AvailablePairs = common.JoinStrings(exchangeProducts, ",")
e.AvailablePairs = exchangeProducts
return cfg.UpdateExchangeConfig(exch)
}
return nil
}

View File

@@ -3,11 +3,159 @@ package exchange
import (
"testing"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
func TestSetAssetTypes(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatalf("Test failed. TestSetAssetTypes failed to load config file. Error: %s", err)
}
b := Base{
Name: "TESTNAME",
}
err = b.SetAssetTypes()
if err == nil {
t.Fatal("Test failed. TestSetAssetTypes returned nil error for a non-existant exchange")
}
b.Name = "ANX"
err = b.SetAssetTypes()
if err != nil {
t.Fatalf("Test failed. TestSetAssetTypes. Error %s", err)
}
exch, err := cfg.GetExchangeConfig(b.Name)
if err != nil {
t.Fatalf("Test failed. TestSetAssetTypes load config failed. Error %s", err)
}
exch.AssetTypes = ""
err = cfg.UpdateExchangeConfig(exch)
if err != nil {
t.Fatalf("Test failed. TestSetAssetTypes update config failed. Error %s", err)
}
exch, err = cfg.GetExchangeConfig(b.Name)
if err != nil {
t.Fatalf("Test failed. TestSetAssetTypes load config failed. Error %s", err)
}
if exch.AssetTypes != "" {
t.Fatal("Test failed. TestSetAssetTypes assetTypes != ''")
}
err = b.SetAssetTypes()
if err != nil {
t.Fatalf("Test failed. TestSetAssetTypes. Error %s", err)
}
if !common.DataContains(b.AssetTypes, ticker.Spot) {
t.Fatal("Test failed. TestSetAssetTypes assetTypes is not set")
}
}
func TestGetExchangeAssetTypes(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatalf("Failed to load config file. Error: %s", err)
}
result, err := GetExchangeAssetTypes("Bitfinex")
if err != nil {
t.Fatal("Test failed. Unable to obtain Bitfinex asset types")
}
if !common.DataContains(result, ticker.Spot) {
t.Fatal("Test failed. Bitfinex does not contain default asset type 'SPOT'")
}
_, err = GetExchangeAssetTypes("non-existant-exchange")
if err == nil {
t.Fatal("Test failed. Got asset types for non-existant exchange")
}
}
func TestSetCurrencyPairFormat(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatalf("Test failed. TestSetCurrencyPairFormat failed to load config file. Error: %s", err)
}
b := Base{
Name: "TESTNAME",
}
err = b.SetCurrencyPairFormat()
if err == nil {
t.Fatal("Test failed. TestSetCurrencyPairFormat returned nil error for a non-existant exchange")
}
b.Name = "ANX"
err = b.SetCurrencyPairFormat()
if err != nil {
t.Fatalf("Test failed. TestSetCurrencyPairFormat. Error %s", err)
}
exch, err := cfg.GetExchangeConfig(b.Name)
if err != nil {
t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err)
}
exch.ConfigCurrencyPairFormat = nil
exch.RequestCurrencyPairFormat = nil
err = cfg.UpdateExchangeConfig(exch)
if err != nil {
t.Fatalf("Test failed. TestSetCurrencyPairFormat update config failed. Error %s", err)
}
exch, err = cfg.GetExchangeConfig(b.Name)
if err != nil {
t.Fatalf("Test failed. TestSetCurrencyPairFormat load config failed. Error %s", err)
}
if exch.ConfigCurrencyPairFormat != nil && exch.RequestCurrencyPairFormat != nil {
t.Fatal("Test failed. TestSetCurrencyPairFormat exch values are not nil")
}
err = b.SetCurrencyPairFormat()
if err != nil {
t.Fatalf("Test failed. TestSetCurrencyPairFormat. Error %s", err)
}
if b.ConfigCurrencyPairFormat.Delimiter != "" &&
b.ConfigCurrencyPairFormat.Index != "BTC" &&
b.ConfigCurrencyPairFormat.Uppercase {
t.Fatal("Test failed. TestSetCurrencyPairFormat ConfigCurrencyPairFormat values are incorrect")
}
if b.RequestCurrencyPairFormat.Delimiter != "" &&
b.RequestCurrencyPairFormat.Index != "BTC" &&
b.RequestCurrencyPairFormat.Uppercase {
t.Fatal("Test failed. TestSetCurrencyPairFormat RequestCurrencyPairFormat values are incorrect")
}
}
func TestGetAuthenticatedAPISupport(t *testing.T) {
base := Base{
AuthenticatedAPISupport: false,
}
if base.GetAuthenticatedAPISupport() {
t.Fatal("Test failed. TestGetAuthenticatedAPISupport returned true when it should of been false.")
}
}
func TestGetName(t *testing.T) {
GetName := ExchangeBase{
GetName := Base{
Name: "TESTNAME",
}
@@ -18,20 +166,230 @@ func TestGetName(t *testing.T) {
}
func TestGetEnabledCurrencies(t *testing.T) {
enabledPairs := []string{"BTCUSD", "BTCAUD", "LTCUSD", "LTCAUD"}
GetEnabledCurrencies := ExchangeBase{
Name: "TESTNAME",
EnabledPairs: enabledPairs,
b := Base{
Name: "TESTNAME",
}
enCurr := GetEnabledCurrencies.GetEnabledCurrencies()
if enCurr[0] != "BTCUSD" {
t.Error("Test Failed - Exchange GetEnabledCurrencies() incorrect string")
b.EnabledPairs = []string{"BTC-USD"}
format := config.CurrencyPairFormatConfig{
Delimiter: "-",
Index: "",
}
b.RequestCurrencyPairFormat = format
b.ConfigCurrencyPairFormat = format
c := b.GetEnabledCurrencies()
if c[0].Pair().String() != "BTC-USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
format.Delimiter = "~"
b.RequestCurrencyPairFormat = format
c = b.GetEnabledCurrencies()
if c[0].Pair().String() != "BTC-USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
format.Delimiter = ""
b.ConfigCurrencyPairFormat = format
c = b.GetEnabledCurrencies()
if c[0].Pair().String() != "BTC-USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.EnabledPairs = []string{"BTCDOGE"}
format.Index = "BTC"
b.ConfigCurrencyPairFormat = format
c = b.GetEnabledCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.EnabledPairs = []string{"BTC_USD"}
b.RequestCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Delimiter = "_"
c = b.GetEnabledCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.EnabledPairs = []string{"BTCDOGE"}
b.RequestCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Index = "BTC"
c = b.GetEnabledCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.EnabledPairs = []string{"BTCUSD"}
b.ConfigCurrencyPairFormat.Index = ""
c = b.GetEnabledCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
}
func TestGetAvailableCurrencies(t *testing.T) {
b := Base{
Name: "TESTNAME",
}
b.AvailablePairs = []string{"BTC-USD"}
format := config.CurrencyPairFormatConfig{
Delimiter: "-",
Index: "",
}
b.RequestCurrencyPairFormat = format
b.ConfigCurrencyPairFormat = format
c := b.GetAvailableCurrencies()
if c[0].Pair().String() != "BTC-USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
format.Delimiter = "~"
b.RequestCurrencyPairFormat = format
c = b.GetAvailableCurrencies()
if c[0].Pair().String() != "BTC-USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
format.Delimiter = ""
b.ConfigCurrencyPairFormat = format
c = b.GetAvailableCurrencies()
if c[0].Pair().String() != "BTC-USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.AvailablePairs = []string{"BTCDOGE"}
format.Index = "BTC"
b.ConfigCurrencyPairFormat = format
c = b.GetAvailableCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.AvailablePairs = []string{"BTC_USD"}
b.RequestCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Delimiter = "_"
c = b.GetAvailableCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.AvailablePairs = []string{"BTCDOGE"}
b.RequestCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Delimiter = ""
b.ConfigCurrencyPairFormat.Index = "BTC"
c = b.GetAvailableCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "DOGE" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
b.AvailablePairs = []string{"BTCUSD"}
b.ConfigCurrencyPairFormat.Index = ""
c = b.GetAvailableCurrencies()
if c[0].FirstCurrency.String() != "BTC" && c[0].SecondCurrency.String() != "USD" {
t.Error("Test Failed - Exchange GetAvailableCurrencies() incorrect string")
}
}
func TestGetExchangeFormatCurrencySeperator(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatalf("Failed to load config file. Error: %s", err)
}
expected := true
actual := GetExchangeFormatCurrencySeperator("BTCE")
if expected != actual {
t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v",
expected, actual)
}
expected = false
actual = GetExchangeFormatCurrencySeperator("LocalBitcoins")
if expected != actual {
t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v",
expected, actual)
}
expected = false
actual = GetExchangeFormatCurrencySeperator("blah")
if expected != actual {
t.Errorf("Test failed - TestGetExchangeFormatCurrencySeperator expected %v != actual %v",
expected, actual)
}
}
func TestGetAndFormatExchangeCurrencies(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatalf("Failed to load config file. Error: %s", err)
}
var pairs []pair.CurrencyPair
pairs = append(pairs, pair.NewCurrencyPairDelimiter("BTC_USD", "_"))
pairs = append(pairs, pair.NewCurrencyPairDelimiter("LTC_BTC", "_"))
actual, err := GetAndFormatExchangeCurrencies("Liqui", pairs)
if err != nil {
t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies error %s", err)
}
expected := pair.CurrencyItem("btc_usd-ltc_btc")
if actual.String() != expected.String() {
t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies %s != %s",
actual, expected)
}
_, err = GetAndFormatExchangeCurrencies("non-existant", pairs)
if err == nil {
t.Errorf("Test failed - Exchange TestGetAndFormatExchangeCurrencies returned nil error on non-existant exchange")
}
}
func TestFormatExchangeCurrency(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatalf("Failed to load config file. Error: %s", err)
}
pair := pair.NewCurrencyPair("BTC", "USD")
expected := "BTC-USD"
actual := FormatExchangeCurrency("GDAX", pair)
if actual.String() != expected {
t.Errorf("Test failed - Exchange TestFormatExchangeCurrency %s != %s",
actual, expected)
}
}
func TestFormatCurrency(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatalf("Failed to load config file. Error: %s", err)
}
currency := pair.NewCurrencyPair("btc", "usd")
expected := "BTC-USD"
actual := FormatCurrency(currency).String()
if actual != expected {
t.Errorf("Test failed - Exchange TestFormatCurrency %s != %s",
actual, expected)
}
}
func TestSetEnabled(t *testing.T) {
SetEnabled := ExchangeBase{
SetEnabled := Base{
Name: "TESTNAME",
Enabled: false,
}
@@ -43,7 +401,7 @@ func TestSetEnabled(t *testing.T) {
}
func TestIsEnabled(t *testing.T) {
IsEnabled := ExchangeBase{
IsEnabled := Base{
Name: "TESTNAME",
Enabled: false,
}
@@ -54,33 +412,99 @@ func TestIsEnabled(t *testing.T) {
}
func TestSetAPIKeys(t *testing.T) {
SetAPIKeys := ExchangeBase{
Name: "TESTNAME",
Enabled: false,
SetAPIKeys := Base{
Name: "TESTNAME",
Enabled: false,
AuthenticatedAPISupport: false,
}
SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007", false)
if SetAPIKeys.APIKey != "" && SetAPIKeys.APISecret != "" && SetAPIKeys.ClientID != "" {
t.Error("Test Failed - SetAPIKeys() set values without authenticated API support enabled")
}
SetAPIKeys.AuthenticatedAPISupport = true
SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007", false)
if SetAPIKeys.APIKey != "RocketMan" && SetAPIKeys.APISecret != "Digereedoo" && SetAPIKeys.ClientID != "007" {
t.Error("Test Failed - Exchange SetAPIKeys() did not set correct values")
}
SetAPIKeys.SetAPIKeys("RocketMan", "Digereedoo", "007", true)
}
func TestUpdateEnabledCurrencies(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Fatal("Test failed. TestUpdateEnabledCurrencies failed to load config")
}
UAC := Base{Name: "ANX"}
exchangeProducts := []string{"ltc", "btc", "usd", "aud"}
// Test updating exchange products for an exchange which doesn't exist
UAC.Name = "Blah"
err = UAC.UpdateEnabledCurrencies(exchangeProducts, false)
if err == nil {
t.Errorf("Test Failed - Exchange TestUpdateEnabledCurrencies succeeded on an exchange which doesn't exist")
}
// Test updating exchange products
UAC.Name = "ANX"
err = UAC.UpdateEnabledCurrencies(exchangeProducts, false)
if err != nil {
t.Errorf("Test Failed - Exchange TestUpdateEnabledCurrencies error: %s", err)
}
// Test updating the same new products, diff should be 0
UAC.Name = "ANX"
err = UAC.UpdateEnabledCurrencies(exchangeProducts, false)
if err != nil {
t.Errorf("Test Failed - Exchange TestUpdateEnabledCurrencies error: %s", err)
}
// Test force updating to only one product
exchangeProducts = []string{"btc"}
err = UAC.UpdateEnabledCurrencies(exchangeProducts, true)
if err != nil {
t.Errorf("Test Failed - Forced Exchange TestUpdateEnabledCurrencies error: %s", err)
}
}
func TestUpdateAvailableCurrencies(t *testing.T) {
cfg := config.GetConfig()
err := cfg.LoadConfig(config.CONFIG_TEST_FILE)
err := cfg.LoadConfig(config.ConfigTestFile)
if err != nil {
t.Log("SOMETHING DONE HAPPENED!")
t.Fatal("Test failed. TestUpdateAvailableCurrencies failed to load config")
}
UAC := ExchangeBase{
Name: "ANX",
}
UAC := Base{Name: "ANX"}
exchangeProducts := []string{"ltc", "btc", "usd", "aud"}
err2 := UAC.UpdateAvailableCurrencies(exchangeProducts)
if err2 != nil {
t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() error: %s", err2)
// Test updating exchange products for an exchange which doesn't exist
UAC.Name = "Blah"
err = UAC.UpdateAvailableCurrencies(exchangeProducts, false)
if err == nil {
t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() succeeded on an exchange which doesn't exist")
}
// Test updating exchange products
UAC.Name = "ANX"
err = UAC.UpdateAvailableCurrencies(exchangeProducts, false)
if err != nil {
t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() error: %s", err)
}
// Test updating the same new products, diff should be 0
UAC.Name = "ANX"
err = UAC.UpdateAvailableCurrencies(exchangeProducts, false)
if err != nil {
t.Errorf("Test Failed - Exchange UpdateAvailableCurrencies() error: %s", err)
}
// Test force updating to only one product
exchangeProducts = []string{"btc"}
err = UAC.UpdateAvailableCurrencies(exchangeProducts, true)
if err != nil {
t.Errorf("Test Failed - Forced Exchange UpdateAvailableCurrencies() error: %s", err)
}
}

View File

@@ -12,11 +12,12 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
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"
@@ -34,7 +35,7 @@ const (
)
type GDAX struct {
exchange.ExchangeBase
exchange.Base
}
func (g *GDAX) SetDefaults() {
@@ -46,6 +47,11 @@ func (g *GDAX) SetDefaults() {
g.Verbose = false
g.Websocket = false
g.RESTPollingDelay = 10
g.RequestCurrencyPairFormat.Delimiter = "-"
g.RequestCurrencyPairFormat.Uppercase = true
g.ConfigCurrencyPairFormat.Delimiter = ""
g.ConfigCurrencyPairFormat.Uppercase = true
g.AssetTypes = []string{ticker.Spot}
}
func (g *GDAX) Setup(exch config.ExchangeConfig) {
@@ -61,6 +67,14 @@ func (g *GDAX) Setup(exch config.ExchangeConfig) {
g.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
g.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
g.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := g.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = g.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -370,7 +384,15 @@ func (g *GDAX) GetReportStatus(reportID string) (GDAXReportResponse, error) {
}
func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
if !g.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name)
}
if g.Nonce.Get() == 0 {
g.Nonce.Set(time.Now().Unix())
} else {
g.Nonce.Inc()
}
payload := []byte("")
@@ -386,11 +408,11 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri
}
}
message := timestamp + method + "/" + path + string(payload)
hmac := common.GetHMAC(common.HASH_SHA256, []byte(message), []byte(g.APISecret))
message := g.Nonce.String() + method + "/" + path + string(payload)
hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(g.APISecret))
headers := make(map[string]string)
headers["CB-ACCESS-SIGN"] = common.Base64Encode([]byte(hmac))
headers["CB-ACCESS-TIMESTAMP"] = timestamp
headers["CB-ACCESS-TIMESTAMP"] = g.Nonce.String()
headers["CB-ACCESS-KEY"] = g.APIKey
headers["CB-ACCESS-PASSPHRASE"] = g.ClientID
headers["Content-Type"] = "application/json"
@@ -398,13 +420,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

View File

@@ -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"`
}

View File

@@ -2,20 +2,20 @@ package gdax
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the GDAX go routine
func (g *GDAX) Start() {
go g.Run()
}
// Run implements the GDAX wrapper
func (g *GDAX) Run() {
if g.Verbose {
log.Printf("%s Websocket: %s. (url: %s).\n", g.GetName(), common.IsEnabled(g.Websocket), GDAX_WEBSOCKET_URL)
@@ -37,41 +37,24 @@ func (g *GDAX) Run() {
currencies = append(currencies, x.ID[0:3]+x.ID[4:])
}
}
err = g.UpdateAvailableCurrencies(currencies)
err = g.UpdateAvailableCurrencies(currencies, false)
if err != nil {
log.Printf("%s Failed to get config.\n", g.GetName())
}
}
for g.Enabled {
for _, x := range g.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
currency.Delimiter = "-"
go func() {
ticker, err := g.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("GDAX %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(g.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * g.RESTPollingDelay)
}
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the GDAX exchange
func (e *GDAX) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetAccounts()
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// GDAX exchange
func (g *GDAX) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = g.GetName()
accountBalance, err := g.GetAccounts()
if err != nil {
return response, err
}
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Available
exchangeCurrency.Hold = accountBalance[i].Hold
@@ -81,22 +64,18 @@ func (e *GDAX) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
return response, nil
}
func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(g.GetName(), p)
if err == nil {
return tickerNew, nil
// UpdateTicker updates and returns the ticker for a currency pair
func (g *GDAX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := g.GetTicker(exchange.FormatExchangeCurrency(g.Name, p).String())
if err != nil {
return ticker.Price{}, err
}
var tickerPrice ticker.TickerPrice
tick, err := g.GetTicker(p.Pair().String())
if err != nil {
return ticker.TickerPrice{}, err
}
stats, err := g.GetStats(p.Pair().String())
stats, err := g.GetStats(exchange.FormatExchangeCurrency(g.Name, p).String())
if err != nil {
return ticker.TickerPrice{}, err
return ticker.Price{}, err
}
tickerPrice.Pair = p
@@ -104,32 +83,46 @@ func (g *GDAX) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerPrice.Last = tick.Price
tickerPrice.High = stats.High
tickerPrice.Low = stats.Low
ticker.ProcessTicker(g.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(g.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(g.Name, p, assetType)
}
func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(g.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (g *GDAX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType)
if err != nil {
return g.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := g.GetOrderbook(p.Pair().String(), 2)
// GetOrderbookEx returns orderbook base on the currency pair
func (g *GDAX) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(g.GetName(), p, assetType)
if err == nil {
return g.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (g *GDAX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := g.GetOrderbook(exchange.FormatExchangeCurrency(g.Name, p).String(), 2)
if err != nil {
return orderBook, err
}
obNew := orderbookNew.(GDAXOrderbookL1L2)
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.Bids {
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price})
}
for x, _ := range obNew.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price})
for x := range obNew.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: obNew.Bids[x].Amount, Price: obNew.Bids[x].Price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(g.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(g.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(g.Name, p, assetType)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -36,7 +37,7 @@ const (
)
type Gemini struct {
exchange.ExchangeBase
exchange.Base
}
func (g *Gemini) SetDefaults() {
@@ -45,6 +46,11 @@ func (g *Gemini) SetDefaults() {
g.Verbose = false
g.Websocket = false
g.RESTPollingDelay = 10
g.RequestCurrencyPairFormat.Delimiter = ""
g.RequestCurrencyPairFormat.Uppercase = true
g.ConfigCurrencyPairFormat.Delimiter = ""
g.ConfigCurrencyPairFormat.Uppercase = true
g.AssetTypes = []string{ticker.Spot}
}
func (g *Gemini) Setup(exch config.ExchangeConfig) {
@@ -60,6 +66,14 @@ func (g *Gemini) Setup(exch config.ExchangeConfig) {
g.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
g.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
g.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := g.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = g.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -245,9 +259,19 @@ func (g *Gemini) PostHeartbeat() (bool, error) {
}
func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[string]interface{}, result interface{}) (err error) {
if !g.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, g.Name)
}
if g.Nonce.Get() == 0 {
g.Nonce.Set(time.Now().UnixNano())
} else {
g.Nonce.Inc()
}
request := make(map[string]interface{})
request["request"] = fmt.Sprintf("/v%s/%s", GEMINI_API_VERSION, path)
request["nonce"] = time.Now().UnixNano()
request["nonce"] = g.Nonce.Get()
if params != nil {
for key, value := range params {
@@ -255,18 +279,18 @@ func (g *Gemini) SendAuthenticatedHTTPRequest(method, path string, params map[st
}
}
PayloadJson, err := common.JSONEncode(request)
PayloadJSON, err := common.JSONEncode(request)
if err != nil {
return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON request")
}
if g.Verbose {
log.Printf("Request JSON: %s\n", PayloadJson)
log.Printf("Request JSON: %s\n", PayloadJSON)
}
PayloadBase64 := common.Base64Encode(PayloadJson)
hmac := common.GetHMAC(common.HASH_SHA512_384, []byte(PayloadBase64), []byte(g.APISecret))
PayloadBase64 := common.Base64Encode(PayloadJSON)
hmac := common.GetHMAC(common.HashSHA512_384, []byte(PayloadBase64), []byte(g.APISecret))
headers := make(map[string]string)
headers["X-GEMINI-APIKEY"] = g.APIKey
headers["X-GEMINI-PAYLOAD"] = PayloadBase64
@@ -275,13 +299,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

View File

@@ -3,19 +3,19 @@ package gemini
import (
"log"
"net/url"
"time"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the Gemini go routine
func (g *Gemini) Start() {
go g.Run()
}
// Run implements the Gemini wrapper
func (g *Gemini) Run() {
if g.Verbose {
log.Printf("%s polling delay: %ds.\n", g.GetName(), g.RESTPollingDelay)
@@ -26,55 +26,35 @@ func (g *Gemini) Run() {
if err != nil {
log.Printf("%s Failed to get available symbols.\n", g.GetName())
} else {
err = g.UpdateAvailableCurrencies(exchangeProducts)
err = g.UpdateAvailableCurrencies(exchangeProducts, false)
if err != nil {
log.Printf("%s Failed to get config.\n", g.GetName())
}
}
for g.Enabled {
for _, x := range g.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := g.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("Gemini %s Last %f Bid %f Ask %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.Bid, ticker.Ask, ticker.Volume)
stats.AddExchangeInfo(g.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * g.RESTPollingDelay)
}
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Gemini exchange
func (e *Gemini) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetBalances()
// GetExchangeAccountInfo Retrieves balances for all enabled currencies for the
// Gemini exchange
func (g *Gemini) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = g.GetName()
accountBalance, err := g.GetBalances()
if err != nil {
return response, err
}
for i := 0; i < len(accountBalance); i++ {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = accountBalance[i].Currency
exchangeCurrency.TotalValue = accountBalance[i].Amount
exchangeCurrency.Hold = accountBalance[i].Available
response.Currencies = append(response.Currencies, exchangeCurrency)
}
return response, nil
}
func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(g.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (g *Gemini) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := g.GetTicker(p.Pair().String())
if err != nil {
return tickerPrice, err
@@ -84,31 +64,44 @@ func (g *Gemini) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error)
tickerPrice.Bid = tick.Bid
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Volume.USD
ticker.ProcessTicker(g.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(g.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(g.Name, p, assetType)
}
func (g *Gemini) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(g.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (g *Gemini) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(g.GetName(), p, assetType)
if err != nil {
return g.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
// GetOrderbookEx returns orderbook base on the currency pair
func (g *Gemini) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(g.GetName(), p, assetType)
if err == nil {
return g.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (g *Gemini) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := g.GetOrderbook(p.Pair().String(), url.Values{})
if err != nil {
return orderBook, err
}
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.Bids {
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price})
}
for x, _ := range orderbookNew.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price})
for x := range orderbookNew.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(g.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(g.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(g.Name, p, assetType)
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -19,7 +20,7 @@ const (
)
type HUOBI struct {
exchange.ExchangeBase
exchange.Base
}
func (h *HUOBI) SetDefaults() {
@@ -29,6 +30,11 @@ func (h *HUOBI) SetDefaults() {
h.Verbose = false
h.Websocket = false
h.RESTPollingDelay = 10
h.RequestCurrencyPairFormat.Delimiter = ""
h.RequestCurrencyPairFormat.Uppercase = false
h.ConfigCurrencyPairFormat.Delimiter = ""
h.ConfigCurrencyPairFormat.Uppercase = true
h.AssetTypes = []string{ticker.Spot}
}
func (h *HUOBI) Setup(exch config.ExchangeConfig) {
@@ -44,6 +50,14 @@ func (h *HUOBI) Setup(exch config.ExchangeConfig) {
h.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
h.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
h.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := h.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = h.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -53,7 +67,7 @@ func (h *HUOBI) GetFee() float64 {
func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) {
resp := HuobiTickerResponse{}
path := fmt.Sprintf("http://api.huobi.com/staticmarket/ticker_%s_json.js", symbol)
path := fmt.Sprintf("https://api.huobi.com/staticmarket/ticker_%s_json.js", symbol)
err := common.SendHTTPGetRequest(path, true, &resp)
if err != nil {
@@ -63,7 +77,7 @@ func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) {
}
func (h *HUOBI) GetOrderBook(symbol string) (HuobiOrderbook, error) {
path := fmt.Sprintf("http://api.huobi.com/staticmarket/depth_%s_json.js", symbol)
path := fmt.Sprintf("https://api.huobi.com/staticmarket/depth_%s_json.js", symbol)
resp := HuobiOrderbook{}
err := common.SendHTTPGetRequest(path, true, &resp)
if err != nil {
@@ -177,6 +191,10 @@ func (h *HUOBI) GetOrderIDByTradeID(coinType, orderID int) {
}
func (h *HUOBI) SendAuthenticatedRequest(method string, v url.Values) error {
if !h.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name)
}
v.Set("access_key", h.APIKey)
v.Set("created", strconv.FormatInt(time.Now().Unix(), 10))
v.Set("method", method)
@@ -198,7 +216,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

View File

@@ -2,21 +2,20 @@ package huobi
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the HUOBI go routine
func (h *HUOBI) Start() {
go h.Run()
}
// Run implements the HUOBI wrapper
func (h *HUOBI) Run() {
if h.Verbose {
log.Printf("%s Websocket: %s (url: %s).\n", h.GetName(), common.IsEnabled(h.Websocket), HUOBI_SOCKETIO_ADDRESS)
@@ -27,35 +26,11 @@ func (h *HUOBI) Run() {
if h.Websocket {
go h.WebsocketClient()
}
for h.Enabled {
for _, x := range h.EnabledPairs {
curr := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := h.GetTickerPrice(curr)
if err != nil {
log.Println(err)
return
}
HuobiLastUSD, _ := currency.ConvertCurrency(ticker.Last, "CNY", "USD")
HuobiHighUSD, _ := currency.ConvertCurrency(ticker.High, "CNY", "USD")
HuobiLowUSD, _ := currency.ConvertCurrency(ticker.Low, "CNY", "USD")
log.Printf("Huobi %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", curr.Pair().String(), HuobiLastUSD, ticker.Last, HuobiHighUSD, ticker.High, HuobiLowUSD, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(h.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
stats.AddExchangeInfo(h.GetName(), curr.GetFirstCurrency().String(), "USD", HuobiLastUSD, ticker.Volume)
}()
}
time.Sleep(time.Second * h.RESTPollingDelay)
}
}
func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(h.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (h *HUOBI) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := h.GetTicker(p.GetFirstCurrency().Lower().String())
if err != nil {
return tickerPrice, err
@@ -67,40 +42,54 @@ func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error)
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Vol
tickerPrice.High = tick.High
ticker.ProcessTicker(h.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(h.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(h.Name, p, assetType)
}
func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(h.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (h *HUOBI) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(h.GetName(), p, assetType)
if err != nil {
return h.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
// GetOrderbookEx returns orderbook base on the currency pair
func (h *HUOBI) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(h.GetName(), p, assetType)
if err == nil {
return h.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (h *HUOBI) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := h.GetOrderBook(p.GetFirstCurrency().Lower().String())
if err != nil {
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]})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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]})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(h.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(h.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(h.Name, p, assetType)
}
//TODO: retrieve HUOBI balance info
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the HUOBI exchange
func (e *HUOBI) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
//GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// HUOBI exchange - to-do
func (h *HUOBI) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = h.GetName()
return response, nil
}

View File

@@ -3,6 +3,7 @@ package itbit
import (
"bytes"
"errors"
"fmt"
"log"
"net/url"
"strconv"
@@ -11,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -19,7 +21,7 @@ const (
)
type ItBit struct {
exchange.ExchangeBase
exchange.Base
}
func (i *ItBit) SetDefaults() {
@@ -30,6 +32,11 @@ func (i *ItBit) SetDefaults() {
i.Verbose = false
i.Websocket = false
i.RESTPollingDelay = 10
i.RequestCurrencyPairFormat.Delimiter = ""
i.RequestCurrencyPairFormat.Uppercase = true
i.ConfigCurrencyPairFormat.Delimiter = ""
i.ConfigCurrencyPairFormat.Uppercase = true
i.AssetTypes = []string{ticker.Spot}
}
func (i *ItBit) Setup(exch config.ExchangeConfig) {
@@ -45,33 +52,40 @@ func (i *ItBit) Setup(exch config.ExchangeConfig) {
i.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
i.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
i.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := i.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = i.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
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
}
@@ -227,14 +241,16 @@ func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount
}
func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}) (err error) {
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13]
nonce, err := strconv.Atoi(timestamp)
if err != nil {
return err
if !i.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, i.Name)
}
if i.Nonce.Get() == 0 {
i.Nonce.Set(time.Now().UnixNano())
} else {
i.Nonce.Inc()
}
nonce -= 1
request := make(map[string]interface{})
url := ITBIT_API_URL + path
@@ -244,41 +260,40 @@ 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), i.Nonce.String(), i.Nonce.String()[0:13]})
if err != nil {
log.Println(err)
return
}
hash := common.GetSHA256([]byte(nonceStr + string(message)))
hmac := common.GetHMAC(common.HASH_SHA512, []byte(url+string(hash)), []byte(i.APISecret))
hash := common.GetSHA256([]byte(i.Nonce.String() + string(message)))
hmac := common.GetHMAC(common.HashSHA512, []byte(url+string(hash)), []byte(i.APISecret))
signature := common.Base64Encode(hmac)
headers := make(map[string]string)
headers["Authorization"] = i.ClientID + ":" + signature
headers["X-Auth-Timestamp"] = timestamp
headers["X-Auth-Nonce"] = nonceStr
headers["X-Auth-Timestamp"] = i.Nonce.String()[0:13]
headers["X-Auth-Nonce"] = i.Nonce.String()
headers["Content-Type"] = "application/json"
resp, err := common.SendHTTPRequest(method, url, headers, bytes.NewBuffer([]byte(PayloadJson)))
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
}

View File

@@ -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"`
}

View File

@@ -3,49 +3,31 @@ package itbit
import (
"log"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the ItBit go routine
func (i *ItBit) Start() {
go i.Run()
}
// Run implements the ItBit wrapper
func (i *ItBit) Run() {
if i.Verbose {
log.Printf("%s polling delay: %ds.\n", i.GetName(), i.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", i.GetName(), len(i.EnabledPairs), i.EnabledPairs)
}
for i.Enabled {
for _, x := range i.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
go func() {
ticker, err := i.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("ItBit %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(i.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * i.RESTPollingDelay)
}
}
func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(i.GetName(), p)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
tick, err := i.GetTicker(p.Pair().String())
// UpdateTicker updates and returns the ticker for a currency pair
func (i *ItBit) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := i.GetTicker(exchange.FormatExchangeCurrency(i.Name,
p).String())
if err != nil {
return tickerPrice, err
}
@@ -57,23 +39,38 @@ func (i *ItBit) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error)
tickerPrice.High = tick.High24h
tickerPrice.Low = tick.Low24h
tickerPrice.Volume = tick.Volume24h
ticker.ProcessTicker(i.GetName(), p, tickerPrice)
return tickerPrice, nil
ticker.ProcessTicker(i.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(i.Name, p, assetType)
}
func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(i.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (i *ItBit) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(i.GetName(), p, assetType)
if err != nil {
return i.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := i.GetOrderbook(p.Pair().String())
// GetOrderbookEx returns orderbook base on the currency pair
func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(i.GetName(), p, assetType)
if err == nil {
return i.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (i *ItBit) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := i.GetOrderbook(exchange.FormatExchangeCurrency(i.Name,
p).String())
if err != nil {
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 {
@@ -83,10 +80,10 @@ func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er
if err != nil {
log.Println(err)
}
orderBook.Bids = append(orderBook.Bids, orderbook.OrderbookItem{Amount: amount, Price: price})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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 {
@@ -96,17 +93,17 @@ func (i *ItBit) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, er
if err != nil {
log.Println(err)
}
orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: amount, Price: price})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: amount, Price: price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(i.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(i.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(i.Name, p, assetType)
}
//TODO Get current holdings from ItBit
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the ItBit exchange
func (e *ItBit) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
//ItBit exchange - to-do
func (i *ItBit) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = i.GetName()
return response, nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -41,7 +42,7 @@ const (
)
type Kraken struct {
exchange.ExchangeBase
exchange.Base
CryptoFee, FiatFee float64
Ticker map[string]KrakenTicker
}
@@ -55,6 +56,12 @@ func (k *Kraken) SetDefaults() {
k.Websocket = false
k.RESTPollingDelay = 10
k.Ticker = make(map[string]KrakenTicker)
k.RequestCurrencyPairFormat.Delimiter = ""
k.RequestCurrencyPairFormat.Uppercase = true
k.RequestCurrencyPairFormat.Separator = ","
k.ConfigCurrencyPairFormat.Delimiter = ""
k.ConfigCurrencyPairFormat.Uppercase = true
k.AssetTypes = []string{ticker.Spot}
}
func (k *Kraken) Setup(exch config.ExchangeConfig) {
@@ -70,6 +77,14 @@ func (k *Kraken) Setup(exch config.ExchangeConfig) {
k.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
k.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
k.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := k.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = k.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -178,20 +193,62 @@ func (k *Kraken) GetOHLC(symbol string) error {
return nil
}
func (k *Kraken) GetDepth(symbol string) error {
// GetDepth returns the orderbook for a particular currency
func (k *Kraken) GetDepth(symbol string) (Orderbook, error) {
values := url.Values{}
values.Set("pair", symbol)
var result interface{}
var ob Orderbook
path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_DEPTH, values.Encode())
err := common.SendHTTPGetRequest(path, true, &result)
if err != nil {
return err
return ob, err
}
log.Println(result)
return nil
data := result.(map[string]interface{})
orderbookData := data["result"].(map[string]interface{})
var bidsData []interface{}
var asksData []interface{}
for _, y := range orderbookData {
yData := y.(map[string]interface{})
bidsData = yData["bids"].([]interface{})
asksData = yData["asks"].([]interface{})
}
processOrderbook := func(data []interface{}) ([]OrderbookBase, error) {
var result []OrderbookBase
for x := range data {
entry := data[x].([]interface{})
price, err := strconv.ParseFloat(entry[0].(string), 64)
if err != nil {
return nil, err
}
amount, err := strconv.ParseFloat(entry[1].(string), 64)
if err != nil {
return nil, err
}
result = append(result, OrderbookBase{Price: price, Amount: amount})
}
return result, nil
}
ob.Bids, err = processOrderbook(bidsData)
if err != nil {
return ob, err
}
ob.Asks, err = processOrderbook(asksData)
if err != nil {
return ob, err
}
return ob, nil
}
func (k *Kraken) GetTrades(symbol string) error {
@@ -509,8 +566,18 @@ func (k *Kraken) CancelOrder(orderID int64) {
}
func (k *Kraken) SendAuthenticatedHTTPRequest(method string, values url.Values) (interface{}, error) {
if !k.AuthenticatedAPISupport {
return nil, fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, k.Name)
}
path := fmt.Sprintf("/%s/private/%s", KRAKEN_API_VERSION, method)
values.Set("nonce", strconv.FormatInt(time.Now().UnixNano(), 10))
if k.Nonce.Get() == 0 {
k.Nonce.Set(time.Now().UnixNano())
} else {
k.Nonce.Inc()
}
values.Set("nonce", k.Nonce.String())
secret, err := common.Base64Decode(k.APISecret)
if err != nil {
@@ -518,7 +585,7 @@ func (k *Kraken) SendAuthenticatedHTTPRequest(method string, values url.Values)
}
shasum := common.GetSHA256([]byte(values.Get("nonce") + values.Encode()))
signature := common.Base64Encode(common.GetHMAC(common.HASH_SHA512, append([]byte(path), shasum...), secret))
signature := common.Base64Encode(common.GetHMAC(common.HashSHA512, append([]byte(path), shasum...), secret))
if k.Verbose {
log.Printf("Sending POST request to %s, path: %s.", KRAKEN_API_URL, path)
@@ -535,7 +602,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

View File

@@ -31,6 +31,18 @@ type KrakenTicker struct {
Open float64
}
// OrderbookBase stores the orderbook price and amount data
type OrderbookBase struct {
Price float64
Amount float64
}
// Orderbook stores the bids and asks orderbook data
type Orderbook struct {
Bids []OrderbookBase
Asks []OrderbookBase
}
type KrakenTickerResponse struct {
Ask []string `json:"a"`
Bid []string `json:"b"`

View File

@@ -2,20 +2,19 @@ package kraken
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the Kraken go routine
func (k *Kraken) Start() {
go k.Run()
}
// Run implements the Kraken wrapper
func (k *Kraken) Run() {
if k.Verbose {
log.Printf("%s polling delay: %ds.\n", k.GetName(), k.RESTPollingDelay)
@@ -30,50 +29,87 @@ func (k *Kraken) Run() {
for _, v := range assetPairs {
exchangeProducts = append(exchangeProducts, v.Altname)
}
err = k.UpdateAvailableCurrencies(exchangeProducts)
err = k.UpdateAvailableCurrencies(exchangeProducts, false)
if err != nil {
log.Printf("%s Failed to get config.\n", k.GetName())
}
}
}
for k.Enabled {
err := k.GetTicker(common.JoinStrings(k.EnabledPairs, ","))
if err != nil {
log.Println(err)
} else {
for _, x := range k.EnabledPairs {
ticker := k.Ticker[x]
log.Printf("Kraken %s Last %f High %f Low %f Volume %f\n", x, ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(k.GetName(), x[0:3], x[3:], ticker.Last, ticker.Volume)
}
}
time.Sleep(time.Second * k.RESTPollingDelay)
// UpdateTicker updates and returns the ticker for a currency pair
func (k *Kraken) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
pairs := k.GetEnabledCurrencies()
pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(k.Name, pairs)
if err != nil {
return tickerPrice, err
}
err = k.GetTicker(pairsCollated.String())
if err != nil {
return tickerPrice, err
}
}
//This will return the TickerPrice struct when tickers are completed here..
func (k *Kraken) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
var tickerPrice ticker.TickerPrice
/*
ticker, err := i.GetTicker(currency)
if err != nil {
log.Println(err)
return tickerPrice
for _, x := range pairs {
var tp ticker.Price
tick, ok := k.Ticker[x.Pair().String()]
if !ok {
continue
}
tickerPrice.Ask = ticker.Ask
tickerPrice.Bid = ticker.Bid
*/
return tickerPrice, nil
tp.Pair = x
tp.Last = tick.Last
tp.Ask = tick.Ask
tp.Bid = tick.Bid
tp.High = tick.High
tp.Low = tick.Low
tp.Volume = tick.Volume
ticker.ProcessTicker(k.GetName(), x, tp, assetType)
}
return ticker.GetTicker(k.GetName(), p, assetType)
}
func (k *Kraken) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
return orderbook.OrderbookBase{}, nil
// GetTickerPrice returns the ticker for a currency pair
func (k *Kraken) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(k.GetName(), p, assetType)
if err != nil {
return k.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
//TODO: Retrieve Kraken info
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Kraken exchange
func (e *Kraken) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
// GetOrderbookEx returns orderbook base on the currency pair
func (k *Kraken) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(k.GetName(), p, assetType)
if err == nil {
return k.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (k *Kraken) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := k.GetDepth(exchange.FormatExchangeCurrency(k.GetName(), p).String())
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Bids {
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price})
}
for x := range orderbookNew.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price})
}
orderbook.ProcessOrderbook(k.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(k.Name, p, assetType)
}
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// Kraken exchange - to-do
func (k *Kraken) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = k.GetName()
return response, nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -31,7 +32,7 @@ const (
)
type LakeBTC struct {
exchange.ExchangeBase
exchange.Base
}
func (l *LakeBTC) SetDefaults() {
@@ -42,6 +43,11 @@ func (l *LakeBTC) SetDefaults() {
l.Verbose = false
l.Websocket = false
l.RESTPollingDelay = 10
l.RequestCurrencyPairFormat.Delimiter = ""
l.RequestCurrencyPairFormat.Uppercase = true
l.ConfigCurrencyPairFormat.Delimiter = ""
l.ConfigCurrencyPairFormat.Uppercase = true
l.AssetTypes = []string{ticker.Spot}
}
func (l *LakeBTC) Setup(exch config.ExchangeConfig) {
@@ -57,6 +63,14 @@ func (l *LakeBTC) Setup(exch config.ExchangeConfig) {
l.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
l.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
l.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := l.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = l.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -272,9 +286,18 @@ func (l *LakeBTC) CreateWithdraw(amount float64, accountID int64) (LakeBTCWithdr
}
func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result interface{}) (err error) {
nonce := strconv.FormatInt(time.Now().UnixNano(), 10)
req := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s&params=%s", nonce, l.APIKey, method, params)
hmac := common.GetHMAC(common.HASH_SHA1, []byte(req), []byte(l.APISecret))
if !l.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name)
}
if l.Nonce.Get() == 0 {
l.Nonce.Set(time.Now().UnixNano())
} else {
l.Nonce.Inc()
}
req := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s&params=%s", l.Nonce.String(), l.APIKey, method, params)
hmac := common.GetHMAC(common.HashSHA1, []byte(req), []byte(l.APISecret))
if l.Verbose {
log.Printf("Sending POST request to %s calling method %s with params %s\n", LAKEBTC_API_URL, method, req)
@@ -291,7 +314,7 @@ func (l *LakeBTC) SendAuthenticatedHTTPRequest(method, params string, result int
}
headers := make(map[string]string)
headers["Json-Rpc-Tonce"] = nonce
headers["Json-Rpc-Tonce"] = l.Nonce.String()
headers["Authorization"] = "Basic " + common.Base64Encode([]byte(l.APIKey+":"+common.HexEncodeToString(hmac)))
headers["Content-Type"] = "application/json-rpc"
@@ -301,7 +324,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 +334,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 +344,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

View File

@@ -3,95 +3,91 @@ package lakebtc
import (
"log"
"strconv"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the LakeBTC go routine
func (l *LakeBTC) Start() {
go l.Run()
}
// Run implements the LakeBTC wrapper
func (l *LakeBTC) Run() {
if l.Verbose {
log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs)
}
for l.Enabled {
for _, x := range l.EnabledPairs {
currency := pair.NewCurrencyPair(x[0:3], x[3:])
ticker, err := l.GetTickerPrice(currency)
if err != nil {
log.Println(err)
continue
}
log.Printf("LakeBTC BTC %s: Last %f High %f Low %f Volume %f\n", x[3:], ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}
time.Sleep(time.Second * l.RESTPollingDelay)
}
}
func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(l.GetName(), p)
if err == nil {
return tickerNew, nil
}
// UpdateTicker updates and returns the ticker for a currency pair
func (l *LakeBTC) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tick, err := l.GetTicker()
if err != nil {
return ticker.TickerPrice{}, err
return ticker.Price{}, err
}
result, ok := tick[p.Pair().String()]
if !ok {
return ticker.TickerPrice{}, err
for _, x := range l.GetEnabledCurrencies() {
currency := exchange.FormatExchangeCurrency(l.Name, x).String()
var tickerPrice ticker.Price
tickerPrice.Pair = x
tickerPrice.Ask = tick[currency].Ask
tickerPrice.Bid = tick[currency].Bid
tickerPrice.Volume = tick[currency].Volume
tickerPrice.High = tick[currency].High
tickerPrice.Low = tick[currency].Low
tickerPrice.Last = tick[currency].Last
ticker.ProcessTicker(l.GetName(), x, tickerPrice, assetType)
}
var tickerPrice ticker.TickerPrice
tickerPrice.Pair = p
tickerPrice.Ask = result.Ask
tickerPrice.Bid = result.Bid
tickerPrice.Volume = result.Volume
tickerPrice.High = result.High
tickerPrice.Low = result.Low
tickerPrice.Last = result.Last
ticker.ProcessTicker(l.GetName(), p, tickerPrice)
return tickerPrice, nil
return ticker.GetTicker(l.Name, p, assetType)
}
func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(l.GetName(), p)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (l *LakeBTC) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType)
if err != nil {
return l.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
// GetOrderbookEx returns orderbook base on the currency pair
func (l *LakeBTC) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(l.GetName(), p, assetType)
if err == nil {
return l.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (l *LakeBTC) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := l.GetOrderBook(p.Pair().String())
if err != nil {
return orderBook, err
}
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.Bids {
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price})
}
for x, _ := range orderbookNew.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.OrderbookItem{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price})
for x := range orderbookNew.Asks {
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(l.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(l.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(l.Name, p, assetType)
}
func (l *LakeBTC) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// LakeBTC exchange
func (l *LakeBTC) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = l.GetName()
accountInfo, err := l.GetAccountInfo()
if err != nil {
@@ -101,7 +97,7 @@ func (l *LakeBTC) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error)
for x, y := range accountInfo.Balance {
for z, w := range accountInfo.Locked {
if z == x {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = common.StringToUpper(x)
exchangeCurrency.TotalValue, _ = strconv.ParseFloat(y, 64)
exchangeCurrency.Hold, _ = strconv.ParseFloat(w, 64)

View File

@@ -12,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -33,7 +34,7 @@ const (
)
type Liqui struct {
exchange.ExchangeBase
exchange.Base
Ticker map[string]LiquiTicker
Info LiquiInfo
}
@@ -46,6 +47,12 @@ func (l *Liqui) SetDefaults() {
l.Websocket = false
l.RESTPollingDelay = 10
l.Ticker = make(map[string]LiquiTicker)
l.RequestCurrencyPairFormat.Delimiter = "_"
l.RequestCurrencyPairFormat.Uppercase = false
l.RequestCurrencyPairFormat.Separator = "-"
l.ConfigCurrencyPairFormat.Delimiter = "_"
l.ConfigCurrencyPairFormat.Uppercase = true
l.AssetTypes = []string{ticker.Spot}
}
func (l *Liqui) Setup(exch config.ExchangeConfig) {
@@ -61,6 +68,14 @@ func (l *Liqui) Setup(exch config.ExchangeConfig) {
l.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
l.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
l.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := l.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = l.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -76,7 +91,7 @@ func (l *Liqui) GetFee(currency string) (float64, error) {
func (l *Liqui) GetAvailablePairs(nonHidden bool) []string {
var pairs []string
for x, y := range l.Info.Pairs {
if nonHidden && y.Hidden == 1 {
if nonHidden && y.Hidden == 1 || x == "" {
continue
}
pairs = append(pairs, common.StringToUpper(x))
@@ -249,12 +264,20 @@ func (l *Liqui) WithdrawCoins(coin string, amount float64, address string) (Liqu
}
func (l *Liqui) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) {
nonce := strconv.FormatInt(time.Now().Unix(), 10)
values.Set("nonce", nonce)
if !l.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name)
}
if l.Nonce.Get() == 0 {
l.Nonce.Set(time.Now().Unix())
} else {
l.Nonce.Inc()
}
values.Set("nonce", l.Nonce.String())
values.Set("method", method)
encoded := values.Encode()
hmac := common.GetHMAC(common.HASH_SHA512, []byte(encoded), []byte(l.APISecret))
hmac := common.GetHMAC(common.HashSHA512, []byte(encoded), []byte(l.APISecret))
if l.Verbose {
log.Printf("Sending POST request to %s calling method %s with params %s\n", LIQUI_API_PRIVATE_URL, method, encoded)

View File

@@ -1,22 +1,21 @@
package liqui
import (
"errors"
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the Liqui go routine
func (l *Liqui) Start() {
go l.Run()
}
// Run implements the Liqui wrapper
func (l *Liqui) Run() {
if l.Verbose {
log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay)
@@ -29,92 +28,95 @@ func (l *Liqui) Run() {
log.Printf("%s Unable to fetch info.\n", l.GetName())
} else {
exchangeProducts := l.GetAvailablePairs(true)
err = l.UpdateAvailableCurrencies(exchangeProducts)
err = l.UpdateAvailableCurrencies(exchangeProducts, false)
if err != nil {
log.Printf("%s Failed to get config.\n", l.GetName())
}
}
pairs := []string{}
for _, x := range l.EnabledPairs {
currencies := common.SplitStrings(x, "_")
x = common.StringToLower(currencies[0]) + "_" + common.StringToLower(currencies[1])
pairs = append(pairs, x)
}
pairsString := common.JoinStrings(pairs, "-")
for l.Enabled {
go func() {
ticker, err := l.GetTicker(pairsString)
if err != nil {
log.Println(err)
return
}
for x, y := range ticker {
currency := pair.NewCurrencyPairDelimiter(common.StringToUpper(x), "_")
log.Printf("Liqui %s: Last %f High %f Low %f Volume %f\n", currency.Pair().String(), y.Last, y.High, y.Low, y.Vol_cur)
l.Ticker[x] = y
stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), y.Last, y.Vol_cur)
}
}()
time.Sleep(time.Second * l.RESTPollingDelay)
}
}
func (l *Liqui) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
var tickerPrice ticker.TickerPrice
tick, ok := l.Ticker[p.Pair().Lower().String()]
if !ok {
return tickerPrice, errors.New("Unable to get currency.")
// UpdateTicker updates and returns the ticker for a currency pair
func (l *Liqui) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
pairsString, err := exchange.GetAndFormatExchangeCurrencies(l.Name,
l.GetEnabledCurrencies())
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tick.Buy
tickerPrice.Bid = tick.Sell
tickerPrice.Low = tick.Low
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Vol_cur
tickerPrice.High = tick.High
ticker.ProcessTicker(l.GetName(), p, tickerPrice)
return tickerPrice, nil
result, err := l.GetTicker(pairsString.String())
if err != nil {
return tickerPrice, err
}
for _, x := range l.GetEnabledCurrencies() {
currency := exchange.FormatExchangeCurrency(l.Name, x).String()
var tp ticker.Price
tp.Pair = x
tp.Last = result[currency].Last
tp.Ask = result[currency].Sell
tp.Bid = result[currency].Buy
tp.Last = result[currency].Last
tp.Low = result[currency].Low
tp.Volume = result[currency].Vol_cur
ticker.ProcessTicker(l.Name, x, tp, assetType)
}
return ticker.GetTicker(l.Name, p, assetType)
}
func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(l.GetName(), p)
// GetTickerPrice returns the ticker for a currency pair
func (l *Liqui) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(l.Name, p, assetType)
if err != nil {
return l.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns orderbook base on the currency pair
func (l *Liqui) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(l.Name, p, assetType)
if err == nil {
return ob, nil
return l.UpdateOrderbook(p, assetType)
}
return ob, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := l.GetDepth(p.Pair().Lower().String())
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (l *Liqui) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := l.GetDepth(exchange.FormatExchangeCurrency(l.Name, p).String())
if err != nil {
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]})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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]})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
}
orderBook.Pair = p
orderbook.ProcessOrderbook(l.GetName(), p, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(l.Name, p, orderBook, assetType)
return orderbook.GetOrderbook(l.Name, p, assetType)
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Liqui exchange
func (e *Liqui) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetAccountInfo()
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// Liqui exchange
func (l *Liqui) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = l.GetName()
accountBalance, err := l.GetAccountInfo()
if err != nil {
return response, err
}
for x, y := range accountBalance.Funds {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = common.StringToUpper(x)
exchangeCurrency.TotalValue = y
exchangeCurrency.Hold = 0

View File

@@ -12,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -28,7 +29,7 @@ const (
)
type LocalBitcoins struct {
exchange.ExchangeBase
exchange.Base
}
func (l *LocalBitcoins) SetDefaults() {
@@ -38,6 +39,11 @@ func (l *LocalBitcoins) SetDefaults() {
l.Verbose = false
l.Websocket = false
l.RESTPollingDelay = 10
l.RequestCurrencyPairFormat.Delimiter = ""
l.RequestCurrencyPairFormat.Uppercase = true
l.ConfigCurrencyPairFormat.Delimiter = ""
l.ConfigCurrencyPairFormat.Uppercase = true
l.AssetTypes = []string{ticker.Spot}
}
func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) {
@@ -53,6 +59,14 @@ func (l *LocalBitcoins) Setup(exch config.ExchangeConfig) {
l.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
l.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
l.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := l.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = l.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -267,7 +281,16 @@ func (l *LocalBitcoins) GetWalletAddress() (string, error) {
}
func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values url.Values, result interface{}) (err error) {
nonce := strconv.FormatInt(time.Now().UnixNano(), 10)
if !l.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, l.Name)
}
if l.Nonce.Get() == 0 {
l.Nonce.Set(time.Now().UnixNano())
} else {
l.Nonce.Inc()
}
payload := ""
path = "/api/" + path
@@ -275,24 +298,24 @@ func (l *LocalBitcoins) SendAuthenticatedHTTPRequest(method, path string, values
payload = values.Encode()
}
message := string(nonce) + l.APIKey + path + payload
hmac := common.GetHMAC(common.HASH_SHA256, []byte(message), []byte(l.APISecret))
message := l.Nonce.String() + l.APIKey + path + payload
hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(l.APISecret))
headers := make(map[string]string)
headers["Apiauth-Key"] = l.APIKey
headers["Apiauth-Nonce"] = string(nonce)
headers["Apiauth-Nonce"] = l.Nonce.String()
headers["Apiauth-Signature"] = common.StringToUpper(common.HexEncodeToString(hmac))
headers["Content-Type"] = "application/x-www-form-urlencoded"
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

View File

@@ -2,77 +2,96 @@ package localbitcoins
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the LocalBitcoins go routine
func (l *LocalBitcoins) Start() {
go l.Run()
}
// Run implements the LocalBitcoins wrapper
func (l *LocalBitcoins) Run() {
if l.Verbose {
log.Printf("%s polling delay: %ds.\n", l.GetName(), l.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", l.GetName(), len(l.EnabledPairs), l.EnabledPairs)
}
for l.Enabled {
for _, x := range l.EnabledPairs {
currency := pair.NewCurrencyPair("BTC", x[3:])
ticker, err := l.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("LocalBitcoins BTC %s: Last %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.Volume)
stats.AddExchangeInfo(l.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}
time.Sleep(time.Second * l.RESTPollingDelay)
}
}
func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(l.GetName(), p)
if err == nil {
return tickerNew, nil
}
// UpdateTicker updates and returns the ticker for a currency pair
func (l *LocalBitcoins) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := l.GetTicker()
if err != nil {
return ticker.TickerPrice{}, err
return tickerPrice, err
}
var tickerPrice ticker.TickerPrice
for key, value := range tick {
tickerPrice.Pair = p
tickerPrice.Last = value.Rates.Last
tickerPrice.Pair.SecondCurrency = pair.CurrencyItem(key)
tickerPrice.Volume = value.VolumeBTC
ticker.ProcessTicker(l.GetName(), p, tickerPrice)
for _, x := range l.GetEnabledCurrencies() {
currency := x.SecondCurrency.String()
var tp ticker.Price
tp.Pair = x
tp.Last = tick[currency].Rates.Last
tp.Volume = tick[currency].VolumeBTC
ticker.ProcessTicker(l.GetName(), x, tp, assetType)
}
return tickerPrice, nil
return ticker.GetTicker(l.GetName(), p, assetType)
}
func (l *LocalBitcoins) GetOrderbookEx(p pair.CurrencyPair) (orderbook.OrderbookBase, error) {
return orderbook.OrderbookBase{}, nil
// GetTickerPrice returns the ticker for a currency pair
func (l *LocalBitcoins) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(l.GetName(), p, assetType)
if err == nil {
return l.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the LocalBitcoins exchange
func (e *LocalBitcoins) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetWalletBalance()
// GetOrderbookEx returns orderbook base on the currency pair
func (l *LocalBitcoins) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(l.GetName(), p, assetType)
if err == nil {
return l.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (l *LocalBitcoins) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := l.GetOrderbook(p.GetSecondCurrency().String())
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Bids {
data := orderbookNew.Bids[x]
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
for x := range orderbookNew.Asks {
data := orderbookNew.Asks[x]
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
orderbook.ProcessOrderbook(l.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(l.Name, p, assetType)
}
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// LocalBitcoins exchange
func (l *LocalBitcoins) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = l.GetName()
accountBalance, err := l.GetWalletBalance()
if err != nil {
return response, err
}
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = "BTC"
exchangeCurrency.TotalValue = accountBalance.Total.Balance

49
exchanges/nonce/nonce.go Normal file
View File

@@ -0,0 +1,49 @@
package nonce
import (
"strconv"
"sync"
)
// Nonce struct holds the nonce value
type Nonce struct {
n int64
mtx sync.Mutex
}
// Inc increments the nonce value
func (n *Nonce) Inc() {
n.mtx.Lock()
n.n++
n.mtx.Unlock()
}
// Get retrives the nonce value
func (n *Nonce) Get() int64 {
n.mtx.Lock()
defer n.mtx.Unlock()
return n.n
}
// GetInc increments and returns the value of the nonce
func (n *Nonce) GetInc() int64 {
n.mtx.Lock()
defer n.mtx.Unlock()
n.n++
return n.n
}
// Set sets the nonce value
func (n *Nonce) Set(val int64) {
n.mtx.Lock()
n.n = val
n.mtx.Unlock()
}
// Returns a string version of the nonce
func (n *Nonce) String() string {
n.mtx.Lock()
result := strconv.FormatInt(n.n, 10)
n.mtx.Unlock()
return result
}

View File

@@ -0,0 +1,75 @@
package nonce
import (
"testing"
"time"
)
func TestInc(t *testing.T) {
var nonce Nonce
nonce.Set(1)
nonce.Inc()
expected := int64(2)
result := nonce.Get()
if result != expected {
t.Errorf("Test failed. Expected %d got %d", expected, result)
}
}
func TestGet(t *testing.T) {
var nonce Nonce
nonce.Set(112321313)
expected := int64(112321313)
result := nonce.Get()
if expected != result {
t.Errorf("Test failed. Expected %d got %d", expected, result)
}
}
func TestGetInc(t *testing.T) {
var nonce Nonce
nonce.Set(1)
expected := int64(2)
result := nonce.GetInc()
if expected != result {
t.Errorf("Test failed. Expected %d got %d", expected, result)
}
}
func TestSet(t *testing.T) {
var nonce Nonce
nonce.Set(1)
expected := int64(1)
result := nonce.Get()
if expected != result {
t.Errorf("Test failed. Expected %d got %d", expected, result)
}
}
func TestString(t *testing.T) {
var nonce Nonce
nonce.Set(12312313131)
expected := "12312313131"
result := nonce.String()
if expected != result {
t.Errorf("Test failed. Expected %s got %s", expected, result)
}
}
func TestNonceConcurrency(t *testing.T) {
var nonce Nonce
nonce.Set(12312)
for i := 0; i < 1000; i++ {
go nonce.Inc()
}
// Allow sufficient time for all routines to finish
time.Sleep(time.Second)
result := nonce.Get()
expected := int64(12312 + 1000)
if expected != result {
t.Errorf("Test failed. Expected %d got %d", expected, result)
}
}

View File

@@ -2,6 +2,7 @@ package okcoin
import (
"errors"
"fmt"
"log"
"net/url"
"strconv"
@@ -11,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -70,13 +72,20 @@ var (
)
type OKCoin struct {
exchange.ExchangeBase
exchange.Base
RESTErrors map[string]string
WebsocketErrors map[string]string
FuturesValues []string
WebsocketConn *websocket.Conn
}
func (o *OKCoin) setCurrencyPairFormats() {
o.RequestCurrencyPairFormat.Delimiter = "_"
o.RequestCurrencyPairFormat.Uppercase = false
o.ConfigCurrencyPairFormat.Delimiter = ""
o.ConfigCurrencyPairFormat.Uppercase = true
}
func (o *OKCoin) SetDefaults() {
o.SetErrorDefaults()
o.SetWebsocketErrorDefaults()
@@ -85,16 +94,20 @@ func (o *OKCoin) SetDefaults() {
o.Websocket = false
o.RESTPollingDelay = 10
o.FuturesValues = []string{"this_week", "next_week", "quarter"}
o.AssetTypes = []string{ticker.Spot}
if !okcoinDefaultsSet {
o.AssetTypes = append(o.AssetTypes, o.FuturesValues...)
o.APIUrl = OKCOIN_API_URL
o.Name = "OKCOIN International"
o.WebsocketURL = OKCOIN_WEBSOCKET_URL
okcoinDefaultsSet = true
o.setCurrencyPairFormats()
} else {
o.APIUrl = OKCOIN_API_URL_CHINA
o.Name = "OKCOIN China"
o.WebsocketURL = OKCOIN_WEBSOCKET_URL_CHINA
o.setCurrencyPairFormats()
}
}
@@ -111,6 +124,14 @@ func (o *OKCoin) Setup(exch config.ExchangeConfig) {
o.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
o.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
o.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := o.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = o.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -877,6 +898,10 @@ func (o *OKCoin) GetFuturesUserPosition4Fix(symbol, contractType string) {
}
func (o *OKCoin) SendAuthenticatedHTTPRequest(method string, v url.Values, result interface{}) (err error) {
if !o.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, o.Name)
}
v.Set("api_key", o.APIKey)
hasher := common.GetMD5([]byte(v.Encode() + "&secret_key=" + o.APISecret))
v.Set("sign", strings.ToUpper(common.HexEncodeToString(hasher)))
@@ -898,13 +923,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

View File

@@ -2,21 +2,20 @@ package okcoin
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the OKCoin go routine
func (o *OKCoin) Start() {
go o.Run()
}
// Run implements the OKCoin wrapper
func (o *OKCoin) Run() {
if o.Verbose {
log.Printf("%s Websocket: %s. (url: %s).\n", o.GetName(), common.IsEnabled(o.Websocket), o.WebsocketURL)
@@ -27,128 +26,113 @@ func (o *OKCoin) Run() {
if o.Websocket {
go o.WebsocketClient()
}
}
for o.Enabled {
for _, x := range o.EnabledPairs {
curr := pair.NewCurrencyPair(x[0:3], x[3:])
curr.Delimiter = "_"
if o.APIUrl == OKCOIN_API_URL {
for _, y := range o.FuturesValues {
futuresValue := y
go func() {
ticker, err := o.GetFuturesTicker(curr.Pair().Lower().String(), futuresValue)
if err != nil {
log.Println(err)
return
}
log.Printf("OKCoin Intl Futures %s (%s): Last %f High %f Low %f Volume %f\n", curr.Pair().String(), futuresValue, ticker.Last, ticker.High, ticker.Low, ticker.Vol)
stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Vol)
}()
}
go func() {
ticker, err := o.GetTickerPrice(curr)
if err != nil {
log.Println(err)
return
}
log.Printf("OKCoin Intl Spot %s: Last %f High %f Low %f Volume %f\n", curr.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
} else {
go func() {
ticker, err := o.GetTickerPrice(curr)
if err != nil {
log.Println(err)
return
}
tickerLastUSD, _ := currency.ConvertCurrency(ticker.Last, "CNY", "USD")
tickerHighUSD, _ := currency.ConvertCurrency(ticker.High, "CNY", "USD")
tickerLowUSD, _ := currency.ConvertCurrency(ticker.Low, "CNY", "USD")
log.Printf("OKCoin China %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", curr.Pair().String(), tickerLastUSD, ticker.Last, tickerHighUSD, ticker.High, tickerLowUSD, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), curr.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
stats.AddExchangeInfo(o.GetName(), curr.GetFirstCurrency().String(), "USD", tickerLastUSD, ticker.Volume)
}()
}
// UpdateTicker updates and returns the ticker for a currency pair
func (o *OKCoin) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
currency := exchange.FormatExchangeCurrency(o.Name, p).String()
var tickerPrice ticker.Price
if assetType != ticker.Spot && o.APIUrl == OKCOIN_API_URL {
tick, err := o.GetFuturesTicker(currency, assetType)
if err != nil {
return tickerPrice, err
}
time.Sleep(time.Second * o.RESTPollingDelay)
tickerPrice.Pair = p
tickerPrice.Ask = tick.Sell
tickerPrice.Bid = tick.Buy
tickerPrice.Low = tick.Low
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Vol
tickerPrice.High = tick.High
ticker.ProcessTicker(o.GetName(), p, tickerPrice, assetType)
} else {
tick, err := o.GetTicker(currency)
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = tick.Sell
tickerPrice.Bid = tick.Buy
tickerPrice.Low = tick.Low
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Vol
tickerPrice.High = tick.High
ticker.ProcessTicker(o.GetName(), p, tickerPrice, ticker.Spot)
}
return ticker.GetTicker(o.Name, p, assetType)
}
func (o *OKCoin) GetTickerPrice(currency pair.CurrencyPair) (ticker.TickerPrice, error) {
tickerNew, err := ticker.GetTicker(o.GetName(), currency)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
tick, err := o.GetTicker(currency.Pair().Lower().String())
// GetTickerPrice returns the ticker for a currency pair
func (o *OKCoin) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(o.GetName(), p, assetType)
if err != nil {
return tickerPrice, err
return o.UpdateTicker(p, assetType)
}
tickerPrice.Pair = currency
tickerPrice.Ask = tick.Sell
tickerPrice.Bid = tick.Buy
tickerPrice.Low = tick.Low
tickerPrice.Last = tick.Last
tickerPrice.Volume = tick.Vol
tickerPrice.High = tick.High
ticker.ProcessTicker(o.GetName(), currency, tickerPrice)
return tickerPrice, nil
return tickerNew, nil
}
func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair) (orderbook.OrderbookBase, error) {
ob, err := orderbook.GetOrderbook(o.GetName(), currency)
// GetOrderbookEx returns orderbook base on the currency pair
func (o *OKCoin) GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(o.GetName(), currency, assetType)
if err == nil {
return ob, nil
return o.UpdateOrderbook(currency, assetType)
}
return ob, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := o.GetOrderBook(currency.Pair().Lower().String(), 200, false)
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (o *OKCoin) UpdateOrderbook(currency pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := o.GetOrderBook(exchange.FormatExchangeCurrency(o.Name, currency).String(), 200, false)
if err != nil {
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]})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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]})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data[1], Price: data[0]})
}
orderBook.Pair = currency
orderbook.ProcessOrderbook(o.GetName(), currency, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(o.GetName(), currency, orderBook, assetType)
return orderbook.GetOrderbook(o.Name, currency, assetType)
}
func (e *OKCoin) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
assets, err := e.GetUserInfo()
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// OKCoin exchange
func (o *OKCoin) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = o.GetName()
assets, err := o.GetUserInfo()
if err != nil {
return response, err
}
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "BTC",
TotalValue: assets.Info.Funds.Free.BTC,
Hold: assets.Info.Funds.Freezed.BTC,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "LTC",
TotalValue: assets.Info.Funds.Free.LTC,
Hold: assets.Info.Funds.Freezed.LTC,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "USD",
TotalValue: assets.Info.Funds.Free.USD,
Hold: assets.Info.Funds.Freezed.USD,
})
response.Currencies = append(response.Currencies, exchange.ExchangeAccountCurrencyInfo{
response.Currencies = append(response.Currencies, exchange.AccountCurrencyInfo{
CurrencyName: "CNY",
TotalValue: assets.Info.Funds.Free.CNY,
Hold: assets.Info.Funds.Freezed.CNY,

View File

@@ -7,33 +7,44 @@ import (
"github.com/thrasher-/gocryptotrader/currency/pair"
)
var (
// Const values for orderbook package
const (
ErrOrderbookForExchangeNotFound = "Ticker for exchange does not exist."
ErrPrimaryCurrencyNotFound = "Error primary currency for orderbook not found."
ErrSecondaryCurrencyNotFound = "Error secondary currency for orderbook not found."
Spot = "SPOT"
)
// Vars for the orderbook package
var (
Orderbooks []Orderbook
)
type OrderbookItem struct {
// Item stores the amount and price values
type Item struct {
Amount float64
Price float64
}
type OrderbookBase struct {
// Base holds the fields for the orderbook base
type Base struct {
Pair pair.CurrencyPair `json:"pair"`
CurrencyPair string `json:"CurrencyPair"`
Bids []OrderbookItem `json:"bids"`
Asks []OrderbookItem `json:"asks"`
Bids []Item `json:"bids"`
Asks []Item `json:"asks"`
LastUpdated time.Time `json:"last_updated"`
}
// Orderbook holds the orderbook information for a currency pair and type
type Orderbook struct {
Orderbook map[pair.CurrencyItem]map[pair.CurrencyItem]OrderbookBase
Orderbook map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Base
ExchangeName string
}
func (o *OrderbookBase) CalculateTotalBids() (float64, float64) {
// CalculateTotalBids returns the total amount of bids and the total orderbook
// bids value
func (o *Base) CalculateTotalBids() (float64, float64) {
amountCollated := float64(0)
total := float64(0)
for _, x := range o.Bids {
@@ -43,7 +54,9 @@ func (o *OrderbookBase) CalculateTotalBids() (float64, float64) {
return amountCollated, total
}
func (o *OrderbookBase) CalculateTotalAsks() (float64, float64) {
// CalculateTotalAsks returns the total amount of asks and the total orderbook
// asks value
func (o *Base) CalculateTotalAsks() (float64, float64) {
amountCollated := float64(0)
total := float64(0)
for _, x := range o.Asks {
@@ -53,29 +66,33 @@ func (o *OrderbookBase) CalculateTotalAsks() (float64, float64) {
return amountCollated, total
}
func (o *OrderbookBase) Update(Bids, Asks []OrderbookItem) {
// Update updates the bids and asks
func (o *Base) Update(Bids, Asks []Item) {
o.Bids = Bids
o.Asks = Asks
o.LastUpdated = time.Now()
}
func GetOrderbook(exchange string, p pair.CurrencyPair) (OrderbookBase, error) {
// GetOrderbook checks and returns the orderbook given an exchange name and
// currency pair if it exists
func GetOrderbook(exchange string, p pair.CurrencyPair, orderbookType string) (Base, error) {
orderbook, err := GetOrderbookByExchange(exchange)
if err != nil {
return OrderbookBase{}, err
return Base{}, err
}
if !FirstCurrencyExists(exchange, p.GetFirstCurrency()) {
return OrderbookBase{}, errors.New(ErrPrimaryCurrencyNotFound)
return Base{}, errors.New(ErrPrimaryCurrencyNotFound)
}
if !SecondCurrencyExists(exchange, p) {
return OrderbookBase{}, errors.New(ErrSecondaryCurrencyNotFound)
return Base{}, errors.New(ErrSecondaryCurrencyNotFound)
}
return orderbook.Orderbook[p.GetFirstCurrency()][p.GetSecondCurrency()], nil
return orderbook.Orderbook[p.GetFirstCurrency()][p.GetSecondCurrency()][orderbookType], nil
}
// GetOrderbookByExchange returns an exchange orderbook
func GetOrderbookByExchange(exchange string) (*Orderbook, error) {
for _, y := range Orderbooks {
if y.ExchangeName == exchange {
@@ -85,6 +102,8 @@ func GetOrderbookByExchange(exchange string) (*Orderbook, error) {
return nil, errors.New(ErrOrderbookForExchangeNotFound)
}
// FirstCurrencyExists checks to see if the first currency of the orderbook map
// exists
func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool {
for _, y := range Orderbooks {
if y.ExchangeName == exchange {
@@ -96,6 +115,8 @@ func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool {
return false
}
// SecondCurrencyExists checks to see if the second currency of the orderbook
// map exists
func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool {
for _, y := range Orderbooks {
if y.ExchangeName == exchange {
@@ -109,39 +130,51 @@ func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool {
return false
}
func CreateNewOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew OrderbookBase) Orderbook {
// CreateNewOrderbook creates a new orderbook
func CreateNewOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew Base, orderbookType string) Orderbook {
orderbook := Orderbook{}
orderbook.ExchangeName = exchangeName
orderbook.Orderbook = make(map[pair.CurrencyItem]map[pair.CurrencyItem]OrderbookBase)
sMap := make(map[pair.CurrencyItem]OrderbookBase)
sMap[p.GetSecondCurrency()] = orderbookNew
orderbook.Orderbook[p.GetFirstCurrency()] = sMap
orderbook.Orderbook = make(map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Base)
a := make(map[pair.CurrencyItem]map[string]Base)
b := make(map[string]Base)
b[orderbookType] = orderbookNew
a[p.SecondCurrency] = b
orderbook.Orderbook[p.FirstCurrency] = a
Orderbooks = append(Orderbooks, orderbook)
return orderbook
}
func ProcessOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew OrderbookBase) {
// ProcessOrderbook processes incoming orderbooks, creating or updating the
// Orderbook list
func ProcessOrderbook(exchangeName string, p pair.CurrencyPair, orderbookNew Base, orderbookType string) {
orderbookNew.CurrencyPair = p.Pair().String()
orderbookNew.LastUpdated = time.Now()
if len(Orderbooks) == 0 {
CreateNewOrderbook(exchangeName, p, orderbookNew)
CreateNewOrderbook(exchangeName, p, orderbookNew, orderbookType)
return
} else {
orderbook, err := GetOrderbookByExchange(exchangeName)
if err != nil {
CreateNewOrderbook(exchangeName, p, orderbookNew)
}
orderbook, err := GetOrderbookByExchange(exchangeName)
if err != nil {
CreateNewOrderbook(exchangeName, p, orderbookNew, orderbookType)
return
}
if FirstCurrencyExists(exchangeName, p.GetFirstCurrency()) {
if !SecondCurrencyExists(exchangeName, p) {
a := orderbook.Orderbook[p.FirstCurrency]
b := make(map[string]Base)
b[orderbookType] = orderbookNew
a[p.SecondCurrency] = b
orderbook.Orderbook[p.FirstCurrency] = a
return
}
if FirstCurrencyExists(exchangeName, p.GetFirstCurrency()) {
if !SecondCurrencyExists(exchangeName, p) {
second := orderbook.Orderbook[p.GetFirstCurrency()]
second[p.GetSecondCurrency()] = orderbookNew
orderbook.Orderbook[p.GetFirstCurrency()] = second
return
}
}
sMap := make(map[pair.CurrencyItem]OrderbookBase)
sMap[p.GetSecondCurrency()] = orderbookNew
orderbook.Orderbook[p.GetFirstCurrency()] = sMap
}
a := make(map[pair.CurrencyItem]map[string]Base)
b := make(map[string]Base)
b[orderbookType] = orderbookNew
a[p.SecondCurrency] = b
orderbook.Orderbook[p.FirstCurrency] = a
}

View File

@@ -0,0 +1,266 @@
package orderbook
import (
"testing"
"time"
"github.com/thrasher-/gocryptotrader/currency/pair"
)
func TestCalculateTotalBids(t *testing.T) {
t.Parallel()
currency := pair.NewCurrencyPair("BTC", "USD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Bids: []Item{Item{Price: 100, Amount: 10}},
LastUpdated: time.Now(),
}
a, b := base.CalculateTotalBids()
if a != 10 && b != 1000 {
t.Fatal("Test failed. TestCalculateTotalBids expected a = 10 and b = 1000")
}
}
func TestCalculateTotaAsks(t *testing.T) {
t.Parallel()
currency := pair.NewCurrencyPair("BTC", "USD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
LastUpdated: time.Now(),
}
a, b := base.CalculateTotalAsks()
if a != 10 && b != 1000 {
t.Fatal("Test failed. TestCalculateTotalAsks expected a = 10 and b = 1000")
}
}
func TestUpdate(t *testing.T) {
t.Parallel()
currency := pair.NewCurrencyPair("BTC", "USD")
timeNow := time.Now()
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
Bids: []Item{Item{Price: 200, Amount: 10}},
LastUpdated: timeNow,
}
asks := []Item{Item{Price: 200, Amount: 101}}
bids := []Item{Item{Price: 201, Amount: 100}}
time.Sleep(time.Millisecond * 50)
base.Update(bids, asks)
if !base.LastUpdated.After(timeNow) {
t.Fatal("test failed. TestUpdate expected LastUpdated to be greater then original time")
}
a, b := base.CalculateTotalAsks()
if a != 100 && b != 20200 {
t.Fatal("Test failed. TestUpdate expected a = 100 and b = 20100")
}
a, b = base.CalculateTotalBids()
if a != 100 && b != 20100 {
t.Fatal("Test failed. TestUpdate expected a = 100 and b = 20100")
}
}
func TestGetOrderbook(t *testing.T) {
currency := pair.NewCurrencyPair("BTC", "USD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
Bids: []Item{Item{Price: 200, Amount: 10}},
}
CreateNewOrderbook("Exchange", currency, base, Spot)
result, err := GetOrderbook("Exchange", currency, Spot)
if err != nil {
t.Fatalf("Test failed. TestGetOrderbook failed to get orderbook. Error %s",
err)
}
if result.Pair.Pair() != currency.Pair() {
t.Fatal("Test failed. TestGetOrderbook failed. Mismatched pairs")
}
_, err = GetOrderbook("nonexistant", currency, Spot)
if err == nil {
t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook")
}
currency.FirstCurrency = "blah"
_, err = GetOrderbook("Exchange", currency, Spot)
if err == nil {
t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook using invalid first currency")
}
newCurrency := pair.NewCurrencyPair("BTC", "AUD")
_, err = GetOrderbook("Exchange", newCurrency, Spot)
if err == nil {
t.Fatal("Test failed. TestGetOrderbook retrieved non-existant orderbook using invalid second currency")
}
}
func TestGetOrderbookByExchange(t *testing.T) {
currency := pair.NewCurrencyPair("BTC", "USD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
Bids: []Item{Item{Price: 200, Amount: 10}},
}
CreateNewOrderbook("Exchange", currency, base, Spot)
_, err := GetOrderbookByExchange("Exchange")
if err != nil {
t.Fatalf("Test failed. TestGetOrderbookByExchange failed to get orderbook. Error %s",
err)
}
_, err = GetOrderbookByExchange("nonexistant")
if err == nil {
t.Fatal("Test failed. TestGetOrderbookByExchange retrieved non-existant orderbook")
}
}
func TestFirstCurrencyExists(t *testing.T) {
currency := pair.NewCurrencyPair("BTC", "AUD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
Bids: []Item{Item{Price: 200, Amount: 10}},
}
CreateNewOrderbook("Exchange", currency, base, Spot)
if !FirstCurrencyExists("Exchange", currency.FirstCurrency) {
t.Fatal("Test failed. TestFirstCurrencyExists expected first currency doesn't exist")
}
var item pair.CurrencyItem = "blah"
if FirstCurrencyExists("Exchange", item) {
t.Fatal("Test failed. TestFirstCurrencyExists unexpected first currency exists")
}
}
func TestSecondCurrencyExists(t *testing.T) {
currency := pair.NewCurrencyPair("BTC", "USD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
Bids: []Item{Item{Price: 200, Amount: 10}},
}
CreateNewOrderbook("Exchange", currency, base, Spot)
if !SecondCurrencyExists("Exchange", currency) {
t.Fatal("Test failed. TestSecondCurrencyExists expected first currency doesn't exist")
}
currency.SecondCurrency = "blah"
if SecondCurrencyExists("Exchange", currency) {
t.Fatal("Test failed. TestSecondCurrencyExists unexpected first currency exists")
}
}
func TestCreateNewOrderbook(t *testing.T) {
currency := pair.NewCurrencyPair("BTC", "USD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
Bids: []Item{Item{Price: 200, Amount: 10}},
}
CreateNewOrderbook("Exchange", currency, base, Spot)
result, err := GetOrderbook("Exchange", currency, Spot)
if err != nil {
t.Fatal("Test failed. TestCreateNewOrderbook failed to create new orderbook")
}
if result.Pair.Pair() != currency.Pair() {
t.Fatal("Test failed. TestCreateNewOrderbook result pair is incorrect")
}
a, b := result.CalculateTotalAsks()
if a != 10 && b != 1000 {
t.Fatal("Test failed. TestCreateNewOrderbook CalculateTotalAsks value is incorrect")
}
a, b = result.CalculateTotalBids()
if a != 10 && b != 2000 {
t.Fatal("Test failed. TestCreateNewOrderbook CalculateTotalBids value is incorrect")
}
}
func TestProcessOrderbook(t *testing.T) {
Orderbooks = []Orderbook{}
currency := pair.NewCurrencyPair("BTC", "USD")
base := Base{
Pair: currency,
CurrencyPair: currency.Pair().String(),
Asks: []Item{Item{Price: 100, Amount: 10}},
Bids: []Item{Item{Price: 200, Amount: 10}},
}
ProcessOrderbook("Exchange", currency, base, Spot)
result, err := GetOrderbook("Exchange", currency, Spot)
if err != nil {
t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook")
}
if result.Pair.Pair() != currency.Pair() {
t.Fatal("Test failed. TestProcessOrderbook result pair is incorrect")
}
currency = pair.NewCurrencyPair("BTC", "GBP")
base.Pair = currency
ProcessOrderbook("Exchange", currency, base, Spot)
result, err = GetOrderbook("Exchange", currency, Spot)
if err != nil {
t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook")
}
if result.Pair.Pair() != currency.Pair() {
t.Fatal("Test failed. TestProcessOrderbook result pair is incorrect")
}
base.Asks = []Item{Item{Price: 200, Amount: 200}}
ProcessOrderbook("Exchange", currency, base, "monthly")
result, err = GetOrderbook("Exchange", currency, "monthly")
if err != nil {
t.Fatal("Test failed. TestProcessOrderbook failed to retrieve new orderbook")
}
a, b := result.CalculateTotalAsks()
if a != 200 && b != 40000 {
t.Fatal("Test failed. TestProcessOrderbook CalculateTotalsAsks incorrect values")
}
base.Bids = []Item{Item{Price: 420, Amount: 200}}
ProcessOrderbook("Blah", currency, base, "quarterly")
result, err = GetOrderbook("Blah", currency, "quarterly")
if err != nil {
t.Fatal("Test failed. TestProcessOrderbook failed to create new orderbook")
}
if a != 200 && b != 84000 {
t.Fatal("Test failed. TestProcessOrderbook CalculateTotalsBids incorrect values")
}
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"log"
"net/url"
"strconv"
"time"
@@ -11,6 +12,7 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
@@ -47,7 +49,7 @@ const (
)
type Poloniex struct {
exchange.ExchangeBase
exchange.Base
}
func (p *Poloniex) SetDefaults() {
@@ -57,6 +59,11 @@ func (p *Poloniex) SetDefaults() {
p.Verbose = false
p.Websocket = false
p.RESTPollingDelay = 10
p.RequestCurrencyPairFormat.Delimiter = "_"
p.RequestCurrencyPairFormat.Uppercase = true
p.ConfigCurrencyPairFormat.Delimiter = "_"
p.ConfigCurrencyPairFormat.Uppercase = true
p.AssetTypes = []string{ticker.Spot}
}
func (p *Poloniex) Setup(exch config.ExchangeConfig) {
@@ -72,6 +79,14 @@ func (p *Poloniex) Setup(exch config.ExchangeConfig) {
p.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
p.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
p.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := p.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = p.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
@@ -122,16 +137,22 @@ 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)
price, err := strconv.ParseFloat(data[0].(string), 64)
if err != nil {
return ob, err
}
amount := data[1].(float64)
ob.Asks = append(ob.Asks, PoloniexOrderbookItem{Price: price, Amount: amount})
}
for x, _ := range resp.Bids {
data := resp.Asks[x]
price, _ := strconv.ParseFloat(data[0].(string), 64)
for x := range resp.Bids {
data := resp.Bids[x]
price, err := strconv.ParseFloat(data[0].(string), 64)
if err != nil {
return ob, err
}
amount := data[1].(float64)
ob.Bids = append(ob.Bids, PoloniexOrderbookItem{Price: price, Amount: amount})
}
@@ -745,17 +766,22 @@ func (p *Poloniex) ToggleAutoRenew(orderNumber int64) (bool, error) {
}
func (p *Poloniex) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error {
if !p.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, p.Name)
}
headers := make(map[string]string)
headers["Content-Type"] = "application/x-www-form-urlencoded"
headers["Key"] = p.APIKey
nonce := time.Now().UnixNano()
nonceStr := strconv.FormatInt(nonce, 10)
values.Set("nonce", nonceStr)
if p.Nonce.Get() == 0 {
p.Nonce.Set(time.Now().UnixNano())
} else {
p.Nonce.Inc()
}
values.Set("nonce", p.Nonce.String())
values.Set("command", endpoint)
hmac := common.GetHMAC(common.HASH_SHA512, []byte(values.Encode()), []byte(p.APISecret))
hmac := common.GetHMAC(common.HashSHA512, []byte(values.Encode()), []byte(p.APISecret))
headers["Sign"] = common.HexEncodeToString(hmac)
path := fmt.Sprintf("%s/%s", POLONIEX_API_URL, POLONIEX_API_TRADING_ENDPOINT)

View File

@@ -2,20 +2,20 @@ package poloniex
import (
"log"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the Poloniex go routine
func (p *Poloniex) Start() {
go p.Run()
}
// Run implements the Poloniex wrapper
func (p *Poloniex) Run() {
if p.Verbose {
log.Printf("%s Websocket: %s (url: %s).\n", p.GetName(), common.IsEnabled(p.Websocket), POLONIEX_WEBSOCKET_ADDRESS)
@@ -26,86 +26,83 @@ func (p *Poloniex) Run() {
if p.Websocket {
go p.WebsocketClient()
}
for p.Enabled {
for _, x := range p.EnabledPairs {
currency := pair.NewCurrencyPairDelimiter(x, "_")
go func() {
ticker, err := p.GetTickerPrice(currency)
if err != nil {
log.Println(err)
return
}
log.Printf("Poloniex %s Last %f High %f Low %f Volume %f\n", currency.Pair().String(), ticker.Last, ticker.High, ticker.Low, ticker.Volume)
stats.AddExchangeInfo(p.GetName(), currency.GetFirstCurrency().String(), currency.GetSecondCurrency().String(), ticker.Last, ticker.Volume)
}()
}
time.Sleep(time.Second * p.RESTPollingDelay)
}
}
func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair) (ticker.TickerPrice, error) {
currency := currencyPair.Pair().String()
tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair)
if err == nil {
return tickerNew, nil
}
var tickerPrice ticker.TickerPrice
// UpdateTicker updates and returns the ticker for a currency pair
func (p *Poloniex) UpdateTicker(currencyPair pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
tick, err := p.GetTicker()
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = currencyPair
tickerPrice.Ask = tick[currency].Last
tickerPrice.Bid = tick[currency].HighestBid
tickerPrice.High = tick[currency].HighestBid
tickerPrice.Last = tick[currency].Last
tickerPrice.Low = tick[currency].LowestAsk
tickerPrice.Volume = tick[currency].BaseVolume
ticker.ProcessTicker(p.GetName(), currencyPair, tickerPrice)
return tickerPrice, nil
for _, x := range p.GetEnabledCurrencies() {
var tp ticker.Price
curr := exchange.FormatExchangeCurrency(p.GetName(), x).String()
tp.Pair = x
tp.Ask = tick[curr].LowestAsk
tp.Bid = tick[curr].HighestBid
tp.High = tick[curr].High24Hr
tp.Last = tick[curr].Last
tp.Low = tick[curr].Low24Hr
tp.Volume = tick[curr].BaseVolume
ticker.ProcessTicker(p.GetName(), x, tp, assetType)
}
return ticker.GetTicker(p.Name, currencyPair, assetType)
}
func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair) (orderbook.OrderbookBase, error) {
currency := currencyPair.Pair().String()
ob, err := orderbook.GetOrderbook(p.GetName(), currencyPair)
if err == nil {
return ob, nil
// GetTickerPrice returns the ticker for a currency pair
func (p *Poloniex) GetTickerPrice(currencyPair pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(p.GetName(), currencyPair, assetType)
if err != nil {
return p.UpdateTicker(currencyPair, assetType)
}
return tickerNew, nil
}
var orderBook orderbook.OrderbookBase
orderbookNew, err := p.GetOrderbook(currency, 1000)
// GetOrderbookEx returns orderbook base on the currency pair
func (p *Poloniex) GetOrderbookEx(currencyPair pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(p.GetName(), currencyPair, assetType)
if err == nil {
return p.UpdateOrderbook(currencyPair, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (p *Poloniex) UpdateOrderbook(currencyPair pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := p.GetOrderbook(exchange.FormatExchangeCurrency(p.GetName(), currencyPair).String(), 1000)
if err != nil {
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})
orderBook.Bids = append(orderBook.Bids, orderbook.Item{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})
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Amount: data.Amount, Price: data.Price})
}
orderBook.Pair = currencyPair
orderbook.ProcessOrderbook(p.GetName(), currencyPair, orderBook)
return orderBook, nil
orderbook.ProcessOrderbook(p.GetName(), currencyPair, orderBook, assetType)
return orderbook.GetOrderbook(p.Name, currencyPair, assetType)
}
//GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the Poloniex exchange
func (e *Poloniex) GetExchangeAccountInfo() (exchange.ExchangeAccountInfo, error) {
var response exchange.ExchangeAccountInfo
response.ExchangeName = e.GetName()
accountBalance, err := e.GetBalances()
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// Poloniex exchange
func (p *Poloniex) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = p.GetName()
accountBalance, err := p.GetBalances()
if err != nil {
return response, err
}
for x, y := range accountBalance.Currency {
var exchangeCurrency exchange.ExchangeAccountCurrencyInfo
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = x
exchangeCurrency.TotalValue = y
response.Currencies = append(response.Currencies, exchangeCurrency)

View File

@@ -3,113 +3,134 @@ package stats
import (
"sort"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/currency/pair"
)
type ExchangeInfo struct {
Exchange string
FirstCurrency string
FiatCurrency string
Price float64
Volume float64
// Item holds various fields for storing currency pair stats
type Item struct {
Exchange string
Pair pair.CurrencyPair
AssetType string
Price float64
Volume float64
}
var ExchInfo []ExchangeInfo
// Items var array
var Items []Item
type ByPrice []ExchangeInfo
// ByPrice allows sorting by price
type ByPrice []Item
func (this ByPrice) Len() int {
return len(this)
func (b ByPrice) Len() int {
return len(b)
}
func (this ByPrice) Less(i, j int) bool {
return this[i].Price < this[j].Price
func (b ByPrice) Less(i, j int) bool {
return b[i].Price < b[j].Price
}
func (this ByPrice) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
func (b ByPrice) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
type ByVolume []ExchangeInfo
// ByVolume allows sorting by volume
type ByVolume []Item
func (this ByVolume) Len() int {
return len(this)
func (b ByVolume) Len() int {
return len(b)
}
func (this ByVolume) Less(i, j int) bool {
return this[i].Volume < this[j].Volume
func (b ByVolume) Less(i, j int) bool {
return b[i].Volume < b[j].Volume
}
func (this ByVolume) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
func (b ByVolume) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func AddExchangeInfo(exchange, crypto, fiat string, price, volume float64) {
if currency.BaseCurrencies == "" {
currency.BaseCurrencies = currency.DEFAULT_CURRENCIES
}
if !currency.IsFiatCurrency(fiat) {
return
}
AppendExchangeInfo(exchange, crypto, fiat, price, volume)
}
func AppendExchangeInfo(exchange, crypto, fiat string, price, volume float64) {
if ExchangeInfoAlreadyExists(exchange, crypto, fiat, price, volume) {
// Add adds or updates the item stats
func Add(exchange string, p pair.CurrencyPair, assetType string, price, volume float64) {
if exchange == "" || assetType == "" || price == 0 || volume == 0 || p.FirstCurrency == "" || p.SecondCurrency == "" {
return
}
exch := ExchangeInfo{}
exch.Exchange = exchange
exch.FirstCurrency = crypto
exch.FiatCurrency = fiat
exch.Price = price
exch.Volume = volume
ExchInfo = append(ExchInfo, exch)
if p.FirstCurrency == "XBT" {
newPair := pair.NewCurrencyPair("BTC", p.SecondCurrency.String())
Append(exchange, newPair, assetType, price, volume)
}
if p.SecondCurrency == "USDT" {
newPair := pair.NewCurrencyPair(p.FirstCurrency.String(), "USD")
Append(exchange, newPair, assetType, price, volume)
}
Append(exchange, p, assetType, price, volume)
}
func ExchangeInfoAlreadyExists(exchange, crypto, fiat string, price, volume float64) bool {
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
// Append adds or updates the item stats for a specific
// currency pair and asset type
func Append(exchange string, p pair.CurrencyPair, assetType string, price, volume float64) {
if AlreadyExists(exchange, p, assetType, price, volume) {
return
}
i := Item{
Exchange: exchange,
Pair: p,
AssetType: assetType,
Price: price,
Volume: volume,
}
Items = append(Items, i)
}
// AlreadyExists checks to see if item info already exists
// for a specific currency pair and asset type
func AlreadyExists(exchange string, p pair.CurrencyPair, assetType string, price, volume float64) bool {
for i := range Items {
if Items[i].Exchange == exchange && Items[i].Pair.Equal(p) && Items[i].AssetType == assetType {
Items[i].Price, Items[i].Volume = price, volume
return true
}
}
return false
}
func SortExchangesByVolume(crypto, fiat string, reverse bool) []ExchangeInfo {
info := []ExchangeInfo{}
for _, x := range ExchInfo {
if x.FirstCurrency == crypto && x.FiatCurrency == fiat {
info = append(info, x)
// SortExchangesByVolume sorts item info by volume for a specific
// currency pair and asset type. Reverse will reverse the order from lowest to
// highest
func SortExchangesByVolume(p pair.CurrencyPair, assetType string, reverse bool) []Item {
var result []Item
for x := range Items {
if Items[x].Pair.Equal(p) && Items[x].AssetType == assetType {
result = append(result, Items[x])
}
}
if reverse {
sort.Sort(sort.Reverse(ByVolume(info)))
sort.Sort(sort.Reverse(ByVolume(result)))
} else {
sort.Sort(ByVolume(info))
sort.Sort(ByVolume(result))
}
return info
return result
}
func SortExchangesByPrice(crypto, fiat string, reverse bool) []ExchangeInfo {
info := []ExchangeInfo{}
for _, x := range ExchInfo {
if x.FirstCurrency == crypto && x.FiatCurrency == fiat {
info = append(info, x)
// SortExchangesByPrice sorts item info by volume for a specific
// currency pair and asset type. Reverse will reverse the order from lowest to
// highest
func SortExchangesByPrice(p pair.CurrencyPair, assetType string, reverse bool) []Item {
var result []Item
for x := range Items {
if Items[x].Pair.Equal(p) && Items[x].AssetType == assetType {
result = append(result, Items[x])
}
}
if reverse {
sort.Sort(sort.Reverse(ByPrice(info)))
sort.Sort(sort.Reverse(ByPrice(result)))
} else {
sort.Sort(ByPrice(info))
sort.Sort(ByPrice(result))
}
return info
return result
}

View File

@@ -2,138 +2,180 @@ package stats
import (
"testing"
"github.com/thrasher-/gocryptotrader/currency/pair"
)
func TestLenByPrice(t *testing.T) {
exchangeInfo := ExchangeInfo{
Exchange: "ANX",
FirstCurrency: "BTC",
FiatCurrency: "USD",
Price: 1200,
Volume: 5,
p := pair.NewCurrencyPair("BTC", "USD")
i := Item{
Exchange: "ANX",
Pair: p,
AssetType: "SPOT",
Price: 1200,
Volume: 5,
}
ExchInfo = append(ExchInfo, exchangeInfo)
if ByPrice.Len(ExchInfo) < 1 {
Items = append(Items, i)
if ByPrice.Len(Items) < 1 {
t.Error("Test Failed - stats LenByPrice() length not correct.")
}
}
func TestLessByPrice(t *testing.T) {
exchangeInfo := ExchangeInfo{
Exchange: "alphapoint",
FirstCurrency: "BTC",
FiatCurrency: "USD",
Price: 1200,
Volume: 5,
p := pair.NewCurrencyPair("BTC", "USD")
i := Item{
Exchange: "alphapoint",
Pair: p,
AssetType: "SPOT",
Price: 1200,
Volume: 5,
}
exchangeInfo2 := ExchangeInfo{
Exchange: "bitfinex",
FirstCurrency: "BTC",
FiatCurrency: "USD",
Price: 1198,
Volume: 20,
i2 := Item{
Exchange: "bitfinex",
Pair: p,
AssetType: "SPOT",
Price: 1198,
Volume: 20,
}
ExchInfo = append(ExchInfo, exchangeInfo)
ExchInfo = append(ExchInfo, exchangeInfo2)
Items = append(Items, i)
Items = append(Items, i2)
if !ByPrice.Less(ExchInfo, 2, 1) {
if !ByPrice.Less(Items, 2, 1) {
t.Error("Test Failed - stats LessByPrice() incorrect return.")
}
if ByPrice.Less(ExchInfo, 1, 2) {
if ByPrice.Less(Items, 1, 2) {
t.Error("Test Failed - stats LessByPrice() incorrect return.")
}
}
func TestSwapByPrice(t *testing.T) {
exchangeInfo := ExchangeInfo{
Exchange: "bitstamp",
FirstCurrency: "BTC",
FiatCurrency: "USD",
Price: 1324,
Volume: 5,
p := pair.NewCurrencyPair("BTC", "USD")
i := Item{
Exchange: "bitstamp",
Pair: p,
AssetType: "SPOT",
Price: 1324,
Volume: 5,
}
exchangeInfo2 := ExchangeInfo{
Exchange: "btcc",
FirstCurrency: "BTC",
FiatCurrency: "USD",
Price: 7863,
Volume: 20,
i2 := Item{
Exchange: "btcc",
Pair: p,
AssetType: "SPOT",
Price: 7863,
Volume: 20,
}
ExchInfo = append(ExchInfo, exchangeInfo)
ExchInfo = append(ExchInfo, exchangeInfo2)
ByPrice.Swap(ExchInfo, 3, 4)
if ExchInfo[3].Exchange != "btcc" || ExchInfo[4].Exchange != "bitstamp" {
Items = append(Items, i)
Items = append(Items, i2)
ByPrice.Swap(Items, 3, 4)
if Items[3].Exchange != "btcc" || Items[4].Exchange != "bitstamp" {
t.Error("Test Failed - stats SwapByPrice did not swap values.")
}
}
func TestLenByVolume(t *testing.T) {
if ByVolume.Len(ExchInfo) != 5 {
if ByVolume.Len(Items) != 5 {
t.Error("Test Failed - stats lenByVolume did not swap values.")
}
}
func TestLessByVolume(t *testing.T) {
if !ByVolume.Less(ExchInfo, 1, 2) {
if !ByVolume.Less(Items, 1, 2) {
t.Error("Test Failed - stats LessByVolume() incorrect return.")
}
if ByVolume.Less(ExchInfo, 2, 1) {
if ByVolume.Less(Items, 2, 1) {
t.Error("Test Failed - stats LessByVolume() incorrect return.")
}
}
func TestSwapByVolume(t *testing.T) {
ByPrice.Swap(ExchInfo, 3, 4)
ByPrice.Swap(Items, 3, 4)
if ExchInfo[4].Exchange != "btcc" || ExchInfo[3].Exchange != "bitstamp" {
if Items[4].Exchange != "btcc" || Items[3].Exchange != "bitstamp" {
t.Error("Test Failed - stats SwapByVolume did not swap values.")
}
}
func TestAddExchangeInfo(t *testing.T) {
ExchInfo = ExchInfo[:0]
AddExchangeInfo("ANX", "BTC", "USD", 1200, 42)
func TestAdd(t *testing.T) {
Items = Items[:0]
p := pair.NewCurrencyPair("BTC", "USD")
Add("ANX", p, "SPOT", 1200, 42)
if len(ExchInfo) < 1 {
t.Error("Test Failed - stats AddExchangeInfo did not add exchange info.")
if len(Items) < 1 {
t.Error("Test Failed - stats Add did not add exchange info.")
}
Add("", p, "", 0, 0)
if len(Items) != 1 {
t.Error("Test Failed - stats Add did not add exchange info.")
}
p.FirstCurrency = "XBT"
Add("ANX", p, "SPOT", 1201, 43)
if Items[1].Pair.Pair() != "XBTUSD" {
t.Fatal("Test failed. stats Add did not add exchange info.")
}
p = pair.NewCurrencyPair("ETH", "USDT")
Add("ANX", p, "SPOT", 300, 1000)
if Items[2].Pair.Pair() != "ETHUSD" {
t.Fatal("Test failed. stats Add did not add exchange info.")
}
}
func TestAppendExchangeInfo(t *testing.T) {
AppendExchangeInfo("sillyexchange", "BTC", "USD", 1234, 45)
if len(ExchInfo) < 2 {
t.Error("Test Failed - stats AppendExchangeInfo did not add exchange values.")
func TestAppend(t *testing.T) {
p := pair.NewCurrencyPair("BTC", "USD")
Append("sillyexchange", p, "SPOT", 1234, 45)
if len(Items) < 2 {
t.Error("Test Failed - stats Append did not add exchange values.")
}
AppendExchangeInfo("sillyexchange", "BTC", "USD", 1234, 45)
if len(ExchInfo) == 3 {
t.Error("Test Failed - stats AppendExchangeInfo added exchange values")
Append("sillyexchange", p, "SPOT", 1234, 45)
if len(Items) == 3 {
t.Error("Test Failed - stats Append added exchange values")
}
}
func TestExchangeInfoAlreadyExists(t *testing.T) {
if !ExchangeInfoAlreadyExists("ANX", "BTC", "USD", 1200, 42) {
t.Error("Test Failed - stats ExchangeInfoAlreadyExists exchange does not exist.")
func TestAlreadyExists(t *testing.T) {
p := pair.NewCurrencyPair("BTC", "USD")
if !AlreadyExists("ANX", p, "SPOT", 1200, 42) {
t.Error("Test Failed - stats AlreadyExists exchange does not exist.")
}
if ExchangeInfoAlreadyExists("bla", "dii", "USD", 1234, 123) {
t.Error("Test Failed - stats ExchangeInfoAlreadyExists found incorrect exchange.")
p.FirstCurrency = "dii"
if AlreadyExists("bla", p, "SPOT", 1234, 123) {
t.Error("Test Failed - stats AlreadyExists found incorrect exchange.")
}
}
func TestSortExchangesByVolume(t *testing.T) {
topVolume := SortExchangesByVolume("BTC", "USD", true)
p := pair.NewCurrencyPair("BTC", "USD")
topVolume := SortExchangesByVolume(p, "SPOT", true)
if topVolume[0].Exchange != "sillyexchange" {
t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.")
}
topVolume = SortExchangesByVolume(p, "SPOT", false)
if topVolume[0].Exchange != "ANX" {
t.Error("Test Failed - stats SortExchangesByVolume incorrectly sorted values.")
}
}
func TestSortExchangesByPrice(t *testing.T) {
topPrice := SortExchangesByPrice("BTC", "USD", true)
p := pair.NewCurrencyPair("BTC", "USD")
topPrice := SortExchangesByPrice(p, "SPOT", true)
if topPrice[0].Exchange != "sillyexchange" {
t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.")
}
topPrice = SortExchangesByPrice(p, "SPOT", false)
if topPrice[0].Exchange != "ANX" {
t.Error("Test Failed - stats SortExchangesByPrice incorrectly sorted values.")
}
}

View File

@@ -8,15 +8,22 @@ import (
"github.com/thrasher-/gocryptotrader/currency/pair"
)
var (
// Const values for the ticker package
const (
ErrTickerForExchangeNotFound = "Ticker for exchange does not exist."
ErrPrimaryCurrencyNotFound = "Error primary currency for ticker not found."
ErrSecondaryCurrencyNotFound = "Error secondary currency for ticker not found."
Spot = "SPOT"
)
// Vars for the ticker package
var (
Tickers []Ticker
)
type TickerPrice struct {
// Price struct stores the currency pair and pricing information
type Price struct {
Pair pair.CurrencyPair `json:"Pair"`
CurrencyPair string `json:"CurrencyPair"`
Last float64 `json:"Last"`
@@ -28,50 +35,55 @@ type TickerPrice struct {
PriceATH float64 `json:"PriceATH"`
}
// Ticker struct holds the ticker information for a currency pair and type
type Ticker struct {
Price map[pair.CurrencyItem]map[pair.CurrencyItem]TickerPrice
Price map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Price
ExchangeName string
}
func (t *Ticker) PriceToString(p pair.CurrencyPair, priceType string) string {
// PriceToString returns the string version of a stored price field
func (t *Ticker) PriceToString(p pair.CurrencyPair, priceType, tickerType string) string {
priceType = common.StringToLower(priceType)
switch priceType {
case "last":
return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Last, 'f', -1, 64)
return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Last, 'f', -1, 64)
case "high":
return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].High, 'f', -1, 64)
return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].High, 'f', -1, 64)
case "low":
return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Low, 'f', -1, 64)
return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Low, 'f', -1, 64)
case "bid":
return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Bid, 'f', -1, 64)
return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Bid, 'f', -1, 64)
case "ask":
return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Ask, 'f', -1, 64)
return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Ask, 'f', -1, 64)
case "volume":
return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].Volume, 'f', -1, 64)
return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].Volume, 'f', -1, 64)
case "ath":
return strconv.FormatFloat(t.Price[p.GetFirstCurrency()][p.GetSecondCurrency()].PriceATH, 'f', -1, 64)
return strconv.FormatFloat(t.Price[p.FirstCurrency][p.SecondCurrency][tickerType].PriceATH, 'f', -1, 64)
default:
return ""
}
}
func GetTicker(exchange string, p pair.CurrencyPair) (TickerPrice, error) {
// GetTicker checks and returns a requested ticker if it exists
func GetTicker(exchange string, p pair.CurrencyPair, tickerType string) (Price, error) {
ticker, err := GetTickerByExchange(exchange)
if err != nil {
return TickerPrice{}, err
return Price{}, err
}
if !FirstCurrencyExists(exchange, p.GetFirstCurrency()) {
return TickerPrice{}, errors.New(ErrPrimaryCurrencyNotFound)
if !FirstCurrencyExists(exchange, p.FirstCurrency) {
return Price{}, errors.New(ErrPrimaryCurrencyNotFound)
}
if !SecondCurrencyExists(exchange, p) {
return TickerPrice{}, errors.New(ErrSecondaryCurrencyNotFound)
return Price{}, errors.New(ErrSecondaryCurrencyNotFound)
}
return ticker.Price[p.GetFirstCurrency()][p.GetSecondCurrency()], nil
return ticker.Price[p.FirstCurrency][p.SecondCurrency][tickerType], nil
}
// GetTickerByExchange returns an exchange Ticker
func GetTickerByExchange(exchange string) (*Ticker, error) {
for _, y := range Tickers {
if y.ExchangeName == exchange {
@@ -81,6 +93,8 @@ func GetTickerByExchange(exchange string) (*Ticker, error) {
return nil, errors.New(ErrTickerForExchangeNotFound)
}
// FirstCurrencyExists checks to see if the first currency of the Price map
// exists
func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool {
for _, y := range Tickers {
if y.ExchangeName == exchange {
@@ -92,6 +106,8 @@ func FirstCurrencyExists(exchange string, currency pair.CurrencyItem) bool {
return false
}
// SecondCurrencyExists checks to see if the second currency of the Price map
// exists
func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool {
for _, y := range Tickers {
if y.ExchangeName == exchange {
@@ -105,40 +121,49 @@ func SecondCurrencyExists(exchange string, p pair.CurrencyPair) bool {
return false
}
func CreateNewTicker(exchangeName string, p pair.CurrencyPair, tickerNew TickerPrice) Ticker {
// CreateNewTicker creates a new Ticker
func CreateNewTicker(exchangeName string, p pair.CurrencyPair, tickerNew Price, tickerType string) Ticker {
ticker := Ticker{}
ticker.ExchangeName = exchangeName
ticker.Price = make(map[pair.CurrencyItem]map[pair.CurrencyItem]TickerPrice)
sMap := make(map[pair.CurrencyItem]TickerPrice)
sMap[p.GetSecondCurrency()] = tickerNew
ticker.Price[p.GetFirstCurrency()] = sMap
ticker.Price = make(map[pair.CurrencyItem]map[pair.CurrencyItem]map[string]Price)
a := make(map[pair.CurrencyItem]map[string]Price)
b := make(map[string]Price)
b[tickerType] = tickerNew
a[p.SecondCurrency] = b
ticker.Price[p.FirstCurrency] = a
Tickers = append(Tickers, ticker)
return ticker
}
func ProcessTicker(exchangeName string, p pair.CurrencyPair, tickerNew TickerPrice) {
// ProcessTicker processes incoming tickers, creating or updating the Tickers
// list
func ProcessTicker(exchangeName string, p pair.CurrencyPair, tickerNew Price, tickerType string) {
tickerNew.CurrencyPair = p.Pair().String()
if len(Tickers) == 0 {
CreateNewTicker(exchangeName, p, tickerNew)
//issue - not appending
CreateNewTicker(exchangeName, p, tickerNew, tickerType)
return
} else {
ticker, err := GetTickerByExchange(exchangeName)
if err != nil {
CreateNewTicker(exchangeName, p, tickerNew)
}
ticker, err := GetTickerByExchange(exchangeName)
if err != nil {
CreateNewTicker(exchangeName, p, tickerNew, tickerType)
return
}
if FirstCurrencyExists(exchangeName, p.FirstCurrency) {
if !SecondCurrencyExists(exchangeName, p) {
a := ticker.Price[p.FirstCurrency]
b := make(map[string]Price)
b[tickerType] = tickerNew
a[p.SecondCurrency] = b
ticker.Price[p.FirstCurrency] = a
return
}
if FirstCurrencyExists(exchangeName, p.GetFirstCurrency()) {
if !SecondCurrencyExists(exchangeName, p) {
second := ticker.Price[p.GetFirstCurrency()]
second[p.GetSecondCurrency()] = tickerNew
ticker.Price[p.GetFirstCurrency()] = second
return
}
}
sMap := make(map[pair.CurrencyItem]TickerPrice)
sMap[p.GetSecondCurrency()] = tickerNew
ticker.Price[p.GetFirstCurrency()] = sMap
}
a := make(map[pair.CurrencyItem]map[string]Price)
b := make(map[string]Price)
b[tickerType] = tickerNew
a[p.SecondCurrency] = b
ticker.Price[p.FirstCurrency] = a
}

View File

@@ -8,10 +8,8 @@ import (
)
func TestPriceToString(t *testing.T) {
t.Parallel()
newPair := pair.NewCurrencyPair("BTC", "USD")
priceStruct := TickerPrice{
priceStruct := Price{
Pair: newPair,
CurrencyPair: newPair.Pair().String(),
Last: 1200,
@@ -23,39 +21,37 @@ func TestPriceToString(t *testing.T) {
PriceATH: 1337,
}
newTicker := CreateNewTicker("ANX", newPair, priceStruct)
newTicker := CreateNewTicker("ANX", newPair, priceStruct, Spot)
if newTicker.PriceToString(newPair, "last") != "1200" {
if newTicker.PriceToString(newPair, "last", Spot) != "1200" {
t.Error("Test Failed - ticker PriceToString last value is incorrect")
}
if newTicker.PriceToString(newPair, "high") != "1298" {
if newTicker.PriceToString(newPair, "high", Spot) != "1298" {
t.Error("Test Failed - ticker PriceToString high value is incorrect")
}
if newTicker.PriceToString(newPair, "low") != "1148" {
if newTicker.PriceToString(newPair, "low", Spot) != "1148" {
t.Error("Test Failed - ticker PriceToString low value is incorrect")
}
if newTicker.PriceToString(newPair, "bid") != "1195" {
if newTicker.PriceToString(newPair, "bid", Spot) != "1195" {
t.Error("Test Failed - ticker PriceToString bid value is incorrect")
}
if newTicker.PriceToString(newPair, "ask") != "1220" {
if newTicker.PriceToString(newPair, "ask", Spot) != "1220" {
t.Error("Test Failed - ticker PriceToString ask value is incorrect")
}
if newTicker.PriceToString(newPair, "volume") != "5" {
if newTicker.PriceToString(newPair, "volume", Spot) != "5" {
t.Error("Test Failed - ticker PriceToString volume value is incorrect")
}
if newTicker.PriceToString(newPair, "ath") != "1337" {
if newTicker.PriceToString(newPair, "ath", Spot) != "1337" {
t.Error("Test Failed - ticker PriceToString ath value is incorrect")
}
if newTicker.PriceToString(newPair, "obtuse") != "" {
if newTicker.PriceToString(newPair, "obtuse", Spot) != "" {
t.Error("Test Failed - ticker PriceToString obtuse value is incorrect")
}
}
func TestGetTicker(t *testing.T) {
t.Parallel()
newPair := pair.NewCurrencyPair("BTC", "USD")
priceStruct := TickerPrice{
priceStruct := Price{
Pair: newPair,
CurrencyPair: newPair.Pair().String(),
Last: 1200,
@@ -67,23 +63,47 @@ func TestGetTicker(t *testing.T) {
PriceATH: 1337,
}
bitfinexTicker := CreateNewTicker("bitfinex", newPair, priceStruct)
Tickers = append(Tickers, bitfinexTicker)
tickerPrice, err := GetTicker("bitfinex", newPair)
ProcessTicker("bitfinex", newPair, priceStruct, Spot)
tickerPrice, err := GetTicker("bitfinex", newPair, Spot)
if err != nil {
t.Errorf("Test Failed - Ticker GetTicker init error: %s", err)
}
if tickerPrice.CurrencyPair != "BTCUSD" {
t.Error("Test Failed - ticker tickerPrice.CurrencyPair value is incorrect")
}
_, err = GetTicker("blah", newPair, Spot)
if err == nil {
t.Fatal("Test Failed. TestGetTicker returned nil error on invalid exchange")
}
newPair.FirstCurrency = "ETH"
_, err = GetTicker("bitfinex", newPair, Spot)
if err == nil {
t.Fatal("Test Failed. TestGetTicker returned ticker for invalid first currency")
}
btcltcPair := pair.NewCurrencyPair("BTC", "LTC")
_, err = GetTicker("bitfinex", btcltcPair, Spot)
if err == nil {
t.Fatal("Test Failed. TestGetTicker returned ticker for invalid second currency")
}
priceStruct.PriceATH = 9001
ProcessTicker("bitfinex", newPair, priceStruct, "futures_3m")
tickerPrice, err = GetTicker("bitfinex", newPair, "futures_3m")
if err != nil {
t.Errorf("Test Failed - Ticker GetTicker init error: %s", err)
}
if tickerPrice.PriceATH != 9001 {
t.Error("Test Failed - ticker tickerPrice.PriceATH value is incorrect")
}
}
func TestGetTickerByExchange(t *testing.T) {
t.Parallel()
newPair := pair.NewCurrencyPair("BTC", "USD")
priceStruct := TickerPrice{
priceStruct := Price{
Pair: newPair,
CurrencyPair: newPair.Pair().String(),
Last: 1200,
@@ -95,7 +115,7 @@ func TestGetTickerByExchange(t *testing.T) {
PriceATH: 1337,
}
anxTicker := CreateNewTicker("ANX", newPair, priceStruct)
anxTicker := CreateNewTicker("ANX", newPair, priceStruct, Spot)
Tickers = append(Tickers, anxTicker)
tickerPtr, err := GetTickerByExchange("ANX")
@@ -108,10 +128,8 @@ func TestGetTickerByExchange(t *testing.T) {
}
func TestFirstCurrencyExists(t *testing.T) {
t.Parallel()
newPair := pair.NewCurrencyPair("BTC", "USD")
priceStruct := TickerPrice{
priceStruct := Price{
Pair: newPair,
CurrencyPair: newPair.Pair().String(),
Last: 1200,
@@ -123,7 +141,7 @@ func TestFirstCurrencyExists(t *testing.T) {
PriceATH: 1337,
}
alphaTicker := CreateNewTicker("alphapoint", newPair, priceStruct)
alphaTicker := CreateNewTicker("alphapoint", newPair, priceStruct, Spot)
Tickers = append(Tickers, alphaTicker)
if !FirstCurrencyExists("alphapoint", "BTC") {
@@ -138,7 +156,7 @@ func TestSecondCurrencyExists(t *testing.T) {
t.Parallel()
newPair := pair.NewCurrencyPair("BTC", "USD")
priceStruct := TickerPrice{
priceStruct := Price{
Pair: newPair,
CurrencyPair: newPair.Pair().String(),
Last: 1200,
@@ -150,7 +168,7 @@ func TestSecondCurrencyExists(t *testing.T) {
PriceATH: 1337,
}
bitstampTicker := CreateNewTicker("bitstamp", newPair, priceStruct)
bitstampTicker := CreateNewTicker("bitstamp", newPair, priceStruct, "SPOT")
Tickers = append(Tickers, bitstampTicker)
if !SecondCurrencyExists("bitstamp", newPair) {
@@ -164,10 +182,8 @@ func TestSecondCurrencyExists(t *testing.T) {
}
func TestCreateNewTicker(t *testing.T) {
t.Parallel()
newPair := pair.NewCurrencyPair("BTC", "USD")
priceStruct := TickerPrice{
priceStruct := Price{
Pair: newPair,
CurrencyPair: newPair.Pair().String(),
Last: 1200,
@@ -179,7 +195,7 @@ func TestCreateNewTicker(t *testing.T) {
PriceATH: 1337,
}
newTicker := CreateNewTicker("ANX", newPair, priceStruct)
newTicker := CreateNewTicker("ANX", newPair, priceStruct, Spot)
if reflect.ValueOf(newTicker).NumField() != 2 {
t.Error("Test Failed - ticker CreateNewTicker struct change/or updated")
@@ -191,40 +207,39 @@ func TestCreateNewTicker(t *testing.T) {
t.Error("Test Failed - ticker CreateNewTicker.ExchangeName value is not ANX")
}
if newTicker.Price["BTC"]["USD"].Pair.Pair().String() != "BTCUSD" {
if newTicker.Price["BTC"]["USD"][Spot].Pair.Pair().String() != "BTCUSD" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Pair.Pair().String() value is not expected 'BTCUSD'")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Ask).String() != "float64" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Ask).String() != "float64" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Ask value is not a float64")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Bid).String() != "float64" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Bid).String() != "float64" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Bid value is not a float64")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].CurrencyPair).String() != "string" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].CurrencyPair).String() != "string" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].CurrencyPair value is not a string")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].High).String() != "float64" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].High).String() != "float64" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].High value is not a float64")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Last).String() != "float64" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Last).String() != "float64" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Last value is not a float64")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Low).String() != "float64" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Low).String() != "float64" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Low value is not a float64")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].PriceATH).String() != "float64" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].PriceATH).String() != "float64" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].PriceATH value is not a float64")
}
if reflect.TypeOf(newTicker.Price["BTC"]["USD"].Volume).String() != "float64" {
if reflect.TypeOf(newTicker.Price["BTC"]["USD"][Spot].Volume).String() != "float64" {
t.Error("Test Failed - ticker newTicker.Price[BTC][USD].Volume value is not a float64")
}
}
func TestProcessTicker(t *testing.T) { //non-appending function to tickers
t.Parallel()
Tickers = []Ticker{}
newPair := pair.NewCurrencyPair("BTC", "USD")
priceStruct := TickerPrice{
priceStruct := Price{
Pair: newPair,
CurrencyPair: newPair.Pair().String(),
Last: 1200,
@@ -236,5 +251,28 @@ func TestProcessTicker(t *testing.T) { //non-appending function to tickers
PriceATH: 1337,
}
ProcessTicker("btcc", newPair, priceStruct)
ProcessTicker("btcc", newPair, priceStruct, Spot)
result, err := GetTicker("btcc", newPair, Spot)
if err != nil {
t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker")
}
if result.Pair.Pair() != newPair.Pair() {
t.Fatal("Test failed. TestProcessTicker pair mismatch")
}
secondPair := pair.NewCurrencyPair("BTC", "AUD")
priceStruct.Pair = secondPair
ProcessTicker("btcc", secondPair, priceStruct, Spot)
result, err = GetTicker("btcc", secondPair, Spot)
if err != nil {
t.Fatal("Test failed. TestProcessTicker failed to create and return a new ticker")
}
result, err = GetTicker("btcc", newPair, Spot)
if err != nil {
t.Fatal("Test failed. TestProcessTicker failed to return an existing ticker")
}
}

393
exchanges/wex/wex.go Normal file
View File

@@ -0,0 +1,393 @@
package wex
import (
"errors"
"fmt"
"log"
"net/url"
"strconv"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
wexAPIPublicURL = "https://wex.nz/api"
wexAPIPrivateURL = "https://wex.nz/tapi"
wexAPIPublicVersion = "3"
wexAPIPrivateVersion = "1"
wexInfo = "info"
wexTicker = "ticker"
wexDepth = "depth"
wexTrades = "trades"
wexAccountInfo = "getInfo"
wexTrade = "Trade"
wexActiveOrders = "ActiveOrders"
wexOrderInfo = "OrderInfo"
wexCancelOrder = "CancelOrder"
wexTradeHistory = "TradeHistory"
wexTransactionHistory = "TransHistory"
wexWithdrawCoin = "WithdrawCoin"
wexCoinDepositAddress = "CoinDepositAddress"
wexCreateCoupon = "CreateCoupon"
wexRedeemCoupon = "RedeemCoupon"
)
// WEX is the overarching type across the wex package
type WEX struct {
exchange.Base
Ticker map[string]Ticker
}
// SetDefaults sets current default value for WEX
func (w *WEX) SetDefaults() {
w.Name = "WEX"
w.Enabled = false
w.Fee = 0.2
w.Verbose = false
w.Websocket = false
w.RESTPollingDelay = 10
w.Ticker = make(map[string]Ticker)
w.RequestCurrencyPairFormat.Delimiter = "_"
w.RequestCurrencyPairFormat.Uppercase = false
w.RequestCurrencyPairFormat.Separator = "-"
w.ConfigCurrencyPairFormat.Delimiter = ""
w.ConfigCurrencyPairFormat.Uppercase = true
w.AssetTypes = []string{ticker.Spot}
}
// Setup sets exchange configuration parameters for WEX
func (w *WEX) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
w.SetEnabled(false)
} else {
w.Enabled = true
w.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
w.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
w.RESTPollingDelay = exch.RESTPollingDelay
w.Verbose = exch.Verbose
w.Websocket = exch.Websocket
w.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
w.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
w.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := w.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = w.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
}
}
// GetFee returns the exchange fee
func (w *WEX) GetFee() float64 {
return w.Fee
}
// GetInfo returns the WEX info
func (w *WEX) GetInfo() (Info, error) {
req := fmt.Sprintf("%s/%s/%s/", wexAPIPublicURL, wexAPIPublicVersion, wexInfo)
resp := Info{}
err := common.SendHTTPGetRequest(req, true, &resp)
if err != nil {
return resp, err
}
return resp, nil
}
// GetTicker returns a ticker for a specific currency
func (w *WEX) GetTicker(symbol string) (map[string]Ticker, error) {
type Response struct {
Data map[string]Ticker
}
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTicker, symbol)
err := common.SendHTTPGetRequest(req, true, &response.Data)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetDepth returns the depth for a specific currency
func (w *WEX) GetDepth(symbol string) (Orderbook, error) {
type Response struct {
Data map[string]Orderbook
}
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexDepth, symbol)
err := common.SendHTTPGetRequest(req, true, &response.Data)
if err != nil {
return Orderbook{}, err
}
depth := response.Data[symbol]
return depth, nil
}
// GetTrades returns the trades for a specific currency
func (w *WEX) GetTrades(symbol string) ([]Trades, error) {
type Response struct {
Data map[string][]Trades
}
response := Response{}
req := fmt.Sprintf("%s/%s/%s/%s", wexAPIPublicURL, wexAPIPublicVersion, wexTrades, symbol)
err := common.SendHTTPGetRequest(req, true, &response.Data)
if err != nil {
return nil, err
}
trades := response.Data[symbol]
return trades, nil
}
// GetAccountInfo returns a users account info
func (w *WEX) GetAccountInfo() (AccountInfo, error) {
var result AccountInfo
err := w.SendAuthenticatedHTTPRequest(wexAccountInfo, url.Values{}, &result)
if err != nil {
return result, err
}
return result, nil
}
// GetActiveOrders returns the active orders for a specific currency
func (w *WEX) GetActiveOrders(pair string) (map[string]ActiveOrders, error) {
req := url.Values{}
req.Add("pair", pair)
var result map[string]ActiveOrders
err := w.SendAuthenticatedHTTPRequest(wexActiveOrders, req, &result)
if err != nil {
return result, err
}
return result, nil
}
// GetOrderInfo returns the order info for a specific order ID
func (w *WEX) GetOrderInfo(OrderID int64) (map[string]OrderInfo, error) {
req := url.Values{}
req.Add("order_id", strconv.FormatInt(OrderID, 10))
var result map[string]OrderInfo
err := w.SendAuthenticatedHTTPRequest(wexOrderInfo, req, &result)
if err != nil {
return result, err
}
return result, nil
}
// CancelOrder cancels an order for a specific order ID
func (w *WEX) CancelOrder(OrderID int64) (bool, error) {
req := url.Values{}
req.Add("order_id", strconv.FormatInt(OrderID, 10))
var result CancelOrder
err := w.SendAuthenticatedHTTPRequest(wexCancelOrder, req, &result)
if err != nil {
return false, err
}
return true, nil
}
// Trade places an order and returns the order ID if successful or an error
func (w *WEX) Trade(pair, orderType string, amount, price float64) (int64, error) {
req := url.Values{}
req.Add("pair", pair)
req.Add("type", orderType)
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("rate", strconv.FormatFloat(price, 'f', -1, 64))
var result Trade
err := w.SendAuthenticatedHTTPRequest(wexTrade, req, &result)
if err != nil {
return 0, err
}
return int64(result.OrderID), nil
}
// GetTransactionHistory returns the transaction history
func (w *WEX) GetTransactionHistory(TIDFrom, Count, TIDEnd int64, order, since, end string) (map[string]TransHistory, error) {
req := url.Values{}
req.Add("from", strconv.FormatInt(TIDFrom, 10))
req.Add("count", strconv.FormatInt(Count, 10))
req.Add("from_id", strconv.FormatInt(TIDFrom, 10))
req.Add("end_id", strconv.FormatInt(TIDEnd, 10))
req.Add("order", order)
req.Add("since", since)
req.Add("end", end)
var result map[string]TransHistory
err := w.SendAuthenticatedHTTPRequest(wexTransactionHistory, req, &result)
if err != nil {
return result, err
}
return result, nil
}
// GetTradeHistory returns the trade history
func (w *WEX) GetTradeHistory(TIDFrom, Count, TIDEnd int64, order, since, end, pair string) (map[string]TradeHistory, error) {
req := url.Values{}
req.Add("from", strconv.FormatInt(TIDFrom, 10))
req.Add("count", strconv.FormatInt(Count, 10))
req.Add("from_id", strconv.FormatInt(TIDFrom, 10))
req.Add("end_id", strconv.FormatInt(TIDEnd, 10))
req.Add("order", order)
req.Add("since", since)
req.Add("end", end)
req.Add("pair", pair)
var result map[string]TradeHistory
err := w.SendAuthenticatedHTTPRequest(wexTradeHistory, req, &result)
if err != nil {
return result, err
}
return result, nil
}
// WithdrawCoins withdraws coins for a specific coin
func (w *WEX) WithdrawCoins(coin string, amount float64, address string) (WithdrawCoins, error) {
req := url.Values{}
req.Add("coinName", coin)
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("address", address)
var result WithdrawCoins
err := w.SendAuthenticatedHTTPRequest(wexWithdrawCoin, req, &result)
if err != nil {
return result, err
}
return result, nil
}
// CoinDepositAddress returns the deposit address for a specific currency
func (w *WEX) CoinDepositAddress(coin string) (string, error) {
req := url.Values{}
req.Add("coinName", coin)
var result CoinDepositAddress
err := w.SendAuthenticatedHTTPRequest(wexCoinDepositAddress, req, &result)
if err != nil {
return "", nil
}
return result.Address, nil
}
// CreateCoupon creates an exchange coupon for a sepcific currency
func (w *WEX) CreateCoupon(currency string, amount float64) (CreateCoupon, error) {
req := url.Values{}
req.Add("currency", currency)
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
var result CreateCoupon
err := w.SendAuthenticatedHTTPRequest(wexCreateCoupon, req, &result)
if err != nil {
return result, err
}
return result, nil
}
// RedeemCoupon redeems an exchange coupon
func (w *WEX) RedeemCoupon(coupon string) (RedeemCoupon, error) {
req := url.Values{}
req.Add("coupon", coupon)
var result RedeemCoupon
err := w.SendAuthenticatedHTTPRequest(wexRedeemCoupon, req, &result)
if err != nil {
return result, err
}
return result, nil
}
// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to WEX
func (w *WEX) SendAuthenticatedHTTPRequest(method string, values url.Values, result interface{}) (err error) {
if !w.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, w.Name)
}
if w.Nonce.Get() == 0 {
w.Nonce.Set(time.Now().Unix())
} else {
w.Nonce.Inc()
}
values.Set("nonce", w.Nonce.String())
values.Set("method", method)
encoded := values.Encode()
hmac := common.GetHMAC(common.HashSHA512, []byte(encoded), []byte(w.APISecret))
if w.Verbose {
log.Printf("Sending POST request to %s calling method %s with params %s\n", wexAPIPrivateURL, method, encoded)
}
headers := make(map[string]string)
headers["Key"] = w.APIKey
headers["Sign"] = common.HexEncodeToString(hmac)
headers["Content-Type"] = "application/x-www-form-urlencoded"
resp, err := common.SendHTTPRequest("POST", wexAPIPrivateURL, headers, strings.NewReader(encoded))
if err != nil {
return err
}
response := Response{}
err = common.JSONDecode([]byte(resp), &response)
if err != nil {
return err
}
if response.Success != 1 {
return errors.New(response.Error)
}
JSONEncoded, err := common.JSONEncode(response.Return)
if err != nil {
return err
}
err = common.JSONDecode(JSONEncoded, &result)
if err != nil {
return err
}
return nil
}

View File

@@ -1,23 +1,26 @@
package btce
package wex
type BTCeTicker struct {
High float64
Low float64
Avg float64
Vol float64
Vol_cur float64
Last float64
Buy float64
Sell float64
Updated int64
// Ticker stores the ticker information
type Ticker struct {
High float64
Low float64
Avg float64
Vol float64
VolumeCurrent float64 `json:"vol_cur"`
Last float64
Buy float64
Sell float64
Updated int64
}
type BTCEOrderbook struct {
// Orderbook stores the asks and bids orderbook information
type Orderbook struct {
Asks [][]float64 `json:"asks"`
Bids [][]float64 `json:"bids"`
}
type BTCETrades struct {
// Trades stores trade information
type Trades struct {
Type string `json:"type"`
Price float64 `json:"bid"`
Amount float64 `json:"amount"`
@@ -25,13 +28,15 @@ type BTCETrades struct {
Timestamp int64 `json:"timestamp"`
}
type BTCEResponse struct {
// Response is a generic struct used for exchange API request result
type Response struct {
Return interface{} `json:"return"`
Success int `json:"success"`
Error string `json:"error"`
}
type BTCEPair struct {
// Pair holds pair information
type Pair struct {
DecimalPlaces int `json:"decimal_places"`
MinPrice float64 `json:"min_price"`
MaxPrice float64 `json:"max_price"`
@@ -40,12 +45,14 @@ type BTCEPair struct {
Fee float64 `json:"fee"`
}
type BTCEInfo struct {
ServerTime int64 `json:"server_time"`
Pairs map[string]BTCEPair `json:"pairs"`
// Info holds server time and pair information
type Info struct {
ServerTime int64 `json:"server_time"`
Pairs map[string]Pair `json:"pairs"`
}
type BTCEAccountInfo struct {
// AccountInfo stores the account information for a user
type AccountInfo struct {
Funds map[string]float64 `json:"funds"`
OpenOrders int `json:"open_orders"`
Rights struct {
@@ -57,7 +64,8 @@ type BTCEAccountInfo struct {
TransactionCount int `json:"transaction_count"`
}
type BTCEActiveOrders struct {
// ActiveOrders stores active order information
type ActiveOrders struct {
Pair string `json:"pair"`
Type string `json:"sell"`
Amount float64 `json:"amount"`
@@ -66,7 +74,8 @@ type BTCEActiveOrders struct {
Status int `json:"status"`
}
type BTCEOrderInfo struct {
// OrderInfo stores order information
type OrderInfo struct {
Pair string `json:"pair"`
Type string `json:"sell"`
StartAmount float64 `json:"start_amount"`
@@ -76,19 +85,22 @@ type BTCEOrderInfo struct {
Status int `json:"status"`
}
type BTCECancelOrder struct {
// CancelOrder is used for the CancelOrder API request response
type CancelOrder struct {
OrderID float64 `json:"order_id"`
Funds map[string]float64 `json:"funds"`
}
type BTCETrade struct {
// Trade stores the trade information
type Trade struct {
Received float64 `json:"received"`
Remains float64 `json:"remains"`
OrderID float64 `json:"order_id"`
Funds map[string]float64 `json:"funds"`
}
type BTCETransHistory struct {
// TransHistory stores transaction history
type TransHistory struct {
Type int `json:"type"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
@@ -97,7 +109,8 @@ type BTCETransHistory struct {
Timestamp float64 `json:"timestamp"`
}
type BTCETradeHistory struct {
// TradeHistory stores trade history
type TradeHistory struct {
Pair string `json:"pair"`
Type string `json:"type"`
Amount float64 `json:"amount"`
@@ -107,19 +120,27 @@ type BTCETradeHistory struct {
Timestamp float64 `json:"timestamp"`
}
type BTCEWithdrawCoins struct {
// CoinDepositAddress stores a curency deposit address
type CoinDepositAddress struct {
Address string `json:"address"`
}
// WithdrawCoins stores information for a withdrawcoins request
type WithdrawCoins struct {
TID int64 `json:"tId"`
AmountSent float64 `json:"amountSent"`
Funds map[string]float64 `json:"funds"`
}
type BTCECreateCoupon struct {
// CreateCoupon stores information coupon information
type CreateCoupon struct {
Coupon string `json:"coupon"`
TransID int64 `json:"transID"`
Funds map[string]float64 `json:"funds"`
}
type BTCERedeemCoupon struct {
// RedeemCoupon stores redeem coupon information
type RedeemCoupon struct {
CouponAmount float64 `json:"couponAmount,string"`
CouponCurrency string `json:"couponCurrency"`
TransID int64 `json:"transID"`

View File

@@ -0,0 +1,114 @@
package wex
import (
"log"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// Start starts the WEX go routine
func (w *WEX) Start() {
go w.Run()
}
// Run implements the WEX wrapper
func (w *WEX) Run() {
if w.Verbose {
log.Printf("%s Websocket: %s.", w.GetName(), common.IsEnabled(w.Websocket))
log.Printf("%s polling delay: %ds.\n", w.GetName(), w.RESTPollingDelay)
log.Printf("%s %d currencies enabled: %s.\n", w.GetName(), len(w.EnabledPairs), w.EnabledPairs)
}
}
// UpdateTicker updates and returns the ticker for a currency pair
func (w *WEX) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
pairsCollated, err := exchange.GetAndFormatExchangeCurrencies(w.Name, w.GetEnabledCurrencies())
if err != nil {
return tickerPrice, err
}
result, err := w.GetTicker(pairsCollated.String())
if err != nil {
return tickerPrice, err
}
for _, x := range w.GetEnabledCurrencies() {
currency := exchange.FormatExchangeCurrency(w.Name, x).Lower().String()
var tp ticker.Price
tp.Pair = x
tp.Last = result[currency].Last
tp.Ask = result[currency].Sell
tp.Bid = result[currency].Buy
tp.Last = result[currency].Last
tp.Low = result[currency].Low
tp.Volume = result[currency].VolumeCurrent
ticker.ProcessTicker(w.Name, x, tp, assetType)
}
return ticker.GetTicker(w.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (w *WEX) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tick, err := ticker.GetTicker(w.GetName(), p, assetType)
if err != nil {
return w.UpdateTicker(p, assetType)
}
return tick, nil
}
// GetOrderbookEx returns the orderbook for a currency pair
func (w *WEX) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(w.GetName(), p, assetType)
if err == nil {
return w.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (w *WEX) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
var orderBook orderbook.Base
orderbookNew, err := w.GetDepth(exchange.FormatExchangeCurrency(w.Name, p).String())
if err != nil {
return orderBook, err
}
for x := range orderbookNew.Bids {
data := orderbookNew.Bids[x]
orderBook.Bids = append(orderBook.Bids, orderbook.Item{Price: data[0], Amount: data[1]})
}
for x := range orderbookNew.Asks {
data := orderbookNew.Asks[x]
orderBook.Asks = append(orderBook.Asks, orderbook.Item{Price: data[0], Amount: data[1]})
}
orderbook.ProcessOrderbook(w.GetName(), p, orderBook, assetType)
return orderbook.GetOrderbook(w.Name, p, assetType)
}
// GetExchangeAccountInfo retrieves balances for all enabled currencies for the
// WEX exchange
func (w *WEX) GetExchangeAccountInfo() (exchange.AccountInfo, error) {
var response exchange.AccountInfo
response.ExchangeName = w.GetName()
accountBalance, err := w.GetAccountInfo()
if err != nil {
return response, err
}
for x, y := range accountBalance.Funds {
var exchangeCurrency exchange.AccountCurrencyInfo
exchangeCurrency.CurrencyName = common.StringToUpper(x)
exchangeCurrency.TotalValue = y
exchangeCurrency.Hold = 0
response.Currencies = append(response.Currencies, exchangeCurrency)
}
return response, nil
}

107
helpers.go Normal file
View File

@@ -0,0 +1,107 @@
package main
import (
"errors"
"fmt"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// GetSpecificOrderbook returns a specific orderbook given the currency,
// exchangeName and assetType
func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.Base, error) {
var specificOrderbook orderbook.Base
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificOrderbook, err = bot.exchanges[i].GetOrderbookEx(
pair.NewCurrencyPairFromString(currency),
assetType,
)
break
}
}
}
return specificOrderbook, err
}
// GetSpecificTicker returns a specific ticker given the currency,
// exchangeName and assetType
func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, error) {
var specificTicker ticker.Price
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificTicker, err = bot.exchanges[i].GetTickerPrice(
pair.NewCurrencyPairFromString(currency),
assetType,
)
break
}
}
}
return specificTicker, err
}
// GetCollatedExchangeAccountInfoByCoin collates individual exchange account
// information and turns into into a map string of
// exchange.AccountCurrencyInfo
func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.AccountInfo) map[string]exchange.AccountCurrencyInfo {
result := make(map[string]exchange.AccountCurrencyInfo)
for i := 0; i < len(accounts); i++ {
for j := 0; j < len(accounts[i].Currencies); j++ {
currencyName := accounts[i].Currencies[j].CurrencyName
avail := accounts[i].Currencies[j].TotalValue
onHold := accounts[i].Currencies[j].Hold
info, ok := result[currencyName]
if !ok {
accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail}
result[currencyName] = accountInfo
} else {
info.Hold += onHold
info.TotalValue += avail
result[currencyName] = info
}
}
}
return result
}
// GetAccountCurrencyInfoByExchangeName returns info for an exchange
func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) {
for i := 0; i < len(accounts); i++ {
if accounts[i].ExchangeName == exchangeName {
return accounts[i], nil
}
}
return exchange.AccountInfo{}, errors.New(exchange.ErrExchangeNotFound)
}
// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest
// price for a given currency pair and asset type
func GetExchangeHighestPriceByCurrencyPair(p pair.CurrencyPair, assetType string) (string, error) {
result := stats.SortExchangesByPrice(p, assetType, true)
if len(result) != 1 {
return "", fmt.Errorf("no stats for supplied currency pair and asset type")
}
return result[0].Exchange, nil
}
// GetExchangeLowestPriceByCurrencyPair returns the exchange with the lowest
// price for a given currency pair and asset type
func GetExchangeLowestPriceByCurrencyPair(p pair.CurrencyPair, assetType string) (string, error) {
result := stats.SortExchangesByPrice(p, assetType, false)
if len(result) != 1 {
return "", fmt.Errorf("no stats for supplied currency pair and asset type")
}
return result[0].Exchange, nil
}

166
main.go
View File

@@ -1,6 +1,7 @@
package main
import (
"flag"
"log"
"net/http"
"os"
@@ -16,8 +17,8 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/anx"
"github.com/thrasher-/gocryptotrader/exchanges/bitfinex"
"github.com/thrasher-/gocryptotrader/exchanges/bitstamp"
"github.com/thrasher-/gocryptotrader/exchanges/bittrex"
"github.com/thrasher-/gocryptotrader/exchanges/btcc"
"github.com/thrasher-/gocryptotrader/exchanges/btce"
"github.com/thrasher-/gocryptotrader/exchanges/btcmarkets"
"github.com/thrasher-/gocryptotrader/exchanges/coinut"
"github.com/thrasher-/gocryptotrader/exchanges/gdax"
@@ -31,16 +32,19 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/okcoin"
"github.com/thrasher-/gocryptotrader/exchanges/poloniex"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wex"
"github.com/thrasher-/gocryptotrader/portfolio"
"github.com/thrasher-/gocryptotrader/smsglobal"
)
// ExchangeMain contains all the necessary exchange packages
type ExchangeMain struct {
anx anx.ANX
btcc btcc.BTCC
bitstamp bitstamp.Bitstamp
bitfinex bitfinex.Bitfinex
btce btce.BTCE
bittrex bittrex.Bittrex
wex wex.WEX
btcmarkets btcmarkets.BTCMarkets
coinut coinut.COINUT
gdax gdax.GDAX
@@ -56,13 +60,17 @@ type ExchangeMain struct {
kraken kraken.Kraken
}
// Bot contains configuration, portfolio, exchange & ticker data and is the
// overarching type across this code base.
type Bot struct {
config *config.Config
portfolio *portfolio.PortfolioBase
exchange ExchangeMain
exchanges []exchange.IBotExchange
tickers []ticker.Ticker
shutdown chan bool
config *config.Config
smsglobal *smsglobal.Base
portfolio *portfolio.Base
exchange ExchangeMain
exchanges []exchange.IBotExchange
tickers []ticker.Ticker
shutdown chan bool
configFile string
}
var bot Bot
@@ -74,10 +82,18 @@ func setupBotExchanges() {
if bot.exchanges[i].GetName() == exch.Name {
bot.exchanges[i].Setup(exch)
if bot.exchanges[i].IsEnabled() {
log.Printf("%s: Exchange support: %s (Authenticated API support: %s - Verbose mode: %s).\n", exch.Name, common.IsEnabled(exch.Enabled), common.IsEnabled(exch.AuthenticatedAPISupport), common.IsEnabled(exch.Verbose))
log.Printf(
"%s: Exchange support: %s (Authenticated API support: %s - Verbose mode: %s).\n",
exch.Name, common.IsEnabled(exch.Enabled),
common.IsEnabled(exch.AuthenticatedAPISupport),
common.IsEnabled(exch.Verbose),
)
bot.exchanges[i].Start()
} else {
log.Printf("%s: Exchange support: %s\n", exch.Name, common.IsEnabled(exch.Enabled))
log.Printf(
"%s: Exchange support: %s\n", exch.Name,
common.IsEnabled(exch.Enabled),
)
}
}
}
@@ -87,30 +103,38 @@ func setupBotExchanges() {
func main() {
HandleInterrupt()
bot.config = &config.Cfg
log.Printf("Loading config file %s..\n", config.CONFIG_FILE)
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)
}
log.Printf("Bot '%s' started.\n", bot.config.Name)
AdjustGoMaxProcs()
log.Printf("Bot '%s' started.\n", bot.config.Name)
log.Printf("Fiat display currency: %s.", bot.config.FiatDisplayCurrency)
if bot.config.SMS.Enabled {
err = bot.config.CheckSMSGlobalConfigValues()
if err != nil {
log.Println(err) // non fatal event
bot.config.SMS.Enabled = false
} else {
log.Printf("SMS support enabled. Number of SMS contacts %d.\n", smsglobal.GetEnabledSMSContacts(bot.config.SMS))
}
bot.smsglobal = smsglobal.New(bot.config.SMS.Username, bot.config.SMS.Password,
bot.config.Name, bot.config.SMS.Contacts)
log.Printf(
"SMS support enabled. Number of SMS contacts %d.\n",
bot.smsglobal.GetEnabledContacts(),
)
} else {
log.Println("SMS support disabled.")
}
log.Printf("Available Exchanges: %d. Enabled Exchanges: %d.\n", len(bot.config.Exchanges), bot.config.GetConfigEnabledExchanges())
log.Printf(
"Available Exchanges: %d. Enabled Exchanges: %d.\n",
len(bot.config.Exchanges), bot.config.GetConfigEnabledExchanges(),
)
log.Println("Bot Exchange support:")
bot.exchanges = []exchange.IBotExchange{
@@ -119,7 +143,8 @@ func main() {
new(btcc.BTCC),
new(bitstamp.Bitstamp),
new(bitfinex.Bitfinex),
new(btce.BTCE),
new(bittrex.Bittrex),
new(wex.WEX),
new(btcmarkets.BTCMarkets),
new(coinut.COINUT),
new(gdax.GDAX),
@@ -137,17 +162,33 @@ func main() {
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
bot.exchanges[i].SetDefaults()
log.Printf("Exchange %s successfully set default settings.\n", bot.exchanges[i].GetName())
log.Printf(
"Exchange %s successfully set default settings.\n",
bot.exchanges[i].GetName(),
)
}
}
setupBotExchanges()
bot.config.RetrieveConfigCurrencyPairs()
if bot.config.CurrencyExchangeProvider == "yahoo" {
currency.SetProvider(true)
} else {
currency.SetProvider(false)
}
log.Printf("Using %s as currency exchange provider.", bot.config.CurrencyExchangeProvider)
bot.config.RetrieveConfigCurrencyPairs()
err = currency.SeedCurrencyData(currency.BaseCurrencies)
if err != nil {
log.Fatalf("Fatal error retrieving config currencies. Error: %s", err)
currency.SwapProvider()
log.Printf("'%s' currency exchange provider failed, swapping to %s and testing..",
bot.config.CurrencyExchangeProvider, currency.GetProvider())
err = currency.SeedCurrencyData(currency.BaseCurrencies)
if err != nil {
log.Fatalf("Fatal error retrieving config currencies. Error: %s", err)
}
}
log.Println("Successfully retrieved config currencies.")
@@ -157,26 +198,29 @@ func main() {
SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data)
go portfolio.StartPortfolioWatcher()
log.Println("Starting websocket handler")
go WebsocketHandler()
go TickerUpdaterRoutine()
go OrderbookUpdaterRoutine()
if bot.config.Webserver.Enabled {
err := bot.config.CheckWebserverConfigValues()
if err != nil {
log.Println(err) // non fatal event
//bot.config.Webserver.Enabled = false
} else {
listenAddr := bot.config.Webserver.ListenAddress
log.Printf("HTTP Webserver support enabled. Listen URL: http://%s:%d/\n", common.ExtractHost(listenAddr), common.ExtractPort(listenAddr))
router := NewRouter(bot.exchanges)
log.Fatal(http.ListenAndServe(listenAddr, router))
}
}
if !bot.config.Webserver.Enabled {
log.Println("HTTP Webserver support disabled.")
listenAddr := bot.config.Webserver.ListenAddress
log.Printf(
"HTTP Webserver support enabled. Listen URL: http://%s:%d/\n",
common.ExtractHost(listenAddr), common.ExtractPort(listenAddr),
)
router := NewRouter(bot.exchanges)
log.Fatal(http.ListenAndServe(listenAddr, router))
} else {
log.Println("HTTP RESTful Webserver support disabled.")
}
<-bot.shutdown
Shutdown()
}
// AdjustGoMaxProcs adjusts the maximum processes that the CPU can handle.
func AdjustGoMaxProcs() {
log.Println("Adjusting bot runtime performance..")
maxProcsEnv := os.Getenv("GOMAXPROCS")
@@ -186,17 +230,20 @@ func AdjustGoMaxProcs() {
if maxProcsEnv != "" {
log.Println("GOMAXPROCS env =", maxProcsEnv)
env, err := strconv.Atoi(maxProcsEnv)
if err != nil {
log.Println("Unable to convert GOMAXPROCS to int, using", maxProcs)
} else {
maxProcs = env
}
}
if i := runtime.GOMAXPROCS(maxProcs); i != maxProcs {
log.Fatal("Go Max Procs were not set correctly.")
}
log.Println("Set GOMAXPROCS to:", maxProcs)
runtime.GOMAXPROCS(maxProcs)
}
// HandleInterrupt monitors and captures the SIGTERM in a new goroutine then
// shuts down bot
func HandleInterrupt() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@@ -207,10 +254,11 @@ func HandleInterrupt() {
}()
}
// Shutdown correctly shuts down bot saving configuration files
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.")
@@ -222,7 +270,8 @@ func Shutdown() {
os.Exit(1)
}
func SeedExchangeAccountInfo(data []exchange.ExchangeAccountInfo) {
// SeedExchangeAccountInfo seeds account info
func SeedExchangeAccountInfo(data []exchange.AccountInfo) {
if len(data) == 0 {
return
}
@@ -237,14 +286,33 @@ func SeedExchangeAccountInfo(data []exchange.ExchangeAccountInfo) {
avail := data[i].Currencies[j].TotalValue
total := onHold + avail
if total <= 0 {
continue
}
if !port.ExchangeAddressExists(exchangeName, currencyName) {
port.Addresses = append(port.Addresses, portfolio.PortfolioAddress{Address: exchangeName, CoinType: currencyName, Balance: total, Decscription: portfolio.PORTFOLIO_ADDRESS_EXCHANGE})
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, 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 {
balance, ok := port.GetAddressBalance(exchangeName, currencyName, portfolio.PortfolioAddressExchange)
if !ok {
continue
}
if balance != total {
log.Printf("Portfolio: Updating %s %s entry with balance %f.\n",
exchangeName, currencyName, total)
port.UpdateExchangeAddressBalance(exchangeName, currencyName, total)
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More