Merge branch 'master' into engine

This commit is contained in:
Adrian Gallagher
2019-10-28 11:01:23 +11:00
5 changed files with 519 additions and 512 deletions

View File

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

View File

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

View File

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

View File

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

View File

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