Add REST and websocket support for BTSE (#250)

* Add REST and websocket support for BTSE exchange

TO-DO: Finish wrappers and expand test coverage

* Fill out wrapper funcs and expand test coverage
This commit is contained in:
Adrian Gallagher
2019-03-08 16:33:10 +11:00
committed by GitHub
parent 6d6b0ae067
commit 3ac8b7746f
44 changed files with 1543 additions and 52 deletions

View File

@@ -29,6 +29,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Bittrex | Yes | No | NA |
| BTCC | Yes | Yes | No |
| BTCMarkets | Yes | No | NA |
| BTSE | Yes | Yes | NA |
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| CoinbasePro | Yes | Yes | No|

View File

@@ -11,7 +11,7 @@ import (
const (
// Default number of enabled exchanges. Modify this whenever an exchange is
// added or removed
defaultEnabledExchanges = 27
defaultEnabledExchanges = 28
)
func TestGetCurrencyConfig(t *testing.T) {

View File

@@ -509,6 +509,47 @@
}
]
},
{
"name": "BTSE",
"enabled": true,
"verbose": false,
"websocket": true,
"useSandbox": false,
"restPollingDelay": 10,
"httpTimeout": 15000000000,
"httpUserAgent": "",
"authenticatedApiSupport": false,
"apiKey": "Key",
"apiSecret": "Secret",
"apiUrl": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"apiUrlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
"proxyAddress": "",
"websocketUrl": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API",
"availablePairs": "BTC-USD",
"enabledPairs": "BTC-USD",
"baseCurrencies": "USD",
"assetTypes": "SPOT",
"supportsAutoPairUpdates": true,
"configCurrencyPairFormat": {
"uppercase": true,
"delimiter": "-"
},
"requestCurrencyPairFormat": {
"uppercase": true,
"delimiter": "-"
},
"bankAccounts": [
{
"bankName": "",
"bankAddress": "",
"accountName": "",
"accountNumber": "",
"swiftCode": "",
"iban": "",
"supportedCurrencies": ""
}
]
},
{
"name": "BTC Markets",
"enabled": true,

View File

@@ -16,6 +16,7 @@ import (
"github.com/thrasher-/gocryptotrader/exchanges/bittrex"
"github.com/thrasher-/gocryptotrader/exchanges/btcc"
"github.com/thrasher-/gocryptotrader/exchanges/btcmarkets"
"github.com/thrasher-/gocryptotrader/exchanges/btse"
"github.com/thrasher-/gocryptotrader/exchanges/coinbasepro"
"github.com/thrasher-/gocryptotrader/exchanges/coinut"
"github.com/thrasher-/gocryptotrader/exchanges/exmo"
@@ -154,6 +155,8 @@ func LoadExchange(name string, useWG bool, wg *sync.WaitGroup) error {
exch = new(btcc.BTCC)
case "btc markets":
exch = new(btcmarkets.BTCMarkets)
case "btse":
exch = new(btse.BTSE)
case "coinut":
exch = new(coinut.COINUT)
case "exmo":

View File

@@ -155,7 +155,7 @@ func (a *Alphapoint) CancelAllOrders(orderCancellation exchange.OrderCancellatio
}
// GetOrderInfo returns information on a current open order
func (a *Alphapoint) GetOrderInfo(orderID int64) (float64, error) {
func (a *Alphapoint) GetOrderInfo(orderID string) (float64, error) {
orders, err := a.GetOrders()
if err != nil {
return 0, err
@@ -163,7 +163,7 @@ func (a *Alphapoint) GetOrderInfo(orderID int64) (float64, error) {
for x := range orders {
for y := range orders[x].OpenOrders {
if int64(orders[x].OpenOrders[y].ServerOrderID) == orderID {
if strconv.Itoa(orders[x].OpenOrders[y].ServerOrderID) == orderID {
return orders[x].OpenOrders[y].QtyRemaining, nil
}
}

View File

@@ -303,7 +303,7 @@ func (a *ANX) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelAllO
}
// GetOrderInfo returns information on a current open order
func (a *ANX) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (a *ANX) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -265,7 +265,7 @@ func (b *Binance) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Cancel
}
// GetOrderInfo returns information on a current open order
func (b *Binance) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *Binance) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -211,7 +211,7 @@ func (b *Bitfinex) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Cance
}
// GetOrderInfo returns information on a current open order
func (b *Bitfinex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *Bitfinex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -178,7 +178,7 @@ func (b *Bitflyer) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Cance
}
// GetOrderInfo returns information on a current open order
func (b *Bitflyer) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *Bitflyer) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -244,7 +244,7 @@ func (b *Bithumb) CancelAllOrders(orderCancellation exchange.OrderCancellation)
}
// GetOrderInfo returns information on a current open order
func (b *Bithumb) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *Bithumb) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -246,7 +246,7 @@ func (b *Bitmex) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelA
}
// GetOrderInfo returns information on a current open order
func (b *Bitmex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *Bitmex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -218,7 +218,7 @@ func (b *Bitstamp) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Cance
}
// GetOrderInfo returns information on a current open order
func (b *Bitstamp) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *Bitstamp) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -240,7 +240,7 @@ func (b *Bittrex) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Cancel
}
// GetOrderInfo returns information on a current open order
func (b *Bittrex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *Bittrex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -121,7 +121,7 @@ func (b *BTCC) CancelAllOrders(orderCancellation exchange.OrderCancellation) (ex
}
// GetOrderInfo returns information on a current open order
func (b *BTCC) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *BTCC) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
return exchange.OrderDetail{}, common.ErrNotYetImplemented
}

View File

