mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-18 15:10:03 +00:00
* ALMOST THERE * more api wips * more api thingz * testing n more api wipz * more apiz * more wips * what is goin on * more wips * whip n testing * testing * testing no keys * remove log * kraken is broken ugh * still broken * fixing auth funcs + usdtm api docs * wip * api stuffs * whip * more wips * whip * more wip * api wip n testing * wip * wip * unsaved * wip n testing * wip * wip * wip * wip * wip * wip * wip * wip * wip * whip * wrapper authenticated functions * adding asset type and fixing dependencies * wip * binance auth wrapper start * wrapper functionality * wip * wip * wip * wrapper cancel functions * order submission for wrappers * wip * more error fixing and nits * websocket beginning n error fix * wip * WOW * glorious n shazzy nits * useless nits * wip * fixing things * merge stuffs * crapveyor * crapveyor rebuild * probably broke more things than he fixed * rm lns n other thangs * hope * please * stop it * done * ofcourse * rm vb * fix lbank * appveyor please * float lev * DONT ASK RYAN FOR HELP EVER * wip * wip * endpoint upgrades continued * path upgrade * NeeeNeeeNeeeNeeeNING * fix stuffs * fixing time issue * fixing broken funcs * glorious nits * shaz changes * fixing errors for fundmon * more error fixing for fundmon * test running past 30s * basic changes * THX AGAIN SHAZBERT * path system upgrade * config upgrade * unsaved stuffs * broken wip config upgrade * path system upgrade contd. * path system upgrade contd * path upgrade ready for review * testing verbose removed * linter stuffs * appveyor stuffs * appveyor stuff * fixed? * bugfix * wip * broken stuff * fix test * wierd hack fix * appveyor pls stop * error found * more useless nits * bitmex err * broken wip * broken wip path upgrade change to uint32 * changed url lookups to uint * WOW * ready4review * config fixed HOPEFULLY * config fix and glorious changes * efficient way of getting orders and open orders * binance wrapper logic fixing * testing, adding tests and fixing lot of errrrrs * merge master * appveyor stuffs * appveyor stuffs * fmt * test * octalLiteral issue fix? * octalLiteral fix? * rm vb * prnt ln to restart * adding testz * test fixzzz * READY FOR REVIEW * Actually ready now * FORMATTING * addressing shazzy n glorious nits * crapveyor * rm vb * small change * fixing err * shazbert nits * review changes * requested changes * more requested changes * noo * last nit fixes * restart appveyor * improving test cov * Update .golangci.yml * shazbert changes * moving pair formatting * format pair update wip * path upgrade complete * error fix * appveyor linters * more linters * remove testexch * more formatting changes * changes * shazbert changes * checking older requested changes to ensure completion * wip * fixing broken code * error fix * all fixed * additional changes * more changes * remove commented code * ftx margin api * appveyor fixes * more appveyor issues + test addition * more appveyor issues + test addition * remove unnecessary * testing * testing, fixing okex api, error fix * git merge fix * go sum * glorious changes and error fix * rm vb * more glorious changes and go mod tidy * fixed now * okex testing upgrade * old config migration and batch fetching fix * added test * glorious requested changes WIP * tested and fixed * go fmted * go fmt and test fix * additional funcs and tests for fundingRates * OKEX tested and fixed * appveyor fixes * ineff assign * 1 glorious change * error fix * typo * shazbert changes * glorious code changes and path fixing huobi WIP * adding assetType to accountinfo functions * fixing panic * panic fix and updating account info wrappers WIP * updateaccountinfo updated * testing WIP binance USDT n Coin Margined and Kraken Futures * auth functions tested and fixed * added test * config reverted * shazbert and glorious changes * shazbert and glorious changes * latest changes and portfolio update * go fmt change: * remove commented codes * improved error checking * index out of range fix * rm ln * critical nit * glorious latest changes * appveyor changes * shazbert change * easier readability * latest glorious changes * shadow dec * assetstore updated * last change * another last change * merge changes * go mod tidy * thrasher requested changes wip * improving struct layouts * appveyor go fmt * remove unnecessary code * shazbert changes * small change * oopsie * tidy * configtest reverted * error fix * oopsie * for what * test patch fix * insecurities * fixing tests * fix config
612 lines
18 KiB
Go
612 lines
18 KiB
Go
package btse
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
// BTSE is the overarching type across this package
|
|
type BTSE struct {
|
|
exchange.Base
|
|
}
|
|
|
|
const (
|
|
btseAPIURL = "https://api.btse.com"
|
|
btseSPOTPath = "/spot"
|
|
btseSPOTAPIPath = "/api/v3.2/"
|
|
btseFuturesPath = "/futures"
|
|
btseFuturesAPIPath = "/api/v2.1/"
|
|
|
|
// Public endpoints
|
|
btseMarketOverview = "market_summary"
|
|
btseMarkets = "markets"
|
|
btseOrderbook = "orderbook"
|
|
btseTrades = "trades"
|
|
btseTime = "time"
|
|
btseOHLCV = "ohlcv"
|
|
btsePrice = "price"
|
|
btseFuturesFunding = "funding_history"
|
|
|
|
// Authenticated endpoints
|
|
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"
|
|
)
|
|
|
|
// FetchFundingHistory gets funding history
|
|
func (b *BTSE) FetchFundingHistory(symbol string) (map[string][]FundingHistoryData, error) {
|
|
var resp map[string][]FundingHistoryData
|
|
params := url.Values{}
|
|
if symbol != "" {
|
|
params.Set("symbol", symbol)
|
|
}
|
|
return resp, b.SendHTTPRequest(exchange.RestFutures, http.MethodGet, btseFuturesFunding+params.Encode(), &resp, false, queryFunc)
|
|
}
|
|
|
|
// 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(exchange.RestSpot, http.MethodGet, path, &m, spot, queryFunc)
|
|
}
|
|
|
|
// FetchOrderBook gets orderbook data for a given pair
|
|
func (b *BTSE) FetchOrderBook(symbol string, group, limitBids, limitAsks int, spot bool) (*Orderbook, error) {
|
|
var o Orderbook
|
|
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(exchange.RestSpot, 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(exchange.RestSpot, http.MethodGet, endpoint, &o, true, queryFunc)
|
|
}
|
|
|
|
// GetTrades returns a list of trades for the specified symbol
|
|
func (b *BTSE) GetTrades(symbol string, start, end time.Time, beforeSerialID, afterSerialID, count int, includeOld, spot bool) ([]Trade, error) {
|
|
var t []Trade
|
|
urlValues := url.Values{}
|
|
urlValues.Add("symbol", symbol)
|
|
if count > 0 {
|
|
urlValues.Add("count", strconv.Itoa(count))
|
|
}
|
|
if !start.IsZero() {
|
|
urlValues.Add("start", strconv.FormatInt(start.Unix(), 10))
|
|
}
|
|
if !end.IsZero() {
|
|
urlValues.Add("end", strconv.FormatInt(end.Unix(), 10))
|
|
}
|
|
if !start.IsZero() && !end.IsZero() && start.After(end) {
|
|
return t, errors.New("start cannot be after end time")
|
|
}
|
|
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(exchange.RestSpot, http.MethodGet,
|
|
common.EncodeURLValues(btseTrades, urlValues), &t, spot, queryFunc)
|
|
}
|
|
|
|
// 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(exchange.RestSpot, 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(exchange.RestSpot, 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(exchange.RestSpot, http.MethodGet, btseTime, &s, true, queryFunc)
|
|
}
|
|
|
|
// GetWalletInformation returns the users account balance
|
|
func (b *BTSE) GetWalletInformation() ([]CurrencyBalance, error) {
|
|
var a []CurrencyBalance
|
|
return a, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, 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(exchange.RestSpot, 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(exchange.RestSpot, 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(exchange.RestSpot, 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(exchange.RestSpot, 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(exchange.RestSpot, http.MethodPost, btseWalletWithdrawal, true, nil, req, &resp, queryFunc)
|
|
}
|
|
|
|
// CreateOrder creates an order
|
|
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{})
|
|
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 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
|
|
}
|
|
|
|
var r []Order
|
|
return r, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost, btseOrder, true, url.Values{}, req, &r, orderFunc)
|
|
}
|
|
|
|
// GetOrders returns all pending orders
|
|
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(exchange.RestSpot, http.MethodGet, btsePendingOrders, true, req, nil, &o, orderFunc)
|
|
}
|
|
|
|
// CancelExistingOrder cancels an order
|
|
func (b *BTSE) CancelExistingOrder(orderID, symbol, clOrderID string) (CancelOrder, error) {
|
|
var c CancelOrder
|
|
req := url.Values{}
|
|
if orderID != "" {
|
|
req.Add("orderID", orderID)
|
|
}
|
|
req.Add("symbol", symbol)
|
|
if clOrderID != "" {
|
|
req.Add("clOrderID", clOrderID)
|
|
}
|
|
|
|
return c, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodDelete, btseOrder, true, req, nil, &c, orderFunc)
|
|
}
|
|
|
|
// CancelAllAfter cancels all orders after timeout
|
|
func (b *BTSE) CancelAllAfter(timeout int) error {
|
|
req := make(map[string]interface{})
|
|
req["timeout"] = timeout
|
|
return b.SendAuthenticatedHTTPRequest(exchange.RestSpot, 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 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
|
|
}
|
|
|
|
return o, b.SendAuthenticatedHTTPRequest(exchange.RestSpot, http.MethodPost, btsePegOrder, true, url.Values{}, req, nil, orderFunc)
|
|
}
|
|
|
|
// 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 !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))
|
|
}
|
|
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(exchange.RestSpot, http.MethodGet, btseExchangeHistory, true, urlValues, nil, &resp, queryFunc)
|
|
}
|
|
|
|
// SendHTTPRequest sends an HTTP request to the desired endpoint
|
|
func (b *BTSE) SendHTTPRequest(ep exchange.URL, method, endpoint string, result interface{}, spotEndpoint bool, f request.EndpointLimit) error {
|
|
ePoint, err := b.API.Endpoints.GetURL(ep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p := btseSPOTPath + btseSPOTAPIPath
|
|
if !spotEndpoint {
|
|
p = btseFuturesPath + btseFuturesAPIPath
|
|
}
|
|
return b.SendPayload(context.Background(), &request.Item{
|
|
Method: method,
|
|
Path: ePoint + p + endpoint,
|
|
Result: result,
|
|
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(ep exchange.URL, 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)
|
|
}
|
|
|
|
ePoint, err := b.API.Endpoints.GetURL(ep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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 := ePoint
|
|
if isSpot {
|
|
host += btseSPOTPath + btseSPOTAPIPath + endpoint
|
|
endpoint = btseSPOTAPIPath + endpoint
|
|
} else {
|
|
host += btseFuturesPath + btseFuturesAPIPath
|
|
endpoint += btseFuturesAPIPath
|
|
}
|
|
var hmac []byte
|
|
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(reqPayload)
|
|
hmac = crypto.GetHMAC(
|
|
crypto.HashSHA512_384,
|
|
[]byte((endpoint + nonce + string(reqPayload))),
|
|
[]byte(b.API.Credentials.Secret),
|
|
)
|
|
headers["Content-Type"] = "application/json"
|
|
} else {
|
|
hmac = crypto.GetHMAC(
|
|
crypto.HashSHA512_384,
|
|
[]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",
|
|
b.Name, method, endpoint)
|
|
}
|
|
|
|
return b.SendPayload(context.Background(), &request.Item{
|
|
Method: method,
|
|
Path: host,
|
|
Headers: headers,
|
|
Body: body,
|
|
Result: result,
|
|
AuthRequest: true,
|
|
Verbose: b.Verbose,
|
|
HTTPDebugging: b.HTTPDebugging,
|
|
HTTPRecording: b.HTTPRecording,
|
|
Endpoint: f,
|
|
})
|
|
}
|
|
|
|
// 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 = b.calculateTradingFee(feeBuilder) * feeBuilder.Amount * feeBuilder.PurchasePrice
|
|
case exchange.CryptocurrencyWithdrawalFee:
|
|
switch feeBuilder.Pair.Base {
|
|
case currency.USDT:
|
|
fee = 1.08
|
|
case currency.TUSD:
|
|
fee = 1.09
|
|
case currency.BTC:
|
|
fee = 0.0005
|
|
case currency.ETH:
|
|
fee = 0.01
|
|
case currency.LTC:
|
|
fee = 0.001
|
|
}
|
|
case exchange.InternationalBankDepositFee:
|
|
fee = getInternationalBankDepositFee(feeBuilder.Amount)
|
|
case exchange.InternationalBankWithdrawalFee:
|
|
fee = getInternationalBankWithdrawalFee(feeBuilder.Amount)
|
|
case exchange.OfflineTradeFee:
|
|
fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
|
|
}
|
|
return fee, nil
|
|
}
|
|
|
|
// getOfflineTradeFee calculates the worst case-scenario trading fee
|
|
func getOfflineTradeFee(price, amount float64) float64 {
|
|
return 0.001 * price * amount
|
|
}
|
|
|
|
// 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 <= 100 {
|
|
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.0009
|
|
|
|
if fee < 25 {
|
|
return 25
|
|
}
|
|
return fee
|
|
}
|
|
|
|
// 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
|
|
}
|
|
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(common.SimpleTimeFormat, timeStr)
|
|
}
|