mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
Merge branch 'master' into engine
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
package btse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
@@ -23,38 +24,50 @@ type BTSE struct {
|
||||
}
|
||||
|
||||
const (
|
||||
btseAPIURL = "https://api.btse.com/v1/restapi"
|
||||
btseAPIURLv2 = "https://api.btse.com/spot/v2"
|
||||
btseAPIVersion = "1"
|
||||
btseAPIURL = "https://api.btse.com"
|
||||
btseAPIPath = "/spot/v2/"
|
||||
|
||||
// Public endpoints
|
||||
btseMarkets = "markets"
|
||||
btseTrades = "trades"
|
||||
btseTicker = "ticker"
|
||||
btseOrderbook = "orderbook"
|
||||
btseStats = "stats"
|
||||
btseTime = "time"
|
||||
btseMarketOverview = "market_summary"
|
||||
btseMarkets = "markets"
|
||||
btseOrderbook = "orderbook"
|
||||
btseTrades = "trades"
|
||||
btseTicker = "ticker"
|
||||
btseStats = "stats"
|
||||
btseTime = "time"
|
||||
|
||||
// Authenticated endpoints
|
||||
btseAccount = "account"
|
||||
btseOrder = "order"
|
||||
btsePendingOrders = "pending"
|
||||
btseDeleteOrder = "deleteOrder"
|
||||
btseDeleteOrders = "deleteOrders"
|
||||
btseFills = "fills"
|
||||
)
|
||||
|
||||
// GetMarketsSummary stores market summary data
|
||||
func (b *BTSE) GetMarketsSummary() (*HighLevelMarketData, error) {
|
||||
var m HighLevelMarketData
|
||||
return &m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m)
|
||||
}
|
||||
|
||||
// GetMarkets returns a list of markets available on BTSE
|
||||
func (b *BTSE) GetMarkets() (*Markets, error) {
|
||||
var m Markets
|
||||
return &m, b.SendHTTPRequest(http.MethodGet, btseMarkets, &m)
|
||||
func (b *BTSE) GetMarkets() ([]Market, error) {
|
||||
var m []Market
|
||||
return m, b.SendHTTPRequest(http.MethodGet, btseMarkets, &m)
|
||||
}
|
||||
|
||||
// FetchOrderBook gets orderbook data for a given pair
|
||||
func (b *BTSE) FetchOrderBook(symbol string) (*Orderbook, error) {
|
||||
var o Orderbook
|
||||
endpoint := fmt.Sprintf("%s/%s", btseOrderbook, symbol)
|
||||
return &o, b.SendHTTPRequest(http.MethodGet, endpoint, &o)
|
||||
}
|
||||
|
||||
// GetTrades returns a list of trades for the specified symbol
|
||||
func (b *BTSE) GetTrades(symbol string) (*Trades, error) {
|
||||
var t Trades
|
||||
func (b *BTSE) GetTrades(symbol string) ([]Trade, error) {
|
||||
var t []Trade
|
||||
endpoint := fmt.Sprintf("%s/%s", btseTrades, symbol)
|
||||
return &t, b.SendHTTPRequest(http.MethodGet, endpoint, &t)
|
||||
return t, b.SendHTTPRequest(http.MethodGet, endpoint, &t)
|
||||
|
||||
}
|
||||
|
||||
@@ -69,34 +82,6 @@ func (b *BTSE) GetTicker(symbol string) (*Ticker, error) {
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// GetOrderbook returns the orderbook for a specified symbol
|
||||
func (b *BTSE) GetOrderbook(symbol string, group, limitAsks, limitBids int64) (*Orderbook, error) {
|
||||
var t Orderbook
|
||||
vals := url.Values{}
|
||||
if group != 0 {
|
||||
vals.Set("group", strconv.FormatInt(group, 10))
|
||||
}
|
||||
|
||||
if limitAsks != 0 {
|
||||
vals.Set("limit_asks", strconv.FormatInt(limitAsks, 10))
|
||||
}
|
||||
|
||||
if limitBids != 0 {
|
||||
vals.Set("limit_bids", strconv.FormatInt(limitBids, 10))
|
||||
}
|
||||
|
||||
if symbol == "" {
|
||||
return nil, errors.New("symbol not set")
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("%s/%s", btseOrderbook, symbol)
|
||||
err := b.SendHTTPRequestv2(http.MethodGet, endpoint, vals, &t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// GetMarketStatistics gets market statistics for a specificed market
|
||||
func (b *BTSE) GetMarketStatistics(symbol string) (*MarketStatistics, error) {
|
||||
var m MarketStatistics
|
||||
@@ -111,24 +96,28 @@ func (b *BTSE) GetServerTime() (*ServerTime, error) {
|
||||
}
|
||||
|
||||
// GetAccountBalance returns the users account balance
|
||||
func (b *BTSE) GetAccountBalance() (*AccountBalance, error) {
|
||||
var a AccountBalance
|
||||
return &a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseAccount, nil, &a)
|
||||
func (b *BTSE) GetAccountBalance() ([]CurrencyBalance, error) {
|
||||
var a []CurrencyBalance
|
||||
return a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseAccount, nil, &a)
|
||||
}
|
||||
|
||||
// CreateOrder creates an order
|
||||
func (b *BTSE) CreateOrder(amount, price float64, side, orderType, symbol, timeInForce, tag string) (*string, error) {
|
||||
req := make(map[string]interface{})
|
||||
req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64)
|
||||
req["price"] = strconv.FormatFloat(price, 'f', -1, 64)
|
||||
req["side"] = side
|
||||
req["type"] = orderType
|
||||
req["product_id"] = symbol
|
||||
|
||||
req["amount"] = amount
|
||||
req["price"] = price
|
||||
if side != "" {
|
||||
req["side"] = side
|
||||
}
|
||||
if orderType != "" {
|
||||
req["type"] = orderType
|
||||
}
|
||||
if symbol != "" {
|
||||
req["symbol"] = symbol
|
||||
}
|
||||
if timeInForce != "" {
|
||||
req["time_in_force"] = timeInForce
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
req["tag"] = tag
|
||||
}
|
||||
@@ -142,42 +131,30 @@ func (b *BTSE) CreateOrder(amount, price float64, side, orderType, symbol, timeI
|
||||
}
|
||||
|
||||
// GetOrders returns all pending orders
|
||||
func (b *BTSE) GetOrders(productID string) (*OpenOrders, error) {
|
||||
func (b *BTSE) GetOrders(symbol string) ([]OpenOrder, error) {
|
||||
req := make(map[string]interface{})
|
||||
if productID != "" {
|
||||
req["product_id"] = productID
|
||||
if symbol != "" {
|
||||
req["symbol"] = symbol
|
||||
}
|
||||
var o OpenOrders
|
||||
return &o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o)
|
||||
var o []OpenOrder
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o)
|
||||
}
|
||||
|
||||
// CancelExistingOrder cancels an order
|
||||
func (b *BTSE) CancelExistingOrder(orderID, productID string) (*CancelOrder, error) {
|
||||
func (b *BTSE) CancelExistingOrder(orderID, symbol string) (*CancelOrder, error) {
|
||||
var c CancelOrder
|
||||
req := make(map[string]interface{})
|
||||
req["order_id"] = orderID
|
||||
req["product_id"] = productID
|
||||
req["symbol"] = symbol
|
||||
return &c, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseDeleteOrder, req, &c)
|
||||
}
|
||||
|
||||
// CancelOrders cancels all orders
|
||||
// productID optional. If product ID is sent, all orders of that specified market
|
||||
// will be cancelled. If not specified, all orders of all markets will be cancelled
|
||||
func (b *BTSE) CancelOrders(productID string) (*CancelOrder, error) {
|
||||
var c CancelOrder
|
||||
req := make(map[string]interface{})
|
||||
if productID != "" {
|
||||
req["product_id"] = productID
|
||||
}
|
||||
return &c, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseDeleteOrders, req, &c)
|
||||
}
|
||||
|
||||
// GetFills gets all filled orders
|
||||
func (b *BTSE) GetFills(orderID, productID, before, after, limit string) (*FilledOrders, error) {
|
||||
if orderID != "" && productID != "" {
|
||||
return nil, errors.New("orderID and productID cannot co-exist in the same query")
|
||||
} else if orderID == "" && productID == "" {
|
||||
return nil, errors.New("orderID OR productID must be set")
|
||||
func (b *BTSE) GetFills(orderID, symbol, before, after, limit, username string) ([]FilledOrder, error) {
|
||||
if orderID != "" && symbol != "" {
|
||||
return nil, errors.New("orderID and symbol cannot co-exist in the same query")
|
||||
} else if orderID == "" && symbol == "" {
|
||||
return nil, errors.New("orderID OR symbol must be set")
|
||||
}
|
||||
|
||||
req := make(map[string]interface{})
|
||||
@@ -185,8 +162,8 @@ func (b *BTSE) GetFills(orderID, productID, before, after, limit string) (*Fille
|
||||
req["order_id"] = orderID
|
||||
}
|
||||
|
||||
if productID != "" {
|
||||
req["product_id"] = productID
|
||||
if symbol != "" {
|
||||
req["symbol"] = symbol
|
||||
}
|
||||
|
||||
if before != "" {
|
||||
@@ -200,31 +177,18 @@ func (b *BTSE) GetFills(orderID, productID, before, after, limit string) (*Fille
|
||||
if limit != "" {
|
||||
req["limit"] = limit
|
||||
}
|
||||
if username != "" {
|
||||
req["username"] = username
|
||||
}
|
||||
|
||||
var o FilledOrders
|
||||
return &o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseFills, req, &o)
|
||||
var o []FilledOrder
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseFills, req, &o)
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an HTTP request to the desired endpoint
|
||||
func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}) error {
|
||||
return b.SendPayload(method,
|
||||
fmt.Sprintf("%s/%s", b.API.Endpoints.URL, endpoint),
|
||||
nil,
|
||||
nil,
|
||||
&result,
|
||||
false,
|
||||
false,
|
||||
b.Verbose,
|
||||
b.HTTPDebugging,
|
||||
b.HTTPRecording)
|
||||
}
|
||||
|
||||
// SendHTTPRequestv2 sends an HTTP request to the desired endpoint
|
||||
func (b *BTSE) SendHTTPRequestv2(method, endpoint string, values url.Values, result interface{}) error {
|
||||
path := fmt.Sprintf("%s/%s", btseAPIURLv2, endpoint)
|
||||
path = common.EncodeURLValues(path, values)
|
||||
return b.SendPayload(method,
|
||||
path,
|
||||
b.API.Endpoints.URL+btseAPIPath+endpoint,
|
||||
nil,
|
||||
nil,
|
||||
&result,
|
||||
@@ -241,27 +205,43 @@ func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[str
|
||||
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet,
|
||||
b.Name)
|
||||
}
|
||||
|
||||
payload, err := common.JSONEncode(req)
|
||||
if err != nil {
|
||||
return errors.New("sendAuthenticatedAPIRequest: unable to JSON request")
|
||||
}
|
||||
|
||||
path := btseAPIPath + endpoint
|
||||
headers := make(map[string]string)
|
||||
headers["API-KEY"] = b.API.Credentials.Key
|
||||
headers["API-PASSPHRASE"] = b.API.Credentials.Secret
|
||||
if len(payload) > 0 {
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["btse-api"] = b.API.Credentials.Key
|
||||
nonce := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
headers["btse-nonce"] = nonce
|
||||
var body io.Reader
|
||||
var hmac []byte
|
||||
var payload []byte
|
||||
if len(req) != 0 {
|
||||
var err error
|
||||
payload, err = common.JSONEncode(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = bytes.NewBuffer(payload)
|
||||
hmac = crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte((path + nonce + string(payload))),
|
||||
[]byte(b.API.Credentials.Secret),
|
||||
)
|
||||
} else {
|
||||
hmac = crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte((path + nonce)),
|
||||
[]byte(b.API.Credentials.Secret),
|
||||
)
|
||||
}
|
||||
|
||||
p := fmt.Sprintf("%s/%s", b.API.Endpoints.URL, endpoint)
|
||||
headers["btse-sign"] = crypto.HexEncodeToString(hmac)
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "Sending %s request to URL %s with params %s\n", method, p, string(payload))
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Sending %s request to URL %s with params %s\n",
|
||||
b.Name, method, path, string(payload))
|
||||
}
|
||||
return b.SendPayload(method,
|
||||
p,
|
||||
btseAPIURL+path,
|
||||
headers,
|
||||
strings.NewReader(string(payload)),
|
||||
body,
|
||||
&result,
|
||||
true,
|
||||
false,
|
||||
@@ -276,12 +256,19 @@ func (b *BTSE) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
|
||||
switch feeBuilder.FeeType {
|
||||
case exchange.CryptocurrencyTradeFee:
|
||||
fee = calculateTradingFee(feeBuilder.IsMaker)
|
||||
fee = calculateTradingFee(feeBuilder.IsMaker) * feeBuilder.Amount * feeBuilder.PurchasePrice
|
||||
case exchange.CryptocurrencyWithdrawalFee:
|
||||
if feeBuilder.Pair.Base.Match(currency.BTC) {
|
||||
switch feeBuilder.Pair.Base {
|
||||
case currency.USDT:
|
||||
fee = 1.08
|
||||
case currency.TUSD:
|
||||
fee = 1.09
|
||||
case currency.BTC:
|
||||
fee = 0.0005
|
||||
} else if feeBuilder.Pair.Base.Match(currency.USDT) {
|
||||
fee = 5
|
||||
case currency.ETH:
|
||||
fee = 0.01
|
||||
case currency.LTC:
|
||||
fee = 0.001
|
||||
}
|
||||
case exchange.InternationalBankDepositFee:
|
||||
fee = getInternationalBankDepositFee(feeBuilder.Amount)
|
||||
@@ -295,7 +282,7 @@ func (b *BTSE) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
|
||||
// getOfflineTradeFee calculates the worst case-scenario trading fee
|
||||
func getOfflineTradeFee(price, amount float64) float64 {
|
||||
return 0.0015 * price * amount
|
||||
return 0.001 * price * amount
|
||||
}
|
||||
|
||||
// getInternationalBankDepositFee returns international deposit fee
|
||||
@@ -304,7 +291,7 @@ func getOfflineTradeFee(price, amount float64) float64 {
|
||||
// The small deposit fee is charged in whatever currency it comes in.
|
||||
func getInternationalBankDepositFee(amount float64) float64 {
|
||||
var fee float64
|
||||
if amount <= 1000 {
|
||||
if amount <= 100 {
|
||||
fee = amount * 0.0025
|
||||
if fee < 3 {
|
||||
return 3
|
||||
@@ -316,7 +303,7 @@ func getInternationalBankDepositFee(amount float64) float64 {
|
||||
// getInternationalBankWithdrawalFee returns international withdrawal fee
|
||||
// 0.1% (min25 USD)
|
||||
func getInternationalBankWithdrawalFee(amount float64) float64 {
|
||||
fee := amount * 0.001
|
||||
fee := amount * 0.0009
|
||||
|
||||
if fee < 25 {
|
||||
return 25
|
||||
@@ -329,7 +316,7 @@ func getInternationalBankWithdrawalFee(amount float64) float64 {
|
||||
func calculateTradingFee(isMaker bool) float64 {
|
||||
fee := 0.00050
|
||||
if !isMaker {
|
||||
fee = 0.0015
|
||||
fee = 0.001
|
||||
}
|
||||
return fee
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ package btse
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
@@ -19,135 +20,162 @@ const (
|
||||
|
||||
var b BTSE
|
||||
|
||||
func TestSetDefaults(t *testing.T) {
|
||||
func TestMain(m *testing.M) {
|
||||
b.SetDefaults()
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("../../testdata/configtest.json", true)
|
||||
if err != nil {
|
||||
log.Fatal("BTSE load config error", err)
|
||||
log.Fatal(err)
|
||||
}
|
||||
btseConfig, err := cfg.GetExchangeConfig("BTSE")
|
||||
if err != nil {
|
||||
t.Error("BTSE Setup() init error")
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
btseConfig.API.AuthenticatedSupport = true
|
||||
btseConfig.API.Credentials.Key = apiKey
|
||||
btseConfig.API.Credentials.Secret = apiSecret
|
||||
|
||||
err = b.Setup(btseConfig)
|
||||
b.Setup(btseConfig)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func areTestAPIKeysSet() bool {
|
||||
return b.ValidateAPICredentials()
|
||||
}
|
||||
|
||||
func TestGetMarketsSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetMarketsSummary()
|
||||
if err != nil {
|
||||
t.Fatal("BTSE setup error", err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarkets(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
t.Parallel()
|
||||
_, err := b.GetMarkets()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %s", err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchOrderBook(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.FetchOrderBook("BTC-USD")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTrades(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
t.Parallel()
|
||||
_, err := b.GetTrades("BTC-USD")
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %s", err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTicker(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
t.Parallel()
|
||||
_, err := b.GetTicker("BTC-USD")
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderbook(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
_, err := b.GetOrderbook("BTC-USD", 0, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %s", err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarketStatistics(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
t.Parallel()
|
||||
_, err := b.GetMarketStatistics("BTC-USD")
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %s", err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerTime(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
t.Parallel()
|
||||
_, err := b.GetServerTime()
|
||||
if err != nil {
|
||||
t.Fatalf("Err: %s", err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccount(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetAccountBalance()
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
t.Errorf("Could not get account balance: %s", err)
|
||||
} else if !areTestAPIKeysSet() && err == nil {
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFills(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
|
||||
_, err := b.GetFills("", "BTC-USD", "", "", "")
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
t.Errorf("Could not get fills: %s", err)
|
||||
} else if !areTestAPIKeysSet() && err == nil {
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetFills("", "BTC-USD", "", "", "", "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
func TestCreateOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
}
|
||||
_, err := b.CreateOrder(0.1, 10000, "sell", "limit", "BTC-USD", "", "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetOrders("")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetActiveOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
var getOrdersRequest = exchange.GetOrdersRequest{
|
||||
OrderType: exchange.AnyOrderType,
|
||||
}
|
||||
|
||||
_, err := b.GetActiveOrders(&getOrdersRequest)
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
t.Errorf("Could not get open orders: %s", err)
|
||||
} else if !areTestAPIKeysSet() && err == nil {
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
var getOrdersRequest = exchange.GetOrdersRequest{
|
||||
OrderType: exchange.AnyOrderType,
|
||||
}
|
||||
|
||||
_, err := b.GetOrderHistory(&getOrdersRequest)
|
||||
if err != common.ErrFunctionNotSupported {
|
||||
t.Fatal("Expected different result")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
t.Parallel()
|
||||
expected := exchange.NoAPIWithdrawalMethodsText
|
||||
actual := b.FormatWithdrawPermissions()
|
||||
if actual != expected {
|
||||
@@ -158,10 +186,11 @@ func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
// TestGetFeeByTypeOfflineTradeFee logic test
|
||||
func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
|
||||
feeBuilder := &exchange.FeeBuilder{
|
||||
FeeType: exchange.CryptocurrencyTradeFee,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
IsMaker: true,
|
||||
Amount: 1000,
|
||||
FeeType: exchange.CryptocurrencyTradeFee,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
IsMaker: true,
|
||||
Amount: 1,
|
||||
PurchasePrice: 1000,
|
||||
}
|
||||
|
||||
b.GetFeeByType(feeBuilder)
|
||||
@@ -177,24 +206,24 @@ func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFee(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
t.Parallel()
|
||||
|
||||
feeBuilder := &exchange.FeeBuilder{
|
||||
FeeType: exchange.CryptocurrencyTradeFee,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
IsMaker: true,
|
||||
Amount: 1000,
|
||||
FeeType: exchange.CryptocurrencyTradeFee,
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
IsMaker: true,
|
||||
Amount: 1,
|
||||
PurchasePrice: 1000,
|
||||
}
|
||||
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 0.00050 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 0.00050, resp)
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 0.500000 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 0.500000, resp)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
feeBuilder.IsMaker = false
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 0.0015 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 0.0015, resp)
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 1.00000 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 1.00000, resp)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -205,8 +234,8 @@ func TestGetFee(t *testing.T) {
|
||||
}
|
||||
|
||||
feeBuilder.Pair.Base = currency.USDT
|
||||
if resp, err := b.GetFee(feeBuilder); resp != float64(5) || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(5), resp)
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 1.080000 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 1.080000, resp)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -223,8 +252,8 @@ func TestGetFee(t *testing.T) {
|
||||
}
|
||||
|
||||
feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee
|
||||
if resp, err := b.GetFee(feeBuilder); resp != float64(1000) || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(1000), resp)
|
||||
if resp, err := b.GetFee(feeBuilder); resp != float64(900) || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(900), resp)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -243,29 +272,22 @@ func TestParseOrderTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func areTestAPIKeysSet() bool {
|
||||
return b.ValidateAPICredentials()
|
||||
}
|
||||
|
||||
// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them
|
||||
// ----------------------------------------------------------------------------------------------------------------------------
|
||||
func TestSubmitOrder(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
}
|
||||
|
||||
var orderSubmission = &exchange.OrderSubmission{
|
||||
Pair: currency.Pair{
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
OrderSide: exchange.BuyOrderSide,
|
||||
OrderSide: exchange.SellOrderSide,
|
||||
OrderType: exchange.LimitOrderType,
|
||||
Price: 1,
|
||||
Amount: 1,
|
||||
Price: 100000,
|
||||
Amount: 0.1,
|
||||
ClientID: "meowOrder",
|
||||
}
|
||||
response, err := b.SubmitOrder(orderSubmission)
|
||||
@@ -277,41 +299,31 @@ func TestSubmitOrder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCancelExchangeOrder(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
}
|
||||
|
||||
currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
currency.USD.String(),
|
||||
"-")
|
||||
|
||||
var orderCancellation = &exchange.OrderCancellation{
|
||||
OrderID: "0b66ccaf-dfd4-4b9f-a30b-2380b9c7b66d",
|
||||
OrderID: "b334ecef-2b42-4998-b8a4-b6b14f6d2671",
|
||||
WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB",
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
}
|
||||
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
if !areTestAPIKeysSet() && err == nil {
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
}
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
t.Errorf("Could not cancel orders: %v", err)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
b.SetDefaults()
|
||||
TestSetup(t)
|
||||
|
||||
if areTestAPIKeysSet() && !canManipulateRealOrders {
|
||||
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
}
|
||||
|
||||
currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
currency.USD.String(),
|
||||
"-")
|
||||
@@ -322,17 +334,14 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
AccountID: "1",
|
||||
CurrencyPair: currencyPair,
|
||||
}
|
||||
|
||||
resp, err := b.CancelAllOrders(orderCancellation)
|
||||
|
||||
if !areTestAPIKeysSet() && err == nil {
|
||||
t.Error("Expecting an error when no keys are set")
|
||||
}
|
||||
if areTestAPIKeysSet() && err != nil {
|
||||
if err != nil {
|
||||
t.Errorf("Could not cancel orders: %v", err)
|
||||
}
|
||||
|
||||
if len(resp.OrderStatus) > 0 {
|
||||
t.Errorf("%v orders failed to cancel", len(resp.OrderStatus))
|
||||
for k, v := range resp.OrderStatus {
|
||||
if strings.Contains(v, "Failed") {
|
||||
t.Errorf("order id: %s failed to cancel: %v", k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,38 @@ package btse
|
||||
|
||||
import "time"
|
||||
|
||||
// Market stores market data
|
||||
type Market struct {
|
||||
Symbol string `json:"symbol"`
|
||||
BaseCurrency string `json:"base_currency"`
|
||||
QuoteCurrency string `json:"quote_currency"`
|
||||
BaseMinSize float64 `json:"base_min_size"`
|
||||
BaseMaxSize float64 `json:"base_max_size"`
|
||||
BaseIncremementSize float64 `json:"base_increment_size"`
|
||||
QuoteMinPrice float64 `json:"quote_min_price"`
|
||||
QuoteIncrement float64 `json:"quote_increment"`
|
||||
Status string `json:"status"`
|
||||
const (
|
||||
// Default order type is good till cancel (or filled)
|
||||
goodTillCancel = "gtc"
|
||||
)
|
||||
|
||||
// OverviewData stores market overview data
|
||||
type OverviewData struct {
|
||||
High24Hr float64 `json:"high24hr,string"`
|
||||
HighestBid float64 `json:"highestbid,string"`
|
||||
Last float64 `json:"last,string"`
|
||||
Low24Hr float64 `json:"low24hr,string"`
|
||||
LowestAsk float64 `json:"lowest_ask,string"`
|
||||
PercentageChange float64 `json:"percent_change,string"`
|
||||
Volume float64 `json:"volume,string"`
|
||||
}
|
||||
|
||||
// Markets stores an array of market data
|
||||
type Markets []Market
|
||||
// HighLevelMarketData stores market overview data
|
||||
type HighLevelMarketData map[string]OverviewData
|
||||
|
||||
// Market stores market data
|
||||
type Market struct {
|
||||
Symbol string `json:"symbol"`
|
||||
ID string `json:"id"`
|
||||
BaseCurrency string `json:"base_currency"`
|
||||
QuoteCurrency string `json:"quote_currency"`
|
||||
BaseMinSize float64 `json:"base_min_size"`
|
||||
BaseMaxSize float64 `json:"base_max_size"`
|
||||
BaseIncrementSize float64 `json:"base_increment_size"`
|
||||
QuoteMinPrice float64 `json:"quote_min_price"`
|
||||
QuoteIncrement float64 `json:"quote_increment"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// Trade stores trade data
|
||||
type Trade struct {
|
||||
@@ -28,8 +45,19 @@ type Trade struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Trades stores an array of trade data
|
||||
type Trades []Trade
|
||||
// QuoteData stores quote data
|
||||
type QuoteData struct {
|
||||
Price float64 `json:"price,string"`
|
||||
Size float64 `json:"size,string"`
|
||||
}
|
||||
|
||||
// Orderbook stores orderbook info
|
||||
type Orderbook struct {
|
||||
BuyQuote []QuoteData `json:"buyQuote"`
|
||||
SellQuote []QuoteData `json:"sellQuote"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Ticker stores the ticker data
|
||||
type Ticker struct {
|
||||
@@ -41,18 +69,6 @@ type Ticker struct {
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// OrderbookItem stores the price and size orderbook data
|
||||
type OrderbookItem struct {
|
||||
Price float64 `json:"price,string"`
|
||||
Size float64 `json:"size,string"`
|
||||
}
|
||||
|
||||
// Orderbook stores the orderbook bids and asks
|
||||
type Orderbook struct {
|
||||
Bids []OrderbookItem `json:"buyQuote"`
|
||||
Asks []OrderbookItem `json:"sellQuote"`
|
||||
}
|
||||
|
||||
// MarketStatistics stores market statistics for a particular product
|
||||
type MarketStatistics struct {
|
||||
Open float64 `json:"open,string"`
|
||||
@@ -76,9 +92,6 @@ type CurrencyBalance struct {
|
||||
Available float64 `json:"available,string"`
|
||||
}
|
||||
|
||||
// AccountBalance stores an array of currency balances
|
||||
type AccountBalance []CurrencyBalance
|
||||
|
||||
// Order stores the order info
|
||||
type Order struct {
|
||||
ID string `json:"id"`
|
||||
@@ -87,7 +100,7 @@ type Order struct {
|
||||
Price float64 `json:"price"`
|
||||
Amount float64 `json:"amount"`
|
||||
Tag string `json:"tag"`
|
||||
ProductID string `json:"product_id"`
|
||||
Symbol string `json:"symbol"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -97,9 +110,6 @@ type OpenOrder struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// OpenOrders stores an array of orders
|
||||
type OpenOrders []OpenOrder
|
||||
|
||||
// CancelOrder stores the cancel order response data
|
||||
type CancelOrder struct {
|
||||
Code int `json:"code"`
|
||||
@@ -115,35 +125,43 @@ type FilledOrder struct {
|
||||
Tag string `json:"tag"`
|
||||
ID int64 `json:"id"`
|
||||
TradeID string `json:"trade_id"`
|
||||
ProductID string `json:"product_id"`
|
||||
Symbol string `json:"symbol"`
|
||||
OrderID string `json:"order_id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// FilledOrders stores an array of filled orders
|
||||
type FilledOrders []FilledOrder
|
||||
|
||||
type websocketSubscribe struct {
|
||||
Type string `json:"type"`
|
||||
Channels []websocketChannel `json:"channels"`
|
||||
type wsSub struct {
|
||||
Operation string `json:"op"`
|
||||
Arguments []string `json:"args"`
|
||||
}
|
||||
|
||||
type websocketChannel struct {
|
||||
Name string `json:"name"`
|
||||
ProductIDs []string `json:"product_ids"`
|
||||
type wsQuoteData struct {
|
||||
Total string `json:"cumulativeTotal"`
|
||||
Price string `json:"price"`
|
||||
Size string `json:"size"`
|
||||
}
|
||||
|
||||
type wsTicker struct {
|
||||
BestAsk float64 `json:"best_ask,string"`
|
||||
BestBids float64 `json:"best_bid,string"`
|
||||
LastSize float64 `json:"last_size,string"`
|
||||
Price interface{} `json:"price"`
|
||||
ProductID string `json:"product_id"`
|
||||
type wsOBData struct {
|
||||
Currency string `json:"currency"`
|
||||
BuyQuote []wsQuoteData `json:"buyQuote"`
|
||||
SellQuote []wsQuoteData `json:"sellQuote"`
|
||||
}
|
||||
|
||||
type websocketOrderbookSnapshot struct {
|
||||
ProductID string `json:"product_id"`
|
||||
Type string `json:"type"`
|
||||
Bids [][]interface{} `json:"bids"`
|
||||
Asks [][]interface{} `json:"asks"`
|
||||
type wsOrderBook struct {
|
||||
Topic string `json:"topic"`
|
||||
Data wsOBData `json:"data"`
|
||||
}
|
||||
|
||||
type wsTradeData struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Gain int64 `json:"gain"`
|
||||
Newest int64 `json:"newest"`
|
||||
Price float64 `json:"price"`
|
||||
ID int64 `json:"serialId"`
|
||||
TransactionTime int64 `json:"transactionUnixTime"`
|
||||
}
|
||||
|
||||
type wsTradeHistory struct {
|
||||
Topic string `json:"topic"`
|
||||
Data []wsTradeData `json:"data"`
|
||||
}
|
||||
|
||||
@@ -2,23 +2,25 @@ package btse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler"
|
||||
log "github.com/thrasher-corp/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
btseWebsocket = "wss://ws.btse.com/api/ws-feed"
|
||||
btseWebsocket = "wss://ws.btse.com/spotWS"
|
||||
btseWebsocketTimer = 57 * time.Second
|
||||
)
|
||||
|
||||
// WsConnect connects the websocket client
|
||||
@@ -31,7 +33,7 @@ func (b *BTSE) WsConnect() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go b.Pinger()
|
||||
go b.WsHandleData()
|
||||
b.GenerateDefaultSubscriptions()
|
||||
|
||||
@@ -59,133 +61,112 @@ func (b *BTSE) WsHandleData() {
|
||||
}
|
||||
b.Websocket.TrafficAlert <- struct{}{}
|
||||
|
||||
type MsgType struct {
|
||||
Type string `json:"type"`
|
||||
ProductID string `json:"product_id"`
|
||||
}
|
||||
|
||||
if strings.Contains(string(resp.Raw), "Connected. Welcome to BTSE!") {
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "%s websocket client successfully connected to %s",
|
||||
b.Name, b.Websocket.GetWebsocketURL())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
msgType := MsgType{}
|
||||
err = common.JSONDecode(resp.Raw, &msgType)
|
||||
type Result map[string]interface{}
|
||||
result := Result{}
|
||||
err = common.JSONDecode(resp.Raw, &result)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
switch msgType.Type {
|
||||
case "ticker":
|
||||
var t wsTicker
|
||||
switch {
|
||||
case strings.Contains(result["topic"].(string), "tradeHistory"):
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s: Buy/Sell side functionality is broken for this exchange "+
|
||||
"currently! 'gain' has no correlation with buy side or "+
|
||||
"sell side",
|
||||
b.Name)
|
||||
var tradeHistory wsTradeHistory
|
||||
err = common.JSONDecode(resp.Raw, &tradeHistory)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
for x := range tradeHistory.Data {
|
||||
side := exchange.BuyOrderSide.ToString()
|
||||
if tradeHistory.Data[x].Gain == -1 {
|
||||
side = exchange.SellOrderSide.ToString()
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.TradeData{
|
||||
Timestamp: time.Unix(tradeHistory.Data[x].TransactionTime, 0),
|
||||
CurrencyPair: currency.NewPairFromString(strings.Replace(tradeHistory.Topic, "tradeHistory", "", 1)),
|
||||
AssetType: asset.Spot,
|
||||
Exchange: b.Name,
|
||||
Price: tradeHistory.Data[x].Price,
|
||||
Amount: tradeHistory.Data[x].Amount,
|
||||
Side: side,
|
||||
}
|
||||
}
|
||||
case strings.Contains(result["topic"].(string), "orderBookApi"):
|
||||
var t wsOrderBook
|
||||
err = common.JSONDecode(resp.Raw, &t)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
p := strings.Replace(t.Price.(string), ",", "", -1)
|
||||
price, err := strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.TickerData{
|
||||
Exchange: b.Name,
|
||||
Close: price,
|
||||
Bid: t.BestBids,
|
||||
Ask: t.BestAsk,
|
||||
AssetType: asset.Spot,
|
||||
Pair: currency.NewPairDelimiter(t.ProductID, b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
}
|
||||
case "snapshot":
|
||||
snapshot := websocketOrderbookSnapshot{}
|
||||
err := common.JSONDecode(resp.Raw, &snapshot)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
|
||||
err = b.wsProcessSnapshot(&snapshot)
|
||||
var price, amount float64
|
||||
var asks, bids []orderbook.Item
|
||||
for i := range t.Data.SellQuote {
|
||||
p := strings.Replace(t.Data.SellQuote[i].Price, ",", "", -1)
|
||||
price, err = strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
a := strings.Replace(t.Data.SellQuote[i].Size, ",", "", -1)
|
||||
amount, err = strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
asks = append(asks, orderbook.Item{Price: price, Amount: amount})
|
||||
}
|
||||
for j := range t.Data.BuyQuote {
|
||||
p := strings.Replace(t.Data.BuyQuote[j].Price, ",", "", -1)
|
||||
price, err = strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
a := strings.Replace(t.Data.BuyQuote[j].Size, ",", "", -1)
|
||||
amount, err = strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
bids = append(bids, orderbook.Item{Price: price, Amount: amount})
|
||||
}
|
||||
var newOB orderbook.Base
|
||||
newOB.Asks = asks
|
||||
newOB.Bids = bids
|
||||
newOB.AssetType = asset.Spot
|
||||
newOB.Pair = currency.NewPairFromString(t.Topic[strings.Index(t.Topic, ":")+1 : strings.Index(t.Topic, "_")])
|
||||
newOB.ExchangeName = b.Name
|
||||
err = b.Websocket.Orderbook.LoadSnapshot(&newOB)
|
||||
if err != nil {
|
||||
b.Websocket.DataHandler <- err
|
||||
continue
|
||||
}
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{Pair: newOB.Pair,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.Name}
|
||||
default:
|
||||
log.Warnf(log.ExchangeSys,
|
||||
"%s: unhandled websocket response: %s", b.Name, resp.Raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessSnapshot processes the initial orderbook snap shot
|
||||
func (b *BTSE) wsProcessSnapshot(snapshot *websocketOrderbookSnapshot) error {
|
||||
var base orderbook.Base
|
||||
for i := range snapshot.Bids {
|
||||
p := strings.Replace(snapshot.Bids[i][0].(string), ",", "", -1)
|
||||
price, err := strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := strings.Replace(snapshot.Bids[i][1].(string), ",", "", -1)
|
||||
amount, err := strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
base.Bids = append(base.Bids,
|
||||
orderbook.Item{Price: price, Amount: amount})
|
||||
}
|
||||
|
||||
for i := range snapshot.Asks {
|
||||
p := strings.Replace(snapshot.Asks[i][0].(string), ",", "", -1)
|
||||
price, err := strconv.ParseFloat(p, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := strings.Replace(snapshot.Asks[i][1].(string), ",", "", -1)
|
||||
amount, err := strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
base.Asks = append(base.Asks,
|
||||
orderbook.Item{Price: price, Amount: amount})
|
||||
}
|
||||
|
||||
p := currency.NewPairDelimiter(snapshot.ProductID, "-")
|
||||
base.AssetType = asset.Spot
|
||||
base.Pair = p
|
||||
base.LastUpdated = time.Now()
|
||||
base.ExchangeName = b.Name
|
||||
|
||||
err := b.Websocket.Orderbook.LoadSnapshot(&base)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Websocket.DataHandler <- wshandler.WebsocketOrderbookUpdate{
|
||||
Pair: p,
|
||||
Asset: asset.Spot,
|
||||
Exchange: b.GetName(),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
|
||||
func (b *BTSE) GenerateDefaultSubscriptions() {
|
||||
var channels = []string{"snapshot", "ticker"}
|
||||
enabledCurrencies := b.GetEnabledPairs(asset.Spot)
|
||||
var channels = []string{"orderBookApi:%s_0", "tradeHistory:%s"}
|
||||
pairs := b.GetEnabledPairs(asset.Spot)
|
||||
var subscriptions []wshandler.WebsocketChannelSubscription
|
||||
for i := range channels {
|
||||
for j := range enabledCurrencies {
|
||||
for j := range pairs {
|
||||
subscriptions = append(subscriptions, wshandler.WebsocketChannelSubscription{
|
||||
Channel: channels[i],
|
||||
Currency: enabledCurrencies[j],
|
||||
Channel: fmt.Sprintf(channels[i], pairs[j]),
|
||||
Currency: pairs[j],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -194,28 +175,32 @@ func (b *BTSE) GenerateDefaultSubscriptions() {
|
||||
|
||||
// Subscribe sends a websocket message to receive data from the channel
|
||||
func (b *BTSE) Subscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
subscribe := websocketSubscribe{
|
||||
Type: "subscribe",
|
||||
Channels: []websocketChannel{
|
||||
{
|
||||
Name: channelToSubscribe.Channel,
|
||||
ProductIDs: []string{channelToSubscribe.Currency.String()},
|
||||
},
|
||||
},
|
||||
}
|
||||
return b.WebsocketConn.SendMessage(subscribe)
|
||||
var sub wsSub
|
||||
sub.Operation = "subscribe"
|
||||
sub.Arguments = []string{channelToSubscribe.Channel}
|
||||
return b.WebsocketConn.SendMessage(sub)
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (b *BTSE) Unsubscribe(channelToSubscribe wshandler.WebsocketChannelSubscription) error {
|
||||
subscribe := websocketSubscribe{
|
||||
Type: "unsubscribe",
|
||||
Channels: []websocketChannel{
|
||||
{
|
||||
Name: channelToSubscribe.Channel,
|
||||
ProductIDs: []string{channelToSubscribe.Currency.String()},
|
||||
},
|
||||
},
|
||||
}
|
||||
return b.WebsocketConn.SendMessage(subscribe)
|
||||
var unSub wsSub
|
||||
unSub.Operation = "unsubscribe"
|
||||
unSub.Arguments = []string{channelToSubscribe.Channel}
|
||||
return b.WebsocketConn.SendMessage(unSub)
|
||||
}
|
||||
|
||||
// Pinger pings
|
||||
func (b *BTSE) Pinger() {
|
||||
ticker := time.NewTicker(btseWebsocketTimer)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-b.Websocket.ShutdownC:
|
||||
ticker.Stop()
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
b.WebsocketConn.Connection.WriteMessage(websocket.PingMessage, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,8 @@ func (b *BTSE) SetDefaults() {
|
||||
OrderbookFetching: true,
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
// TradeHistory is supported but it is currently broken on BTSE's
|
||||
// API so it has been left as unsupported
|
||||
},
|
||||
WithdrawPermissions: exchange.NoAPIWithdrawalMethods,
|
||||
},
|
||||
@@ -184,26 +186,26 @@ func (b *BTSE) Run() {
|
||||
|
||||
err := b.UpdateTradablePairs(false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", b.Name, err)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s Failed to update tradable pairs. Error: %s", b.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (b *BTSE) FetchTradablePairs(asset asset.Item) ([]string, error) {
|
||||
markets, err := b.GetMarkets()
|
||||
m, err := b.GetMarkets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pairs []string
|
||||
for _, m := range *markets {
|
||||
if m.Status != "active" {
|
||||
var currencies []string
|
||||
for x := range m {
|
||||
if m[x].Status != "active" {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, m.Symbol)
|
||||
currencies = append(currencies, m[x].Symbol)
|
||||
}
|
||||
|
||||
return pairs, nil
|
||||
return currencies, nil
|
||||
}
|
||||
|
||||
// UpdateTradablePairs updates the exchanges available pairs and stores
|
||||
@@ -270,40 +272,28 @@ func (b *BTSE) FetchOrderbook(p currency.Pair, assetType asset.Item) (orderbook.
|
||||
|
||||
// UpdateOrderbook updates and returns the orderbook for a currency pair
|
||||
func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType asset.Item) (orderbook.Base, error) {
|
||||
var orderBook orderbook.Base
|
||||
obNew, err := b.GetOrderbook(
|
||||
b.FormatExchangeCurrency(p, assetType).String(), 0, 0, 0)
|
||||
var resp orderbook.Base
|
||||
a, err := b.FetchOrderBook(b.FormatExchangeCurrency(p, assetType).String())
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
return resp, err
|
||||
}
|
||||
|
||||
for x := range obNew.Bids {
|
||||
orderBook.Bids = append(orderBook.Bids,
|
||||
orderbook.Item{
|
||||
Amount: obNew.Bids[x].Size,
|
||||
Price: obNew.Bids[x].Price,
|
||||
},
|
||||
)
|
||||
for x := range a.BuyQuote {
|
||||
resp.Bids = append(resp.Bids, orderbook.Item{
|
||||
Price: a.BuyQuote[x].Price,
|
||||
Amount: a.BuyQuote[x].Size})
|
||||
}
|
||||
|
||||
for x := range obNew.Asks {
|
||||
orderBook.Asks = append(orderBook.Asks,
|
||||
orderbook.Item{
|
||||
Amount: obNew.Asks[x].Size,
|
||||
Price: obNew.Asks[x].Price,
|
||||
},
|
||||
)
|
||||
for x := range a.SellQuote {
|
||||
resp.Asks = append(resp.Asks, orderbook.Item{
|
||||
Price: a.SellQuote[x].Price,
|
||||
Amount: a.SellQuote[x].Size})
|
||||
}
|
||||
|
||||
orderBook.Pair = p
|
||||
orderBook.ExchangeName = b.Name
|
||||
orderBook.AssetType = assetType
|
||||
|
||||
err = orderBook.Process()
|
||||
resp.Pair = p
|
||||
resp.ExchangeName = b.Name
|
||||
resp.AssetType = assetType
|
||||
err = resp.Process()
|
||||
if err != nil {
|
||||
return orderBook, err
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return orderbook.Get(b.Name, p, assetType)
|
||||
}
|
||||
|
||||
@@ -317,12 +307,12 @@ func (b *BTSE) GetAccountInfo() (exchange.AccountInfo, error) {
|
||||
}
|
||||
|
||||
var currencies []exchange.AccountCurrencyInfo
|
||||
for _, b := range *balance {
|
||||
for b := range balance {
|
||||
currencies = append(currencies,
|
||||
exchange.AccountCurrencyInfo{
|
||||
CurrencyName: currency.NewCode(b.Currency),
|
||||
TotalValue: b.Total,
|
||||
Hold: b.Available,
|
||||
CurrencyName: currency.NewCode(balance[b].Currency),
|
||||
TotalValue: balance[b].Total,
|
||||
Hold: balance[b].Available,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -357,9 +347,13 @@ func (b *BTSE) SubmitOrder(order *exchange.OrderSubmission) (exchange.SubmitOrde
|
||||
return resp, err
|
||||
}
|
||||
|
||||
r, err := b.CreateOrder(order.Amount, order.Price, order.OrderSide.ToString(),
|
||||
order.OrderType.ToString(), b.FormatExchangeCurrency(order.Pair,
|
||||
asset.Spot).String(), "GTC", order.ClientID)
|
||||
r, err := b.CreateOrder(order.Amount,
|
||||
order.Price,
|
||||
order.OrderSide.ToString(),
|
||||
order.OrderType.ToString(),
|
||||
b.FormatExchangeCurrency(order.Pair, asset.Spot).String(),
|
||||
goodTillCancel,
|
||||
order.ClientID)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
@@ -402,19 +396,35 @@ func (b *BTSE) CancelOrder(order *exchange.OrderCancellation) error {
|
||||
// If not specified, all orders of all markets will be cancelled
|
||||
func (b *BTSE) CancelAllOrders(orderCancellation *exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) {
|
||||
var resp exchange.CancelAllOrdersResponse
|
||||
r, err := b.CancelOrders(b.FormatExchangeCurrency(
|
||||
orderCancellation.CurrencyPair, asset.Spot).String())
|
||||
markets, err := b.GetMarkets()
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
switch r.Code {
|
||||
case -1:
|
||||
return resp, errors.New("order cancellation unsuccessful")
|
||||
case 4:
|
||||
return resp, errors.New("order cancellation timeout")
|
||||
resp.OrderStatus = make(map[string]string)
|
||||
for x := range markets {
|
||||
strPair := b.FormatExchangeCurrency(orderCancellation.CurrencyPair,
|
||||
orderCancellation.AssetType).String()
|
||||
checkPair := currency.NewPairWithDelimiter(markets[x].BaseCurrency,
|
||||
markets[x].QuoteCurrency,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter).String()
|
||||
if strPair != "" && strPair != checkPair {
|
||||
continue
|
||||
} else {
|
||||
orders, err := b.GetOrders(checkPair)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
for y := range orders {
|
||||
success := "Order Cancelled"
|
||||
_, err = b.CancelExistingOrder(orders[y].Order.ID, checkPair)
|
||||
if err != nil {
|
||||
success = "Order Cancellation Failed"
|
||||
}
|
||||
resp.OrderStatus[orders[y].Order.ID] = success
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -426,48 +436,46 @@ func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
|
||||
}
|
||||
|
||||
var od exchange.OrderDetail
|
||||
if len(*o) == 0 {
|
||||
if len(o) == 0 {
|
||||
return od, errors.New("no orders found")
|
||||
}
|
||||
|
||||
for i := range *o {
|
||||
o := (*o)[i]
|
||||
if o.ID != orderID {
|
||||
for i := range o {
|
||||
if o[i].ID != orderID {
|
||||
continue
|
||||
}
|
||||
|
||||
var side = exchange.BuyOrderSide
|
||||
if strings.EqualFold(o.Side, exchange.AskOrderSide.ToString()) {
|
||||
if strings.EqualFold(o[i].Side, exchange.AskOrderSide.ToString()) {
|
||||
side = exchange.SellOrderSide
|
||||
}
|
||||
|
||||
od.CurrencyPair = currency.NewPairDelimiter(o.ProductID,
|
||||
od.CurrencyPair = currency.NewPairDelimiter(o[i].Symbol,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter)
|
||||
od.Exchange = b.Name
|
||||
od.Amount = o.Amount
|
||||
od.ID = o.ID
|
||||
od.OrderDate = parseOrderTime(o.CreatedAt)
|
||||
od.Amount = o[i].Amount
|
||||
od.ID = o[i].ID
|
||||
od.OrderDate = parseOrderTime(o[i].CreatedAt)
|
||||
od.OrderSide = side
|
||||
od.OrderType = exchange.OrderType(strings.ToUpper(o.Type))
|
||||
od.Price = o.Price
|
||||
od.Status = o.Status
|
||||
od.OrderType = exchange.OrderType(strings.ToUpper(o[i].Type))
|
||||
od.Price = o[i].Price
|
||||
od.Status = o[i].Status
|
||||
|
||||
fills, err := b.GetFills(orderID, "", "", "", "")
|
||||
fills, err := b.GetFills(orderID, "", "", "", "", "")
|
||||
if err != nil {
|
||||
return od, fmt.Errorf("unable to get order fills for orderID %s", orderID)
|
||||
}
|
||||
|
||||
for i := range *fills {
|
||||
f := (*fills)[i]
|
||||
createdAt, _ := time.Parse(time.RFC3339, f.CreatedAt)
|
||||
for i := range fills {
|
||||
createdAt, _ := time.Parse(time.RFC3339, fills[i].CreatedAt)
|
||||
od.Trades = append(od.Trades, exchange.TradeHistory{
|
||||
Timestamp: createdAt,
|
||||
TID: f.ID,
|
||||
Price: f.Price,
|
||||
Amount: f.Amount,
|
||||
TID: fills[i].ID,
|
||||
Price: fills[i].Price,
|
||||
Amount: fills[i].Amount,
|
||||
Exchange: b.Name,
|
||||
Type: exchange.OrderSide(f.Side).ToString(),
|
||||
Fee: f.Fee,
|
||||
Type: fills[i].Side,
|
||||
Fee: fills[i].Fee,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -510,43 +518,43 @@ func (b *BTSE) GetActiveOrders(getOrdersRequest *exchange.GetOrdersRequest) ([]e
|
||||
}
|
||||
|
||||
var orders []exchange.OrderDetail
|
||||
for i := range *resp {
|
||||
order := (*resp)[i]
|
||||
for i := range resp {
|
||||
var side = exchange.BuyOrderSide
|
||||
if strings.EqualFold(order.Side, exchange.AskOrderSide.ToString()) {
|
||||
if strings.EqualFold(resp[i].Side, exchange.AskOrderSide.ToString()) {
|
||||
side = exchange.SellOrderSide
|
||||
}
|
||||
|
||||
openOrder := exchange.OrderDetail{
|
||||
CurrencyPair: currency.NewPairDelimiter(order.ProductID,
|
||||
CurrencyPair: currency.NewPairDelimiter(resp[i].Symbol,
|
||||
b.GetPairFormat(asset.Spot, false).Delimiter),
|
||||
Exchange: b.Name,
|
||||
Amount: order.Amount,
|
||||
ID: order.ID,
|
||||
OrderDate: parseOrderTime(order.CreatedAt),
|
||||
Amount: resp[i].Amount,
|
||||
ID: resp[i].ID,
|
||||
OrderDate: parseOrderTime(resp[i].CreatedAt),
|
||||
OrderSide: side,
|
||||
OrderType: exchange.OrderType(strings.ToUpper(order.Type)),
|
||||
Price: order.Price,
|
||||
Status: order.Status,
|
||||
OrderType: exchange.OrderType(strings.ToUpper(resp[i].Type)),
|
||||
Price: resp[i].Price,
|
||||
Status: resp[i].Status,
|
||||
}
|
||||
|
||||
fills, err := b.GetFills(order.ID, "", "", "", "")
|
||||
fills, err := b.GetFills(resp[i].ID, "", "", "", "", "")
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys, "unable to get order fills for orderID %s", order.ID)
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s: Unable to get order fills for orderID %s", b.Name,
|
||||
resp[i].ID)
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range *fills {
|
||||
f := (*fills)[i]
|
||||
createdAt, _ := time.Parse(time.RFC3339, f.CreatedAt)
|
||||
for i := range fills {
|
||||
createdAt, _ := time.Parse(time.RFC3339, fills[i].CreatedAt)
|
||||
openOrder.Trades = append(openOrder.Trades, exchange.TradeHistory{
|
||||
Timestamp: createdAt,
|
||||
TID: f.ID,
|
||||
Price: f.Price,
|
||||
Amount: f.Amount,
|
||||
TID: fills[i].ID,
|
||||
Price: fills[i].Price,
|
||||
Amount: fills[i].Amount,
|
||||
Exchange: b.Name,
|
||||
Type: exchange.OrderSide(f.Side).ToString(),
|
||||
Fee: f.Fee,
|
||||
Type: fills[i].Side,
|
||||
Fee: fills[i].Fee,
|
||||
})
|
||||
}
|
||||
orders = append(orders, openOrder)
|
||||
|
||||
Reference in New Issue
Block a user