@@ -160,7 +160,7 @@ func TestCancelOrder(t *testing.T) {
}
func TestGetOrderInfo(t *testing.T) {
_, err := b.GetOrderInfo(1337)
_, err := b.GetOrderInfo("1337")
if err == nil {
t.Error("Test failed - GetOrderInfo() error", err)
}

View File

@@ -230,10 +230,15 @@ func (b *BTCMarkets) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Can
}
// GetOrderInfo returns information on a current open order
func (b *BTCMarkets) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (b *BTCMarkets) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var OrderDetail exchange.OrderDetail
orders, err := b.GetOrderDetail([]int64{orderID})
o, err := strconv.ParseInt(orderID, 10, 64)
if err != nil {
return OrderDetail, err
}
orders, err := b.GetOrderDetail([]int64{o})
if err != nil {
return OrderDetail, err
}

30
exchanges/btse/README.md Normal file
View File

@@ -0,0 +1,30 @@
# GoCryptoTrader Btse Exchange Wrapper
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
An exchange interface wrapper for the GoCryptoTrader application.
## This is still in active development
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
## Current Btse Exchange Features
+ REST Support
+ Websocket Support
+ Can be used as a package
## Notes
+ Please add notes here with any production issues
+ Please provide link to exchange website and API documentation
## Contributors
+ Please add your information
|User|Github|Contribution|
|--|--|--|
|AliasGoesHere|https://github.com/AliasGoesHere |WHAT-YOU-DID|

367
exchanges/btse/btse.go Normal file
View File

