mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Exchange: BTSE API 3.2 upgrade (#548)
* BTSE: bump API version to 3.2 * BTSE: added AccountFee endpoint * BTSE: ratelimit, modified GetFee() to use GetFeeInformation, CreateWalletAddress support * BTSE: gofmt * BTSE: renamed ratelimits to ratelimit * BTSE: comments on exported methods, reworked const * BTSE: remove verbose * BTSE: increased test coverage * BTSE: removed futures test * BTSE: comments, correct types, moon * Add support for futures ticker/orderbook, pass data from OHLCV to append because data is important :D * BTSE: update futures test pair * BTSE: updated creatorder test to use negative numbers * BTSE: updated test wording * BTSE: no BTSEx anymore * BTSE: use GetEnabledPairs * BTSE: updated order structu * BTSE: goimport test package * BTSE: added orderIntToType method * BTSE: added extra params to Trade/OpenOrders, kline format method added * BTSE: CreateOrder and IndexOrderPeg updates * removed binary * BTSE: type fixes for orderid, comments * BTSE: remove float tos tring conversion correct casing * BTSE: updated return types * BTSE: return slice * BTSE: update type to string, fixed comment on Price(), removed verbose flag * BTSE: use FormatExchangeCurrency() * BTSE: status -> string * BTSE: added withinLimit method to confirm order is within valid limits * BTSE: gofmt * BTSE: updated comment * BTSE: comment update * BTSE: init map for cancelallexcahgneorders * BTSE: updated json structs for trade history, reworked withinlimits and ordersizelimits to use sync.map * BTSE: test other currencies to confirm matching values for incerement * BTSE: comment, changed type * BTSE: added ordersizelimits seed data to test * BTSE: fpair -> fPair naming & kline sort update * BTSE: removed format call & asset param from withinLimits * BTSE: range over pairs for active orders * BTSE: verbose removal pass * BTSE: ticker batching support * BTSE: remove old pair formatter
This commit is contained in:
@@ -73,7 +73,7 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
|
||||
| Bitstamp | Y |
|
||||
| BTC Markets | Y |
|
||||
| Bittrex | |
|
||||
| BTSE | |
|
||||
| BTSE | Y |
|
||||
| Coinbase Pro | Y |
|
||||
| Coinbene | Y |
|
||||
| Coinut | |
|
||||
|
||||
@@ -8,12 +8,16 @@ import (
|
||||
"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/asset"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
@@ -25,99 +29,246 @@ type BTSE struct {
|
||||
|
||||
const (
|
||||
btseAPIURL = "https://api.btse.com"
|
||||
btseSPOTAPIPath = "/spot/v2/"
|
||||
btseFuturesAPIPath = "/futures/api/v2.1/"
|
||||
btseSPOTPath = "/spot"
|
||||
btseSPOTAPIPath = "/api/v3.2/"
|
||||
btseFuturesPath = "/futures"
|
||||
btseFuturesAPIPath = "/api/v2.1/"
|
||||
|
||||
// Public endpoints
|
||||
btseMarketOverview = "market_summary"
|
||||
btseMarkets = "markets"
|
||||
btseOrderbook = "orderbook"
|
||||
btseTrades = "trades"
|
||||
btseTicker = "ticker"
|
||||
btseStats = "stats"
|
||||
btseTime = "time"
|
||||
btseOHLCV = "ohlcv"
|
||||
btsePrice = "price"
|
||||
|
||||
// Authenticated endpoints
|
||||
btseAccount = "account"
|
||||
btseOrder = "order"
|
||||
btsePendingOrders = "pending"
|
||||
btseDeleteOrder = "deleteOrder"
|
||||
btseFills = "fills"
|
||||
btseTimeLayout = "2006-01-02 15:04:04"
|
||||
btseWallet = "user/wallet"
|
||||
btseWalletHistory = "user/wallet_history"
|
||||
btseWalletAddress = "user/wallet/address"
|
||||
btseWalletWithdrawal = "user/wallet/withdraw"
|
||||
btseExchangeHistory = "user/trade_history"
|
||||
btseUserFee = "user/fees"
|
||||
btseOrder = "order"
|
||||
btsePegOrder = "order/peg"
|
||||
btsePendingOrders = "user/open_orders"
|
||||
btseCancelAllAfter = "order/cancelAllAfter"
|
||||
)
|
||||
|
||||
// GetMarketsSummary stores market summary data
|
||||
func (b *BTSE) GetMarketsSummary() (*HighLevelMarketData, error) {
|
||||
var m HighLevelMarketData
|
||||
return &m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m, true)
|
||||
}
|
||||
|
||||
// GetSpotMarkets returns a list of spot markets available on BTSE
|
||||
func (b *BTSE) GetSpotMarkets() ([]SpotMarket, error) {
|
||||
var m []SpotMarket
|
||||
return m, b.SendHTTPRequest(http.MethodGet, btseMarkets, &m, true)
|
||||
}
|
||||
|
||||
// GetFuturesMarkets returns a list of futures markets available on BTSE
|
||||
func (b *BTSE) GetFuturesMarkets() ([]FuturesMarket, error) {
|
||||
var m []FuturesMarket
|
||||
return m, b.SendHTTPRequest(http.MethodGet, btseMarketOverview, &m, false)
|
||||
// GetMarketSummary stores market summary data
|
||||
func (b *BTSE) GetMarketSummary(symbol string, spot bool) (MarketSummary, error) {
|
||||
var m MarketSummary
|
||||
path := btseMarketOverview
|
||||
if symbol != "" {
|
||||
path += "?symbol=" + url.QueryEscape(symbol)
|
||||
}
|
||||
return m, b.SendHTTPRequest(http.MethodGet, path, &m, spot, queryFunc)
|
||||
}
|
||||
|
||||
// FetchOrderBook gets orderbook data for a given pair
|
||||
func (b *BTSE) FetchOrderBook(symbol string) (*Orderbook, error) {
|
||||
func (b *BTSE) FetchOrderBook(symbol string, group, limitBids, limitAsks int, spot bool) (*Orderbook, error) {
|
||||
var o Orderbook
|
||||
endpoint := fmt.Sprintf("%s/%s", btseOrderbook, symbol)
|
||||
return &o, b.SendHTTPRequest(http.MethodGet, endpoint, &o, true)
|
||||
urlValues := url.Values{}
|
||||
urlValues.Add("symbol", symbol)
|
||||
if limitBids > 0 {
|
||||
urlValues.Add("limit_bids", strconv.Itoa(limitBids))
|
||||
}
|
||||
if limitAsks > 0 {
|
||||
urlValues.Add("limit_asks", strconv.Itoa(limitAsks))
|
||||
}
|
||||
if group > 0 {
|
||||
urlValues.Add("group", strconv.Itoa(group))
|
||||
}
|
||||
return &o, b.SendHTTPRequest(http.MethodGet,
|
||||
common.EncodeURLValues(btseOrderbook, urlValues), &o, spot, queryFunc)
|
||||
}
|
||||
|
||||
// FetchOrderBookL2 retrieve level 2 orderbook for requested symbol and depth
|
||||
func (b *BTSE) FetchOrderBookL2(symbol string, depth int) (*Orderbook, error) {
|
||||
var o Orderbook
|
||||
urlValues := url.Values{}
|
||||
urlValues.Add("symbol", symbol)
|
||||
urlValues.Add("depth", strconv.FormatInt(int64(depth), 10))
|
||||
endpoint := common.EncodeURLValues(btseOrderbook+"/L2", urlValues)
|
||||
return &o, b.SendHTTPRequest(http.MethodGet, endpoint, &o, true, queryFunc)
|
||||
}
|
||||
|
||||
// GetTrades returns a list of trades for the specified symbol
|
||||
func (b *BTSE) GetTrades(symbol string) ([]Trade, error) {
|
||||
func (b *BTSE) GetTrades(symbol string, start, end time.Time, beforeSerialID, afterSerialID, count int, includeOld bool) ([]Trade, error) {
|
||||
var t []Trade
|
||||
endpoint := fmt.Sprintf("%s/%s", btseTrades, symbol)
|
||||
return t, b.SendHTTPRequest(http.MethodGet, endpoint, &t, true)
|
||||
}
|
||||
|
||||
// GetTicker returns the ticker for a specified symbol
|
||||
func (b *BTSE) GetTicker(symbol string) (*Ticker, error) {
|
||||
var t Ticker
|
||||
endpoint := fmt.Sprintf("%s/%s", btseTicker, symbol)
|
||||
err := b.SendHTTPRequest(http.MethodGet, endpoint, &t, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
urlValues := url.Values{}
|
||||
urlValues.Add("symbol", symbol)
|
||||
if count > 0 {
|
||||
urlValues.Add("count", strconv.Itoa(count))
|
||||
}
|
||||
return &t, nil
|
||||
if !start.IsZero() && !end.IsZero() {
|
||||
if start.After(end) {
|
||||
return t, errors.New("start cannot be after end time")
|
||||
}
|
||||
urlValues.Add("start", strconv.FormatInt(start.Unix(), 10))
|
||||
urlValues.Add("end", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
if beforeSerialID > 0 {
|
||||
urlValues.Add("beforeSerialId", strconv.Itoa(beforeSerialID))
|
||||
}
|
||||
if afterSerialID > 0 {
|
||||
urlValues.Add("afterSerialId", strconv.Itoa(afterSerialID))
|
||||
}
|
||||
if includeOld {
|
||||
urlValues.Add("includeOld", "true")
|
||||
}
|
||||
return t, b.SendHTTPRequest(http.MethodGet,
|
||||
common.EncodeURLValues(btseTrades, urlValues), &t, true, queryFunc)
|
||||
}
|
||||
|
||||
// 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, true)
|
||||
// OHLCV retrieve and return OHLCV candle data for requested symbol
|
||||
func (b *BTSE) OHLCV(symbol string, start, end time.Time, resolution int) (OHLCV, error) {
|
||||
var o OHLCV
|
||||
urlValues := url.Values{}
|
||||
urlValues.Add("symbol", symbol)
|
||||
|
||||
if !start.IsZero() && !end.IsZero() {
|
||||
if start.After(end) {
|
||||
return o, errors.New("start cannot be after end time")
|
||||
}
|
||||
urlValues.Add("start", strconv.FormatInt(start.Unix(), 10))
|
||||
urlValues.Add("end", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
var res = 60
|
||||
if resolution != 0 {
|
||||
res = resolution
|
||||
}
|
||||
urlValues.Add("resolution", strconv.FormatInt(int64(res), 10))
|
||||
endpoint := common.EncodeURLValues(btseOHLCV, urlValues)
|
||||
return o, b.SendHTTPRequest(http.MethodGet, endpoint, &o, true, queryFunc)
|
||||
}
|
||||
|
||||
// GetPrice get current price for requested symbol
|
||||
func (b *BTSE) GetPrice(symbol string) (Price, error) {
|
||||
var p Price
|
||||
path := btsePrice + "?symbol=" + url.QueryEscape(symbol)
|
||||
return p, b.SendHTTPRequest(http.MethodGet, path, &p, true, queryFunc)
|
||||
}
|
||||
|
||||
// GetServerTime returns the exchanges server time
|
||||
func (b *BTSE) GetServerTime() (*ServerTime, error) {
|
||||
var s ServerTime
|
||||
return &s, b.SendHTTPRequest(http.MethodGet, btseTime, &s, true)
|
||||
return &s, b.SendHTTPRequest(http.MethodGet, btseTime, &s, true, queryFunc)
|
||||
}
|
||||
|
||||
// GetAccountBalance returns the users account balance
|
||||
func (b *BTSE) GetAccountBalance() ([]CurrencyBalance, error) {
|
||||
// GetWalletInformation returns the users account balance
|
||||
func (b *BTSE) GetWalletInformation() ([]CurrencyBalance, error) {
|
||||
var a []CurrencyBalance
|
||||
return a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseAccount, nil, &a, true)
|
||||
return a, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseWallet, true, nil, nil, &a, queryFunc)
|
||||
}
|
||||
|
||||
// GetFeeInformation retrieve fee's (maker/taker) for requested symbol
|
||||
func (b *BTSE) GetFeeInformation(symbol string) ([]AccountFees, error) {
|
||||
var resp []AccountFees
|
||||
urlValues := url.Values{}
|
||||
if symbol != "" {
|
||||
urlValues.Add("symbol", symbol)
|
||||
}
|
||||
return resp, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseUserFee, true, urlValues, nil, &resp, queryFunc)
|
||||
}
|
||||
|
||||
// GetWalletHistory returns the users account balance
|
||||
func (b *BTSE) GetWalletHistory(symbol string, start, end time.Time, count int) (WalletHistory, error) {
|
||||
var resp WalletHistory
|
||||
|
||||
urlValues := url.Values{}
|
||||
if symbol != "" {
|
||||
urlValues.Add("symbol", symbol)
|
||||
}
|
||||
if !start.IsZero() && !end.IsZero() {
|
||||
if start.After(end) || end.Before(start) {
|
||||
return resp, errors.New("start cannot be after end time")
|
||||
}
|
||||
urlValues.Add("start", strconv.FormatInt(start.Unix(), 10))
|
||||
urlValues.Add("end", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
if count > 0 {
|
||||
urlValues.Add("count", strconv.Itoa(count))
|
||||
}
|
||||
return resp, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseWalletHistory, true, urlValues, nil, &resp, queryFunc)
|
||||
}
|
||||
|
||||
// GetWalletAddress returns the users account balance
|
||||
func (b *BTSE) GetWalletAddress(currency string) (WalletAddress, error) {
|
||||
var resp WalletAddress
|
||||
|
||||
urlValues := url.Values{}
|
||||
if currency != "" {
|
||||
urlValues.Add("currency", currency)
|
||||
}
|
||||
|
||||
return resp, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseWalletAddress, true, urlValues, nil, &resp, queryFunc)
|
||||
}
|
||||
|
||||
// CreateWalletAddress create new deposit address for requested currency
|
||||
func (b *BTSE) CreateWalletAddress(currency string) (WalletAddress, error) {
|
||||
var resp WalletAddress
|
||||
req := make(map[string]interface{}, 1)
|
||||
req["currency"] = currency
|
||||
err := b.SendAuthenticatedHTTPRequest(http.MethodPost, btseWalletAddress, true, nil, req, &resp, queryFunc)
|
||||
if err != nil {
|
||||
errResp := ErrorResponse{}
|
||||
errResponseStr := strings.Split(err.Error(), "raw response: ")
|
||||
err := json.Unmarshal([]byte(errResponseStr[1]), &errResp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if errResp.ErrorCode == 3528 {
|
||||
walletAddress := strings.Split(errResp.Message, "BADREQUEST: ")
|
||||
return WalletAddress{
|
||||
{
|
||||
Address: walletAddress[1],
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// WalletWithdrawal submit request to withdraw crypto currency
|
||||
func (b *BTSE) WalletWithdrawal(currency, address, tag, amount string) (WithdrawalResponse, error) {
|
||||
var resp WithdrawalResponse
|
||||
req := make(map[string]interface{}, 4)
|
||||
req["currency"] = currency
|
||||
req["address"] = address
|
||||
req["tag"] = tag
|
||||
req["amount"] = amount
|
||||
return resp, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseWalletWithdrawal, true, nil, req, &resp, queryFunc)
|
||||
}
|
||||
|
||||
// CreateOrder creates an order
|
||||
func (b *BTSE) CreateOrder(amount, price float64, side, orderType, symbol, timeInForce, tag string) (*string, error) {
|
||||
func (b *BTSE) CreateOrder(clOrderID string, deviation float64, postOnly bool, price float64, side string, size, stealth, stopPrice float64, symbol, timeInForce string, trailValue, triggerPrice float64, txType, orderType string) ([]Order, error) {
|
||||
req := make(map[string]interface{})
|
||||
req["amount"] = amount
|
||||
req["price"] = price
|
||||
if clOrderID != "" {
|
||||
req["clOrderID"] = clOrderID
|
||||
}
|
||||
if deviation > 0.0 {
|
||||
req["deviation"] = deviation
|
||||
}
|
||||
if postOnly {
|
||||
req["postOnly"] = postOnly
|
||||
}
|
||||
if price > 0.0 {
|
||||
req["price"] = price
|
||||
}
|
||||
if side != "" {
|
||||
req["side"] = side
|
||||
}
|
||||
if orderType != "" {
|
||||
req["type"] = orderType
|
||||
if size > 0.0 {
|
||||
req["size"] = size
|
||||
}
|
||||
if stealth > 0.0 {
|
||||
req["stealth"] = stealth
|
||||
}
|
||||
if stopPrice > 0.0 {
|
||||
req["stopPrice"] = stopPrice
|
||||
}
|
||||
if symbol != "" {
|
||||
req["symbol"] = symbol
|
||||
@@ -125,78 +276,149 @@ func (b *BTSE) CreateOrder(amount, price float64, side, orderType, symbol, timeI
|
||||
if timeInForce != "" {
|
||||
req["time_in_force"] = timeInForce
|
||||
}
|
||||
if tag != "" {
|
||||
req["tag"] = tag
|
||||
if trailValue > 0.0 {
|
||||
req["trailValue"] = trailValue
|
||||
}
|
||||
if triggerPrice > 0.0 {
|
||||
req["triggerPrice"] = triggerPrice
|
||||
}
|
||||
if txType != "" {
|
||||
req["txType"] = txType
|
||||
}
|
||||
if orderType != "" {
|
||||
req["type"] = orderType
|
||||
}
|
||||
|
||||
type orderResp struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
var r orderResp
|
||||
return &r.ID, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseOrder, req, &r, true)
|
||||
var r []Order
|
||||
return r, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseOrder, true, url.Values{}, req, &r, orderFunc)
|
||||
}
|
||||
|
||||
// GetOrders returns all pending orders
|
||||
func (b *BTSE) GetOrders(symbol string) ([]OpenOrder, error) {
|
||||
req := make(map[string]interface{})
|
||||
if symbol != "" {
|
||||
req["symbol"] = symbol
|
||||
func (b *BTSE) GetOrders(symbol, orderID, clOrderID string) ([]OpenOrder, error) {
|
||||
req := url.Values{}
|
||||
if orderID != "" {
|
||||
req.Add("orderID", orderID)
|
||||
}
|
||||
req.Add("symbol", symbol)
|
||||
if clOrderID != "" {
|
||||
req.Add("clOrderID", clOrderID)
|
||||
}
|
||||
var o []OpenOrder
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, req, &o, true)
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodGet, btsePendingOrders, true, req, nil, &o, orderFunc)
|
||||
}
|
||||
|
||||
// CancelExistingOrder cancels an order
|
||||
func (b *BTSE) CancelExistingOrder(orderID, symbol string) (*CancelOrder, error) {
|
||||
func (b *BTSE) CancelExistingOrder(orderID, symbol, clOrderID string) (CancelOrder, error) {
|
||||
var c CancelOrder
|
||||
req := make(map[string]interface{})
|
||||
req["order_id"] = orderID
|
||||
req["symbol"] = symbol
|
||||
return &c, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseDeleteOrder, req, &c, true)
|
||||
req := url.Values{}
|
||||
if orderID != "" {
|
||||
req.Add("orderID", orderID)
|
||||
}
|
||||
req.Add("symbol", symbol)
|
||||
if clOrderID != "" {
|
||||
req.Add("clOrderID", clOrderID)
|
||||
}
|
||||
|
||||
return c, b.SendAuthenticatedHTTPRequest(http.MethodDelete, btseOrder, true, req, nil, &c, orderFunc)
|
||||
}
|
||||
|
||||
// GetFills gets all filled orders
|
||||
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")
|
||||
}
|
||||
|
||||
// CancelAllAfter cancels all orders after timeout
|
||||
func (b *BTSE) CancelAllAfter(timeout int) error {
|
||||
req := make(map[string]interface{})
|
||||
if orderID != "" {
|
||||
req["order_id"] = orderID
|
||||
}
|
||||
req["timeout"] = timeout
|
||||
return b.SendAuthenticatedHTTPRequest(http.MethodPost, btseCancelAllAfter, true, url.Values{}, req, nil, orderFunc)
|
||||
}
|
||||
|
||||
// IndexOrderPeg create peg order that will track a certain percentage above/below the index price
|
||||
func (b *BTSE) IndexOrderPeg(clOrderID string, deviation float64, postOnly bool, price float64, side string, size, stealth, stopPrice float64, symbol, timeInForce string, trailValue, triggerPrice float64, txType, orderType string) ([]Order, error) {
|
||||
var o []Order
|
||||
req := make(map[string]interface{})
|
||||
if clOrderID != "" {
|
||||
req["clOrderID"] = clOrderID
|
||||
}
|
||||
if deviation > 0.0 {
|
||||
req["deviation"] = deviation
|
||||
}
|
||||
if postOnly {
|
||||
req["postOnly"] = postOnly
|
||||
}
|
||||
if price > 0.0 {
|
||||
req["price"] = price
|
||||
}
|
||||
if side != "" {
|
||||
req["side"] = side
|
||||
}
|
||||
if size > 0.0 {
|
||||
req["size"] = size
|
||||
}
|
||||
if stealth > 0.0 {
|
||||
req["stealth"] = stealth
|
||||
}
|
||||
if stopPrice > 0.0 {
|
||||
req["stopPrice"] = stopPrice
|
||||
}
|
||||
if symbol != "" {
|
||||
req["symbol"] = symbol
|
||||
}
|
||||
|
||||
if before != "" {
|
||||
req["before"] = before
|
||||
if timeInForce != "" {
|
||||
req["time_in_force"] = timeInForce
|
||||
}
|
||||
if trailValue > 0.0 {
|
||||
req["trailValue"] = trailValue
|
||||
}
|
||||
if triggerPrice > 0.0 {
|
||||
req["triggerPrice"] = triggerPrice
|
||||
}
|
||||
if txType != "" {
|
||||
req["txType"] = txType
|
||||
}
|
||||
if orderType != "" {
|
||||
req["type"] = orderType
|
||||
}
|
||||
|
||||
if after != "" {
|
||||
req["after"] = after
|
||||
}
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btsePegOrder, true, url.Values{}, req, nil, orderFunc)
|
||||
}
|
||||
|
||||
if limit != "" {
|
||||
req["limit"] = limit
|
||||
// TradeHistory returns previous trades on exchange
|
||||
func (b *BTSE) TradeHistory(symbol string, start, end time.Time, beforeSerialID, afterSerialID, count int, includeOld bool, clOrderID, orderID string) (TradeHistory, error) {
|
||||
var resp TradeHistory
|
||||
urlValues := url.Values{}
|
||||
if symbol != "" {
|
||||
urlValues.Add("symbol", symbol)
|
||||
}
|
||||
if username != "" {
|
||||
req["username"] = username
|
||||
if !start.IsZero() && !end.IsZero() {
|
||||
if start.After(end) || end.Before(start) {
|
||||
return resp, errors.New("start and end must both be valid")
|
||||
}
|
||||
urlValues.Add("start", strconv.FormatInt(start.Unix(), 10))
|
||||
urlValues.Add("end", strconv.FormatInt(end.Unix(), 10))
|
||||
}
|
||||
|
||||
var o []FilledOrder
|
||||
return o, b.SendAuthenticatedHTTPRequest(http.MethodPost, btseFills, req, &o, true)
|
||||
if beforeSerialID > 0 {
|
||||
urlValues.Add("beforeSerialId", strconv.Itoa(beforeSerialID))
|
||||
}
|
||||
if afterSerialID > 0 {
|
||||
urlValues.Add("afterSerialId", strconv.Itoa(afterSerialID))
|
||||
}
|
||||
if includeOld {
|
||||
urlValues.Add("includeOld", "true")
|
||||
}
|
||||
if count > 0 {
|
||||
urlValues.Add("count", strconv.Itoa(count))
|
||||
}
|
||||
if clOrderID != "" {
|
||||
urlValues.Add("clOrderId", clOrderID)
|
||||
}
|
||||
if orderID != "" {
|
||||
urlValues.Add("orderID", orderID)
|
||||
}
|
||||
return resp, b.SendAuthenticatedHTTPRequest(http.MethodGet, btseExchangeHistory, true, urlValues, nil, &resp, queryFunc)
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an HTTP request to the desired endpoint
|
||||
func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}, spotEndpoint bool) error {
|
||||
p := btseSPOTAPIPath
|
||||
func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}, spotEndpoint bool, f request.EndpointLimit) error {
|
||||
p := btseSPOTPath + btseSPOTAPIPath
|
||||
if !spotEndpoint {
|
||||
p = btseFuturesAPIPath
|
||||
p = btseFuturesPath + btseFuturesAPIPath
|
||||
}
|
||||
return b.SendPayload(context.Background(), &request.Item{
|
||||
Method: method,
|
||||
@@ -205,66 +427,76 @@ func (b *BTSE) SendHTTPRequest(method, endpoint string, result interface{}, spot
|
||||
Verbose: b.Verbose,
|
||||
HTTPDebugging: b.HTTPDebugging,
|
||||
HTTPRecording: b.HTTPRecording,
|
||||
Endpoint: f,
|
||||
})
|
||||
}
|
||||
|
||||
// SendAuthenticatedHTTPRequest sends an authenticated HTTP request to the desired endpoint
|
||||
func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, req map[string]interface{}, result interface{}, spotEndpoint bool) error {
|
||||
func (b *BTSE) SendAuthenticatedHTTPRequest(method, endpoint string, isSpot bool, values url.Values, req map[string]interface{}, result interface{}, f request.EndpointLimit) error {
|
||||
if !b.AllowAuthenticatedRequest() {
|
||||
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet,
|
||||
b.Name)
|
||||
}
|
||||
|
||||
p := btseSPOTAPIPath
|
||||
if !spotEndpoint {
|
||||
p = btseFuturesAPIPath
|
||||
// The concatenation is done this way because BTSE expect endpoint+nonce or endpoint+nonce+body
|
||||
// when signing the data but the full path of the request is /spot/api/v3.2/<endpoint>
|
||||
// its messy but it works and supports futures as well
|
||||
host := b.API.Endpoints.URL
|
||||
if isSpot {
|
||||
host += btseSPOTPath + btseSPOTAPIPath + endpoint
|
||||
endpoint = btseSPOTAPIPath + endpoint
|
||||
} else {
|
||||
host += btseFuturesPath + btseFuturesAPIPath
|
||||
endpoint += btseFuturesAPIPath
|
||||
}
|
||||
|
||||
path := p + endpoint
|
||||
headers := make(map[string]string)
|
||||
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 = json.Marshal(req)
|
||||
var body io.Reader
|
||||
nonce := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
headers := map[string]string{
|
||||
"btse-api": b.API.Credentials.Key,
|
||||
"btse-nonce": nonce,
|
||||
}
|
||||
if req != nil {
|
||||
reqPayload, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = bytes.NewBuffer(payload)
|
||||
body = bytes.NewBuffer(reqPayload)
|
||||
hmac = crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte((path + nonce + string(payload))),
|
||||
[]byte((endpoint + nonce + string(reqPayload))),
|
||||
[]byte(b.API.Credentials.Secret),
|
||||
)
|
||||
headers["Content-Type"] = "application/json"
|
||||
} else {
|
||||
hmac = crypto.GetHMAC(
|
||||
crypto.HashSHA512_384,
|
||||
[]byte((path + nonce)),
|
||||
[]byte((endpoint + nonce)),
|
||||
[]byte(b.API.Credentials.Secret),
|
||||
)
|
||||
if len(values) > 0 {
|
||||
host += "?" + values.Encode()
|
||||
}
|
||||
}
|
||||
headers["btse-sign"] = crypto.HexEncodeToString(hmac)
|
||||
|
||||
if b.Verbose {
|
||||
log.Debugf(log.ExchangeSys,
|
||||
"%s Sending %s request to URL %s with params %s\n",
|
||||
b.Name, method, path, string(payload))
|
||||
"%s Sending %s request to URL %s",
|
||||
b.Name, method, endpoint)
|
||||
}
|
||||
|
||||
return b.SendPayload(context.Background(), &request.Item{
|
||||
Method: method,
|
||||
Path: b.API.Endpoints.URL + path,
|
||||
Path: host,
|
||||
Headers: headers,
|
||||
Body: body,
|
||||
Result: result,
|
||||
AuthRequest: true,
|
||||
NonceEnabled: true,
|
||||
Verbose: b.Verbose,
|
||||
HTTPDebugging: b.HTTPDebugging,
|
||||
HTTPRecording: b.HTTPRecording,
|
||||
Endpoint: f,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -274,7 +506,7 @@ func (b *BTSE) GetFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
|
||||
|
||||
switch feeBuilder.FeeType {
|
||||
case exchange.CryptocurrencyTradeFee:
|
||||
fee = calculateTradingFee(feeBuilder.IsMaker) * feeBuilder.Amount * feeBuilder.PurchasePrice
|
||||
fee = b.calculateTradingFee(feeBuilder) * feeBuilder.Amount * feeBuilder.PurchasePrice
|
||||
case exchange.CryptocurrencyWithdrawalFee:
|
||||
switch feeBuilder.Pair.Base {
|
||||
case currency.USDT:
|
||||
@@ -329,16 +561,28 @@ func getInternationalBankWithdrawalFee(amount float64) float64 {
|
||||
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.001
|
||||
// calculateTradingFee return fee based on users current fee tier or default values
|
||||
func (b *BTSE) calculateTradingFee(feeBuilder *exchange.FeeBuilder) float64 {
|
||||
formattedPair, err := b.FormatExchangeCurrency(feeBuilder.Pair, asset.Spot)
|
||||
if err != nil {
|
||||
if feeBuilder.IsMaker {
|
||||
return 0.001
|
||||
}
|
||||
return 0.002
|
||||
}
|
||||
return fee
|
||||
feeTiers, err := b.GetFeeInformation(formattedPair.String())
|
||||
if err != nil {
|
||||
if feeBuilder.IsMaker {
|
||||
return 0.001
|
||||
}
|
||||
return 0.002
|
||||
}
|
||||
if feeBuilder.IsMaker {
|
||||
return feeTiers[0].MakerFee
|
||||
}
|
||||
return feeTiers[0].TakerFee
|
||||
}
|
||||
|
||||
func parseOrderTime(timeStr string) (time.Time, error) {
|
||||
return time.Parse(btseTimeLayout, timeStr)
|
||||
return time.Parse(common.SimpleTimeFormat, timeStr)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package btse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/config"
|
||||
"github.com/thrasher-corp/gocryptotrader/core"
|
||||
"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/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
)
|
||||
@@ -53,57 +58,186 @@ func areTestAPIKeysSet() bool {
|
||||
|
||||
func TestGetMarketsSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetMarketsSummary()
|
||||
_, err := b.GetMarketSummary("", true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSpotMarkets(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetSpotMarkets()
|
||||
ret, err := b.GetMarketSummary(testPair, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFuturesMarkets(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetFuturesMarkets()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
if len(ret) != 1 {
|
||||
t.Errorf("expected only one result when requesting BTC-USD data received: %v", len(ret))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchOrderBook(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.FetchOrderBook(testPair)
|
||||
_, err := b.FetchOrderBook(testPair, 0, 1, 1, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = b.FetchOrderBook("BTCPFC", 0, 1, 1, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
_, err = b.FetchOrderBook(testPair, 1, 1, 1, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOrderbook(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p, err := currency.NewPairFromString(testPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.UpdateOrderbook(p, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := currency.NewPairFromString("BTC-PFC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = b.UpdateOrderbook(f, asset.Futures)
|
||||
if err != nil {
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchOrderBookL2(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.FetchOrderBookL2(testPair, 20)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOHLCV(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.OHLCV(testPair,
|
||||
time.Now().AddDate(0, 0, -1),
|
||||
time.Now(), 60)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.OHLCV(testPair, time.Now(), time.Now().AddDate(0, 0, -1), 60)
|
||||
if err == nil {
|
||||
t.Fatal("expected error if start is after end date")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPrice(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetPrice(testPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatExchangeKlineInterval(t *testing.T) {
|
||||
ret := b.FormatExchangeKlineInterval(kline.OneDay)
|
||||
if ret != "1440" {
|
||||
t.Fatalf("unexpected result received: %v", ret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandles(t *testing.T) {
|
||||
t.Parallel()
|
||||
curr, err := currency.NewPairFromString(testPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.GetHistoricCandles(
|
||||
curr, asset.Spot,
|
||||
time.Time{}, time.Time{},
|
||||
kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.GetHistoricCandles(
|
||||
curr, asset.Spot,
|
||||
time.Time{}, time.Time{},
|
||||
kline.OneDay)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curr.Quote = currency.XRP
|
||||
_, err = b.GetHistoricCandles(
|
||||
curr, asset.Spot,
|
||||
time.Time{}, time.Time{},
|
||||
kline.OneMin)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when requesting with disabled pair")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHistoricCandlesExtended(t *testing.T) {
|
||||
t.Parallel()
|
||||
curr, err := currency.NewPairFromString(testPair)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.GetHistoricCandlesExtended(
|
||||
curr, asset.Spot,
|
||||
time.Time{}, time.Time{},
|
||||
kline.OneMin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetTrades(testPair)
|
||||
_, err := b.GetTrades(testPair,
|
||||
time.Now().AddDate(0, 0, -1), time.Now(),
|
||||
0, 0, 50, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetTicker(testPair)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
_, err = b.GetTrades(testPair,
|
||||
time.Now(), time.Now().AddDate(0, -1, 0),
|
||||
0, 0, 50, false)
|
||||
if err == nil {
|
||||
t.Error("expected error if start time is after end time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarketStatistics(t *testing.T) {
|
||||
func TestUpdateTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := b.GetMarketStatistics(testPair)
|
||||
curr, err := currency.NewPairFromString(testPair)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.UpdateTicker(curr, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curr, err = currency.NewPairFromString("BTC-PFC")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.UpdateTicker(curr, asset.Futures)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,23 +249,69 @@ func TestGetServerTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccount(t *testing.T) {
|
||||
func TestGetWalletInformation(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetAccountBalance()
|
||||
_, err := b.GetWalletInformation()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFills(t *testing.T) {
|
||||
func TestGetFeeInformation(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetFills("", testPair, "", "", "", "")
|
||||
_, err := b.GetFeeInformation("")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWalletHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetWalletHistory(testPair,
|
||||
time.Time{}, time.Time{},
|
||||
50)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWalletAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetWalletAddress("XRP")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateWalletAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.CreateWalletAddress("XRP")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDepositAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetDepositAddress(currency.XRP, "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -140,15 +320,30 @@ func TestGetFills(t *testing.T) {
|
||||
func TestCreateOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
|
||||
}
|
||||
_, err := b.CreateOrder(0.1,
|
||||
10000,
|
||||
order.Sell.String(),
|
||||
order.Limit.String(),
|
||||
testPair,
|
||||
"",
|
||||
"")
|
||||
_, err := b.CreateOrder("", 0.0,
|
||||
false,
|
||||
-1, "BUY", 100, 0, 0,
|
||||
testPair, "GTC",
|
||||
0.0, 0.0,
|
||||
"LIMIT", "LIMIT")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBTSEIndexOrderPeg(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
|
||||
}
|
||||
_, err := b.IndexOrderPeg("", 0.0,
|
||||
false,
|
||||
-1, "BUY", 100, 0, 0,
|
||||
testPair, "GTC",
|
||||
0.0, 0.0,
|
||||
"", "LIMIT")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -159,7 +354,7 @@ func TestGetOrders(t *testing.T) {
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.GetOrders("")
|
||||
_, err := b.GetOrders(testPair, "", "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -171,6 +366,18 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
var getOrdersRequest = order.GetOrdersRequest{
|
||||
Pairs: []currency.Pair{
|
||||
{
|
||||
Delimiter: "-",
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
{
|
||||
Delimiter: "-",
|
||||
Base: currency.XRP,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
},
|
||||
Type: order.AnyType,
|
||||
}
|
||||
|
||||
@@ -180,6 +387,21 @@ func TestGetActiveOrders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExchangeHistory(t *testing.T) {
|
||||
curr, _ := currency.NewPairFromString(testPair)
|
||||
_, err := b.GetExchangeHistory(curr, asset.Spot, time.Now().AddDate(0, -6, 0), time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = b.GetExchangeHistory(curr, asset.Futures, time.Now().AddDate(0, -6, 0), time.Now())
|
||||
if err != nil {
|
||||
if !errors.Is(err, common.ErrNotYetImplemented) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
@@ -194,6 +416,21 @@ func TestGetOrderHistory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTradeHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() {
|
||||
t.Skip("API keys not set, skipping test")
|
||||
}
|
||||
_, err := b.TradeHistory("",
|
||||
time.Time{}, time.Time{},
|
||||
0, 0, 0,
|
||||
false,
|
||||
"", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatWithdrawPermissions(t *testing.T) {
|
||||
t.Parallel()
|
||||
expected := exchange.NoAPIWithdrawalMethodsText
|
||||
@@ -236,14 +473,14 @@ func TestGetFee(t *testing.T) {
|
||||
PurchasePrice: 1000,
|
||||
}
|
||||
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 0.500000 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 0.500000, resp)
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 1.000000 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 1.000000, resp)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
feeBuilder.IsMaker = false
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 1.00000 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 1.00000, resp)
|
||||
if resp, err := b.GetFee(feeBuilder); resp != 2.00000 || err != nil {
|
||||
t.Errorf("GetFee() error. Expected: %f, Received: %f", 2.00000, resp)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -285,7 +522,7 @@ func TestGetFee(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseOrderTime(t *testing.T) {
|
||||
expected := int64(1534794360)
|
||||
expected := int64(1534792846)
|
||||
actual, err := parseOrderTime("2018-08-20 19:20:46")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -300,18 +537,19 @@ func TestParseOrderTime(t *testing.T) {
|
||||
func TestSubmitOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
|
||||
}
|
||||
var orderSubmission = &order.Submit{
|
||||
Pair: currency.Pair{
|
||||
Base: currency.BTC,
|
||||
Quote: currency.USD,
|
||||
},
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: 100000,
|
||||
Amount: 0.1,
|
||||
ClientID: "meowOrder",
|
||||
Side: order.Buy,
|
||||
Type: order.Limit,
|
||||
Price: -100000000,
|
||||
Amount: 1,
|
||||
ClientID: "",
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
response, err := b.SubmitOrder(orderSubmission)
|
||||
if areTestAPIKeysSet() && (err != nil || !response.IsOrderPlaced) {
|
||||
@@ -321,10 +559,22 @@ func TestSubmitOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelAllAfter(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
|
||||
}
|
||||
|
||||
err := b.CancelAllAfter(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelExchangeOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
|
||||
}
|
||||
currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
currency.USD.String(),
|
||||
@@ -334,6 +584,7 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
Pair: currencyPair,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
err := b.CancelOrder(orderCancellation)
|
||||
if err != nil {
|
||||
@@ -341,10 +592,21 @@ func TestCancelExchangeOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
|
||||
}
|
||||
_, err := b.CancelExistingOrder("", testPair, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !areTestAPIKeysSet() || !canManipulateRealOrders {
|
||||
t.Skip("skipping test, either api keys or manipulaterealorders isnt set correctly")
|
||||
t.Skip("skipping test, either api keys are unset or canManipulateRealOrders is false")
|
||||
}
|
||||
currencyPair := currency.NewPairWithDelimiter(currency.BTC.String(),
|
||||
currency.USD.String(),
|
||||
@@ -354,6 +616,7 @@ func TestCancelAllExchangeOrders(t *testing.T) {
|
||||
WalletAddress: core.BitcoinDonationAddress,
|
||||
AccountID: "1",
|
||||
Pair: currencyPair,
|
||||
AssetType: asset.Spot,
|
||||
}
|
||||
resp, err := b.CancelAllOrders(orderCancellation)
|
||||
|
||||
@@ -419,6 +682,7 @@ func TestStatusToStandardStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFetchTradablePairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
assets := b.GetAssetTypes()
|
||||
for i := range assets {
|
||||
data, err := b.FetchTradablePairs(assets[i])
|
||||
@@ -430,3 +694,132 @@ func TestFetchTradablePairs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchType(t *testing.T) {
|
||||
t.Parallel()
|
||||
ret := matchType(1, order.AnyType)
|
||||
if !ret {
|
||||
t.Fatal("expected true value")
|
||||
}
|
||||
|
||||
ret = matchType(76, order.Market)
|
||||
if ret {
|
||||
t.Fatal("expected false match")
|
||||
}
|
||||
|
||||
ret = matchType(76, order.Limit)
|
||||
if !ret {
|
||||
t.Fatal("expected true match")
|
||||
}
|
||||
|
||||
ret = matchType(77, order.Market)
|
||||
if !ret {
|
||||
t.Fatal("expected true match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedOrderSizeLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := b.seedOrderSizeLimits()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrderSizeLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
seedOrderSizeLimitMap()
|
||||
_, ok := OrderSizeLimits(testPair)
|
||||
if !ok {
|
||||
t.Fatal("expected BTC-USD to be found in map")
|
||||
}
|
||||
|
||||
_, ok = OrderSizeLimits("XRP-GARBAGE")
|
||||
if ok {
|
||||
t.Fatal("expected false value for XRP-GARBAGE")
|
||||
}
|
||||
}
|
||||
|
||||
func seedOrderSizeLimitMap() {
|
||||
testOrderSizeLimits := []struct {
|
||||
name string
|
||||
o OrderSizeLimit
|
||||
}{
|
||||
{
|
||||
name: "XRP-USD",
|
||||
o: OrderSizeLimit{
|
||||
MinSizeIncrement: 1,
|
||||
MinOrderSize: 1,
|
||||
MaxOrderSize: 1000000,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LTC-USD",
|
||||
o: OrderSizeLimit{
|
||||
MinSizeIncrement: 0.01,
|
||||
MinOrderSize: 0.01,
|
||||
MaxOrderSize: 5000,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "BTC-USD",
|
||||
o: OrderSizeLimit{
|
||||
MinSizeIncrement: 0.0001,
|
||||
MinOrderSize: 1,
|
||||
MaxOrderSize: 1000000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
orderSizeLimitMap.Range(func(key interface{}, _ interface{}) bool {
|
||||
orderSizeLimitMap.Delete(key)
|
||||
return true
|
||||
})
|
||||
|
||||
for x := range testOrderSizeLimits {
|
||||
orderSizeLimitMap.Store(testOrderSizeLimits[x].name, testOrderSizeLimits[x].o)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithinLimits(t *testing.T) {
|
||||
t.Parallel()
|
||||
seedOrderSizeLimitMap()
|
||||
p, _ := currency.NewPairDelimiter("XRP-USD", "-")
|
||||
v := b.withinLimits(p, 1.0)
|
||||
if !v {
|
||||
t.Fatal("expected valid limits")
|
||||
}
|
||||
v = b.withinLimits(p, 5.0000001)
|
||||
if v {
|
||||
t.Fatal("expected invalid limits")
|
||||
}
|
||||
v = b.withinLimits(p, 100)
|
||||
if !v {
|
||||
t.Fatal("expected valid limits")
|
||||
}
|
||||
v = b.withinLimits(p, 10.1)
|
||||
if v {
|
||||
t.Fatal("expected invalid limits")
|
||||
}
|
||||
|
||||
p.Base = currency.LTC
|
||||
v = b.withinLimits(p, 10)
|
||||
if v {
|
||||
t.Fatal("expected valid limits")
|
||||
}
|
||||
|
||||
v = b.withinLimits(p, 0.009)
|
||||
if !v {
|
||||
t.Fatal("expected invalid limits")
|
||||
}
|
||||
p.Base = currency.BTC
|
||||
v = b.withinLimits(p, 10)
|
||||
if v {
|
||||
t.Fatal("expected valid limits")
|
||||
}
|
||||
|
||||
v = b.withinLimits(p, 0.001)
|
||||
if !v {
|
||||
t.Fatal("expected invalid limits")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,65 @@
|
||||
package btse
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Default order type is good till cancel (or filled)
|
||||
goodTillCancel = "gtc"
|
||||
goodTillCancel = "GTC"
|
||||
|
||||
orderInserted = 2
|
||||
orderCancelled = 6
|
||||
)
|
||||
|
||||
// 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"`
|
||||
// MarketSummary response data
|
||||
type MarketSummary []struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Last float64 `json:"last"`
|
||||
LowestAsk float64 `json:"lowestAsk"`
|
||||
HighestBid float64 `json:"highestBid"`
|
||||
PercentageChange float64 `json:"percentageChange"`
|
||||
Volume float64 `json:"volume"`
|
||||
High24Hr float64 `json:"high24Hr"`
|
||||
Low24Hr float64 `json:"low24Hr"`
|
||||
Base string `json:"base"`
|
||||
Quote string `json:"quote"`
|
||||
Active bool `json:"active"`
|
||||
Size float64 `json:"size"`
|
||||
MinValidPrice float64 `json:"minValidPrice"`
|
||||
MinPriceIncrement float64 `json:"minPriceIncrement"`
|
||||
MinOrderSize float64 `json:"minOrderSize"`
|
||||
MaxOrderSize float64 `json:"maxOrderSize"`
|
||||
MinSizeIncrement float64 `json:"minSizeIncrement"`
|
||||
OpenInterest float64 `json:"openInterest"`
|
||||
OpenInterestUSD float64 `json:"openInterestUSD"`
|
||||
ContractStart int64 `json:"contractStart"`
|
||||
ContractEnd int64 `json:"contractEnd"`
|
||||
TimeBasedContract bool `json:"timeBasedContract"`
|
||||
OpenTime int64 `json:"openTime"`
|
||||
CloseTime int64 `json:"closeTime"`
|
||||
StartMatching int64 `json:"startMatching"`
|
||||
InactiveTime int64 `json:"inactiveTime"`
|
||||
FundingRate float64 `json:"fundingRate"`
|
||||
ContractSize float64 `json:"contractSize"`
|
||||
MaxPosition int64 `json:"maxPosition"`
|
||||
MinRiskLimit int `json:"minRiskLimit"`
|
||||
MaxRiskLimit int `json:"maxRiskLimit"`
|
||||
AvailableSettlement []string `json:"availableSettlement"`
|
||||
Futures bool `json:"futures"`
|
||||
}
|
||||
|
||||
// HighLevelMarketData stores market overview data
|
||||
type HighLevelMarketData map[string]OverviewData
|
||||
// OHLCV holds Open, High Low, Close, Volume data for set symbol
|
||||
type OHLCV [][]float64
|
||||
|
||||
// Price stores last price for requested symbol
|
||||
type Price []struct {
|
||||
IndexPrice float64 `json:"indexPrice"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
MarkPrice float64 `json:"markPrice"`
|
||||
Symbol string `json:"symbol"`
|
||||
}
|
||||
|
||||
// SpotMarket stores market data
|
||||
type SpotMarket struct {
|
||||
@@ -72,11 +112,12 @@ type FuturesMarket struct {
|
||||
|
||||
// Trade stores trade data
|
||||
type Trade struct {
|
||||
SerialID string `json:"serial_id"`
|
||||
SerialID int `json:"serialId"`
|
||||
Symbol string `json:"symbol"`
|
||||
Price float64 `json:"price"`
|
||||
Amount float64 `json:"amount"`
|
||||
Time string `json:"time"`
|
||||
Amount float64 `json:"size"`
|
||||
Time int64 `json:"timestamp"`
|
||||
Side string `json:"side"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
@@ -117,52 +158,125 @@ type MarketStatistics struct {
|
||||
// ServerTime stores the server time data
|
||||
type ServerTime struct {
|
||||
ISO time.Time `json:"iso"`
|
||||
Epoch string `json:"epoch"`
|
||||
Epoch int64 `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"`
|
||||
Total float64 `json:"total"`
|
||||
Available float64 `json:"available"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
Symbol string `json:"symbol"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
// AccountFees stores fee for each currency pair
|
||||
type AccountFees struct {
|
||||
MakerFee float64 `json:"makerFee"`
|
||||
Symbol string `json:"symbol"`
|
||||
TakerFee float64 `json:"takerFee"`
|
||||
}
|
||||
|
||||
// TradeHistory stores user trades for exchange
|
||||
type TradeHistory []struct {
|
||||
Base string `json:"base"`
|
||||
ClOrderID string `json:"clOrderID"`
|
||||
FeeAmount float64 `json:"feeAmount"`
|
||||
FeeCurrency string `json:"feeCurrency"`
|
||||
FilledPrice float64 `json:"filledPrice"`
|
||||
FilledSize float64 `json:"filledSize"`
|
||||
OrderID string `json:"orderId"`
|
||||
OrderType int `json:"orderType"`
|
||||
Price float64 `json:"price"`
|
||||
Quote string `json:"quote"`
|
||||
RealizedPnl float64 `json:"realizedPnl"`
|
||||
SerialID int64 `json:"serialId"`
|
||||
Side string `json:"side"`
|
||||
Size float64 `json:"size"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Total float64 `json:"total"`
|
||||
TradeID string `json:"tradeId"`
|
||||
TriggerPrice float64 `json:"triggerPrice"`
|
||||
TriggerType int `json:"triggerType"`
|
||||
Username string `json:"username"`
|
||||
Wallet string `json:"wallet"`
|
||||
}
|
||||
|
||||
// WalletHistory stores account funding history
|
||||
type WalletHistory []struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Description string `json:"description"`
|
||||
Fees float64 `json:"fees"`
|
||||
OrderID string `json:"orderId"`
|
||||
Status string `json:"status"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
Username string `json:"username"`
|
||||
Wallet string `json:"wallet"`
|
||||
}
|
||||
|
||||
// WalletAddress stores address for crypto deposit's
|
||||
type WalletAddress []struct {
|
||||
Address string `json:"address"`
|
||||
Created int `json:"created"`
|
||||
}
|
||||
|
||||
// WithdrawalResponse response received when submitting a crypto withdrawal request
|
||||
type WithdrawalResponse struct {
|
||||
WithdrawID string `json:"withdraw_id"`
|
||||
}
|
||||
|
||||
// OpenOrder stores an open order info
|
||||
type OpenOrder struct {
|
||||
Order
|
||||
Status string `json:"status"`
|
||||
AverageFillPrice float64 `json:"averageFillPrice"`
|
||||
CancelDuration int64 `json:"cancelDuration"`
|
||||
ClOrderID string `json:"clOrderID"`
|
||||
FillSize float64 `json:"fillSize"`
|
||||
FilledSize float64 `json:"filledSize"`
|
||||
OrderID string `json:"orderID"`
|
||||
OrderState string `json:"orderState"`
|
||||
OrderType int `json:"orderType"`
|
||||
OrderValue float64 `json:"orderValue"`
|
||||
PegPriceDeviation float64 `json:"pegPriceDeviation"`
|
||||
PegPriceMax float64 `json:"pegPriceMax"`
|
||||
PegPriceMin float64 `json:"pegPriceMin"`
|
||||
Price float64 `json:"price"`
|
||||
Side string `json:"side"`
|
||||
Size float64 `json:"size"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
TrailValue float64 `json:"trailValue"`
|
||||
TriggerOrder bool `json:"triggerOrder"`
|
||||
TriggerOrderType int `json:"triggerOrderType"`
|
||||
TriggerOriginalPrice float64 `json:"triggerOriginalPrice"`
|
||||
TriggerPrice float64 `json:"triggerPrice"`
|
||||
TriggerStopPrice float64 `json:"triggerStopPrice"`
|
||||
TriggerTrailingStopDeviation float64 `json:"triggerTrailingStopDeviation"`
|
||||
Triggered bool `json:"triggered"`
|
||||
}
|
||||
|
||||
// CancelOrder stores the cancel order response data
|
||||
type CancelOrder struct {
|
||||
Code int `json:"code"`
|
||||
Time int64 `json:"time"`
|
||||
}
|
||||
// CancelOrder stores slice of orders
|
||||
type CancelOrder []Order
|
||||
|
||||
// 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"`
|
||||
Symbol string `json:"symbol"`
|
||||
OrderID string `json:"order_id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
// Order stores information for a single order
|
||||
type Order struct {
|
||||
AverageFillPrice float64 `json:"averageFillPrice"`
|
||||
ClOrderID string `json:"clOrderID"`
|
||||
Deviation float64 `json:"deviation"`
|
||||
FillSize float64 `json:"fillSize"`
|
||||
Message string `json:"message"`
|
||||
OrderID string `json:"orderID"`
|
||||
OrderType int `json:"orderType"`
|
||||
Price float64 `json:"price"`
|
||||
Side string `json:"side"`
|
||||
Size float64 `json:"size"`
|
||||
Status int `json:"status"`
|
||||
Stealth float64 `json:"stealth"`
|
||||
StopPrice float64 `json:"stopPrice"`
|
||||
Symbol string `json:"symbol"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Trigger bool `json:"trigger"`
|
||||
TriggerPrice float64 `json:"triggerPrice"`
|
||||
}
|
||||
|
||||
type wsSub struct {
|
||||
@@ -220,3 +334,20 @@ type wsOrderUpdate struct {
|
||||
TriggerPrice float64 `json:"triggerPrice,string"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// ErrorResponse contains errors received from API
|
||||
type ErrorResponse struct {
|
||||
ErrorCode int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// OrderSizeLimit holds accepted minimum, maximum, and size increment when submitting new orders
|
||||
type OrderSizeLimit struct {
|
||||
MinOrderSize float64
|
||||
MaxOrderSize float64
|
||||
MinSizeIncrement float64
|
||||
}
|
||||
|
||||
// orderSizeLimitMap map of OrderSizeLimit per currency
|
||||
var orderSizeLimitMap sync.Map
|
||||
|
||||
@@ -3,6 +3,7 @@ package btse
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -90,6 +91,7 @@ func (b *BTSE) SetDefaults() {
|
||||
Websocket: true,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerFetching: true,
|
||||
TickerBatching: true,
|
||||
KlineFetching: true,
|
||||
TradeFetching: true,
|
||||
OrderbookFetching: true,
|
||||
@@ -114,14 +116,38 @@ func (b *BTSE) SetDefaults() {
|
||||
GetOrder: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.NoAPIWithdrawalMethods,
|
||||
Kline: kline.ExchangeCapabilitiesSupported{
|
||||
DateRanges: true,
|
||||
Intervals: true,
|
||||
},
|
||||
},
|
||||
Enabled: exchange.FeaturesEnabled{
|
||||
AutoPairUpdates: true,
|
||||
Kline: kline.ExchangeCapabilitiesEnabled{
|
||||
Intervals: map[string]bool{
|
||||
kline.OneMin.Word(): true,
|
||||
kline.ThreeMin.Word(): true,
|
||||
kline.FiveMin.Word(): true,
|
||||
kline.FifteenMin.Word(): true,
|
||||
kline.ThirtyMin.Word(): true,
|
||||
kline.OneHour.Word(): true,
|
||||
kline.TwoHour.Word(): true,
|
||||
kline.FourHour.Word(): true,
|
||||
kline.SixHour.Word(): true,
|
||||
kline.TwelveHour.Word(): true,
|
||||
kline.OneDay.Word(): true,
|
||||
kline.ThreeDay.Word(): true,
|
||||
kline.OneWeek.Word(): true,
|
||||
kline.OneMonth.Word(): true,
|
||||
},
|
||||
ResultLimit: 300,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.Requester = request.New(b.Name,
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout))
|
||||
common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
|
||||
request.WithLimiter(SetRateLimit()))
|
||||
|
||||
b.API.Endpoints.URLDefault = btseAPIURL
|
||||
b.API.Endpoints.URL = b.API.Endpoints.URLDefault
|
||||
@@ -162,6 +188,11 @@ func (b *BTSE) Setup(exch *config.ExchangeConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.seedOrderSizeLimits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
@@ -197,32 +228,17 @@ func (b *BTSE) Run() {
|
||||
// FetchTradablePairs returns a list of the exchanges tradable pairs
|
||||
func (b *BTSE) FetchTradablePairs(a asset.Item) ([]string, error) {
|
||||
var currencies []string
|
||||
if a == asset.Spot {
|
||||
m, err := b.GetSpotMarkets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for x := range m {
|
||||
if m[x].Status != "active" {
|
||||
continue
|
||||
}
|
||||
currencies = append(currencies, m[x].Symbol)
|
||||
}
|
||||
} else if a == asset.Futures {
|
||||
m, err := b.GetFuturesMarkets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for x := range m {
|
||||
if !m[x].Active {
|
||||
continue
|
||||
}
|
||||
currencies = append(currencies, m[x].Symbol)
|
||||
}
|
||||
m, err := b.GetMarketSummary("", a == asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for x := range m {
|
||||
if !m[x].Active {
|
||||
continue
|
||||
}
|
||||
currencies = append(currencies, m[x].Symbol)
|
||||
}
|
||||
return currencies, nil
|
||||
}
|
||||
|
||||
@@ -251,41 +267,32 @@ func (b *BTSE) UpdateTradablePairs(forceUpdate bool) error {
|
||||
|
||||
// UpdateTicker updates and returns the ticker for a currency pair
|
||||
func (b *BTSE) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
|
||||
if assetType == asset.Futures {
|
||||
// Futures REST implementation needs to be done before this can be
|
||||
// removed
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
tickers, err := b.GetMarketSummary("", assetType == asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for x := range tickers {
|
||||
var pair currency.Pair
|
||||
pair, err = currency.NewPairFromString(tickers[x].Symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := b.GetTicker(fpair.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Pair: pair,
|
||||
Ask: tickers[x].LowestAsk,
|
||||
Bid: tickers[x].HighestBid,
|
||||
Low: tickers[x].Low24Hr,
|
||||
Last: tickers[x].Last,
|
||||
Volume: tickers[x].Volume,
|
||||
High: tickers[x].High24Hr,
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
s, err := b.GetMarketStatistics(fpair.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ticker.ProcessTicker(&ticker.Price{
|
||||
Pair: p,
|
||||
Ask: t.Ask,
|
||||
Bid: t.Bid,
|
||||
Low: s.Low,
|
||||
Last: t.Price,
|
||||
Volume: s.Volume,
|
||||
High: s.High,
|
||||
LastUpdated: s.Time,
|
||||
ExchangeName: b.Name,
|
||||
AssetType: assetType})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ticker.GetTicker(b.Name, p, assetType)
|
||||
}
|
||||
|
||||
@@ -309,17 +316,11 @@ 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) {
|
||||
if assetType == asset.Futures {
|
||||
// Futures REST implementation needs to be done before this can be
|
||||
// removed
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
fpair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
fPair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, err := b.FetchOrderBook(fpair.String())
|
||||
a, err := b.FetchOrderBook(fPair.String(), 0, 0, 0, assetType == asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -349,7 +350,7 @@ func (b *BTSE) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderboo
|
||||
// BTSE exchange
|
||||
func (b *BTSE) UpdateAccountInfo() (account.Holdings, error) {
|
||||
var a account.Holdings
|
||||
balance, err := b.GetAccountBalance()
|
||||
balance, err := b.GetWalletInformation()
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
@@ -397,7 +398,47 @@ func (b *BTSE) GetFundingHistory() ([]exchange.FundHistory, error) {
|
||||
|
||||
// GetExchangeHistory returns historic trade data within the timeframe provided.
|
||||
func (b *BTSE) GetExchangeHistory(p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]exchange.TradeHistory, error) {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
if assetType != asset.Spot {
|
||||
return nil, common.ErrNotYetImplemented
|
||||
}
|
||||
|
||||
fPair, err := b.FormatExchangeCurrency(p, assetType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trades, err := b.GetTrades(fPair.String(),
|
||||
timestampStart, timestampEnd,
|
||||
0, 0, 0,
|
||||
false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []exchange.TradeHistory
|
||||
for x := range trades {
|
||||
tempExch := exchange.TradeHistory{
|
||||
Timestamp: time.Unix(0, trades[x].Time*int64(time.Millisecond)),
|
||||
Price: trades[x].Price,
|
||||
Amount: trades[x].Amount,
|
||||
Exchange: b.Name,
|
||||
Side: trades[x].Side,
|
||||
Type: trades[x].Type,
|
||||
TID: strconv.Itoa(trades[x].SerialID),
|
||||
}
|
||||
|
||||
resp = append(resp, tempExch)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *BTSE) withinLimits(pair currency.Pair, amount float64) bool {
|
||||
val, found := OrderSizeLimits(pair.String())
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
return (math.Mod(amount, val.MinSizeIncrement) == 0) ||
|
||||
amount < val.MinOrderSize ||
|
||||
amount > val.MaxOrderSize
|
||||
}
|
||||
|
||||
// SubmitOrder submits a new order
|
||||
@@ -407,26 +448,28 @@ func (b *BTSE) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
fpair, err := b.FormatExchangeCurrency(s.Pair, s.AssetType)
|
||||
fPair, err := b.FormatExchangeCurrency(s.Pair, s.AssetType)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
inLimits := b.withinLimits(fPair, s.Amount)
|
||||
if !inLimits {
|
||||
return resp, errors.New("order outside of limits")
|
||||
}
|
||||
|
||||
r, err := b.CreateOrder(s.ClientID, 0.0,
|
||||
false,
|
||||
s.Price, s.Side.String(), s.Amount, 0, 0,
|
||||
fPair.String(), goodTillCancel,
|
||||
0.0, s.TriggerPrice,
|
||||
"", s.Type.String())
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
r, err := b.CreateOrder(s.Amount,
|
||||
s.Price,
|
||||
s.Side.String(),
|
||||
s.Type.String(),
|
||||
fpair.String(),
|
||||
goodTillCancel,
|
||||
s.ClientID)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp.IsOrderPlaced = true
|
||||
resp.OrderID = r[0].OrderID
|
||||
|
||||
if *r != "" {
|
||||
resp.IsOrderPlaced = true
|
||||
resp.OrderID = *r
|
||||
}
|
||||
if s.Type == order.Market {
|
||||
resp.FullyMatched = true
|
||||
}
|
||||
@@ -441,24 +484,17 @@ func (b *BTSE) ModifyOrder(action *order.Modify) (string, error) {
|
||||
|
||||
// CancelOrder cancels an order by its corresponding ID number
|
||||
func (b *BTSE) CancelOrder(order *order.Cancel) error {
|
||||
fpair, err := b.FormatExchangeCurrency(order.Pair,
|
||||
fPair, err := b.FormatExchangeCurrency(order.Pair,
|
||||
order.AssetType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := b.CancelExistingOrder(order.ID, fpair.String())
|
||||
_, err = b.CancelExistingOrder(order.ID, fPair.String(), order.ClientOrderID)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -467,50 +503,39 @@ func (b *BTSE) CancelOrder(order *order.Cancel) error {
|
||||
// If not specified, all orders of all markets will be cancelled
|
||||
func (b *BTSE) CancelAllOrders(orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
|
||||
var resp order.CancelAllResponse
|
||||
markets, err := b.GetSpotMarkets()
|
||||
|
||||
fPair, err := b.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
orderCancellation.AssetType)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(orderCancellation.AssetType, false)
|
||||
allOrders, err := b.CancelExistingOrder("", fPair.String(), "")
|
||||
if err != nil {
|
||||
return resp, err
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
resp.Status = make(map[string]string)
|
||||
for x := range markets {
|
||||
fair, err := b.FormatExchangeCurrency(orderCancellation.Pair,
|
||||
orderCancellation.AssetType)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
checkPair := currency.NewPairWithDelimiter(markets[x].BaseCurrency,
|
||||
markets[x].QuoteCurrency,
|
||||
format.Delimiter).String()
|
||||
if fair.String() != 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.Status[orders[y].Order.ID] = success
|
||||
}
|
||||
for x := range allOrders {
|
||||
if allOrders[x].Status == orderCancelled {
|
||||
resp.Status[allOrders[x].OrderID] = order.Cancelled.String()
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func orderIntToType(i int) order.Type {
|
||||
if i == 77 {
|
||||
return order.Market
|
||||
} else if i == 76 {
|
||||
return order.Limit
|
||||
}
|
||||
return order.UnknownType
|
||||
}
|
||||
|
||||
// GetOrderInfo returns information on a current open order
|
||||
func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
o, err := b.GetOrders("")
|
||||
o, err := b.GetOrders("", orderID, "")
|
||||
if err != nil {
|
||||
return order.Detail{}, err
|
||||
}
|
||||
@@ -526,7 +551,7 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
}
|
||||
|
||||
for i := range o {
|
||||
if o[i].ID != orderID {
|
||||
if o[i].OrderID != orderID {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -544,39 +569,41 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
err)
|
||||
}
|
||||
od.Exchange = b.Name
|
||||
od.Amount = o[i].Amount
|
||||
od.ID = o[i].ID
|
||||
od.Date, err = parseOrderTime(o[i].CreatedAt)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetOrderInfo unable to parse time: %s\n", b.Name, err)
|
||||
}
|
||||
od.Amount = o[i].Size
|
||||
od.ID = o[i].OrderID
|
||||
od.Date = time.Unix(o[i].Timestamp, 0)
|
||||
od.Side = side
|
||||
od.Type = order.Type(strings.ToUpper(o[i].Type))
|
||||
od.Price = o[i].Price
|
||||
od.Status = order.Status(o[i].Status)
|
||||
|
||||
fills, err := b.GetFills(orderID, "", "", "", "", "")
|
||||
od.Type = orderIntToType(o[i].OrderType)
|
||||
|
||||
od.Price = o[i].Price
|
||||
od.Status = order.Status(o[i].OrderState)
|
||||
|
||||
th, err := b.TradeHistory("",
|
||||
time.Time{}, time.Time{},
|
||||
0, 0, 0,
|
||||
false,
|
||||
"", orderID)
|
||||
if err != nil {
|
||||
return od,
|
||||
fmt.Errorf("unable to get order fills for orderID %s",
|
||||
orderID)
|
||||
}
|
||||
|
||||
for i := range fills {
|
||||
createdAt, err := parseOrderTime(fills[i].CreatedAt)
|
||||
for i := range th {
|
||||
createdAt, err := parseOrderTime(th[i].TradeID)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetOrderInfo unable to parse time: %s\n", b.Name, err)
|
||||
}
|
||||
od.Trades = append(od.Trades, order.TradeHistory{
|
||||
Timestamp: createdAt,
|
||||
TID: strconv.FormatInt(fills[i].ID, 10),
|
||||
Price: fills[i].Price,
|
||||
Amount: fills[i].Amount,
|
||||
TID: th[i].TradeID,
|
||||
Price: th[i].Price,
|
||||
Amount: th[i].Size,
|
||||
Exchange: b.Name,
|
||||
Side: order.Side(fills[i].Side),
|
||||
Fee: fills[i].Fee,
|
||||
Side: order.Side(th[i].Side),
|
||||
Fee: th[i].FeeAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -585,13 +612,38 @@ func (b *BTSE) GetOrderInfo(orderID string) (order.Detail, error) {
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (b *BTSE) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) {
|
||||
return "", common.ErrFunctionNotSupported
|
||||
address, err := b.GetWalletAddress(cryptocurrency.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(address) == 0 {
|
||||
addressCreate, err := b.CreateWalletAddress(cryptocurrency.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(addressCreate) != 0 {
|
||||
return addressCreate[0].Address, nil
|
||||
}
|
||||
return "", errors.New("address not found")
|
||||
}
|
||||
return address[0].Address, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
// submitted
|
||||
func (b *BTSE) WithdrawCryptocurrencyFunds(withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
amountToString := strconv.FormatFloat(withdrawRequest.Amount, 'f', 8, 64)
|
||||
resp, err := b.WalletWithdrawal(withdrawRequest.Currency.String(),
|
||||
withdrawRequest.Crypto.Address,
|
||||
withdrawRequest.Crypto.AddressTag,
|
||||
amountToString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &withdraw.ExchangeResponse{
|
||||
Name: b.Name,
|
||||
ID: resp.WithdrawID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is
|
||||
@@ -608,80 +660,92 @@ func (b *BTSE) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.Re
|
||||
|
||||
// GetActiveOrders retrieves any orders that are active/open
|
||||
func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
resp, err := b.GetOrders("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(req.Pairs) == 0 {
|
||||
return nil, errors.New("no pair provided")
|
||||
}
|
||||
|
||||
var orders []order.Detail
|
||||
for i := range resp {
|
||||
var side = order.Buy
|
||||
if strings.EqualFold(resp[i].Side, order.Ask.String()) {
|
||||
side = order.Sell
|
||||
}
|
||||
|
||||
tm, err := parseOrderTime(resp[i].CreatedAt)
|
||||
for x := range req.Pairs {
|
||||
formattedPair, err := b.FormatExchangeCurrency(req.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetActiveOrders unable to parse time: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(resp[i].Symbol,
|
||||
format.Delimiter)
|
||||
resp, err := b.GetOrders(formattedPair.String(), "", "")
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetActiveOrders unable to parse currency pair: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
openOrder := order.Detail{
|
||||
Pair: p,
|
||||
Exchange: b.Name,
|
||||
Amount: resp[i].Amount,
|
||||
ID: resp[i].ID,
|
||||
Date: tm,
|
||||
Side: side,
|
||||
Type: order.Type(strings.ToUpper(resp[i].Type)),
|
||||
Price: resp[i].Price,
|
||||
Status: order.Status(resp[i].Status),
|
||||
}
|
||||
|
||||
fills, err := b.GetFills(resp[i].ID, "", "", "", "", "")
|
||||
format, err := b.GetPairFormat(asset.Spot, false)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s: Unable to get order fills for orderID %s",
|
||||
b.Name,
|
||||
resp[i].ID)
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range fills {
|
||||
createdAt, err := parseOrderTime(fills[i].CreatedAt)
|
||||
for i := range resp {
|
||||
var side = order.Buy
|
||||
if strings.EqualFold(resp[i].Side, order.Ask.String()) {
|
||||
side = order.Sell
|
||||
}
|
||||
|
||||
p, err := currency.NewPairDelimiter(resp[i].Symbol,
|
||||
format.Delimiter)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetActiveOrders unable to parse time: %s\n",
|
||||
"%s GetActiveOrders unable to parse currency pair: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
openOrder.Trades = append(openOrder.Trades, order.TradeHistory{
|
||||
Timestamp: createdAt,
|
||||
TID: strconv.FormatInt(fills[i].ID, 10),
|
||||
Price: fills[i].Price,
|
||||
Amount: fills[i].Amount,
|
||||
Exchange: b.Name,
|
||||
Side: order.Side(fills[i].Side),
|
||||
Fee: fills[i].Fee,
|
||||
})
|
||||
|
||||
openOrder := order.Detail{
|
||||
Pair: p,
|
||||
Exchange: b.Name,
|
||||
Amount: resp[i].Size,
|
||||
ID: resp[i].OrderID,
|
||||
Date: time.Unix(resp[i].Timestamp, 0),
|
||||
Side: side,
|
||||
Price: resp[i].Price,
|
||||
Status: order.Status(resp[i].OrderState),
|
||||
}
|
||||
|
||||
if resp[i].OrderType == 77 {
|
||||
openOrder.Type = order.Market
|
||||
} else if resp[i].OrderType == 76 {
|
||||
openOrder.Type = order.Limit
|
||||
}
|
||||
|
||||
fills, err := b.TradeHistory(
|
||||
"",
|
||||
time.Time{}, time.Time{},
|
||||
0, 0, 0,
|
||||
false,
|
||||
"", resp[i].OrderID)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s: Unable to get order fills for orderID %s",
|
||||
b.Name,
|
||||
resp[i].OrderID)
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range fills {
|
||||
createdAt, err := parseOrderTime(fills[i].Timestamp)
|
||||
if err != nil {
|
||||
log.Errorf(log.ExchangeSys,
|
||||
"%s GetActiveOrders unable to parse time: %s\n",
|
||||
b.Name,
|
||||
err)
|
||||
}
|
||||
openOrder.Trades = append(openOrder.Trades, order.TradeHistory{
|
||||
Timestamp: createdAt,
|
||||
TID: fills[i].TradeID,
|
||||
Price: fills[i].Price,
|
||||
Amount: fills[i].Size,
|
||||
Exchange: b.Name,
|
||||
Side: order.Side(fills[i].Side),
|
||||
Fee: fills[i].FeeAmount,
|
||||
})
|
||||
}
|
||||
orders = append(orders, openOrder)
|
||||
}
|
||||
orders = append(orders, openOrder)
|
||||
}
|
||||
|
||||
order.FilterOrdersByType(&orders, req.Type)
|
||||
@@ -690,10 +754,60 @@ func (b *BTSE) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, err
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
func matchType(input int, required order.Type) bool {
|
||||
if (required == order.AnyType) || (input == 76 && required == order.Limit) || input == 77 && required == order.Market {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetOrderHistory retrieves account order information
|
||||
// Can Limit response to specific order status
|
||||
func (b *BTSE) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]order.Detail, error) {
|
||||
return nil, common.ErrFunctionNotSupported
|
||||
var resp []order.Detail
|
||||
if len(getOrdersRequest.Pairs) == 0 {
|
||||
var err error
|
||||
getOrdersRequest.Pairs, err = b.GetEnabledPairs(asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
orderDeref := *getOrdersRequest
|
||||
for x := range orderDeref.Pairs {
|
||||
fPair, err := b.FormatExchangeCurrency(orderDeref.Pairs[x], asset.Spot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentOrder, err := b.GetOrders(fPair.String(), "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for y := range currentOrder {
|
||||
if !matchType(currentOrder[y].OrderType, orderDeref.Type) {
|
||||
continue
|
||||
}
|
||||
tempOrder := order.Detail{
|
||||
Price: currentOrder[y].Price,
|
||||
Amount: currentOrder[y].Size,
|
||||
Side: order.Side(currentOrder[y].Side),
|
||||
Pair: orderDeref.Pairs[x],
|
||||
}
|
||||
switch currentOrder[x].OrderState {
|
||||
case "STATUS_ACTIVE":
|
||||
tempOrder.Status = order.Active
|
||||
case "ORDER_CANCELLED":
|
||||
tempOrder.Status = order.Cancelled
|
||||
case "ORDER_FULLY_TRANSACTED":
|
||||
tempOrder.Status = order.Filled
|
||||
case "ORDER_PARTIALLY_TRANSACTED":
|
||||
tempOrder.Status = order.PartiallyFilled
|
||||
default:
|
||||
tempOrder.Status = order.UnknownStatus
|
||||
}
|
||||
resp = append(resp, tempOrder)
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetFeeByType returns an estimate of fee based on type of transaction
|
||||
@@ -712,12 +826,152 @@ func (b *BTSE) ValidateCredentials() error {
|
||||
return b.CheckTransientError(err)
|
||||
}
|
||||
|
||||
// FormatExchangeKlineInterval formats kline interval to exchange requested type
|
||||
func (b *BTSE) FormatExchangeKlineInterval(in kline.Interval) string {
|
||||
return strconv.FormatFloat(in.Duration().Minutes(), 'f', 0, 64)
|
||||
}
|
||||
|
||||
// GetHistoricCandles returns candles between a time period for a set time interval
|
||||
func (b *BTSE) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
if err := b.ValidateKline(pair, a, interval); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
fPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
intervalInt, err := strconv.Atoi(b.FormatExchangeKlineInterval(interval))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
klineRet := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: fPair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
switch a {
|
||||
case asset.Spot:
|
||||
req, err := b.OHLCV(fPair.String(),
|
||||
start,
|
||||
end,
|
||||
intervalInt)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
for x := range req {
|
||||
klineRet.Candles = append(klineRet.Candles, kline.Candle{
|
||||
Time: time.Unix(int64(req[x][0]), 0),
|
||||
Open: req[x][1],
|
||||
High: req[x][2],
|
||||
Low: req[x][3],
|
||||
Close: req[x][4],
|
||||
Volume: req[x][5],
|
||||
})
|
||||
}
|
||||
case asset.Futures:
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
default:
|
||||
return kline.Item{}, fmt.Errorf("asset %v not supported", a.String())
|
||||
}
|
||||
|
||||
klineRet.SortCandlesByTimestamp(false)
|
||||
return klineRet, nil
|
||||
}
|
||||
|
||||
// GetHistoricCandlesExtended returns candles between a time period for a set time interval
|
||||
func (b *BTSE) GetHistoricCandlesExtended(pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
if err := b.ValidateKline(pair, a, interval); err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
if kline.TotalCandlesPerInterval(start, end, interval) > b.Features.Enabled.Kline.ResultLimit {
|
||||
return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits)
|
||||
}
|
||||
|
||||
fPair, err := b.FormatExchangeCurrency(pair, a)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
intervalInt, err := strconv.Atoi(b.FormatExchangeKlineInterval(interval))
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
|
||||
klineRet := kline.Item{
|
||||
Exchange: b.Name,
|
||||
Pair: fPair,
|
||||
Asset: a,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
switch a {
|
||||
case asset.Spot:
|
||||
req, err := b.OHLCV(fPair.String(),
|
||||
start,
|
||||
end,
|
||||
intervalInt)
|
||||
if err != nil {
|
||||
return kline.Item{}, err
|
||||
}
|
||||
for x := range req {
|
||||
klineRet.Candles = append(klineRet.Candles, kline.Candle{
|
||||
Time: time.Unix(int64(req[x][0]), 0),
|
||||
Open: req[x][1],
|
||||
High: req[x][2],
|
||||
Low: req[x][3],
|
||||
Close: req[x][4],
|
||||
Volume: req[x][5],
|
||||
})
|
||||
}
|
||||
case asset.Futures:
|
||||
return kline.Item{}, common.ErrNotYetImplemented
|
||||
default:
|
||||
return kline.Item{}, fmt.Errorf("asset %v not supported", a.String())
|
||||
}
|
||||
|
||||
klineRet.SortCandlesByTimestamp(false)
|
||||
return klineRet, nil
|
||||
}
|
||||
|
||||
func (b *BTSE) seedOrderSizeLimits() error {
|
||||
pairs, err := b.GetMarketSummary("", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for x := range pairs {
|
||||
tempValues := OrderSizeLimit{
|
||||
MinOrderSize: pairs[x].MinOrderSize,
|
||||
MaxOrderSize: pairs[x].MaxOrderSize,
|
||||
MinSizeIncrement: pairs[x].MinSizeIncrement,
|
||||
}
|
||||
orderSizeLimitMap.Store(pairs[x].Symbol, tempValues)
|
||||
}
|
||||
|
||||
pairs, err = b.GetMarketSummary("", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for x := range pairs {
|
||||
tempValues := OrderSizeLimit{
|
||||
MinOrderSize: pairs[x].MinOrderSize,
|
||||
MaxOrderSize: pairs[x].MaxOrderSize,
|
||||
MinSizeIncrement: pairs[x].MinSizeIncrement,
|
||||
}
|
||||
orderSizeLimitMap.Store(pairs[x].Symbol, tempValues)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderSizeLimits looks up currency pair in orderSizeLimitMap and returns OrderSizeLimit
|
||||
func OrderSizeLimits(pair string) (limits OrderSizeLimit, found bool) {
|
||||
resp, ok := orderSizeLimitMap.Load(pair)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
val, ok := resp.(OrderSizeLimit)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
42
exchanges/btse/ratelimit.go
Normal file
42
exchanges/btse/ratelimit.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package btse
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
btseRateInterval = time.Second
|
||||
btseQueryLimit = 15
|
||||
btseOrdersLimit = 75
|
||||
|
||||
queryFunc request.EndpointLimit = iota
|
||||
orderFunc
|
||||
)
|
||||
|
||||
// RateLimit implements the request.Limiter interface
|
||||
type RateLimit struct {
|
||||
Query *rate.Limiter
|
||||
Orders *rate.Limiter
|
||||
}
|
||||
|
||||
// Limit executes rate limiting functionality for exchange
|
||||
func (r *RateLimit) Limit(f request.EndpointLimit) error {
|
||||
switch f {
|
||||
case orderFunc:
|
||||
time.Sleep(r.Orders.Reserve().Delay())
|
||||
default:
|
||||
time.Sleep(r.Query.Reserve().Delay())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRateLimit returns the rate limit for the exchange
|
||||
func SetRateLimit() *RateLimit {
|
||||
return &RateLimit{
|
||||
Orders: request.NewRateLimit(btseRateInterval, btseOrdersLimit),
|
||||
Query: request.NewRateLimit(btseRateInterval, btseQueryLimit),
|
||||
}
|
||||
}
|
||||
@@ -291,9 +291,9 @@ func CalcDateRanges(start, end time.Time, interval Interval, limit uint32) (out
|
||||
}
|
||||
|
||||
// SortCandlesByTimestamp sorts candles by timestamp
|
||||
func (k *Item) SortCandlesByTimestamp(asc bool) {
|
||||
func (k *Item) SortCandlesByTimestamp(desc bool) {
|
||||
sort.Slice(k.Candles, func(i, j int) bool {
|
||||
if asc {
|
||||
if desc {
|
||||
return k.Candles[i].Time.After(k.Candles[j].Time)
|
||||
}
|
||||
return k.Candles[i].Time.Before(k.Candles[j].Time)
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user