Files
gocryptotrader/exchanges/btse/btse.go
Ryan O'Hara-Reid 0990f9d118 Currency package update (#247)
* Initial currency overhaul before service system implementation

* Remove redundant currency string in orderbook.Base
Unexport lastupdated field in orderbook.Base as it was being instantiated multiple times
Add error handling for process orderbook

*  Remove redundant currency string in ticker.Price
 Unexport lastupdated field in ticker.Price
 Add error handling for process ticker function and fix tests

* Phase Two Update

* Update translations to use map type - thankyou to kempeng for spotting this

* Change pair method name from Display -> Format for better readability

* Fixes misspelling and tests

* Implement requested changes from GloriousCode

* Remove reduntant function and streamlined return in currency_translation.go

* Revert pair method naming conventions

* Change currency naming conventions

* Changed code type to exported Item type with underlying string to reduce complexity

* Added interim orderbook process method to orderbook.Base type

* Changed feebuilder struct field to currency.Pair

* Adds fall over system for backup fx providers

* deprecate function and children and fix linter issue with btcmarkets

* Fixed requested changes

* Fix bug and move mtx for rates

* Fixed after rebase oopsies

* Fix linter issues

* Fixes race conditions in testing functions

* Final phase coinmarketcap update

* fix linter issues

* Implement requested changes

* Adds configuration variables to increase/decrease time durations between updating currency file and fetching new currency rates

* Add a collection of tests to improve codecov

* After rebase oopsy fixes for btse

* Fix requested changes

* fix after rebase oopsies and add more efficient comparison checks within currency pair

* Fix linter issues
2019-03-19 11:49:05 +11:00

368 lines
10 KiB
Go

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