@@ -0,0 +1,367 @@
package btse
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/symbol"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/request"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
log "github.com/thrasher-/gocryptotrader/logger"
)
// BTSE is the overarching type across this package
type BTSE struct {
exchange.Base
WebsocketConn *websocket.Conn
}
const (
btseAPIURL = "https://api.btse.com/v1/restapi"
btseAPIVersion = "1"
// Public endpoints
btseMarkets = "markets"
btseTrades = "trades"
btseTicker = "ticker"
btseStats = "stats"
btseTime = "time"
// Authenticated endpoints
btseAccount = "account"
btseOrder = "order"
btsePendingOrders = "pending"
btseDeleteOrder = "deleteOrder"
btseDeleteOrders = "deleteOrders"
btseFills = "fills"
)
// SetDefaults sets the basic defaults for BTSE
func (b *BTSE) SetDefaults() {
b.Name = "BTSE"
b.Enabled = false
b.Verbose = false
b.RESTPollingDelay = 10
b.APIWithdrawPermissions = exchange.NoAPIWithdrawalMethods
b.RequestCurrencyPairFormat.Delimiter = "-"
b.RequestCurrencyPairFormat.Uppercase = true
b.ConfigCurrencyPairFormat.Delimiter = "-"
b.ConfigCurrencyPairFormat.Uppercase = true
b.AssetTypes = []string{ticker.Spot}
b.Requester = request.New(b.Name,
request.NewRateLimit(time.Second, 0),
request.NewRateLimit(time.Second, 0),
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
b.APIUrlDefault = btseAPIURL
b.APIUrl = b.APIUrlDefault
b.SupportsAutoPairUpdating = true
b.SupportsRESTTickerBatching = false
b.WebsocketInit()
b.Websocket.Functionality = exchange.WebsocketOrderbookSupported |
exchange.WebsocketTickerSupported
}
// Setup takes in the supplied exchange configuration details and sets params
func (b *BTSE) Setup(exch config.ExchangeConfig) {
if !exch.Enabled {
b.SetEnabled(false)
} else {
b.Enabled = true
b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport
b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false)
b.SetHTTPClientTimeout(exch.HTTPTimeout)
b.SetHTTPClientUserAgent(exch.HTTPUserAgent)
b.RESTPollingDelay = exch.RESTPollingDelay
b.Verbose = exch.Verbose
b.Websocket.SetEnabled(exch.Websocket)
b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",")
b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",")
b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",")
err := b.SetCurrencyPairFormat()
if err != nil {
log.Fatal(err)
}
err = b.SetAssetTypes()
if err != nil {
log.Fatal(err)
}
err = b.SetAutoPairDefaults()
if err != nil {
log.Fatal(err)
}
err = b.SetAPIURL(exch)
if err != nil {
log.Fatal(err)
}
err = b.SetClientProxyAddress(exch.ProxyAddress)
if err != nil {
log.Fatal(err)
}
err = b.WebsocketSetup(b.WsConnect,
exch.Name,
exch.Websocket,
btseWebsocket,
exch.WebsocketURL)
if err != nil {
log.Fatal(err)
}
}
}
// 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)
}
// GetTrades returns a list of trades for the specified symbol
func (b *BTSE) GetTrades(symbol string) (*Trades, error) {
var t Trades
endpoint := fmt.Sprintf("%s/%s", btseTrades, symbol)
return &t, b.SendHTTPRequest(http.MethodGet, endpoint, &t)
}
// GetTicker returns the ticker for a specified symbol
func (b *BTSE) GetTicker(symbol string) (*Ticker, error) {
type tickerResponse struct {
Price interface{} `json:"price"`
Size float64 `json:"size,string"`
Bid float64 `json:"bid,string"`
Ask float64 `json:"ask,string"`
Volume float64 `json:"volume,string"`
Time string `json:"time"`
}
var r tickerResponse
endpoint := fmt.Sprintf("%s/%s", btseTicker, symbol)
err := b.SendHTTPRequest(http.MethodGet, endpoint, &r)
if err != nil {
return nil, err
}
p := strings.Replace(r.Price.(string), ",", "", -1)
price, err := strconv.ParseFloat(p, 64)
if err != nil {
return nil, err
}
return &Ticker{
Price: price,
Size: r.Size,
Bid: r.Bid,
Ask: r.Ask,
Volume: r.Volume,
Time: r.Time,
}, nil
}
// GetMarketStatistics gets market statistics for a specificed market
func (b *BTSE) GetMarketStatistics(symbol string) (*MarketStatistics, error) {
var m MarketStatistics
endpoint := fmt.Sprintf("%s/%s", btseStats, symbol)
return &m, b.SendHTTPRequest(http.MethodGet, endpoint, &m)
}
// GetServerTime returns the exchanges server time
func (b *BTSE) GetServerTime() (*ServerTime, error) {
var s ServerTime
return &s, b.SendHTTPRequest(http.MethodGet, btseTime, &s)
}
// GetAccountBalance returns the users account balance
func (b *BTSE) GetAccountBalance() (*AccountBalance, error) {
var a AccountBalance
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
if timeInForce != "" {
req["time_in_force"] = timeInForce
}
if tag != "" {
req["tag"] = tag
}
type orderResp struct {
ID string `json:"id"`
}
var r orderResp
return &r.ID, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseOrder, req, &r)
}
// GetOrders returns all pending orders
func (b *BTSE) GetOrders(productID string) (*OpenOrders, error) {
req := make(map[string]interface{})
if productID != "" {
req["product_id"] = productID
}
var o OpenOrders
return &o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o)
}
// CancelExistingOrder cancels an order
func (b *BTSE) CancelExistingOrder(orderID, productID string) (*CancelOrder, error) {
var c CancelOrder
req := make(map[string]interface{})
req["order_id"] = orderID
req["product_id"] = productID
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")
}
req := make(map[string]interface{})
if orderID != "" {
req["order_id"] = orderID
}
if productID != "" {
req["product_id"] = productID
}
if before != "" {
req["before"] = before
}
if after != "" {
req["after"] = after
}
if limit != "" {
req["limit"] = limit
}
var o FilledOrders
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 {
p := fmt.Sprintf("%s/%s", btseAPIURL, endpoint)
return b.SendPayload(method, p, nil, nil, &result, false, b.Verbose)
}
// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the desired endpoint
func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}) error {
if !b.AuthenticatedAPISupport {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name)
}
payload, err := common.JSONEncode(req)
if err != nil {
return errors.New("sendAuthenticatedAPIRequest: unable to JSON request")
}
headers := make(map[string]string)
headers["API-KEY"] = b.APIKey
headers["API-PASSPHRASE"] = b.APISecret
if len(payload) > 0 {
headers["Content-Type"] = "application/json"
}
p := fmt.Sprintf("%s/%s", btseAPIURL, endpoint)
if b.Verbose {
log.Debugf("Sending %s request to URL %s with params %s\n", method, p, string(payload))
}
return b.SendPayload(method, p, headers, strings.NewReader(string(payload)),
&result, true, b.Verbose)
}
// GetFee returns an estimate of fee based on type of transaction
func (b *BTSE) GetFee(feeBuilder exchange.FeeBuilder) (float64, error) {
var fee float64
switch feeBuilder.FeeType {
case exchange.CryptocurrencyTradeFee:
fee = calculateTradingFee(feeBuilder.IsMaker)
case exchange.CryptocurrencyWithdrawalFee:
if feeBuilder.FirstCurrency == symbol.BTC {
fee = 0.0005
} else if feeBuilder.FirstCurrency == symbol.USDT {
fee = 5
}
case exchange.InternationalBankDepositFee:
fee = getInternationalBankDepositFee(feeBuilder.Amount)
case exchange.InternationalBankWithdrawalFee:
fee = getInternationalBankWithdrawalFee(feeBuilder.Amount)
}
return fee, nil
}
// getInternationalBankDepositFee returns international deposit fee
// Only when the initial deposit amount is less than $1000 or equivalent,
// BTSE will charge a small fee (0.25% or $3 USD equivalent, whichever is greater).
// The small deposit fee is charged in whatever currency it comes in.
func getInternationalBankDepositFee(amount float64) float64 {
var fee float64
if amount <= 1000 {
fee = amount * 0.0025
if fee < 3 {
return 3
}
}
return fee
}
// getInternationalBankWithdrawalFee returns international withdrawal fee
// 0.1% (min25 USD)
func getInternationalBankWithdrawalFee(amount float64) float64 {
fee := amount * 0.001
if fee < 25 {
return 25
}
return fee
}
// calculateTradingFee BTSE has fee tiers, but does not disclose them via API,
// so the largest fee has to be assumed
func calculateTradingFee(isMaker bool) float64 {
fee := 0.00050
if !isMaker {
fee = 0.0015
}
return fee
}
func parseOrderTime(timeStr string) time.Time {
t, _ := time.Parse("2006-01-02 15:04:04", timeStr)
return t
}

298
exchanges/btse/btse_test.go Normal file
View File

@@ -0,0 +1,298 @@
package btse
import (
"testing"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/currency/symbol"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
)
// Please supply your own keys here to do better tests
const (
apiKey = ""
apiSecret = ""
canManipulateRealOrders = false
)
var b BTSE
func TestSetDefaults(t *testing.T) {
b.SetDefaults()
}
func TestSetup(t *testing.T) {
cfg := config.GetConfig()
cfg.LoadConfig("../../testdata/configtest.json")
btseConfig, err := cfg.GetExchangeConfig("BTSE")
if err != nil {
t.Error("Test Failed - BTSE Setup() init error")
}
btseConfig.AuthenticatedAPISupport = true
btseConfig.APIKey = apiKey
btseConfig.APISecret = apiSecret
b.Setup(btseConfig)
}
func TestGetMarkets(t *testing.T) {
b.SetDefaults()
_, err := b.GetMarkets()
if err != nil {
t.Fatalf("Test failed. Err: %s", err)
}
}
func TestGetTrades(t *testing.T) {
b.SetDefaults()
_, err := b.GetTrades("BTC-USD")
if err != nil {
t.Fatalf("Test failed. Err: %s", err)
}
}
func TestGetTicker(t *testing.T) {
b.SetDefaults()
_, err := b.GetTicker("BTC-USD")
if err != nil {
t.Fatalf("Test failed. Err: %s", err)
}
}
func TestGetMarketStatistics(t *testing.T) {
b.SetDefaults()
_, err := b.GetMarketStatistics("BTC-USD")
if err != nil {
t.Fatalf("Test failed. Err: %s", err)
}
}
func TestGetServerTime(t *testing.T) {
b.SetDefaults()
_, err := b.GetServerTime()
if err != nil {
t.Fatalf("Test failed. Err: %s", err)
}
}
func TestGetAccount(t *testing.T) {
b.SetDefaults()
TestSetup(t)
_, 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")
}
}
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")
}
}
func TestGetActiveOrders(t *testing.T) {
b.SetDefaults()
TestSetup(t)
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")
}
}
func TestGetOrderHistory(t *testing.T) {
b.SetDefaults()
TestSetup(t)
var getOrdersRequest = exchange.GetOrdersRequest{
OrderType: exchange.AnyOrderType,
}
_, err := b.GetOrderHistory(getOrdersRequest)
if err != common.ErrFunctionNotSupported {
t.Fatal("Test failed. Expected different result")
}
}
func TestFormatWithdrawPermissions(t *testing.T) {
b.SetDefaults()
expected := exchange.NoAPIWithdrawalMethodsText
actual := b.FormatWithdrawPermissions()
if actual != expected {
t.Errorf("Expected: %s, Received: %s", expected, actual)
}
}
func TestGetFee(t *testing.T) {
b.SetDefaults()
TestSetup(t)
feeBuilder := exchange.FeeBuilder{
FeeType: exchange.CryptocurrencyTradeFee,
FirstCurrency: "BTC",
SecondCurrency: "USD",
IsMaker: true,
Amount: 1000,
}
if resp, err := b.GetFee(feeBuilder); resp != 0.00050 || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", 0.00050, resp)
t.Error(err)
}
feeBuilder.IsMaker = false
if resp, err := b.GetFee(feeBuilder); resp != 0.0015 || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", 0.0015, resp)
t.Error(err)
}
feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee
if resp, err := b.GetFee(feeBuilder); resp != 0.0005 || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", 0.0005, resp)
t.Error(err)
}
feeBuilder.FirstCurrency = "USDT"
if resp, err := b.GetFee(feeBuilder); resp != float64(5) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(5), resp)
t.Error(err)
}
feeBuilder.FeeType = exchange.InternationalBankDepositFee
if resp, err := b.GetFee(feeBuilder); resp != float64(3) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(3), resp)
t.Error(err)
}
feeBuilder.Amount = 1000000
if resp, err := b.GetFee(feeBuilder); resp != float64(0) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(0), resp)
t.Error(err)
}
feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee
if resp, err := b.GetFee(feeBuilder); resp != float64(1000) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(1000), resp)
t.Error(err)
}
feeBuilder.Amount = 1000
if resp, err := b.GetFee(feeBuilder); resp != float64(25) || err != nil {
t.Errorf("Test Failed - GetFee() error. Expected: %f, Received: %f", float64(25), resp)
t.Error(err)
}
}
func TestParseOrderTime(t *testing.T) {
expected := int64(1534794360)
actual := parseOrderTime("2018-08-20 19:20:46").Unix()
if expected != actual {
t.Errorf("Test Failed. TestParseOrderTime expected: %d, got %d", expected, actual)
}
}
func areTestAPIKeysSet() bool {
if b.APIKey != "" && b.APIKey != "Key" &&
b.APISecret != "" && b.APISecret != "Secret" {
return true
}
return false
}
// 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")
}
var p = pair.CurrencyPair{
Delimiter: "",
FirstCurrency: symbol.BTC,
SecondCurrency: symbol.USD,
}
response, err := b.SubmitOrder(p, exchange.SellOrderSide, exchange.LimitOrderType, 0.01, 1000000, "clientId")
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
t.Errorf("Order failed to be placed: %v", err)
} else if !areTestAPIKeysSet() && err == nil {
t.Error("Expecting an error when no keys are set")
}
}
func TestCancelExchangeOrder(t *testing.T) {
b.SetDefaults()
TestSetup(t)
if areTestAPIKeysSet() && !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
currencyPair := pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USD, "-")
var orderCancellation = exchange.OrderCancellation{
OrderID: "0b66ccaf-dfd4-4b9f-a30b-2380b9c7b66d",
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)
}
}
func TestCancelAllExchangeOrders(t *testing.T) {
b.SetDefaults()
TestSetup(t)
if areTestAPIKeysSet() && !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
currencyPair := pair.NewCurrencyPairWithDelimiter(symbol.BTC, symbol.USD, "-")
var orderCancellation = exchange.OrderCancellation{
OrderID: "1",
WalletAddress: "1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB",
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 {
t.Errorf("Could not cancel orders: %v", err)
}
if len(resp.OrderStatus) > 0 {
t.Errorf("%v orders failed to cancel", len(resp.OrderStatus))
}
}

View File

@@ -0,0 +1,135 @@
package btse
// Market stores market data
type Market struct {
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"`
BaseIncremementSize float64 `json:"base_increment_size"`
QuoteMinPrice float64 `json:"quote_min_price"`
QuoteIncrement float64 `json:"quote_increment"`
Status string `json:"status"`
}
// Markets stores an array of market data
type Markets []Market
// Trade stores trade data
type Trade struct {
SerialID string `json:"serial_id"`
Symbol string `json:"symbol"`
Price float64 `json:"price"`
Amount float64 `json:"amount"`
Time string `json:"time"`
Type string `json:"type"`
}
// Trades stores an array of trade data
type Trades []Trade
// Ticker stores the ticker data
type Ticker struct {
Price float64
Size float64
Bid float64
Ask float64
Volume float64
Time string
}
// MarketStatistics stores market statistics for a particular product
type MarketStatistics struct {
Open float64 `json:"open,string"`
Low float64 `json:"low,string"`
High float64 `json:"high,string"`
Close float64 `json:"close,string"`
Volume float64 `json:"volume,string"`
Time string `json:"time"`
}
// ServerTime stores the server time data
type ServerTime struct {
ISO string `json:"iso"`
Epoch float64 `json:"epoch"`
}
// CurrencyBalance stores the account info data
type CurrencyBalance struct {
Currency string `json:"currency"`
Total float64 `json:"total,string"`
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"`
Type string `json:"type"`
Side string `json:"side"`
Price float64 `json:"price"`
Amount float64 `json:"amount"`
Tag string `json:"tag"`
ProductID string `json:"product_id"`
CreatedAt string `json:"created_at"`
}
// OpenOrder stores an open order info
type OpenOrder struct {
Order
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"`
Time int64 `json:"time"`
}
// FilledOrder stores filled order data
type FilledOrder struct {
Price float64 `json:"price"`
Amount float64 `json:"amount"`
Fee float64 `json:"fee"`
Side string `json:"side"`
Tag string `json:"tag"`
ID int64 `json:"id"`
TradeID string `json:"trade_id"`
ProductID string `json:"product_id"`
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 websocketChannel struct {
Name string `json:"name"`
ProductIDs []string `json:"product_ids"`
}
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 websocketOrderbookSnapshot struct {
ProductID string `json:"product_id"`
Type string `json:"type"`
Bids [][]interface{} `json:"bids"`
Asks [][]interface{} `json:"asks"`
}

View File

@@ -0,0 +1,223 @@
package btse
import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
)
const (
btseWebsocket = "wss://ws.btse.com/api/ws-feed"
)
// WebsocketSubscriber subscribes to websocket channels with respect to enabled
// currencies
func (b *BTSE) WebsocketSubscriber() error {
subscribe := websocketSubscribe{
Type: "subscribe",
Channels: []websocketChannel{
{
Name: "snapshot",
ProductIDs: b.EnabledPairs,
},
{
Name: "ticker",
ProductIDs: b.EnabledPairs,
},
},
}
data, err := common.JSONEncode(subscribe)
if err != nil {
return err
}
return b.WebsocketConn.WriteMessage(websocket.TextMessage, data)
}
// WsConnect connects the websocket client
func (b *BTSE) WsConnect() error {
if !b.Websocket.IsEnabled() || !b.IsEnabled() {
return errors.New(exchange.WebsocketNotEnabled)
}
var dialer websocket.Dialer
if b.Websocket.GetProxyAddress() != "" {
proxy, err := url.Parse(b.Websocket.GetProxyAddress())
if err != nil {
return fmt.Errorf("%s websocket error - proxy address %s",
b.Name, err)
}
dialer.Proxy = http.ProxyURL(proxy)
}
var err error
b.WebsocketConn, _, err = dialer.Dial(b.Websocket.GetWebsocketURL(),
http.Header{})
if err != nil {
return fmt.Errorf("%s websocket error - unable to connect %s",
b.Name, err)
}
err = b.WebsocketSubscriber()
if err != nil {
return err
}
go b.WsHandleData()
return nil
}
// WsReadData reads data from the websocket connection
func (b *BTSE) WsReadData() (exchange.WebsocketResponse, error) {
_, resp, err := b.WebsocketConn.ReadMessage()
if err != nil {
return exchange.WebsocketResponse{}, err
}
b.Websocket.TrafficAlert <- struct{}{}
return exchange.WebsocketResponse{Raw: resp}, nil
}
// WsHandleData handles read data from websocket connection
func (b *BTSE) WsHandleData() {
b.Websocket.Wg.Add(1)
defer func() {
err := b.WebsocketConn.Close()
if err != nil {
b.Websocket.DataHandler <- fmt.Errorf("%s - Unable to to close Websocket connection. Error: %s",
b.Name, err)
}
b.Websocket.Wg.Done()
}()
for {
select {
case <-b.Websocket.ShutdownC:
return
default:
resp, err := b.WsReadData()
if err != nil {
b.Websocket.DataHandler <- err
return
}
type MsgType struct {
Type string `json:"type"`
ProductID string `json:"product_id"`
}
msgType := MsgType{}
err = common.JSONDecode(resp.Raw, &msgType)
if err != nil {
b.Websocket.DataHandler <- err
continue
}
switch msgType.Type {
case "ticker":
var t wsTicker
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 <- exchange.TickerData{
Timestamp: time.Now(),
Pair: pair.NewCurrencyPairDelimiter(t.ProductID, "-"),
AssetType: "SPOT",
Exchange: b.GetName(),
OpenPrice: price,
}
case "snapshot":
snapshot := websocketOrderbookSnapshot{}
err := common.JSONDecode(resp.Raw, &snapshot)
if err != nil {
b.Websocket.DataHandler <- err
continue
}
err = b.wsProcessSnapshot(snapshot)
if err != nil {
b.Websocket.DataHandler <- err
continue
}
}
}
}
}
// ProcessSnapshot processes the initial orderbook snap shot
func (b *BTSE) wsProcessSnapshot(snapshot websocketOrderbookSnapshot) error {
var base orderbook.Base
for _, bid := range snapshot.Bids {
p := strings.Replace(bid[0].(string), ",", "", -1)
price, err := strconv.ParseFloat(p, 64)
if err != nil {
return err
}
a := strings.Replace(bid[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 _, ask := range snapshot.Asks {
p := strings.Replace(ask[0].(string), ",", "", -1)
price, err := strconv.ParseFloat(p, 64)
if err != nil {
return err
}
a := strings.Replace(ask[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 := pair.NewCurrencyPairDelimiter(snapshot.ProductID, "-")
base.AssetType = "SPOT"
base.Pair = p
base.CurrencyPair = snapshot.ProductID
base.LastUpdated = time.Now()
orderbook.ProcessOrderbook(b.Name, p, base, "SPOT")
b.Websocket.DataHandler <- exchange.WebsocketOrderbookUpdate{
Pair: p,
Asset: "SPOT",
Exchange: b.GetName(),
}
return nil
}

View File

@@ -0,0 +1,346 @@
package btse
import (
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
log "github.com/thrasher-/gocryptotrader/logger"
)
// Start starts the BTSE go routine
func (b *BTSE) Start(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
b.Run()
wg.Done()
}()
}
// Run implements the BTSE wrapper
func (b *BTSE) Run() {
if b.Verbose {
log.Debugf("%s Websocket: %s. (url: %s).\n", b.GetName(), common.IsEnabled(b.Websocket.IsEnabled()), b.Websocket.GetWebsocketURL())
log.Debugf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay)
log.Debugf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs)
}
markets, err := b.GetMarkets()
if err != nil {
log.Errorf("%s failed to get trading pairs. Err: %s", b.Name, err)
} else {
var currencies []string
for _, m := range *markets {
currencies = append(currencies, m.ID)
}
err = b.UpdateCurrencies(currencies, false, false)
if err != nil {
log.Errorf("%s Failed to update available currencies.\n", b.Name)
}
}
}
// UpdateTicker updates and returns the ticker for a currency pair
func (b *BTSE) UpdateTicker(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
var tickerPrice ticker.Price
t, err := b.GetTicker(exchange.FormatExchangeCurrency(b.Name, p).String())
if err != nil {
return tickerPrice, err
}
s, err := b.GetMarketStatistics(exchange.FormatExchangeCurrency(b.Name, p).String())
if err != nil {
return tickerPrice, err
}
tickerPrice.Pair = p
tickerPrice.Ask = t.Ask
tickerPrice.Bid = t.Bid
tickerPrice.Low = s.Low
tickerPrice.Last = t.Price
tickerPrice.Volume = s.Volume
tickerPrice.High = s.High
ticker.ProcessTicker(b.GetName(), p, tickerPrice, assetType)
return ticker.GetTicker(b.Name, p, assetType)
}
// GetTickerPrice returns the ticker for a currency pair
func (b *BTSE) GetTickerPrice(p pair.CurrencyPair, assetType string) (ticker.Price, error) {
tickerNew, err := ticker.GetTicker(b.GetName(), p, assetType)
if err != nil {
return b.UpdateTicker(p, assetType)
}
return tickerNew, nil
}
// GetOrderbookEx returns orderbook base on the currency pair
func (b *BTSE) GetOrderbookEx(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
ob, err := orderbook.GetOrderbook(b.GetName(), p, assetType)
if err != nil {
return b.UpdateOrderbook(p, assetType)
}
return ob, nil
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (b *BTSE) UpdateOrderbook(p pair.CurrencyPair, assetType string) (orderbook.Base, error) {
return orderbook.Base{}, common.ErrFunctionNotSupported
}
// GetAccountInfo retrieves balances for all enabled currencies for the
// BTSE exchange
func (b *BTSE) GetAccountInfo() (exchange.AccountInfo, error) {
var a exchange.AccountInfo
balance, err := b.GetAccountBalance()
if err != nil {
return a, err
}
var currencies []exchange.AccountCurrencyInfo
for _, b := range *balance {
currencies = append(currencies,
exchange.AccountCurrencyInfo{
CurrencyName: b.Currency,
TotalValue: b.Total,
Hold: b.Available,
},
)
}
a.Exchange = b.Name
a.Accounts = []exchange.Account{
{
Currencies: currencies,
},
}
return a, nil
}
// GetFundingHistory returns funding history, deposits and
// withdrawals
func (b *BTSE) GetFundingHistory() ([]exchange.FundHistory, error) {
return nil, common.ErrFunctionNotSupported
}
// GetExchangeHistory returns historic trade data since exchange opening.
func (b *BTSE) GetExchangeHistory(p pair.CurrencyPair, assetType string) ([]exchange.TradeHistory, error) {
return nil, common.ErrNotYetImplemented
}
// SubmitOrder submits a new order
func (b *BTSE) SubmitOrder(p pair.CurrencyPair, side exchange.OrderSide, orderType exchange.OrderType, amount, price float64, clientID string) (exchange.SubmitOrderResponse, error) {
var resp exchange.SubmitOrderResponse
r, err := b.CreateOrder(amount, price, side.ToString(),
orderType.ToString(), exchange.FormatExchangeCurrency(b.Name, p).String(), "GTC", clientID)
if err != nil {
return resp, err
}
if *r != "" {
resp.IsOrderPlaced = true
resp.OrderID = *r
}
return resp, nil
}
// ModifyOrder will allow of changing orderbook placement and limit to
// market conversion
func (b *BTSE) ModifyOrder(action exchange.ModifyOrder) (string, error) {
return "", common.ErrFunctionNotSupported
}
// CancelOrder cancels an order by its corresponding ID number
func (b *BTSE) CancelOrder(order exchange.OrderCancellation) error {
r, err := b.CancelExistingOrder(order.OrderID,
exchange.FormatExchangeCurrency(b.Name, order.CurrencyPair).String())
if err != nil {
return err
}
switch r.Code {
case -1:
return errors.New("order cancellation unsuccessful")
case 4:
return errors.New("order cancellation timeout")
}
return nil
}
// CancelAllOrders cancels all orders associated with a currency pair
// 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) CancelAllOrders(orderCancellation exchange.OrderCancellation) (exchange.CancelAllOrdersResponse, error) {
r, err := b.CancelOrders(exchange.FormatExchangeCurrency(b.Name,
orderCancellation.CurrencyPair).String())
if err != nil {
return exchange.CancelAllOrdersResponse{}, err
}
var resp exchange.CancelAllOrdersResponse
switch r.Code {
case -1:
return resp, errors.New("order cancellation unsuccessful")
case 4:
return resp, errors.New("order cancellation timeout")
}
return resp, nil
}
// GetOrderInfo returns information on a current open order
func (b *BTSE) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
o, err := b.GetOrders("")
if err != nil {
return exchange.OrderDetail{}, err
}
var od exchange.OrderDetail
if len(*o) == 0 {
return od, errors.New("no orders found")
}
for _, o := range *o {
if o.ID != orderID {
continue
}
var side = exchange.BuyOrderSide
if strings.EqualFold(o.Side, exchange.AskOrderSide.ToString()) {
side = exchange.SellOrderSide
}
od.CurrencyPair = pair.NewCurrencyPairDelimiter(o.ProductID,
b.ConfigCurrencyPairFormat.Delimiter)
od.Exchange = b.Name
od.Amount = o.Amount
od.ID = o.ID
od.OrderDate = parseOrderTime(o.CreatedAt)
od.OrderSide = side
od.OrderType = exchange.OrderType(strings.ToUpper(o.Type))
od.Price = o.Price
od.Status = o.Status
fills, err := b.GetFills(orderID, "", "", "", "")
if err != nil {
return od, fmt.Errorf("unable to get order fills for orderID %s", orderID)
}
for _, f := range *fills {
createdAt, _ := time.Parse(time.RFC3339, f.CreatedAt)
od.Trades = append(od.Trades, exchange.TradeHistory{
Timestamp: createdAt,
TID: f.ID,
Price: f.Price,
Amount: f.Amount,
Exchange: b.Name,
Type: exchange.OrderSide(f.Side).ToString(),
Fee: f.Fee,
})
}
}
return od, nil
}
// GetDepositAddress returns a deposit address for a specified currency
func (b *BTSE) GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error) {
return "", common.ErrFunctionNotSupported
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
// submitted
func (b *BTSE) WithdrawCryptocurrencyFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
return "", common.ErrFunctionNotSupported
}
// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is
// submitted
func (b *BTSE) WithdrawFiatFunds(withdrawRequest exchange.WithdrawRequest) (string, error) {
return "", common.ErrFunctionNotSupported
}
// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is
// submitted
func (b *BTSE) WithdrawFiatFundsToInternationalBank(withdrawRequest exchange.WithdrawRequest) (string, error) {
return "", common.ErrFunctionNotSupported
}
// GetWebsocket returns a pointer to the exchange websocket
func (b *BTSE) GetWebsocket() (*exchange.Websocket, error) {
return b.Websocket, nil
}
// GetActiveOrders retrieves any orders that are active/open
func (b *BTSE) GetActiveOrders(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
resp, err := b.GetOrders("")
if err != nil {
return nil, err
}
var orders []exchange.OrderDetail
for _, order := range *resp {
var side = exchange.BuyOrderSide
if strings.EqualFold(order.Side, exchange.AskOrderSide.ToString()) {
side = exchange.SellOrderSide
}
openOrder := exchange.OrderDetail{
CurrencyPair: pair.NewCurrencyPairDelimiter(order.ProductID,
b.ConfigCurrencyPairFormat.Delimiter),
Exchange: b.Name,
Amount: order.Amount,
ID: order.ID,
OrderDate: parseOrderTime(order.CreatedAt),
OrderSide: side,
OrderType: exchange.OrderType(strings.ToUpper(order.Type)),
Price: order.Price,
Status: order.Status,
}
fills, err := b.GetFills(order.ID, "", "", "", "")
if err != nil {
log.Errorf("unable to get order fills for orderID %s", order.ID)
continue
}
for _, f := range *fills {
createdAt, _ := time.Parse(time.RFC3339, f.CreatedAt)
openOrder.Trades = append(openOrder.Trades, exchange.TradeHistory{
Timestamp: createdAt,
TID: f.ID,
Price: f.Price,
Amount: f.Amount,
Exchange: b.Name,
Type: exchange.OrderSide(f.Side).ToString(),
Fee: f.Fee,
})
}
orders = append(orders, openOrder)
}
exchange.FilterOrdersByType(&orders, getOrdersRequest.OrderType)
exchange.FilterOrdersByTickRange(&orders, getOrdersRequest.StartTicks, getOrdersRequest.EndTicks)
exchange.FilterOrdersBySide(&orders, getOrdersRequest.OrderSide)
return orders, nil
}
// GetOrderHistory retrieves account order information
// Can Limit response to specific order status
func (b *BTSE) GetOrderHistory(getOrdersRequest exchange.GetOrdersRequest) ([]exchange.OrderDetail, error) {
return nil, common.ErrFunctionNotSupported
}
// GetFeeByType returns an estimate of fee based on type of transaction
func (b *BTSE) GetFeeByType(feeBuilder exchange.FeeBuilder) (float64, error) {
return b.GetFee(feeBuilder)
}

View File

@@ -198,7 +198,7 @@ func (c *CoinbasePro) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Ca
}
// GetOrderInfo returns information on a current open order
func (c *CoinbasePro) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (c *CoinbasePro) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -330,7 +330,7 @@ func (c *COINUT) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelA
}
// GetOrderInfo returns information on a current open order
func (c *COINUT) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (c *COINUT) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -319,7 +319,7 @@ type IBotExchange interface {
ModifyOrder(action ModifyOrder) (string, error)
CancelOrder(order OrderCancellation) error
CancelAllOrders(orders OrderCancellation) (CancelAllOrdersResponse, error)
GetOrderInfo(orderID int64) (OrderDetail, error)
GetOrderInfo(orderID string) (OrderDetail, error)
GetDepositAddress(cryptocurrency pair.CurrencyItem, accountID string) (string, error)
GetOrderHistory(getOrdersRequest GetOrdersRequest) ([]OrderDetail, error)

View File

@@ -252,7 +252,7 @@ func (e *EXMO) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelAll
}
// GetOrderInfo returns information on a current open order
func (e *EXMO) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (e *EXMO) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -263,7 +263,7 @@ func (g *Gateio) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelA
}
// GetOrderInfo returns information on a current open order
func (g *Gateio) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (g *Gateio) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -189,7 +189,7 @@ func (g *Gemini) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelA
}
// GetOrderInfo returns information on a current open order
func (g *Gemini) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (g *Gemini) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -218,7 +218,7 @@ func (h *HitBTC) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelA
}
// GetOrderInfo returns information on a current open order
func (h *HitBTC) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (h *HitBTC) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -332,7 +332,7 @@ func (h *HUOBI) CancelAllOrders(orderCancellation exchange.OrderCancellation) (e
}
// GetOrderInfo returns information on a current open order
func (h *HUOBI) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (h *HUOBI) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -299,7 +299,7 @@ func (h *HUOBIHADAX) CancelAllOrders(orderCancellation exchange.OrderCancellatio
}
// GetOrderInfo returns information on a current open order
func (h *HUOBIHADAX) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (h *HUOBIHADAX) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -237,7 +237,7 @@ func (i *ItBit) CancelAllOrders(orderCancellation exchange.OrderCancellation) (e
}
// GetOrderInfo returns information on a current open order
func (i *ItBit) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (i *ItBit) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -237,7 +237,7 @@ func (k *Kraken) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelA
}
// GetOrderInfo returns information on a current open order
func (k *Kraken) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (k *Kraken) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -204,7 +204,7 @@ func (l *LakeBTC) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Cancel
}
// GetOrderInfo returns information on a current open order
func (l *LakeBTC) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (l *LakeBTC) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -246,7 +246,7 @@ func (l *LocalBitcoins) CancelAllOrders(_ exchange.OrderCancellation) (exchange.
}
// GetOrderInfo returns information on a current open order
func (l *LocalBitcoins) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (l *LocalBitcoins) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -283,7 +283,7 @@ func (o *OKCoin) CancelAllOrders(orderCancellation exchange.OrderCancellation) (
}
// GetOrderInfo returns information on a current open order
func (o *OKCoin) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (o *OKCoin) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -279,7 +279,7 @@ func (o *OKEX) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelAll
}
// GetOrderInfo returns information on a current open order
func (o *OKEX) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (o *OKEX) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -237,7 +237,7 @@ func (p *Poloniex) CancelAllOrders(_ exchange.OrderCancellation) (exchange.Cance
}
// GetOrderInfo returns information on a current open order
func (p *Poloniex) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (p *Poloniex) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -89,7 +89,7 @@ func TestGetOpenOrders(t *testing.T) {
func TestGetOrderInfo(t *testing.T) {
t.Parallel()
_, err := y.GetOrderInfo(6196974)
_, err := y.GetOrderInfo("6196974")
if err == nil {
t.Error("Test Failed - GetOrderInfo() error", err)
}

View File

@@ -221,7 +221,7 @@ func (y *Yobit) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelAl
}
// GetOrderInfo returns information on a current open order
func (y *Yobit) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (y *Yobit) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

View File

@@ -249,7 +249,7 @@ func (z *ZB) CancelAllOrders(_ exchange.OrderCancellation) (exchange.CancelAllOr
}
// GetOrderInfo returns information on a current open order
func (z *ZB) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func (z *ZB) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
var orderDetail exchange.OrderDetail
return orderDetail, common.ErrNotYetImplemented
}

File diff suppressed because one or more lines are too long

View File

@@ -30,6 +30,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Bittrex | Yes | No | NA |
| BTCC | Yes | Yes | No |
| BTCMarkets | Yes | No | NA |
| BTSE | Yes | Yes | NA |
| COINUT | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| CoinbasePro | Yes | Yes | No|

View File

@@ -137,7 +137,7 @@ func ({{.Variable}} *{{.CapitalName}}) CancelAllOrders(orderCancellation exchang
}
// GetOrderInfo returns information on a current open order
func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(orderID int64) (exchange.OrderDetail, error) {
func ({{.Variable}} *{{.CapitalName}}) GetOrderInfo(orderID string) (exchange.OrderDetail, error) {
return exchange.OrderDetail{}, common.ErrNotYetImplemented
}